LoginSignup
0
0

列がMultiIndexで列名が重複されているDataFrameに対して、`df[df["col"]>0]`のように絞り込もうとすると期待通りの結果にならない

Last updated at Posted at 2023-06-02

環境

  • 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は常に一意にしておくのがよさそうです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0