web-dev-qa-db-ja.com

あるMultiIndexDataFrameを別のMultiIndexでスライスする方法

3つのレベルのMultiIndexを持つpandasデータフレームがあります。2つのレベルに対応する値のリストに従って、このデータフレームの行を引き出しようとしています。

私はこのようなものを持っています:

_ix = pd.MultiIndex.from_product([[1, 2, 3], ['foo', 'bar'], ['baz', 'can']], names=['a', 'b', 'c'])
data = np.arange(len(ix))
df = pd.DataFrame(data, index=ix, columns=['hi'])
print(df)

           hi
a b   c      
1 foo baz   0
      can   1
  bar baz   2
      can   3
2 foo baz   4
      can   5
  bar baz   6
      can   7
3 foo baz   8
      can   9
  bar baz  10
      can  11
_

ここで、インデックスレベル「b」と「c」がこのインデックスにあるすべての行を取得します。

_ix_use = pd.MultiIndex.from_tuples([('foo', 'can'), ('bar', 'baz')], names=['b', 'c'])
_

つまり、レベルhibにそれぞれ_('foo', 'can')_または_('bar', 'baz')_を持つcの値:_(1, 2, 5, 6, 9, 10)_。

したがって、最初のレベルでslice(None)を取得し、2番目と3番目のレベルで特定のタプルを引き出したいと思います。

当初、マルチインデックスオブジェクトを.locに渡すと、必要な値/レベルが引き出されると思っていましたが、これは機能していません。このようなことをするための最良の方法は何ですか?

17
choldgraf

このスライスを取得する方法は次のとおりです。

_df.sort_index(inplace=True)
idx = pd.IndexSlice
df.loc[idx[:, ('foo','bar'), 'can'], :]
_

降伏

_           hi
a b   c      
1 bar can   3
  foo can   1
2 bar can   7
  foo can   5
3 bar can  11
  foo can   9
_

スライスする前にMultiIndexをソートする必要がある場合があることに注意してください。まあpandasはあなたがそれをする必要がある場合に警告するのに十分親切です:

_KeyError: 'MultiIndex Slicing requires the index to be fully lexsorted Tuple len (3), lexsort depth (1)'
_

docs でスライサーの使用方法の詳細を読むことができます

何らかの理由でスライサーを使用するオプションがない場合は、.isin()メソッドを使用して同じスライスを取得する方法があります。

_df[df.index.get_level_values('b').isin(ix_use.get_level_values(0)) & df.index.get_level_values('c').isin(ix_use.get_level_values(1))]
_

これは明らかにそれほど簡潔ではありません。

更新:

ここで更新した条件については、それを行う方法があります。

_cond1 = (df.index.get_level_values('b').isin(['foo'])) & (df.index.get_level_values('c').isin(['can']))
cond2 = (df.index.get_level_values('b').isin(['bar'])) & (df.index.get_level_values('c').isin(['baz']))
df[cond1 | cond2]
_

生産:

_           hi
a b   c      
1 foo can   1
  bar baz   2
2 foo can   5
  bar baz   6
3 foo can   9
  bar baz  10
_
20
Primer

query()メソッド と同じように このQ&A をお勧めします。

これを使用するだけで、より自然な表現方法だと思います。

In [27]: df.query("(b == 'foo' and c == 'can') or (b == 'bar' and c == 'baz')")
Out[27]: 
           hi
a b   c      
1 foo can   1
  bar baz   2
2 foo can   5
  bar baz   6
3 foo can   9
  bar baz  10
2
YaOzI

これが機能しないのは興味深いと思います。

In [45]: df.loc[(idx[:, 'foo', 'can'], idx[:, 'bar', 'baz']), ]
Out[45]: 
           hi
a b   c      
1 bar baz   2
      can   3
  foo baz   0
      can   1
2 bar baz   6
      can   7
  foo baz   4
      can   5
3 bar baz  10
      can  11
  foo baz   8
      can   9

どういうわけか、それは「あるべき」ように見えます。いずれにせよ、ここに合理的な回避策があります:

スライスするタプルが別のDataFrameのインデックスにあると仮定しましょう(あなたの場合はおそらくareのように聞こえるので!)。

In [53]: ix_use = pd.MultiIndex.from_tuples([('foo', 'can'), ('bar', 'baz')], names=['b', 'c'])
In [55]: other = pd.DataFrame(dict(a=1), index=ix_use)
In [56]: other
Out[56]: 
         a
b   c     
foo can  1
bar baz  1

ここで、dfotherのインデックスでスライスするために、.loc/.ixでタプルのリストを指定できるという事実を使用できます(最後の例 ここ を参照)。

まず、必要なタプルのリストを作成しましょう。

In [13]: idx = [(x, ) + y for x in df.index.levels[0] for y in other.index.values]
In [14]: idx
Out[14]: 
[(1, 'foo', 'can'),
 (1, 'bar', 'baz'),
 (2, 'foo', 'can'),
 (2, 'bar', 'baz'),
 (3, 'foo', 'can'),
 (3, 'bar', 'baz')]

これで、このリストを.ixまたは.locに渡すことができます。

In [17]: df.ix[idx]
Out[17]: 
           hi
a b   c      
1 foo can   1
  bar baz   2
2 foo can   5
  bar baz   6
3 foo can   9
  bar baz  10
0
LondonRob