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?
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
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
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