Pandas: Komplexer Filter für Zeilen von DataFrame

85

Ich möchte Zeilen nach einer Funktion jeder Zeile filtern, z

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

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

Oder für ein anderes komplexeres, erfundenes Beispiel:

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)]

Wie kann ich das machen?

duckworthd
quelle

Antworten:

121

Sie können dies tun, indem Sie DataFrame.applyeine Funktion entlang einer bestimmten Achse anwenden.

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
duckworthd
quelle
15
applyIn dieser Situation besteht keine Notwendigkeit . Ein regulärer boolescher Index funktioniert einwandfrei. df[df['b] > df['c']]. Es gibt sehr wenige Situationen, die tatsächlich erfordern, applyund sogar wenige, die es benötigenaxis=1
Ted Petrou
@TedPetrou Was ist, wenn Sie nicht sicher sind, ob jedes Element in Ihrem Datenrahmen vom richtigen Typ ist? Unterstützt ein regulärer boolescher Index die Ausnahmebehandlung?
D. Ror.
13

Angenommen, ich hätte einen DataFrame wie folgt:

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

Ich kann sin und DataFrame.prod verwenden, um eine boolesche Maske zu erstellen:

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

Verwenden Sie dann die Maske, um aus dem DataFrame auszuwählen:

In [42]: df[mask]
Out[42]: 
      mass1     mass2  velocity
3  0.602463  0.299249  0.474564
4 -0.675339 -0.816702  0.799289
Chang She
quelle
2
Eigentlich war dies wahrscheinlich ein schlechtes Beispiel: Es wird np.sinautomatisch an alle Elemente gesendet. Was wäre, wenn ich es durch eine weniger intelligente Funktion ersetzen würde, die jeweils nur einen Eingang verarbeiten könnte?
duckworthd
5

Ich kann die Antwort von duckworthd nicht kommentieren , aber sie funktioniert nicht perfekt. Es stürzt ab, wenn der Datenrahmen leer ist:

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

Ausgänge:

ValueError: Must pass DataFrame with boolean values only

Für mich sieht es wie ein Fehler in Pandas aus, da {} definitiv eine gültige Menge von Booleschen Werten ist. Eine Lösung finden Sie in der Antwort von Roy Hyunjin Han .

cglacet
quelle
3

Der beste Ansatz, den ich gefunden habe, ist, anstatt reduce=TrueFehler für leeres df zu vermeiden (da dieses Argument sowieso veraltet ist), einfach die df-Größe> 0 zu überprüfen, bevor Sie den Filter anwenden:

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

    return False

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

Sie können die locEigenschaft für das Slice Ihres Datenrahmens verwenden.

Laut Dokumentation , lockann eine haben callable functionals Argument.

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

# define lambda function
In [5]: myfilter = lambda x: x['b'] > x['c']

# use my lambda in loc
In [6]: df1 = df.loc[fif]

Wenn Sie Ihre Filterfunktion fifmit anderen Filterkriterien kombinieren möchten

df1 = df.loc[fif].loc[(df.b >= 0.5)]
Pierock
quelle