108 votes

pandas : filtre complexe sur les lignes de DataFrame

Je voudrais filtrer les lignes par une fonction de chaque ligne, par exemple

def f(row):
  return sin(row['velocity'])/np.prod(['masses']) > 5

df = pandas.DataFrame(...)
filtered = df[apply_to_all_rows(df, f)]

Ou pour un autre exemple plus complexe et artificiel,

def g(row):
  if row['col1'].method1() == 1:
    val = row['col1'].method2() / row['col1'].method3(row['col3'], row['col4'])
  else:
    val = row['col2'].method5(row['col6'])
  return np.sin(val)

df = pandas.DataFrame(...)
filtered = df[apply_to_all_rows(df, g)]

Comment puis-je le faire ?

157voto

duckworthd Points 1830

Vous pouvez le faire en utilisant DataFrame.apply qui applique une fonction le long d'un axe donné,

In [3]: df = pandas.DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c'])

In [4]: df
Out[4]: 
          a         b         c
0 -0.001968 -1.877945 -1.515674
1 -0.540628  0.793913 -0.983315
2 -1.313574  1.946410  0.826350
3  0.015763 -0.267860 -2.228350
4  0.563111  1.195459  0.343168

In [6]: df[df.apply(lambda x: x['b'] > x['c'], axis=1)]
Out[6]: 
          a         b         c
1 -0.540628  0.793913 -0.983315
2 -1.313574  1.946410  0.826350
3  0.015763 -0.267860 -2.228350
4  0.563111  1.195459  0.343168

14voto

Chang She Points 14802

Supposons que j'aie un DataFrame comme suit :

In [39]: df
Out[39]: 
      mass1     mass2  velocity
0  1.461711 -0.404452  0.722502
1 -2.169377  1.131037  0.232047
2  0.009450 -0.868753  0.598470
3  0.602463  0.299249  0.474564
4 -0.675339 -0.816702  0.799289

Je peux utiliser sin et DataFrame.prod pour créer un masque booléen :

In [40]: mask = (np.sin(df.velocity) / df.ix[:, 0:2].prod(axis=1)) > 0

In [41]: mask
Out[41]: 
0    False
1    False
2    False
3     True
4     True

Utilisez ensuite le masque pour sélectionner dans le DataFrame :

In [42]: df[mask]
Out[42]: 
      mass1     mass2  velocity
3  0.602463  0.299249  0.474564
4 -0.675339 -0.816702  0.799289

9voto

user553965 Points 50

La meilleure approche que j'ai trouvée est, au lieu d'utiliser reduce=True pour éviter les erreurs pour les df vides (puisque cette arg est dépréciée de toute façon), vérifiez simplement que la taille du df > 0 avant d'appliquer le filtre :

def my_filter(row):
    if row.columnA == something:
        return True

    return False

if len(df.index) > 0:
    df[df.apply(my_filter, axis=1)]

6voto

Roy Hyunjin Han Points 1246

Précisez reduce=True pour gérer également les DataFrames vides.

import pandas as pd

t = pd.DataFrame(columns=['a', 'b'])
t[t.apply(lambda x: x['a'] > 1, axis=1, reduce=True)]

https://crosscompute.com/n/jAbsB6OIm6oCCJX9PBIbY5FECFKCClyV/-/apply-custom-filter-on-rows-of-dataframe

5voto

Christian G. Points 166

Je ne peux pas faire de commentaires sur réponse de duckworthd mais il ne fonctionne pas parfaitement. Il se bloque lorsque le cadre de données est vide :

df = pandas.DataFrame(columns=['a', 'b', 'c'])
df[df.apply(lambda x: x['b'] > x['c'], axis=1)]

Sorties :

ValueError: Must pass DataFrame with boolean values only

Pour moi, cela ressemble à un bogue dans pandas, puisque { } est définitivement un ensemble valide de valeurs booléennes. Pour une solution, reportez-vous à La réponse de Roy Hyunjin Han .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X