pandas: filtrer les lignes de DataFrame avec chaînage de l'opérateur
la plupart des opérations dans pandas
peuvent être effectuées avec l'opérateur chaînage ( groupby
, aggregate
, apply
, etc), mais le seul moyen que j'ai trouvé pour filtrer les lignes est par l'intermédiaire de l'indexation des crochets normaux
df_filtered = df[df['column'] == value]
ce n'est pas attrayant car il exige que j'attribue df
à une variable avant de pouvoir filtrer sur ses valeurs. Est-il quelque chose de plus semblable à la suivante?
df_filtered = df.mask(lambda x: x['column'] == value)
14 réponses
Je ne suis pas entièrement sûr de ce que vous voulez, et votre dernière ligne de code n'aide pas non plus, mais de toute façon:
"Enchaînés" le filtrage est effectué par le "chaînage" les critères dans le booléen index.
In [96]: df
Out[96]:
A B C D
a 1 4 9 1
b 4 5 0 2
c 5 5 1 0
d 1 3 9 6
In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
A B C D
d 1 3 9 6
si vous voulez enchaîner les méthodes, vous pouvez ajouter votre propre méthode de masque et utiliser celle-ci.
In [90]: def mask(df, key, value):
....: return df[df[key] == value]
....:
In [92]: pandas.DataFrame.mask = mask
In [93]: df = pandas.DataFrame(np.random.randint(0, 10, (4,4)), index=list('abcd'), columns=list('ABCD'))
In [95]: df.ix['d','A'] = df.ix['a', 'A']
In [96]: df
Out[96]:
A B C D
a 1 4 9 1
b 4 5 0 2
c 5 5 1 0
d 1 3 9 6
In [97]: df.mask('A', 1)
Out[97]:
A B C D
a 1 4 9 1
d 1 3 9 6
In [98]: df.mask('A', 1).mask('D', 6)
Out[98]:
A B C D
d 1 3 9 6
filtres peuvent être enchaînés en utilisant un Pandas requête :
df = pd.DataFrame( np.random.randn(30,3), columns = ['a','b','c'])
df_filtered = df.query('a>0').query('0<b<2')
Les filtres peuvent aussi être combinés en une seule requête:
df_filtered = df.query('a>0 and 0<b<2')
la réponse de @lodagro est excellente. Je voudrais l'étendre en généralisant la fonction de masque comme:
def mask(df, f):
return df[f(df)]
alors vous pouvez faire des choses comme:
df.mask(lambda x: x[0] < 0).mask(lambda x: x[1] > 0)
j'offre ceci pour des exemples supplémentaires. C'est la même réponse que https://stackoverflow.com/a/28159296/
je vais ajouter d'autres modifications à faire ce post plus utile.
pandas.DataFrame.query
query
a été conçu dans ce but précis. Considérons le dataframe df
import pandas as pd
import numpy as np
np.random.seed([3,1415])
df = pd.DataFrame(
np.random.randint(10, size=(10, 5)),
columns=list('ABCDE')
)
df
A B C D E
0 0 2 7 3 8
1 7 0 6 8 6
2 0 2 0 4 9
3 7 3 2 4 3
4 3 6 7 7 4
5 5 3 7 5 9
6 8 7 6 4 7
7 6 2 6 6 5
8 2 8 7 5 8
9 4 7 6 1 5
query
pour filtrer toutes les lignes où D > B
df.query('D > B')
A B C D E
0 0 2 7 3 8
1 7 0 6 8 6
2 0 2 0 4 9
3 7 3 2 4 3
4 3 6 7 7 4
5 5 3 7 5 9
7 6 2 6 6 5
que nous enchaînons
df.query('D > B').query('C > B')
# equivalent to
# df.query('D > B and C > B')
# but defeats the purpose of demonstrating chaining
A B C D E
0 0 2 7 3 8
1 7 0 6 8 6
4 3 6 7 7 4
5 5 3 7 5 9
7 6 2 6 6 5
depuis version 0.18.1 la méthode .loc
accepte un appel à la sélection. Avec les fonctions lambda, vous pouvez créer des filtres très flexibles:
import numpy as np
import pandas as pd
df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD'))
df.loc[lambda df: df.A == 80] # equivalent to df[df.A == 80] but chainable
df.sort_values('A').loc[lambda df: df.A > 80].loc[lambda df: df.B > df.A]
si vous ne faites que Filtrer, vous pouvez aussi omettre le .loc
.
j'ai eu la même question sauf que je voulais combiner les critères en une condition ou. Le format donné par Wouter Overmeire combine les critères en une et condition telle que les deux doivent être remplies:
In [96]: df
Out[96]:
A B C D
a 1 4 9 1
b 4 5 0 2
c 5 5 1 0
d 1 3 9 6
In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
A B C D
d 1 3 9 6
mais j'ai trouvé que, si vous envelopper chaque condition dans (... == True)
et joindre les critères avec un tuyau, les critères sont combinés dans un ou condition, satisfait chaque fois que l'un d'eux est vrai:
df[((df.A==1) == True) | ((df.D==6) == True)]
ma réponse est similaire aux autres. Si vous ne voulez pas créer une nouvelle fonction, vous pouvez utiliser ce pandas a définies pour vous déjà. Utilisez la méthode du tuyau.
df.pipe(lambda d: d[d['column'] == value])
si vous souhaitez appliquer tous les masques booléens courants ainsi que d'un masque d'usage général, vous pouvez jeter ce qui suit dans un fichier et puis tout simplement les assigner comme suit:
pd.DataFrame = apply_masks()
Utilisation:
A = pd.DataFrame(np.random.randn(4, 4), columns=["A", "B", "C", "D"])
A.le_mask("A", 0.7).ge_mask("B", 0.2)... (May be repeated as necessary
c'est un peu hacky mais ça peut rendre les choses un peu plus propres si vous coupez et changez continuellement les ensembles de données selon les filtres. Il y a aussi un filtre universel adapté de Daniel Velkov ci-dessus dans la fonction gen_mask que vous pouvez utiliser avec les fonctions lambda ou autrement si vous le souhaitez.
fichier à sauvegarder (j'utilise masks.py):
import pandas as pd
def eq_mask(df, key, value):
return df[df[key] == value]
def ge_mask(df, key, value):
return df[df[key] >= value]
def gt_mask(df, key, value):
return df[df[key] > value]
def le_mask(df, key, value):
return df[df[key] <= value]
def lt_mask(df, key, value):
return df[df[key] < value]
def ne_mask(df, key, value):
return df[df[key] != value]
def gen_mask(df, f):
return df[f(df)]
def apply_masks():
pd.DataFrame.eq_mask = eq_mask
pd.DataFrame.ge_mask = ge_mask
pd.DataFrame.gt_mask = gt_mask
pd.DataFrame.le_mask = le_mask
pd.DataFrame.lt_mask = lt_mask
pd.DataFrame.ne_mask = ne_mask
pd.DataFrame.gen_mask = gen_mask
return pd.DataFrame
if __name__ == '__main__':
pass
veut juste ajouter une démonstration en utilisant loc
pour filtrer non seulement par des lignes mais aussi par des colonnes et quelques mérites à l'opération enchaînée.
le code ci-dessous peut filtrer les lignes par valeur.
df_filtered = df.loc[df['column'] == value]
En la modifiant un peu, vous pouvez filtrer les colonnes.
df_filtered = df.loc[df['column'] == value, ['year', 'column']]
alors pourquoi voulons-nous une méthode enchaînée? La réponse est qu'il est simple à lire si vous avez de nombreuses opérations. Exemple,
res = df\
.loc[df['station']=='USA', ['TEMP', 'RF']]\
.groupby('year')\
.agg(np.nanmean)
Cette solution est plus hackish en termes de mise en œuvre, mais je la trouve beaucoup plus propre en termes d'utilisation, et elle est certainement plus générale que les autres proposées.
https://github.com/toobaz/generic_utils/blob/master/generic_utils/pandas/where.py
vous n'avez pas besoin de télécharger l'intégralité du rapport: sauvegarder le fichier et faire
from where import where as W
devrait suffire. Alors vous l'utiliser comme ceci:
df = pd.DataFrame([[1, 2, True],
[3, 4, False],
[5, 7, True]],
index=range(3), columns=['a', 'b', 'c'])
# On specific column:
print(df.loc[W['a'] > 2])
print(df.loc[-W['a'] == W['b']])
print(df.loc[~W['c']])
# On entire - or subset of a - DataFrame:
print(df.loc[W.sum(axis=1) > 3])
print(df.loc[W[['a', 'b']].diff(axis=1)['b'] > 1])
un exemple d'usage un peu moins stupide:
data = pd.read_csv('ugly_db.csv').loc[~(W == '$null$').any(axis=1)]
soit dit en passant: même dans le cas où vous n'utilisez que des cols booléens,
df.loc[W['cond1']].loc[W['cond2']]
peut être beaucoup plus efficace que
df.loc[W['cond1'] & W['cond2']]
parce qu'il évalue cond2
seulement où cond1
est True
.
AVERTISSEMENT: j'ai d'abord donné cette réponse ailleurs parce que je ne l'avais pas vu.
pandas fournit deux alternatives à la réponse de Wouter Overmeire qui ne nécessitent pas de passer outre. L'un est .loc[.]
avec un callable, comme dans
df_filtered = df.loc[lambda x: x['column'] == value]
l'autre est .pipe()
, comme dans
df_filtered = df.pipe(lambda x: x['column'] == value)
ce n'est pas attrayant car il exige que j'attribue
df
à une variable avant de pouvoir filtrer sur ses valeurs.
df[df["column_name"] != 5].groupby("other_column_name")
semble fonctionner: vous pouvez également enchaîner l'opérateur []
. Ils l'ont peut-être ajouté Depuis que vous avez posé la question.
si vous définissez vos colonnes de recherche comme index, alors vous pouvez utiliser DataFrame.xs()
pour prendre une section transversale. Ce n'est pas aussi polyvalent que les réponses query
, mais il pourrait être utile dans certaines situations.
import pandas as pd
import numpy as np
np.random.seed([3,1415])
df = pd.DataFrame(
np.random.randint(3, size=(10, 5)),
columns=list('ABCDE')
)
df
# Out[55]:
# A B C D E
# 0 0 2 2 2 2
# 1 1 1 2 0 2
# 2 0 2 0 0 2
# 3 0 2 2 0 1
# 4 0 1 1 2 0
# 5 0 0 0 1 2
# 6 1 0 1 1 1
# 7 0 0 2 0 2
# 8 2 2 2 2 2
# 9 1 2 0 2 1
df.set_index(['A', 'D']).xs([0, 2]).reset_index()
# Out[57]:
# A D B C E
# 0 0 2 2 2 2
# 1 0 2 1 1 0
vous pouvez également utiliser la bibliothèque numpy pour des opérations logiques. Son assez rapide.
df[np.logical_and(df['A'] == 1 ,df['B'] == 6)]