web-dev-qa-db-fra.com

Filtrer dynamiquement une trame de données de pandas

J'essaie de filtrer une trame de données de pandas en utilisant des seuils pour trois colonnes

import pandas as pd
df = pd.DataFrame({"A" : [6, 2, 10, -5, 3],
                   "B" : [2, 5, 3, 2, 6],
                   "C" : [-5, 2, 1, 8, 2]})
df = df.loc[(df.A > 0) & (df.B > 2) & (df.C > -1)].reset_index(drop = True)

df
    A  B  C
0   2  5  2
1  10  3  1
2   3  6  2

Cependant, je veux le faire dans une fonction où les noms des colonnes et leurs seuils me sont donnés dans un dictionnaire. Voici mon premier essai qui fonctionne bien. Essentiellement, je mets le filtre dans la variable cond et le lance simplement: 

df = pd.DataFrame({"A" : [6, 2, 10, -5, 3],
                   "B" : [2, 5, 3, 2, 6],
                   "C" : [-5, 2, 1, 8, 2]})
limits_dic = {"A" : 0, "B" : 2, "C" : -1}
cond = "df = df.loc["
for key in limits_dic.keys():
    cond += "(df." + key + " > " + str(limits_dic[key])+ ") & "
cond = cond[:-2] + "].reset_index(drop = True)"
exec(cond)
df
    A  B  C
0   2  5  2
1  10  3  1
2   3  6  2

Maintenant, finalement, je mets tout dans une fonction et elle cesse de fonctionner (peut-être que la fonction exec n'aime pas être utilisée dans une fonction!):

df = pd.DataFrame({"A" : [6, 2, 10, -5, 3],
                   "B" : [2, 5, 3, 2, 6],
                   "C" : [-5, 2, 1, 8, 2]})
limits_dic = {"A" : 0, "B" : 2, "C" : -1}
def filtering(df, limits_dic):
    cond = "df = df.loc["
    for key in limits_dic.keys():
        cond += "(df." + key + " > " + str(limits_dic[key])+ ") & "
    cond = cond[:-2] + "].reset_index(drop = True)"
    exec(cond)
    return(df)

df = filtering(df, limits_dic)
df
    A  B  C
0   6  2 -5
1   2  5  2
2  10  3  1
3  -5  2  8
4   3  6  2

Je sais que la fonction exec agit différemment dans une fonction, mais je ne savais pas comment résoudre le problème. De plus, je me demande s'il doit exister un moyen plus élégant de définir une fonction pour effectuer le filtrage à partir de deux entrées: 1) df et 2) limits_dic = {"A" : 0, "B" : 2, "C" : -1}. J'apprécierais vos pensées à ce sujet.

18
ahoosh

Si vous essayez de créer une requête dynamique, il existe des moyens plus simples. En voici une qui utilise une compréhension de liste et str.join:

query = ' & '.join(['{}>{}'.format(k, v) for k, v in limits_dic.items()])

Ou, en utilisant f- strings avec python-3.6 +, 

query = ' & '.join([f'{k}>{v}' for k, v in limits_dic.items()])

print(query)

'A>0 & C>-1 & B>2'

Passez la chaîne de requête à df.query, elle est conçue pour cela:

out = df.query(query)
print(out)

    A  B  C
1   2  5  2
2  10  3  1
4   3  6  2

Vous pouvez également utiliser df.eval si vous souhaitez obtenir un masque booléen pour votre requête, puis l'indexation devient simple:

mask = df.eval(query)
print(mask)

0    False
1     True
2     True
3    False
4     True
dtype: bool

out = df[mask]
print(out)

    A  B  C
1   2  5  2
2  10  3  1
4   3  6  2

Données de chaîne

Si vous devez interroger des colonnes qui utilisent des données de chaîne, le code ci-dessus nécessitera une légère modification.

Considérons (données de cette réponse ):

df = pd.DataFrame({'gender':list('MMMFFF'),
                   'height':[4,5,4,5,5,4],
                   'age':[70,80,90,40,2,3]})

print (df)
  gender  height  age
0      M       4   70
1      M       5   80
2      M       4   90
3      F       5   40
4      F       5    2
5      F       4    3

Et une liste de colonnes, d'opérateurs et de valeurs:

column = ['height', 'age', 'gender']
equal = ['>', '>', '==']
condition = [1.68, 20, 'F']

La modification appropriée est la suivante:

query = ' & '.join(f'{i} {j} {repr(k)}' for i, j, k in Zip(column, equal, condition))
df.query(query)

   age gender  height
3   40      F       5

Pour plus d'informations sur la famille de fonctions pd.eval(), leurs fonctionnalités et leurs cas d'utilisation, veuillez consulter Évaluation de l'expression dynamique dans les pandas à l'aide de pd.eval () .

30
coldspeed

Une alternative à la version de @coldspeed:

conditions = None
for key, val in limit_dic.items():
    cond = df[key] > val
    if conditions is None:
        conditions = cond
    else:
        conditions = conditions & cond
print(df[conditions])
0
Victor Yan