Sélection avec des critères complexes de pandas.DataFrame

235

Par exemple, j'ai un DF simple:

import pandas as pd
from random import randint

df = pd.DataFrame({'A': [randint(1, 9) for x in xrange(10)],
                   'B': [randint(1, 9)*10 for x in xrange(10)],
                   'C': [randint(1, 9)*100 for x in xrange(10)]})

Puis-je sélectionner des valeurs dans 'A' pour lesquelles les valeurs correspondantes pour 'B' seront supérieures à 50 et pour 'C' - pas égal à 900, en utilisant les méthodes et les idiomes de Pandas?

Gill Bates
la source
df.queryet pd.evalsemblent être de bons ajustements pour ce cas d'utilisation. Pour plus d'informations sur la pd.eval()famille de fonctions, leurs caractéristiques et leurs cas d'utilisation, veuillez consulter Évaluation des expressions dynamiques dans les pandas à l'aide de pd.eval () .
cs95
Autant vérifier la réponse de @Gecko dans: stackoverflow.com/questions/13611065/…
Nicholas Humphrey

Réponses:

391

Sûr! Installer:

>>> import pandas as pd
>>> from random import randint
>>> df = pd.DataFrame({'A': [randint(1, 9) for x in range(10)],
                   'B': [randint(1, 9)*10 for x in range(10)],
                   'C': [randint(1, 9)*100 for x in range(10)]})
>>> df
   A   B    C
0  9  40  300
1  9  70  700
2  5  70  900
3  8  80  900
4  7  50  200
5  9  30  900
6  2  80  700
7  2  80  400
8  5  80  300
9  7  70  800

Nous pouvons appliquer des opérations sur les colonnes et obtenir des objets de la série booléenne:

>>> df["B"] > 50
0    False
1     True
2     True
3     True
4    False
5    False
6     True
7     True
8     True
9     True
Name: B
>>> (df["B"] > 50) & (df["C"] == 900)
0    False
1    False
2     True
3     True
4    False
5    False
6    False
7    False
8    False
9    False

[Mise à jour, pour passer au nouveau style .loc]:

Et puis nous pouvons les utiliser pour indexer dans l'objet. Pour un accès en lecture, vous pouvez chaîner des index:

>>> df["A"][(df["B"] > 50) & (df["C"] == 900)]
2    5
3    8
Name: A, dtype: int64

mais vous pouvez avoir des ennuis à cause de la différence entre une vue et une copie en faisant cela pour l'accès en écriture. Vous pouvez utiliser à la .locplace:

>>> df.loc[(df["B"] > 50) & (df["C"] == 900), "A"]
2    5
3    8
Name: A, dtype: int64
>>> df.loc[(df["B"] > 50) & (df["C"] == 900), "A"].values
array([5, 8], dtype=int64)
>>> df.loc[(df["B"] > 50) & (df["C"] == 900), "A"] *= 1000
>>> df
      A   B    C
0     9  40  300
1     9  70  700
2  5000  70  900
3  8000  80  900
4     7  50  200
5     9  30  900
6     2  80  700
7     2  80  400
8     5  80  300
9     7  70  800

Notez que j'ai accidentellement tapé == 900et non != 900, ou ~(df["C"] == 900), mais je suis trop paresseux pour le corriger. Exercice pour le lecteur. : ^)

DSM
la source
5
À propos de la .locmise à jour - il serait bon que vous clarifiiez où nous obtenons une copie et où une vue.
Gill Bates
3
est-il possible de filtrer une trame de données pandas et d'utiliser l'opérateur OU. Par exemple, s'il y avait une colonne mois, pourriez-vous dire df = data ['month' == JAN OR 'month' == FEB]? Et peut-être inclure une deuxième colonne rendant la requête plus complexe, newdf où col_month = jan OU feb ET col_day = LUNDI ou WENDNESDAY
yoshiserry
7
@yoshiserry: veuillez poser cette question séparément. Personne ne le verra ici dans les commentaires sur une ancienne réponse.
DSM
2
N'oubliez pas les parenthèses - vous obtiendrez des erreurs étranges comme{TypeError}cannot compare a dtyped [int64] array with a scalar of type [bool]
Mr_and_Mrs_D
Cette utilisation des parenthèses ne conduit-elle pas à des calculs sur toute la série? Et si nous voulons répéter le sous-ensemble pour plus d'efficacité?
ifly6
56

Une autre solution consiste à utiliser la requête méthode de :

import pandas as pd

from random import randint
df = pd.DataFrame({'A': [randint(1, 9) for x in xrange(10)],
                   'B': [randint(1, 9) * 10 for x in xrange(10)],
                   'C': [randint(1, 9) * 100 for x in xrange(10)]})
print df

   A   B    C
0  7  20  300
1  7  80  700
2  4  90  100
3  4  30  900
4  7  80  200
5  7  60  800
6  3  80  900
7  9  40  100
8  6  40  100
9  3  10  600

print df.query('B > 50 and C != 900')

   A   B    C
1  7  80  700
2  4  90  100
4  7  80  200
5  7  60  800

Maintenant, si vous souhaitez modifier les valeurs renvoyées dans la colonne A, vous pouvez enregistrer leur index:

my_query_index = df.query('B > 50 & C != 900').index

.... et utilisez .ilocpour les changer c'est à dire:

df.iloc[my_query_index, 0] = 5000

print df

      A   B    C
0     7  20  300
1  5000  80  700
2  5000  90  100
3     4  30  900
4  5000  80  200
5  5000  60  800
6     3  80  900
7     9  40  100
8     6  40  100
9     3  10  600
Nikos Tavoularis
la source
12

Et n'oubliez pas d'utiliser des parenthèses!

Gardez à l'esprit que l' &opérateur a priorité sur les opérateurs tels que >ou <etc. C'est pourquoi

4 < 5 & 6 > 4

évalue à False. Par conséquent, si vous utilisez pd.loc, vous devez mettre des crochets autour de vos instructions logiques, sinon vous obtenez une erreur. Voilà pourquoi:

df.loc[(df['A'] > 10) & (df['B'] < 15)]

au lieu de

df.loc[df['A'] > 10 & df['B'] < 15]

ce qui entraînerait

TypeError: impossible de comparer un tableau dtypé [float64] avec un scalaire de type [bool]

Tomasz Bartkowiak
la source
3

Vous pouvez utiliser des pandas qui ont des fonctions intégrées pour la comparaison. Donc, si vous souhaitez sélectionner des valeurs de "A" qui sont remplies par les conditions de "B" et "C" (en supposant que vous souhaitez récupérer un objet pandas DataFrame)

df[['A']][df.B.gt(50) & df.C.ne(900)]

df[['A']] vous renverra la colonne A au format DataFrame.

La fonction 'gt' des pandas renverra les positions de la colonne B qui sont supérieures à 50 et 'ne' renverra les positions non égales à 900.

Christopher Matthews
la source