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)
214
demandé sur Daniel Heilper 2012-08-08 21:25:37

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
279
répondu Wouter Overmeire 2016-12-20 15:23:26

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')
65
répondu bscan 2015-01-26 21:44:19

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)
56
répondu Daniel Velkov 2012-08-09 23:20:59

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
12
répondu piRSquared 2018-04-16 17:47:26

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 .

10
répondu Rafael Barbosa 2017-09-05 10:14:25

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)]
7
répondu sharon 2015-03-25 04:00:24

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])
6
répondu Stewbaca 2016-05-27 19:00:59

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
4
répondu dantes_419 2013-04-18 04:44:28

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)
1
répondu Ken T 2018-01-29 06:18:36

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.

1
répondu Pietro Battiston 2018-03-15 16:28:30

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)
1
répondu Pietro Battiston 2018-03-22 14:44:14

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.

1
répondu serv-inc 2018-04-19 08:17:01

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
0
répondu naught101 2017-07-26 03:35:33

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)]
0
répondu Akash Basudevan 2018-01-25 07:22:23