よくある質問 (FAQ)#

DataFrame のメモリ使用量#

DataFrame (インデックスを含む) のメモリ使用量は、info() を呼び出すときに表示されます。設定オプション display.memory_usage (オプションのリスト を参照) は、DataFrame のメモリ使用量が info() メソッドを呼び出すときに表示されるかどうかを指定します。

例えば、以下の 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/真偽値ステートメントを使用する#

pandas は、何かを bool に変換しようとするとエラーを発生させる NumPy の慣例に従います。これは if ステートメントや、ブール演算子 andornot を使用するときに発生します。以下のコードの結果がどうなるべきかは明確ではありません。

>>> 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)
   1575     @final
   1576     def __nonzero__(self) -> NoReturn:
-> 1577         raise ValueError(
   1578             f"The truth value of a {type(self).__name__} is ambiguous. "
   1579             "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   1580         )

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] になるだろうと予想したかもしれません。UDF を受け取る pandas メソッドを使用する場合、内部的に 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:3812, in Index.get_loc(self, key)
   3811 try:
-> 3812     return self._engine.get_loc(casted_key)
   3813 except KeyError as err:

File ~/work/pandas/pandas/pandas/_libs/index.pyx:167, in pandas._libs.index.IndexEngine.get_loc()

File ~/work/pandas/pandas/pandas/_libs/index.pyx:196, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/hashtable_class_helper.pxi:7088, in pandas._libs.hashtable.PyObjectHashTable.get_item()

File pandas/_libs/hashtable_class_helper.pxi:7096, 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:10381, in DataFrame.apply(self, func, axis, raw, result_type, args, by_row, engine, engine_kwargs, **kwargs)
  10367 from pandas.core.apply import frame_apply
  10369 op = frame_apply(
  10370     self,
  10371     func=func,
   (...)
  10379     kwargs=kwargs,
  10380 )
> 10381 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:5402, in Series.pop(self, item)
   5377 def pop(self, item: Hashable) -> Any:
   5378     """
   5379     Return item and drops from series. Raise KeyError if not found.
   5380 
   (...)
   5400     dtype: int64
   5401     """
-> 5402     return super().pop(item=item)

File ~/work/pandas/pandas/pandas/core/generic.py:947, in NDFrame.pop(self, item)
    946 def pop(self, item: Hashable) -> Series | Any:
--> 947     result = self[item]
    948     del self[item]
    950     return result

File ~/work/pandas/pandas/pandas/core/series.py:1130, in Series.__getitem__(self, key)
   1127     return self._values[key]
   1129 elif key_is_scalar:
-> 1130     return self._get_value(key)
   1132 # Convert generator to list before going through hashable part
   1133 # (We will iterate through the generator there to check for slices)
   1134 if is_iterator(key):

File ~/work/pandas/pandas/pandas/core/series.py:1246, in Series._get_value(self, label, takeable)
   1243     return self._values[label]
   1245 # Similar to Index.get_value, but we do not fall back to positional
-> 1246 loc = self.index.get_loc(label)
   1248 if is_integer(loc):
   1249     return self._values[loc]

File ~/work/pandas/pandas/pandas/core/indexes/base.py:3819, in Index.get_loc(self, key)
   3814     if isinstance(casted_key, slice) or (
   3815         isinstance(casted_key, abc.Iterable)
   3816         and any(isinstance(x, slice) for x in casted_key)
   3817     ):
   3818         raise InvalidIndexError(key)
-> 3819     raise KeyError(key) from err
   3820 except TypeError:
   3821     # If we have a listlike key, _check_indexing_error will raise
   3822     #  InvalidIndexError. Otherwise we fall through and re-raise
   3823     #  the TypeError.
   3824     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 型の欠損値表現#

np.nan を NumPy 型の NA 表現として使用#

NumPy や Python 全般で NA (欠損) のサポートが最初から不足しているため、NA は次のように表現できたはずです。

  • マスク付き配列ソリューション: データ配列と、値が存在するか欠損しているかを示すブール値の配列。

  • 特別な番人値、ビットパターン、または一連の番人値を使用して、データ型全体で NA を示す。

特別な値 np.nan (Not-A-Number) が NumPy 型の NA 値として選択され、DataFrame.isna()DataFrame.notna() のような API 関数は、データ型全体で NA 値を検出するために使用できます。しかし、この選択には、整数の NA のサポート に示されているように、欠損した整数データを浮動小数点型として強制するという欠点があります。

NumPy 型の NA 型の昇格#

Series または DataFramereindex() またはその他の手段を介して NA を導入する場合、NA を格納するためにブール型と整数型は異なるデータ型に昇格されます。昇格は以下の表にまとめられています。

型クラス

NA格納用の昇格データ型

浮動小数点

変更なし

object

変更なし

整数

float64 にキャスト

boolean

object にキャスト

整数の NA のサポート#

NumPy に高速な NA サポートが最初から組み込まれていないため、主な犠牲は整数配列で 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 言語には integernumeric (浮動小数点)、characterboolean といった数種類の組み込みデータ型しかありません。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

この問題に対処するには、Series または DataFrame コンストラクタに渡す**前**に、基になる NumPy 配列をネイティブシステムバイト順に変換する必要があります。次のような方法を使用します。

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 ドキュメントのバイト順 を参照してください。