よくある質問(FAQ)#
DataFrameのメモリ使用量#
DataFrame
のメモリ使用量(インデックスを含む)は、info()
を呼び出すと表示されます。設定オプション display.memory_usage
(オプション一覧 を参照)は、info()
メソッドを呼び出す際に DataFrame
のメモリ使用量を表示するかどうかを指定します。
たとえば、以下の DataFrame
のメモリ使用量は、info()
を呼び出すと表示されます。
In [1]: dtypes = [
...: "int64",
...: "float64",
...: "datetime64[ns]",
...: "timedelta64[ns]",
...: "complex128",
...: "object",
...: "bool",
...: ]
...:
In [2]: n = 5000
In [3]: data = {t: np.random.randint(100, size=n).astype(t) for t in dtypes}
In [4]: df = pd.DataFrame(data)
In [5]: df["categorical"] = df["object"].astype("category")
In [6]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5000 entries, 0 to 4999
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 int64 5000 non-null int64
1 float64 5000 non-null float64
2 datetime64[ns] 5000 non-null datetime64[ns]
3 timedelta64[ns] 5000 non-null timedelta64[ns]
4 complex128 5000 non-null complex128
5 object 5000 non-null object
6 bool 5000 non-null bool
7 categorical 5000 non-null category
dtypes: bool(1), category(1), complex128(1), datetime64[ns](1), float64(1), int64(1), object(1), timedelta64[ns](1)
memory usage: 288.2+ KB
+
記号は、pandasが dtype=object
の列の値によって使用されるメモリをカウントしないため、実際のメモリ使用量がもっと多くなる可能性があることを示しています。
memory_usage='deep'
を渡すと、含まれるオブジェクトの完全な使用量を考慮した、より正確なメモリ使用量レポートが有効になります。これは、この詳細なイントロスペクションにはコストがかかる可能性があるため、オプションです。
In [7]: df.info(memory_usage="deep")
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5000 entries, 0 to 4999
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 int64 5000 non-null int64
1 float64 5000 non-null float64
2 datetime64[ns] 5000 non-null datetime64[ns]
3 timedelta64[ns] 5000 non-null timedelta64[ns]
4 complex128 5000 non-null complex128
5 object 5000 non-null object
6 bool 5000 non-null bool
7 categorical 5000 non-null category
dtypes: bool(1), category(1), complex128(1), datetime64[ns](1), float64(1), int64(1), object(1), timedelta64[ns](1)
memory usage: 424.7 KB
デフォルトでは、表示オプションは True
に設定されていますが、info()
を呼び出す際に memory_usage
引数を渡すことで明示的にオーバーライドできます。
各列のメモリ使用量は、memory_usage()
メソッドを呼び出すことで確認できます。これは、列名で表されるインデックスと、各列のメモリ使用量(バイト単位)を示す Series
を返します。上記の DataFrame
の場合、各列のメモリ使用量と総メモリ使用量は、memory_usage()
メソッドを使用して確認できます。
In [8]: df.memory_usage()
Out[8]:
Index 128
int64 40000
float64 40000
datetime64[ns] 40000
timedelta64[ns] 40000
complex128 80000
object 40000
bool 5000
categorical 9968
dtype: int64
# total memory usage of dataframe
In [9]: df.memory_usage().sum()
Out[9]: 295096
デフォルトでは、DataFrame
インデックスのメモリ使用量が返された Series
に表示されます。インデックスのメモリ使用量を抑制するには、index=False
引数を渡します。
In [10]: df.memory_usage(index=False)
Out[10]:
int64 40000
float64 40000
datetime64[ns] 40000
timedelta64[ns] 40000
complex128 80000
object 40000
bool 5000
categorical 9968
dtype: int64
info()
メソッドによって表示されるメモリ使用量は、memory_usage()
メソッドを利用して DataFrame
のメモリ使用量を決定するとともに、出力を人間が読める単位(2進数表現、つまり1KB = 1024バイト)でフォーマットします。
カテゴリのメモリ使用量 も参照してください。
pandasでif/truthステートメントを使用する#
pandasは、何かを bool
に変換しようとするとエラーを発生させるというNumPyの規則に従います。これは、if
ステートメントで、またはブール演算 and
、or
、not
を使用する場合に発生します。次のコードの結果がどうなるべきかは明確ではありません。
>>> if pd.Series([False, True, False]):
... pass
長さゼロではないため True
にするべきでしょうか、それとも False
の値があるため False
にするべきでしょうか? これば不明確なので、pandasは ValueError
を発生させます。
In [11]: if pd.Series([False, True, False]):
....: print("I was true")
....:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-11-5c782b38cd2f> in ?()
----> 1 if pd.Series([False, True, False]):
2 print("I was true")
~/work/pandas/pandas/pandas/core/generic.py in ?(self)
1574 @final
1575 def __nonzero__(self) -> NoReturn:
-> 1576 raise ValueError(
1577 f"The truth value of a {type(self).__name__} is ambiguous. "
1578 "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
1579 )
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
DataFrame
で何をしたいかを明示的に選択する必要があります。たとえば、any()
、all()
、または empty()
を使用します。あるいは、pandasオブジェクトが None
かどうかを比較したい場合があります。
In [12]: if pd.Series([False, True, False]) is not None:
....: print("I was not None")
....:
I was not None
値のいずれかが True
かどうかを確認する方法は次のとおりです。
In [13]: if pd.Series([False, True, False]).any():
....: print("I am any")
....:
I am any
ビット単位のブール演算#
==
や !=
などのビット単位のブール演算子は、スカラーと比較したときに要素ごとの比較を実行するブール値の Series
を返します。
In [14]: s = pd.Series(range(5))
In [15]: s == 4
Out[15]:
0 False
1 False
2 False
3 False
4 True
dtype: bool
詳細な例については、ブール比較 を参照してください。
in
演算子の使用#
Series
に対してPythonの in
演算子を使用すると、値の間のメンバーシップではなく、**インデックス**のメンバーシップがテストされます。
In [16]: s = pd.Series(range(5), index=list("abcde"))
In [17]: 2 in s
Out[17]: False
In [18]: 'b' in s
Out[18]: True
この動作が意外な場合は、Python辞書で in
を使用すると値ではなくキーがテストされ、Series
は辞書のようなものであることに注意してください。値のメンバーシップをテストするには、isin()
メソッドを使用します。
In [19]: s.isin([2])
Out[19]:
a False
b False
c True
d False
e False
dtype: bool
In [20]: s.isin([2]).any()
Out[20]: True
DataFrame
の場合も同様に、in
は列軸に適用され、列名リストのメンバーシップをテストします。
ユーザー定義関数(UDF)メソッドによる変更#
このセクションは、UDFを受け取るpandasメソッドに適用されます。具体的には、DataFrame.apply()
、DataFrame.aggregate()
、DataFrame.transform()
、DataFrame.filter()
メソッドです。
プログラミングでは、コンテナの反復処理中にコンテナを変更すべきではないというのが一般的なルールです。変更するとイテレータが無効になり、予期しない動作が発生します。次の例を考えてみましょう。
In [21]: values = [0, 1, 2, 3, 4, 5]
In [22]: n_removed = 0
In [23]: for k, value in enumerate(values):
....: idx = k - n_removed
....: if value % 2 == 1:
....: del values[idx]
....: n_removed += 1
....: else:
....: values[idx] = value + 1
....:
In [24]: values
Out[24]: [1, 4, 5]
おそらく結果は [1, 3, 5]
になるだろうと予想していたでしょう。pandas のメソッドで UDF を使用する場合、内部的には pandas はしばしば DataFrame
や他の pandas オブジェクトを反復処理しています。そのため、UDF が DataFrame
を変更(ミューテート)する場合、予期しない動作が発生する可能性があります。
DataFrame.apply()
を使用した同様の例を次に示します。
In [25]: def f(s):
....: s.pop("a")
....: return s
....:
In [26]: df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
In [27]: df.apply(f, axis="columns")
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
File ~/work/pandas/pandas/pandas/core/indexes/base.py:3805, in Index.get_loc(self, key)
3804 try:
-> 3805 return self._engine.get_loc(casted_key)
3806 except KeyError as err:
File index.pyx:167, in pandas._libs.index.IndexEngine.get_loc()
File index.pyx:196, in pandas._libs.index.IndexEngine.get_loc()
File pandas/_libs/hashtable_class_helper.pxi:7081, in pandas._libs.hashtable.PyObjectHashTable.get_item()
File pandas/_libs/hashtable_class_helper.pxi:7089, in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'a'
The above exception was the direct cause of the following exception:
KeyError Traceback (most recent call last)
Cell In[27], line 1
----> 1 df.apply(f, axis="columns")
File ~/work/pandas/pandas/pandas/core/frame.py:10361, in DataFrame.apply(self, func, axis, raw, result_type, args, by_row, engine, engine_kwargs, **kwargs)
10347 from pandas.core.apply import frame_apply
10349 op = frame_apply(
10350 self,
10351 func=func,
(...)
10359 kwargs=kwargs,
10360 )
> 10361 return op.apply().__finalize__(self, method="apply")
File ~/work/pandas/pandas/pandas/core/apply.py:916, in FrameApply.apply(self)
913 elif self.raw:
914 return self.apply_raw(engine=self.engine, engine_kwargs=self.engine_kwargs)
--> 916 return self.apply_standard()
File ~/work/pandas/pandas/pandas/core/apply.py:1063, in FrameApply.apply_standard(self)
1061 def apply_standard(self):
1062 if self.engine == "python":
-> 1063 results, res_index = self.apply_series_generator()
1064 else:
1065 results, res_index = self.apply_series_numba()
File ~/work/pandas/pandas/pandas/core/apply.py:1081, in FrameApply.apply_series_generator(self)
1078 with option_context("mode.chained_assignment", None):
1079 for i, v in enumerate(series_gen):
1080 # ignore SettingWithCopy here in case the user mutates
-> 1081 results[i] = self.func(v, *self.args, **self.kwargs)
1082 if isinstance(results[i], ABCSeries):
1083 # If we have a view on v, we need to make a copy because
1084 # series_generator will swap out the underlying data
1085 results[i] = results[i].copy(deep=False)
Cell In[25], line 2, in f(s)
1 def f(s):
----> 2 s.pop("a")
3 return s
File ~/work/pandas/pandas/pandas/core/series.py:5382, in Series.pop(self, item)
5357 def pop(self, item: Hashable) -> Any:
5358 """
5359 Return item and drops from series. Raise KeyError if not found.
5360
(...)
5380 dtype: int64
5381 """
-> 5382 return super().pop(item=item)
File ~/work/pandas/pandas/pandas/core/generic.py:946, in NDFrame.pop(self, item)
945 def pop(self, item: Hashable) -> Series | Any:
--> 946 result = self[item]
947 del self[item]
949 return result
File ~/work/pandas/pandas/pandas/core/series.py:1112, in Series.__getitem__(self, key)
1109 return self._values[key]
1111 elif key_is_scalar:
-> 1112 return self._get_value(key)
1114 # Convert generator to list before going through hashable part
1115 # (We will iterate through the generator there to check for slices)
1116 if is_iterator(key):
File ~/work/pandas/pandas/pandas/core/series.py:1228, in Series._get_value(self, label, takeable)
1225 return self._values[label]
1227 # Similar to Index.get_value, but we do not fall back to positional
-> 1228 loc = self.index.get_loc(label)
1230 if is_integer(loc):
1231 return self._values[loc]
File ~/work/pandas/pandas/pandas/core/indexes/base.py:3812, in Index.get_loc(self, key)
3807 if isinstance(casted_key, slice) or (
3808 isinstance(casted_key, abc.Iterable)
3809 and any(isinstance(x, slice) for x in casted_key)
3810 ):
3811 raise InvalidIndexError(key)
-> 3812 raise KeyError(key) from err
3813 except TypeError:
3814 # If we have a listlike key, _check_indexing_error will raise
3815 # InvalidIndexError. Otherwise we fall through and re-raise
3816 # the TypeError.
3817 self._check_indexing_error(key)
KeyError: 'a'
この問題を解決するには、反復処理中のコンテナに変更が適用されないようにコピーを作成します。
In [28]: values = [0, 1, 2, 3, 4, 5]
In [29]: n_removed = 0
In [30]: for k, value in enumerate(values.copy()):
....: idx = k - n_removed
....: if value % 2 == 1:
....: del values[idx]
....: n_removed += 1
....: else:
....: values[idx] = value + 1
....:
In [31]: values
Out[31]: [1, 3, 5]
In [32]: def f(s):
....: s = s.copy()
....: s.pop("a")
....: return s
....:
In [33]: df = pd.DataFrame({"a": [1, 2, 3], 'b': [4, 5, 6]})
In [34]: df.apply(f, axis="columns")
Out[34]:
b
0 4
1 5
2 6
NumPy 型における欠損値の表現#
NumPy 型の NA 表現としての np.nan
#
NumPy と Python 全般において、NA
(欠損値)のサポートが根本的に欠如しているため、NA
は以下のように表現される可能性がありました。
*マスク配列* ソリューション:データの配列と、値が存在するかどうかを示すブール値の配列。
特別なセンチネル値、ビットパターン、またはセンチネル値のセットを使用して、すべてのデータ型にわたって
NA
を表す。
特別な値 np.nan
(非数)が NumPy 型の NA
値として選択されました。また、DataFrame.isna()
や DataFrame.notna()
などの API 関数があり、すべてのデータ型にわたって NA 値を検出するために使用できます。ただし、この選択には、整数 NA のサポート に示すように、欠損している整数データを浮動小数点型に強制変換するという欠点があります。
NumPy 型の NA 型の昇格#
reindex()
などによって既存の Series
または DataFrame
に NA を導入すると、NA を格納するためにブール型と整数型は別のデータ型に昇格されます。昇格は次の表にまとめられています。
型クラス |
NA を格納するための昇格データ型 |
---|---|
|
変更なし |
|
変更なし |
|
|
|
|
整数 NA
のサポート#
高性能な NA
サポートが NumPy に根本的に組み込まれていないため、主な犠牲となるのは整数配列で NA を表現する機能です。例えば、
In [35]: s = pd.Series([1, 2, 3, 4, 5], index=list("abcde"))
In [36]: s
Out[36]:
a 1
b 2
c 3
d 4
e 5
dtype: int64
In [37]: s.dtype
Out[37]: dtype('int64')
In [38]: s2 = s.reindex(["a", "b", "c", "f", "u"])
In [39]: s2
Out[39]:
a 1.0
b 2.0
c 3.0
f NaN
u NaN
dtype: float64
In [40]: s2.dtype
Out[40]: dtype('float64')
このトレードオフは、主にメモリとパフォーマンスの理由から、そして結果の Series
が「数値」であり続けるように行われます。
欠損値を含む整数を表現する必要がある場合は、pandas または pyarrow によって提供される null 許容整数拡張データ型のいずれかを使用してください。
In [41]: s_int = pd.Series([1, 2, 3, 4, 5], index=list("abcde"), dtype=pd.Int64Dtype())
In [42]: s_int
Out[42]:
a 1
b 2
c 3
d 4
e 5
dtype: Int64
In [43]: s_int.dtype
Out[43]: Int64Dtype()
In [44]: s2_int = s_int.reindex(["a", "b", "c", "f", "u"])
In [45]: s2_int
Out[45]:
a 1
b 2
c 3
f <NA>
u <NA>
dtype: Int64
In [46]: s2_int.dtype
Out[46]: Int64Dtype()
In [47]: s_int_pa = pd.Series([1, 2, None], dtype="int64[pyarrow]")
In [48]: s_int_pa
Out[48]:
0 1
1 2
2 <NA>
dtype: int64[pyarrow]
詳細は、null 許容整数データ型 と PyArrow の機能 を参照してください。
NumPy を R のようにしないのはなぜですか?#
多くの人が、NumPy はよりドメイン特化型の統計プログラミング言語である R に存在する NA
サポートを単純にエミュレートするべきだと提案しています。その理由の一部は、NumPy の型階層にあります。
型クラス |
データ型 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
対照的に、R 言語には、integer
、numeric
(浮動小数点数)、character
、boolean
という少数の組み込みデータ型しかありません。NA
型は、各型に欠損値として使用される特別なビットパターンを予約することによって実装されます。これを NumPy の型階層全体で行うことは可能ですが、それはより大きなトレードオフ(特に 8 ビットおよび 16 ビットデータ型の場合)と実装作業になります。
ただし、R の NA
セマンティクスは、Int64Dtype
や PyArrow 型(ArrowDtype
)などのマスクされた NumPy 型を使用することで利用できるようになりました。
NumPy との違い#
Series
および DataFrame
オブジェクトの場合、var()
は N-1
で正規化して母集団分散の不偏推定量を生成しますが、NumPy の numpy.var()
は N で正規化して標本分散を測定します。cov()
は pandas と NumPy の両方で N-1
で正規化することに注意してください。
スレッドセーフティ#
pandas は 100% スレッドセーフではありません。既知の問題は、copy()
メソッドに関連しています。スレッド間で共有される DataFrame
オブジェクトのコピーを大量に行う場合は、データコピーが発生するスレッド内でロックを保持することをお勧めします。
詳細については、このリンク を参照してください。
バイトオーダーの問題#
Python を実行しているマシンとは異なるバイトオーダーのマシンで作成されたデータを処理する必要がある場合があります。この問題の一般的な症状は、次のようなエラーです。
Traceback
...
ValueError: Big-endian buffer not supported on little-endian compiler
この問題に対処するには、基になる NumPy 配列を Series
または DataFrame
コンストラクターに渡す *前* に、次のようなものを使用してネイティブシステムのバイトオーダーに変換する必要があります。
In [49]: x = np.array(list(range(10)), ">i4") # big endian
In [50]: newx = x.byteswap().view(x.dtype.newbyteorder()) # force native byteorder
In [51]: s = pd.Series(newx)
詳細については、バイトオーダーに関する NumPy のドキュメント を参照してください。