Remarque
Ce message sera structuré de la manière suivante:
- Les questions posées dans le PO seront abordées une par une
- Pour chaque question, une ou plusieurs méthodes applicables pour résoudre ce problème et obtenir le résultat attendu seront démontrées.
Des notes (un peu comme celle-ci) seront incluses pour les lecteurs intéressés à en savoir plus sur les fonctionnalités supplémentaires, les détails de mise en œuvre et d'autres informations superficielles sur le sujet en question. Ces notes ont été compilées en parcourant les documents et en découvrant diverses caractéristiques obscures, et à partir de ma propre expérience (certes limitée).
Tous les exemples de code ont été créés et testés sur pandas v0.23.4, python3.7 . Si quelque chose n'est pas clair, ou factuellement incorrect, ou si vous n'avez pas trouvé de solution applicable à votre cas d'utilisation, n'hésitez pas à suggérer une modification, demander des éclaircissements dans les commentaires ou ouvrir une nouvelle question, .... selon le cas .
Voici une introduction à certains idiomes courants (désormais appelés les quatre idiomes) que nous reviendrons fréquemment
DataFrame.loc
- Une solution générale de sélection par étiquette (+ pd.IndexSlice
pour les applications plus complexes impliquant des tranches)
DataFrame.xs
- Extraire une section transversale particulière d'un Series / DataFrame.
DataFrame.query
- Spécifiez les opérations de découpage et / ou de filtrage de manière dynamique (c'est-à-dire sous la forme d'une expression évaluée dynamiquement. Est plus applicable à certains scénarios qu'à d'autres. Consultez également cette section de la documentation pour les requêtes sur les MultiIndexes.
Indexation booléenne avec un masque généré à l'aide de MultiIndex.get_level_values
(souvent en conjonction avec Index.isin
, notamment lors d'un filtrage avec plusieurs valeurs) Ceci est également très utile dans certaines circonstances.
Il sera utile d'examiner les différents problèmes de découpage et de filtrage en termes des quatre idiomes pour mieux comprendre ce qui peut être appliqué à une situation donnée. Il est très important de comprendre que tous les idiomes ne fonctionneront pas aussi bien (voire pas du tout) dans toutes les circonstances. Si un idiome n'a pas été répertorié comme une solution potentielle à un problème ci-dessous, cela signifie que l'idiome ne peut pas être appliqué efficacement à ce problème.
question 1
Comment sélectionner des lignes ayant «un» au niveau «un»?
col
one two
a t 0
u 1
v 2
w 3
Vous pouvez utiliser loc
, comme solution à usage général applicable à la plupart des situations:
df.loc[['a']]
À ce stade, si vous obtenez
TypeError: Expected tuple, got str
Cela signifie que vous utilisez une ancienne version de pandas. Pensez à mettre à niveau! Sinon, utilisez df.loc[('a', slice(None)), :]
.
Vous pouvez également utiliser xs
ici, car nous extrayons une seule section transversale. Notez les arguments levels
et axis
(des valeurs par défaut raisonnables peuvent être prises ici).
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
Ici, l' drop_level=False
argument est nécessaire pour éviter xs
de laisser tomber le niveau "un" dans le résultat (le niveau sur lequel nous avons découpé).
Encore une autre option ici consiste à utiliser query
:
df.query("one == 'a'")
Si l'index n'avait pas de nom, vous devrez changer votre chaîne de requête en "ilevel_0 == 'a'"
.
Enfin, en utilisant get_level_values
:
df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']
De plus, comment pourrais-je supprimer le niveau «un» dans la sortie?
col
two
t 0
u 1
v 2
w 3
Cela peut être facilement fait en utilisant soit
df.loc['a'] # Notice the single string argument instead the list.
Ou,
df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')
Notez que nous pouvons omettre l' drop_level
argument (il est supposé être True
par défaut).
Remarque
Vous pouvez remarquer qu'un DataFrame filtré peut toujours avoir tous les niveaux, même s'ils n'apparaissent pas lors de l'impression du DataFrame. Par exemple,
v = df.loc[['a']]
print(v)
col
one two
a t 0
u 1
v 2
w 3
print(v.index)
MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']],
labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
names=['one', 'two'])
Vous pouvez vous débarrasser de ces niveaux en utilisant MultiIndex.remove_unused_levels
:
v.index = v.index.remove_unused_levels()
print(v.index)
MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']],
labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
names=['one', 'two'])
Question 1b
Comment découper toutes les lignes avec la valeur "t" au niveau "deux"?
col
one two
a t 0
b t 4
t 8
d t 12
Intuitivement, vous voudriez quelque chose impliquant slice()
:
df.loc[(slice(None), 't'), :]
It Just Works! ™ Mais c'est maladroit. Nous pouvons faciliter une syntaxe de découpage plus naturelle en utilisant l' pd.IndexSlice
API ici.
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
C'est beaucoup, beaucoup plus propre.
Remarque
Pourquoi la tranche de fin :
dans les colonnes est-elle requise? En effet, loc
peut être utilisé pour sélectionner et découper le long des deux axes ( axis=0
ou
axis=1
). Sans préciser explicitement sur quel axe le découpage doit être effectué, l'opération devient ambiguë. Voir la grande boîte rouge dans la documentation sur le tranchage .
Si vous souhaitez supprimer toute nuance d'ambiguïté, loc
accepte un axis
paramètre:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
Sans le axis
paramètre (c'est-à-dire simplement en faisant df.loc[pd.IndexSlice[:, 't']]
), le découpage est supposé être sur les colonnes, et a KeyError
sera déclenché dans ce cas.
Ceci est documenté dans les slicers . Pour les besoins de cet article, cependant, nous spécifierons explicitement tous les axes.
Avec xs
, c'est
df.xs('t', axis=0, level=1, drop_level=False)
Avec query
, c'est
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
Et enfin, avec get_level_values
, tu peux faire
df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
Tout cela dans le même sens.
question 2
Comment puis-je sélectionner les lignes correspondant aux éléments «b» et «d» du niveau «un»?
col
one two
b t 4
u 5
v 6
w 7
t 8
d w 11
t 12
u 13
v 14
w 15
En utilisant loc, cela se fait de la même manière en spécifiant une liste.
df.loc[['b', 'd']]
Pour résoudre le problème ci-dessus de sélection de «b» et «d», vous pouvez également utiliser query
:
items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')
Remarque
Oui, l'analyseur par défaut est 'pandas'
, mais il est important de souligner que cette syntaxe n'est pas conventionnellement python. L'analyseur Pandas génère un arbre d'analyse légèrement différent de l'expression. Ceci est fait pour rendre certaines opérations plus intuitives à spécifier. Pour plus d'informations, veuillez lire mon article sur
l'évaluation des expressions dynamiques dans les pandas en utilisant pd.eval () .
Et, avec get_level_values
+ Index.isin
:
df[df.index.get_level_values("one").isin(['b', 'd'])]
Question 2b
Comment obtenir toutes les valeurs correspondant à «t» et «w» au niveau «deux»?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
Avec loc
, cela n'est possible qu'en conjonction avec pd.IndexSlice
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
Le premier colon :
dans les pd.IndexSlice[:, ['t', 'w']]
moyens à couper en tranches à travers le premier niveau. À mesure que la profondeur du niveau interrogé augmente, vous devrez spécifier plus de tranches, une par niveau étant découpée. Cependant, vous n'aurez pas besoin de spécifier plus de niveaux au - delà de celui qui est découpé.
Avec query
, c'est
items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas')
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')
Avec get_level_values
et Index.isin
(similaire à ci-dessus):
df[df.index.get_level_values('two').isin(['t', 'w'])]
question 3
Comment puis-je récupérer une section transversale, c'est-à-dire une seule ligne ayant des valeurs spécifiques pour l'index df
? Plus précisément, comment puis-je récupérer la section transversale de ('c', 'u')
, donnée par
col
one two
c u 9
À utiliser loc
en spécifiant un tuple de clés:
df.loc[('c', 'u'), :]
Ou,
df.loc[pd.IndexSlice[('c', 'u')]]
Remarque
À ce stade, vous pouvez rencontrer un PerformanceWarning
qui ressemble à ceci:
PerformanceWarning: indexing past lexsort depth may impact performance.
Cela signifie simplement que votre index n'est pas trié. pandas dépend de l'index en cours de tri (dans ce cas, lexicographiquement, car nous avons affaire à des valeurs de chaîne) pour une recherche et une récupération optimales. Une solution rapide serait de trier votre DataFrame à l'avance en utilisant DataFrame.sort_index
. Ceci est particulièrement souhaitable du point de vue des performances si vous prévoyez d'effectuer plusieurs requêtes de ce type en tandem:
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
Vous pouvez également utiliser MultiIndex.is_lexsorted()
pour vérifier si l'index est trié ou non. Cette fonction renvoie True
ou en False
conséquence. Vous pouvez appeler cette fonction pour déterminer si une étape de tri supplémentaire est requise ou non.
Avec xs
, il s'agit à nouveau de passer simplement un seul tuple comme premier argument, avec tous les autres arguments définis sur leurs valeurs par défaut appropriées:
df.xs(('c', 'u'))
Avec query
, les choses deviennent un peu maladroites:
df.query("one == 'c' and two == 'u'")
Vous pouvez voir maintenant que cela va être relativement difficile à généraliser. Mais est toujours OK pour ce problème particulier.
Avec des accès couvrant plusieurs niveaux, get_level_values
peut toujours être utilisé, mais n'est pas recommandé:
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
Question 4
Comment sélectionner les deux lignes correspondant à ('c', 'u')
, et ('a', 'w')
?
col
one two
c u 9
a w 3
Avec loc
, c'est toujours aussi simple que:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
Avec query
, vous devrez générer dynamiquement une chaîne de requête en itérant sur vos sections et niveaux:
cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses)
query = '(' + ') or ('.join([
' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)])
for cs in cses
]) + ')'
print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))
df.query(query)
100% NE RECOMMANDEZ PAS! Mais c'est possible.
Question 5
Comment puis-je récupérer toutes les lignes correspondant à "a" au niveau "un" ou "t" au niveau "deux"?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
C'est en fait très difficile à faire loc
tout en garantissant l'exactitude et en maintenant la clarté du code. df.loc[pd.IndexSlice['a', 't']]
est incorrect, il est interprété comme df.loc[pd.IndexSlice[('a', 't')]]
(c.-à-d. sélectionner une section transversale). Vous pouvez penser à une solution pd.concat
pour gérer chaque étiquette séparément:
pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
col
one two
a t 0
u 1
v 2
w 3
t 0 # Does this look right to you? No, it isn't!
b t 4
t 8
d t 12
Mais vous remarquerez qu'une des lignes est dupliquée. En effet, cette ligne remplissait les deux conditions de tranchage et apparaissait ainsi deux fois. Vous devrez plutôt faire
v = pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]
Mais si votre DataFrame contient intrinsèquement des index en double (que vous voulez), cela ne les conservera pas. Utilisez avec une extrême prudence .
Avec query
, c'est stupidement simple:
df.query("one == 'a' or two == 't'")
Avec get_level_values
, c'est toujours simple, mais pas aussi élégant:
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2]
Question 6
Comment découper des sections transversales spécifiques? Pour "a" et "b", je voudrais sélectionner toutes les lignes avec les sous-niveaux "u" et "v", et pour "d", je voudrais sélectionner les lignes avec le sous-niveau "w".
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
C'est un cas spécial que j'ai ajouté pour aider à comprendre l'applicabilité des quatre idiomes - c'est un cas où aucun d'eux ne fonctionnera efficacement, car le découpage est très spécifique et ne suit aucun modèle réel.
Habituellement, des problèmes de découpage comme celui-ci nécessiteront de passer explicitement une liste de clés à loc
. Une façon de faire est avec:
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
Si vous souhaitez enregistrer un peu de frappe, vous reconnaîtrez qu'il existe un modèle pour découper "a", "b" et ses sous-niveaux, afin que nous puissions séparer la tâche de découpage en deux parties et concat
le résultat:
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
La spécification de découpage pour "a" et "b" est légèrement plus claire (('a', 'b'), ('u', 'v'))
car les mêmes sous-niveaux indexés sont les mêmes pour chaque niveau.
Question 7
Comment obtenir toutes les lignes dont les valeurs du niveau «deux» sont supérieures à 5?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
Cela peut être fait en utilisant query
,
df2.query("two > 5")
Et get_level_values
.
df2[df2.index.get_level_values('two') > 5]
Remarque
Semblable à cet exemple, nous pouvons filtrer en fonction de toute condition arbitraire en utilisant ces constructions. En général, il est utile de s'en souvenir loc
et xs
sont spécifiquement pour l'indexation basée sur des étiquettes, tandis que query
et
get_level_values
sont utiles pour créer des masques conditionnels généraux pour le filtrage.
Question bonus
Et si j'ai besoin de découper une MultiIndex
colonne ?
En fait, la plupart des solutions ici s'appliquent également aux colonnes, avec des modifications mineures. Considérer:
np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
list('ABCD'), list('efgh')
], names=['one','two'])
df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)
one A B C D
two e f g h e f g h e f g h e f g h
0 5 0 3 3 7 9 3 5 2 4 7 6 8 8 1 6
1 7 7 8 1 5 9 8 9 4 3 0 3 5 0 2 3
2 8 1 3 3 3 7 0 1 9 9 0 4 7 3 2 7
Voici les modifications suivantes que vous devrez apporter aux quatre expressions idiomatiques pour qu'elles fonctionnent avec des colonnes.
Pour trancher loc
, utilisez
df3.loc[:, ....] # Notice how we slice across the index with `:`.
ou,
df3.loc[:, pd.IndexSlice[...]]
Pour utiliser xs
le cas échéant, passez simplement un argument axis=1
.
Vous pouvez accéder aux valeurs au niveau de la colonne directement à l'aide de df.columns.get_level_values
. Vous devrez alors faire quelque chose comme
df.loc[:, {condition}]
Où {condition}
représente une condition construite en utilisant columns.get_level_values
.
Pour l'utiliser query
, votre seule option est de transposer, d'interroger sur l'index et de transposer à nouveau:
df3.T.query(...).T
Non recommandé, utilisez l'une des 3 autres options.
level
argument deIndex.isin
!