J'ai un scénario dans lequel un utilisateur souhaite appliquer plusieurs filtres à un objet Pandas DataFrame ou Series. Essentiellement, je souhaite enchaîner efficacement un ensemble de filtrages (opérations de comparaison) qui sont spécifiés au moment de l'exécution par l'utilisateur.
Les filtres doivent être additifs (c'est-à-dire que chacun appliqué doit restreindre les résultats).
J'utilise actuellement reindex()
mais cela crée un nouvel objet à chaque fois et copie les données sous-jacentes (si je comprends correctement la documentation). Donc, cela pourrait être vraiment inefficace lors du filtrage d'une grande série ou DataFrame.
Je pense que l'utilisation de apply()
, map()
ou quelque chose de similaire pourrait être mieux. Je suis assez nouveau sur Pandas, alors j'essaie toujours de comprendre tout.
TL; DR
Je veux prendre un dictionnaire de la forme suivante et appliquer chaque opération à un objet Series donné et renvoyer un objet Series «filtré».
relops = {'>=': [1], '<=': [1]}
Exemple long
Je vais commencer par un exemple de ce que j'ai actuellement et en filtrant simplement un seul objet Series. Voici la fonction que j'utilise actuellement:
def apply_relops(series, relops):
"""
Pass dictionary of relational operators to perform on given series object
"""
for op, vals in relops.iteritems():
op_func = ops[op]
for val in vals:
filtered = op_func(series, val)
series = series.reindex(series[filtered])
return series
L'utilisateur fournit un dictionnaire avec les opérations qu'il souhaite effectuer:
>>> df = pandas.DataFrame({'col1': [0, 1, 2], 'col2': [10, 11, 12]})
>>> print df
>>> print df
col1 col2
0 0 10
1 1 11
2 2 12
>>> from operator import le, ge
>>> ops ={'>=': ge, '<=': le}
>>> apply_relops(df['col1'], {'>=': [1]})
col1
1 1
2 2
Name: col1
>>> apply_relops(df['col1'], relops = {'>=': [1], '<=': [1]})
col1
1 1
Name: col1
Encore une fois, le «problème» avec mon approche ci-dessus est que je pense qu'il y a beaucoup de copies éventuellement inutiles des données pour les étapes intermédiaires.
En outre, je voudrais développer cela afin que le dictionnaire transmis puisse inclure les colonnes sur lesquelles l'opérateur et filtrer un DataFrame entier en fonction du dictionnaire d'entrée. Cependant, je suppose que tout ce qui fonctionne pour la série peut être facilement étendu à un DataFrame.
df.query
etpd.eval
semblent bien adaptés à votre cas d'utilisation. Pour plus d'informations sur lapd.eval()
famille de fonctions, leurs caractéristiques et leurs cas d'utilisation, veuillez visiter Évaluation des expressions dynamiques dans les pandas à l'aide de pd.eval () .Réponses:
Les pandas (et numpy) permettent une indexation booléenne , ce qui sera beaucoup plus efficace:
Si vous souhaitez écrire des fonctions d'assistance pour cela, envisagez quelque chose du genre:
Mise à jour: pandas 0.13 a une méthode de requête pour ce type de cas d'utilisation, en supposant que les noms de colonne sont des identificateurs valides, les travaux suivants (et peuvent être plus efficaces pour les grandes images car il utilise numexpr en arrière-plan):
la source
df[(ge(df['col1'], 1) & le(df['col1'], 1)]
. Le problème pour moi est que le dictionnaire avec les filtres peut contenir de nombreux opérateurs et les enchaîner est compliqué. Peut-être que je pourrais ajouter chaque tableau booléen intermédiaire à un grand tableau, puis l'utiliser simplementmap
pour leur appliquer l'and
opérateur?f()
faut-il prendre*b
au lieu de justeb
? Est-ce que c'est pour que l'utilisateurf()
puisse toujours utiliser leout
paramètre facultatif pourlogical_and()
? Cela conduit à une autre petite question secondaire. Quel est l'avantage / le compromis en termes de performances de la transmission via la baie parout()
rapport à l'utilisation de celle renvoyéelogical_and()
? Merci encore!*b
est nécessaire parce que vous passez les deux tableauxb1
etb2
vous devez les décompresser lors de l' appellogical_and
. Cependant, l'autre question se pose toujours. Y a-t-il un avantage en termes de performances à transmettre un tableau via unout
paramètre àlogical_and()
vs en utilisant simplement sa valeur de retour?Les conditions d'enchaînement créent de longues lignes, qui sont découragées par pep8. L'utilisation de la méthode .query oblige à utiliser des chaînes, ce qui est puissant mais non rythmique et peu dynamique.
Une fois que chacun des filtres est en place, une approche est
np.logical fonctionne et est rapide, mais ne prend pas plus de deux arguments, ce qui est géré par functools.reduce.
Notez que cela a encore quelques redondances: a) les raccourcis ne se produisent pas au niveau global b) Chacune des conditions individuelles s'exécute sur l'ensemble des données initiales. Pourtant, je m'attends à ce que ce soit assez efficace pour de nombreuses applications et il est très lisible.
Vous pouvez également effectuer une disjonction (dans laquelle une seule des conditions doit être vraie) en utilisant à la
np.logical_or
place:la source
c_1
,c_2
,c_3
...c_n
dans une liste, et en faisant passerdata[conjunction(conditions_list)]
mais je reçois une erreurValueError: Item wrong length 5 instead of 37.
aussi essayédata[conjunction(*conditions_list)]
mais j'obtenir un autre résultat quedata[conjunction(c_1, c_2, c_3, ... c_n )]
, pas sûr de ce qui se passe.data[conjunction(*conditions_list)]
fonctionne après avoir emballé les dataframes dans une liste et décompressé la liste en placedf[f_2 & f_3 & f_4 & f_5 ]
avecf_2 = df["a"] >= 0
etc Pas besoin de cette fonction ... (belle utilisation de la fonction d'ordre supérieur mais ...)La plus simple de toutes les solutions:
Utilisation:
Un autre exemple , pour filtrer le dataframe pour les valeurs appartenant à février-2018, utilisez le code ci-dessous
la source
Depuis la mise à jour de pandas 0.22 , des options de comparaison sont disponibles comme:
et beaucoup plus. Ces fonctions renvoient un tableau booléen. Voyons comment nous pouvons les utiliser:
la source
Pourquoi ne pas faire ça?
Démo:
Résultat:
Vous pouvez voir que la colonne 'a' a été filtrée où a> = 2.
C'est légèrement plus rapide (temps de frappe, pas de performances) que l'enchaînement d'opérateurs. Vous pouvez bien sûr placer l'importation en haut du fichier.
la source
Vous pouvez également sélectionner des lignes en fonction des valeurs d'une colonne qui ne sont pas dans une liste ou qui ne sont pas itérables. Nous allons créer une variable booléenne comme avant, mais maintenant nous allons nier la variable booléenne en plaçant ~ devant.
Par exemple
la source