pandas: Filterzeilen von DataFrame mit Operatorverkettung

329

Die meisten Operationen in pandaskann mit Operator Verkettung erreicht werden ( groupby, aggregate, apply, usw.), aber der einzige Weg , ich Filterreihen gefunden habe , ist über normale Klammer Indizierung

df_filtered = df[df['column'] == value]

Dies ist unattraktiv, da ich dfeine Variable zuweisen muss , bevor ich nach ihren Werten filtern kann. Gibt es so etwas wie das Folgende?

df_filtered = df.mask(lambda x: x['column'] == value)
duckworthd
quelle
df.queryund pd.evalscheinen gut für diesen Anwendungsfall zu passen. Informationen zur pd.eval()Funktionsfamilie, ihren Funktionen und Anwendungsfällen finden Sie unter Auswertung dynamischer Ausdrücke in Pandas mit pd.eval () .
CS95

Antworten:

384

Ich bin mir nicht ganz sicher, was Sie wollen, und Ihre letzte Codezeile hilft auch nicht, aber trotzdem:

Die "verkettete" Filterung erfolgt durch "Verketten" der Kriterien im Booleschen Index.

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
   A  B  C  D
d  1  3  9  6

Wenn Sie Methoden verketten möchten, können Sie Ihre eigene Maskenmethode hinzufügen und diese verwenden.

In [90]: def mask(df, key, value):
   ....:     return df[df[key] == value]
   ....:

In [92]: pandas.DataFrame.mask = mask

In [93]: df = pandas.DataFrame(np.random.randint(0, 10, (4,4)), index=list('abcd'), columns=list('ABCD'))

In [95]: df.ix['d','A'] = df.ix['a', 'A']

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [97]: df.mask('A', 1)
Out[97]:
   A  B  C  D
a  1  4  9  1
d  1  3  9  6

In [98]: df.mask('A', 1).mask('D', 6)
Out[98]:
   A  B  C  D
d  1  3  9  6
Wouter Overmeire
quelle
2
Gute Antwort! Ist (df.A == 1) & (df.D == 6)das "&" ein überladener Operator in Pandas?
Shawn
2
siehe auch pandas.pydata.org/pandas-docs/stable/…
Wouter Overmeire
Das ist eine wirklich schöne Lösung - mir war nicht einmal bewusst, dass man solche Methoden in Python als Jury-Rig einsetzen kann. Eine solche Funktion wäre in Pandas selbst sehr schön.
naught101
Das einzige Problem, das ich damit habe, ist die Verwendung von pandas.. Du solltest import pandas as pd.
Daisuke Aramaki
3
In der Tat import pandas as pdist es heute üblich. Ich bezweifle, dass ich die Frage beantwortet habe.
Wouter Overmeire
108

Filter können mithilfe einer Pandas- Abfrage verkettet werden :

df = pd.DataFrame(np.random.randn(30, 3), columns=['a','b','c'])
df_filtered = df.query('a > 0').query('0 < b < 2')

Filter können auch in einer einzigen Abfrage kombiniert werden:

df_filtered = df.query('a > 0 and 0 < b < 2')
bscan
quelle
3
Wenn Sie in Ihrer Abfrage auf Python-Variablen verweisen müssen, heißt es in der Dokumentation : "Sie können auf Variablen in der Umgebung verweisen, indem Sie ihnen ein '@' - Zeichen wie @a + b voranstellen." Beachten Sie, dass Folgendes gültig ist: df.query('a in list([1,2])'), s = set([1,2]); df.query('a in @s').
user3780389
2
Andererseits sieht es so aus, als würde die Abfrageauswertung fehlschlagen, wenn Ihr Spaltenname bestimmte Sonderzeichen enthält: z. B. "Place.Name".
user3780389
2
Verkettung ist das Ziel der Abfrage.
piRSquared
66

Die Antwort von @lodagro ist großartig. Ich würde es erweitern, indem ich die Maskenfunktion wie folgt verallgemeinere:

def mask(df, f):
  return df[f(df)]

Dann können Sie Dinge tun wie:

df.mask(lambda x: x[0] < 0).mask(lambda x: x[1] > 0)
Daniel Velkov
quelle
8
Eine nützliche Verallgemeinerung! Ich wünschte, es wäre bereits direkt in DataFrames integriert !
duckworthd
24

Seit Version 0.18.1.loc akzeptiert die Methode einen Aufruf zur Auswahl. Zusammen mit Lambda-Funktionen können Sie sehr flexible verkettbare Filter erstellen:

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD'))
df.loc[lambda df: df.A == 80]  # equivalent to df[df.A == 80] but chainable

df.sort_values('A').loc[lambda df: df.A > 80].loc[lambda df: df.B > df.A]

Wenn Sie nur filtern, können Sie das auch weglassen .loc.

Rafael Barbosa
quelle
16

Ich biete dies für weitere Beispiele an. Dies ist die gleiche Antwort wie https://stackoverflow.com/a/28159296/

Ich werde weitere Änderungen hinzufügen, um diesen Beitrag nützlicher zu machen.

pandas.DataFrame.query
querywurde genau zu diesem Zweck gemacht. Betrachten Sie den Datenrahmendf

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(
    np.random.randint(10, size=(10, 5)),
    columns=list('ABCDE')
)

df

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
2  0  2  0  4  9
3  7  3  2  4  3
4  3  6  7  7  4
5  5  3  7  5  9
6  8  7  6  4  7
7  6  2  6  6  5
8  2  8  7  5  8
9  4  7  6  1  5

Lassen Sie uns queryalle Zeilen wo filternD > B

df.query('D > B')

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
2  0  2  0  4  9
3  7  3  2  4  3
4  3  6  7  7  4
5  5  3  7  5  9
7  6  2  6  6  5

Was wir verketten

df.query('D > B').query('C > B')
# equivalent to
# df.query('D > B and C > B')
# but defeats the purpose of demonstrating chaining

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
4  3  6  7  7  4
5  5  3  7  5  9
7  6  2  6  6  5
piRSquared
quelle
Ist dies nicht im Grunde die gleiche Antwort wie stackoverflow.com/a/28159296. Fehlt in dieser Antwort etwas, das Ihrer Meinung nach geklärt werden sollte?
BSCAN
9

Ich hatte die gleiche Frage, außer dass ich die Kriterien zu einer ODER-Bedingung kombinieren wollte. Das von Wouter Overmeire angegebene Format kombiniert die Kriterien zu einer UND-Bedingung, sodass beide erfüllt sein müssen:

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
   A  B  C  D
d  1  3  9  6

Ich habe jedoch festgestellt, dass, wenn Sie jede Bedingung einwickeln (... == True)und die Kriterien mit einer Pipe verbinden, die Kriterien in einer ODER-Bedingung kombiniert werden, die erfüllt ist, wenn eine der beiden Bedingungen erfüllt ist:

df[((df.A==1) == True) | ((df.D==6) == True)]
Sharon
quelle
12
Wäre nicht df[(df.A==1) | (df.D==6)]ausreichend für das, was Sie erreichen wollen?
Eenblam
Nein, dies würde nicht der Fall sein, da es bolleanische Ergebnisse liefert (True vs False), anstatt darüber zu filtern, welche Daten alle Bedingungen erfüllen, die die Bedingung erfüllen. Hoffe, dass ich es klar gemacht habe.
MGB.py
8

pandas bietet zwei Alternativen zur Antwort von Wouter Overmeire, für die kein Überschreiben erforderlich ist. Einer ist .loc[.]mit einem Callable wie in

df_filtered = df.loc[lambda x: x['column'] == value]

der andere ist .pipe(), wie in

df_filtered = df.pipe(lambda x: x['column'] == value)
Pietro Battiston
quelle
7

Meine Antwort ist ähnlich wie bei den anderen. Wenn Sie keine neue Funktion erstellen möchten, können Sie das verwenden, was pandas bereits für Sie definiert hat. Verwenden Sie die Rohrmethode.

df.pipe(lambda d: d[d['column'] == value])
Stewbaca
quelle
Dies ist, was Sie wollen, wenn Sie Befehle wiea.join(b).pipe(lambda df: df[df.column_to_filter == 'VALUE'])
Anzeigename
4

Wenn Sie alle gängigen Booleschen Masken sowie eine Allzweckmaske anwenden möchten, können Sie Folgendes in eine Datei einfügen und sie dann einfach wie folgt zuweisen:

pd.DataFrame = apply_masks()

Verwendungszweck:

A = pd.DataFrame(np.random.randn(4, 4), columns=["A", "B", "C", "D"])
A.le_mask("A", 0.7).ge_mask("B", 0.2)... (May be repeated as necessary

Es ist ein bisschen hackig, aber es kann die Dinge ein bisschen sauberer machen, wenn Sie Datensätze kontinuierlich nach Filtern hacken und ändern. Es gibt auch einen Allzweckfilter, der von Daniel Velkov oben in der Funktion gen_mask angepasst wurde und den Sie mit Lambda-Funktionen oder auf andere Weise verwenden können, falls dies gewünscht wird.

Zu speichernde Datei (ich verwende masks.py):

import pandas as pd

def eq_mask(df, key, value):
    return df[df[key] == value]

def ge_mask(df, key, value):
    return df[df[key] >= value]

def gt_mask(df, key, value):
    return df[df[key] > value]

def le_mask(df, key, value):
    return df[df[key] <= value]

def lt_mask(df, key, value):
    return df[df[key] < value]

def ne_mask(df, key, value):
    return df[df[key] != value]

def gen_mask(df, f):
    return df[f(df)]

def apply_masks():

    pd.DataFrame.eq_mask = eq_mask
    pd.DataFrame.ge_mask = ge_mask
    pd.DataFrame.gt_mask = gt_mask
    pd.DataFrame.le_mask = le_mask
    pd.DataFrame.lt_mask = lt_mask
    pd.DataFrame.ne_mask = ne_mask
    pd.DataFrame.gen_mask = gen_mask

    return pd.DataFrame

if __name__ == '__main__':
    pass
dantes_419
quelle
3

Diese Lösung ist in Bezug auf die Implementierung hackiger, aber ich finde sie in Bezug auf die Verwendung viel sauberer und sicherlich allgemeiner als die anderen vorgeschlagenen.

https://github.com/toobaz/generic_utils/blob/master/generic_utils/pandas/where.py

Sie müssen nicht das gesamte Repo herunterladen: Speichern der Datei und Ausführen

from where import where as W

sollte ausreichen. Dann benutzt du es so:

df = pd.DataFrame([[1, 2, True],
                   [3, 4, False], 
                   [5, 7, True]],
                  index=range(3), columns=['a', 'b', 'c'])
# On specific column:
print(df.loc[W['a'] > 2])
print(df.loc[-W['a'] == W['b']])
print(df.loc[~W['c']])
# On entire - or subset of a - DataFrame:
print(df.loc[W.sum(axis=1) > 3])
print(df.loc[W[['a', 'b']].diff(axis=1)['b'] > 1])

Ein etwas weniger dummes Anwendungsbeispiel:

data = pd.read_csv('ugly_db.csv').loc[~(W == '$null$').any(axis=1)]

Übrigens: Auch wenn Sie nur boolesche Spalten verwenden,

df.loc[W['cond1']].loc[W['cond2']]

kann viel effizienter sein als

df.loc[W['cond1'] & W['cond2']]

weil es cond2nur auswertet , wo cond1ist True.

HAFTUNGSAUSSCHLUSS: Ich habe diese Antwort zuerst woanders gegeben, weil ich das nicht gesehen hatte.

Pietro Battiston
quelle
2

Ich möchte nur eine Demonstration mit hinzufügen loc der der verkettete Vorgang nicht nur nach Zeilen, sondern auch nach Spalten und einigen Vorzügen gefiltert wird.

Der folgende Code kann die Zeilen nach Wert filtern.

df_filtered = df.loc[df['column'] == value]

Durch ein wenig Ändern können Sie auch die Spalten filtern.

df_filtered = df.loc[df['column'] == value, ['year', 'column']]

Warum wollen wir also eine verkettete Methode? Die Antwort ist, dass es einfach zu lesen ist, wenn Sie viele Operationen haben. Zum Beispiel,

res =  df\
    .loc[df['station']=='USA', ['TEMP', 'RF']]\
    .groupby('year')\
    .agg(np.nanmean)
Ken T.
quelle
2

Dies ist unattraktiv, da ich dfeine Variable zuweisen muss , bevor ich nach ihren Werten filtern kann.

df[df["column_name"] != 5].groupby("other_column_name")

scheint zu funktionieren: Sie können den []Operator auch verschachteln . Vielleicht haben sie es hinzugefügt, seit Sie die Frage gestellt haben.

serv-inc
quelle
1
Dies ist in einer Kette wenig sinnvoll, da dfjetzt nicht unbedingt auf die Ausgabe des vorherigen Teils der Kette Bezug genommen wird.
Daan Luttik
@DaanLuttik: stimmte zu, es ist keine Verkettung, sondern Verschachtelung. Besser für dich?
serv-inc
1

Wenn Sie Ihre Spalten so einstellen, dass sie als Indizes suchen, können Sie DataFrame.xs()einen Querschnitt verwenden. Dies ist nicht so vielseitig wie die queryAntworten, kann jedoch in einigen Situationen hilfreich sein.

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(
    np.random.randint(3, size=(10, 5)),
    columns=list('ABCDE')
)

df
# Out[55]: 
#    A  B  C  D  E
# 0  0  2  2  2  2
# 1  1  1  2  0  2
# 2  0  2  0  0  2
# 3  0  2  2  0  1
# 4  0  1  1  2  0
# 5  0  0  0  1  2
# 6  1  0  1  1  1
# 7  0  0  2  0  2
# 8  2  2  2  2  2
# 9  1  2  0  2  1

df.set_index(['A', 'D']).xs([0, 2]).reset_index()
# Out[57]: 
#    A  D  B  C  E
# 0  0  2  2  2  2
# 1  0  2  1  1  0
naught101
quelle
1

Sie können die Numpy- Bibliothek auch für logische Operationen nutzen. Es ist ziemlich schnell.

df[np.logical_and(df['A'] == 1 ,df['B'] == 6)]
Akash Basudevan
quelle