環境
- Python 3.12.4
- pandas 2.2.3
- numpy 1.26.4
はじめに
pandasで予期しないZeroDivisionErrorにハマったので、ゼロ除算の挙動を整理しました。
ゼロ除算の前提
プリミティブな数値型の場合
ゼロで割るとZeroDivisionErrorが発生します。
In [652]: 1 / 0
---------------------------------------------------------------------------
ZeroDivisionError: division by zero
In [653]: 0 / 0
---------------------------------------------------------------------------
ZeroDivisionError: division by zero
numpyの数値型の場合
ゼロで割ってもエラーは発生しませんが、RuntimeWarningは表示されます。
In [659]: np.int64(1) / 0
<ipython-input-659-f1425159271a>:1: RuntimeWarning: divide by zero encountered in scalar divide
np.int64(1) / 0
Out[659]: inf
In [661]: np.int64(0) / 0
<ipython-input-661-a8c114fc651e>:1: RuntimeWarning: invalid value encountered in scalar divide
np.int64(0) / 0
Out[661]: nan
numpy.ndarrayも同様の挙動です。
In [654]: arr = np.array([0, 1])
In [655]: arr
Out[655]: array([0, 1])
In [656]: arr / 0
<ipython-input-656-db39a1c38901>:1: RuntimeWarning: divide by zero encountered in divide
arr / 0
<ipython-input-656-db39a1c38901>:1: RuntimeWarning: invalid value encountered in divide
arr / 0
Out[656]: array([nan, inf])
本題
pandas.Seriesに対してゼロで割ってもエラーは発生しません。またwarningも表示されません。
In [595]: a = pd.Series([0, 1])
In [596]: a
Out[596]:
0 0
1 1
dtype: int64
# 要素の型は、numpyの数値型
In [597]: type(a[0])
Out[597]: numpy.int64
In [598]: a / 0
Out[598]:
0 NaN
1 inf
dtype: float64
しかし、astype("object")でdtypeをobjectにするとZeroDivisionErrorが発生します。これは、pandas.Sereisの各要素がnumpyの数値型ではなくプリミティブな数値型であるためです。astype("object")により、数値の型がnumpy.int64からintに変換されました。
In [605]: b = a.astype("object")
In [606]: b
Out[606]:
0 0
1 1
dtype: object
# 要素の型は、プリミティブな数値型
In [607]: type(b[0])
Out[607]: int
In [608]: b / 0
---------------------------------------------------------------------------
ZeroDivisionError: division by zero
dtypeがobjectのSeriesに、numpyの数値型である値が格納されるケース
上の例では、dtypeがobjectのSeriesにプリミティブな数値型の値が格納されていますが、numpyの数値型である値を含める方法もあります。
pandas.Series()にnumpyの数値型の値を格納する
In [648]: c = pd.Series([np.int64(0), np.int64(1)], dtype="object")
In [649]: c
Out[649]:
0 0
1 1
dtype: object
In [650]: type(c[0])
Out[650]: numpy.int64
In [651]: c / 0
Out[651]:
0 NaN
1 inf
dtype: object
pandas.DataFrmae.ilocでpandas.Seriesを取得する
In [677]: df = pd.DataFrame({"name":["Alice","Bob"], "count":[0,1]})
In [698]: df
Out[698]:
name count
0 Alice 0
1 Bob 1
In [699]: row = df.iloc[0]
In [700]: row
Out[700]:
name Alice
count 0
Name: 0, dtype: object
In [701]: type(row["count"])
Out[701]: numpy.int64
In [706]: row[1:] / 0
Out[706]:
count NaN
Name: 0, dtype: object
dtypeがobjectのSeriesに、プリミティブな数値型の値が格納されるケース
以下のケースでは、dtypeがobjectのSeriesに、プリミティブな数値型の値が格納されます。ゼロで割るとZeroDivisionErrorが発生するので注意してください。
astype("object")でpandas.Seriesを取得する
すでに例を示したので、省略します。
pandas.DataFrmae.iterrows()でpandas.Seriesを取得する
In [708]: for i, row in df.iterrows():
...: print(row)
...: print(type(row["count"]))
...: row[1:] / 0
...:
name Alice
count 0
Name: 0, dtype: object
<class 'int'>
---------------------------------------------------------------------------
ZeroDivisionError
pandas.DataFrmae.applyで関数を行に対して適用する
In [331]: df = pd.DataFrame({"name":["Alice","Bob"], "count":[0,1]})
In [342]: def foo(row):
...: print(f"{type(row["count"])=}")
...: return row
...:
In [343]: df.apply(foo, axis=1)
type(row["count"])=<class 'int'>
type(row["count"])=<class 'int'>
Out[343]:
name count
0 Alice 0
1 Bob 1
pandas.Series()にnumpy.arrayを渡す
In [736]: d = pd.Series(np.array([1,0]), dtype="object")
In [737]: d
Out[737]:
0 1
1 0
dtype: object
In [738]: type(d[0])
Out[738]: int
In [739]: d / 0
---------------------------------------------------------------------------
ZeroDivisionError: division by zero
まとめ
-
pandas.Seriesのdtypeが数値型の場合は、ゼロで割ってもZeroDivisionErrorが発生しません。これは、各要素の型がnumpyの数値型であるためです。numpyの数値型の値に対しては、ゼロで割ってもZeroDivisionErrorが発生しません。 -
pandas.Seriesのdtypeがobjectの場合は、ゼロで割るとZeroDivisionErrorが発生するときがあります。astype()やiterrows()でdtypeがobjectであるSeriesを取得したときは、ゼロで割るとZeroDivisionErrorが発生します。各要素の値がプリミティブな数値型であるためです。
補足
numpy.arrayのdtypeがobjectの場合
numpy.arrayのdtypeがobjectの場合は、pandas.Series同様ZeroDivisionErrorが発生するときがあります。
numpy.ndarray.astype("object")を実行する
In [784]: a = np.array([1,0])
In [785]: a
Out[785]: array([1, 0])
In [788]: type(a[0])
Out[788]: numpy.int64
In [786]: b = a.astype("object")
In [787]: b
Out[787]: array([1, 0], dtype=object)
In [789]: type(b[0])
Out[789]: int
In [790]: b / 0
---------------------------------------------------------------------------
ZeroDivisionError: division by zero
numpy.array()のdtype引数を指定する
In [802]: c = np.array([1,0], dtype="object")
In [803]: c
Out[803]: array([1, 0], dtype=object)
In [804]: type(c[0])
Out[804]: int
In [805]: c / 0
---------------------------------------------------------------------------
ZeroDivisionError: division by zero