環境
- Python 3.11.2
- pandas 2.0.2
やりたいこと
MultiIndexである列を持つDataFrameに対して、行を絞り込みたいです。
以下のようなコードで実現したいです。
In [372]: data = [[numpy.nan, 2, 3], [4, numpy.nan, 6], [7, 8, numpy.nan]]
In [376]: df = pandas.DataFrame(data, columns=pandas.MultiIndex.from_tuples([("x","a"),("y","b"),("z","c")]))
In [377]: df
Out[377]:
x y z
a b c
0 NaN 2.0 3.0
1 4.0 NaN 6.0
2 7.0 8.0 NaN
In [378]: df[df[("y","b")]>0]
Out[378]:
x y z
a b c
0 NaN 2.0 3.0
2 7.0 8.0 NaN
起きたこと
列名が重複していて、かつ列名がソートされている状態のDataFrameがあります。
このDataFrameに対して、同様のコードで行を絞り込もうとすると期待する結果になりませんでした。
絞り込まれたDataFrameでは("y","b")
列の値は正しいのです。しかし、("x","a")
列はすべてNaNになっていました。
In [391]: df1 = pandas.DataFrame(data, columns=pandas.MultiIndex.from_tuples([("x","a"),("x","a"),("y","b")]))
In [392]: df1
Out[392]:
x y
a a b
0 NaN 2.0 3.0
1 4.0 NaN 6.0
2 7.0 8.0 NaN
# ("x","a")列がすべてNaNになっている
In [394]: df1[df1[("y","b")]>0]
Out[394]:
x y
a a b
0 NaN NaN 3.0
1 NaN NaN 6.0
2 NaN NaN NaN
原因と解決策
1つずつ確認していきます。
In [395]: df1[("y","b")]
Out[395]:
y
b
0 3.0
1 6.0
2 NaN
In [396]: type(df1[("y","b")])
Out[396]: pandas.core.frame.DataFrame
In [397]: df1[("y","b")]>0
Out[397]:
y
b
0 True
1 True
2 False
df1[("y","b")]
はpandas.Series
ではなくDataFrame
でした。
そのため、df1[df1[("y","b")]>0]
で行を絞り込むと、("y","b")
列以外がNaN
になるようです。
iloc[:,0]
で0番目の列を取得してSeries
を取得すれば、期待通り絞り込むことができました。
In [398]: df1[("y","b")].iloc[:,0]>0
Out[398]:
0 True
1 True
2 False
Name: (y, b), dtype: bool
In [399]: df1[df1[("y","b")].iloc[:,0]>0]
Out[399]:
x y
a a b
0 NaN 2.0 3.0
1 4.0 NaN 6.0
他に検証したこと
列名が重複していて、かつ列名がソートされていない場合
列名が重複していて、かつ列名がソートされていない状態のDataFrameで絞り込もうとすると、ValueError
が発生しました。
In [400]: df2 = pandas.DataFrame(data, columns=pandas.MultiIndex.from_tuples([("x","a"),("y","b"),("x","a")]))
...:
...:
In [401]: df2
Out[401]:
x y x
a b a
0 NaN 2.0 3.0
1 4.0 NaN 6.0
2 7.0 8.0 NaN
In [402]: df2[df2[("y","b")]>0]
<ipython-input-402-784a8fa103c9>:1: PerformanceWarning: indexing past lexsort depth may impact performance.
df2[df2[("y","b")]>0]
---------------------------------------------------------------------------
ValueError: cannot handle a non-unique multi-index!
原因は先ほどと同じなので、iloc[:,0]
でpandas.Series
を取得すれば、絞り込むことができました。
In [404]: df2[df2[("y","b")].iloc[:,0]>0]
<ipython-input-404-e6c5b3f34f81>:1: PerformanceWarning: indexing past lexsort depth may impact performance.
df2[df2[("y","b")].iloc[:,0]>0]
Out[404]:
x y x
a b a
0 NaN 2.0 3.0
2 7.0 8.0 NaN
ただし、列名がソートされていないとPerformanceWarning
が発生しました。
列がIndex
の場合
列がMultiIndex
ではなくIndex
の場合は、重複していない列でアクセスするとSeries
を取得できます。したがって、期待通り絞り込むことができました。
In [408]: df3 = pandas.DataFrame(data, columns=["x","x","y"])
In [419]: df3
Out[419]:
x x y
0 NaN 2.0 3.0
1 4.0 NaN 6.0
2 7.0 8.0 NaN
In [409]: df3["y"]
Out[409]:
0 3.0
1 6.0
2 NaN
Name: y, dtype: float64
In [410]: df3[df3["y"]>0]
Out[410]:
x x y
0 NaN 2.0 3.0
1 4.0 NaN 6.0
まとめ
以下のブログのケースもそうなのですが、MultiIndex
で重複している場合は予期しない動作になることが多そうです。MultiIndex
は常に一意にしておくのがよさそうです。