Comment découper une DataFrame MultiIndex avec le MultiIndex D'une autre

j'ai une base de données pandas avec 3 niveaux D'un MultiIndex. J'essaie d'extraire des lignes de cette dataframe selon une liste de valeurs qui correspondent à deux des niveaux.

j'ai quelque chose comme ça:

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

maintenant je veux prendre toutes les lignes où les niveaux d'index 'b' et 'c' sont dans cet index:

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

, c'est-à-dire les valeurs de hi ayant ('foo', 'can') ou ('bar', 'baz') dans les niveaux b et c respectivement: (1, 2, 5, 6, 9, 10) .

donc j'aimerais prendre un slice(None) au premier niveau, et tirer des tuples spécifiques sur les deuxième et troisième niveaux.

J'ai D'abord pensé que passer un objet Multi-index à .loc tirerait les valeurs / niveaux que je voulais, mais ça ne marche pas. Quelle est la meilleure façon de faire quelque chose comme cela?

15
demandé sur YaOzI 2015-03-26 00:11:12

3 réponses

voici un moyen d'obtenir cette tranche:

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

yield

           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

notez que vous pourriez avoir besoin de trier MultiIndex avant de pouvoir le trancher. Eh bien pandas est assez aimable de prévenir si vous avez besoin de le faire:

KeyError: 'MultiIndex Slicing requires the index to be fully lexsorted tuple len (3), lexsort depth (1)'

vous pouvez en savoir plus sur la façon d'utiliser les trancheuses dans le docs

si pour une raison quelconque l'utilisation de trancheuses n'est pas une option, voici une façon d'obtenir le même tranche selon la méthode .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))]

qui n'est manifestement pas aussi concis.

mise à jour:

Pour les conditions que vous avez mis à jour ici est une façon de le faire:

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]

la production:

           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
19
répondu Primer 2015-03-26 08:02:44

je recommande la" méthode 151910920 comme dans cette Q&R .

simplement en utilisant ceci, qui je pense est une façon plus naturelle d'exprimer:

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
répondu YaOzI 2017-05-23 12:25:33

je trouve intéressant que cela ne fonctionne pas:

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

on dirait qu'il "devrait", en quelque sorte. Dans tous les cas, voici une solution raisonnable:

supposons que les tuples que vous voulez découper sont dans l'index d'un autre DataFrame (puisqu'il sonne comme ils sont probablement sont dans votre cas!).

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

maintenant, trancher df par l'indice de other nous peut utiliser le fait que .loc / .ix vous permettent de donner une liste de tuples (voir le dernier exemple ici ).

construisons D'abord la liste des tuples que nous voulons:

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')]

maintenant nous pouvons passer cette liste à .ix ou .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
répondu LondonRob 2015-08-13 13:42:28