web-dev-qa-db-fra.com

pandas: filtrer les lignes de DataFrame avec chaînage d'opérateurs

La plupart des opérations dans pandaspeuvent être effectuées avec une chaîne d'opérateurs (groupbyname__, aggregatename__, applyname__, etc.), mais le seul moyen que j'ai trouvé de filtrer les lignes est de l'indexer normal.

df_filtered = df[df['column'] == value]

Ceci est peu attrayant car il faut que j'assigne dfà une variable avant de pouvoir filtrer ses valeurs. Y a-t-il quelque chose de plus semblable au suivant?

df_filtered = df.mask(lambda x: x['column'] == value)
266
duckworthd

Je ne suis pas tout à fait sûr de ce que vous voulez, et votre dernière ligne de code n'aide pas non plus, mais de toute façon:

Le filtrage "chaîné" est effectué en "chaînant" les critères de l'index booléen.

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 souhaitez chaîner des méthodes, vous pouvez ajouter votre propre méthode de masque et l'utiliser.

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
330
Wouter Overmeire

Les filtres peuvent être chaînés à l’aide d’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 également être combinés en une seule requête:

df_filtered = df.query('a>0 and 0<b<2')
89
bscan

La réponse de @Lodagro est excellente. Je l'étendrais en généralisant la fonction masque comme suit:

def mask(df, f):
  return df[f(df)]

Ensuite, vous pouvez faire des choses comme:

df.mask(lambda x: x[0] < 0).mask(lambda x: x[1] > 0)
62
Daniel Velkov

Depuis version 0.18.1 , la méthode .loc accepte un appelable pour la sélection. Avec les fonctions lambda, vous pouvez créer des filtres chaînables 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 également omettre le .loc.

17
Rafael Barbosa

Je propose 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 pour rendre ce post plus utile.

pandas.DataFrame.query
querya été créé exactement à cette fin. Considérez la dataframe dfname__

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

Utilisons querypour 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
14
piRSquared

Ma réponse est semblable aux autres. Si vous ne voulez pas créer de nouvelle fonction, vous pouvez utiliser ce que les pandas ont déjà défini pour vous. Utilisez la méthode du tuyau.

df.pipe(lambda d: d[d['column'] == value])
7
Stewbaca

J'avais la même question, sauf que je voulais combiner les critères dans une condition OR. Le format donné par Wouter Overmeire combine les critères dans une condition AND de telle sorte que les deux doivent être satisfaits:

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 encapsulez chaque condition dans (... == True) et joignez les critères à un tuyau, les critères sont combinés dans une condition OR, satisfaite chaque fois que l’une ou l’autre de ces conditions est vraie:

df[((df.A==1) == True) | ((df.D==6) == True)]
7
sharon

pandas propose deux alternatives à la réponse de Wouter Overmeire qui ne nécessitent aucune substitution. On 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)
4
Pietro Battiston

Si vous souhaitez appliquer tous les masques booléens courants ainsi qu'un masque à usage général, vous pouvez insérer les éléments suivants dans un fichier, puis les affecter comme suit:

pd.DataFrame = apply_masks()

Usage:

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 cela peut rendre les choses un peu plus propres si vous coupez continuellement et changez les jeux de données en fonction des filtres. Il existe également un filtre polyvalent 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 __== '__main__':
    pass
4
dantes_419

Cette solution est plus rigoureuse 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 le référentiel entier: enregistrer le fichier et le faire

from where import where as W

devrait suffire. Ensuite, vous l'utilisez 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'utilisation 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 utilisez uniquement des cols booléens,

df.loc[W['cond1']].loc[W['cond2']]

peut être beaucoup plus efficace que

df.loc[W['cond1'] & W['cond2']]

car il évalue cond2 uniquement où cond1 est Truename__.

AVERTISSEMENT: J'ai d'abord donné cette réponse ailleurs parce que je n'avais pas vu cela.

3
Pietro Battiston

Je veux juste ajouter une démonstration en utilisant loc pour filtrer non seulement par lignes, mais également par colonnes et certains avantages de l'opération chaînée.

Le code ci-dessous peut filtrer les lignes par valeur.

df_filtered = df.loc[df['column'] == value]

En le modifiant un peu, vous pouvez également filtrer les colonnes.

df_filtered = df.loc[df['column'] == value, ['year', 'column']]

Alors, pourquoi voulons-nous une méthode chaînée? La réponse est que la lecture est simple si vous avez plusieurs opérations. Par exemple,

res =  df\
    .loc[df['station']=='USA', ['TEMP', 'RF']]\
    .groupby('year')\
    .agg(np.nanmean)
2
Ken T

Ceci est peu attrayant car il faut que j'assigne dfà une variable avant de pouvoir filtrer ses valeurs.

df[df["column_name"] != 5].groupby("other_column_name")

semble fonctionner: vous pouvez également imbriquer l'opérateur []. Peut-être l'ont-ils ajouté depuis que vous avez posé la question.

2
serv-inc

Vous pouvez également utiliser la bibliothèque numpy pour les opérations logiques. C'est assez rapide.

df[np.logical_and(df['A'] == 1 ,df['B'] == 6)]
1
Akash Basudevan

Si vous définissez vos colonnes pour une recherche en tant qu'index, vous pouvez utiliser DataFrame.xs() pour effectuer une section transversale. Ce n’est pas aussi polyvalent que les réponses query, mais cela peut ê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
1
naught101