Es gibt verschiedene Möglichkeiten, Zeilen aus einem Pandas-Datenrahmen auszuwählen:
- Boolesche Indizierung (
df[df['col'] == value
])
- Positionsindizierung (
df.iloc[...]
)
- Etikettenindizierung (
df.xs(...)
)
df.query(...)
API
Im Folgenden zeige ich Ihnen jeweils Beispiele mit Ratschlägen zur Verwendung bestimmter Techniken. Angenommen, unser Kriterium ist Spalte 'A'
=='foo'
(Hinweis zur Leistung: Für jeden Basistyp können wir die Dinge mithilfe der Pandas-API einfach halten oder uns außerhalb der API wagen, normalerweise in die API hinein numpy
, und die Dinge beschleunigen.)
Setup
Das erste, was wir brauchen, ist die Identifizierung einer Bedingung, die als unser Kriterium für die Auswahl von Zeilen dient. Wir beginnen mit dem Fall des OP column_name == some_value
und schließen einige andere häufige Anwendungsfälle ein.
Ausleihen bei @unutbu:
import pandas as pd, numpy as np
df = pd.DataFrame({'A': 'foo bar foo bar foo bar foo foo'.split(),
'B': 'one one two three two two one three'.split(),
'C': np.arange(8), 'D': np.arange(8) * 2})
1. Boolesche Indizierung
... Für die boolesche Indizierung muss ermittelt werden, ob der wahre Wert der 'A'
Spalte jeder Zeile gleich ist 'foo'
, und anhand dieser Wahrheitswerte ermittelt werden, welche Zeilen beibehalten werden sollen. Normalerweise nennen wir diese Reihe eine Reihe von Wahrheitswerten mask
. Das machen wir auch hier.
mask = df['A'] == 'foo'
Wir können diese Maske dann verwenden, um den Datenrahmen zu schneiden oder zu indizieren
df[mask]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Dies ist eine der einfachsten Möglichkeiten, um diese Aufgabe zu erfüllen. Wenn Leistung oder Intuitivität kein Problem darstellen, sollte dies die von Ihnen gewählte Methode sein. Wenn jedoch die Leistung ein Problem darstellt, sollten Sie eine alternative Methode zum Erstellen des in Betracht ziehen mask
.
2. Positionsindizierung
Die Positionsindizierung ( df.iloc[...]
) hat ihre Anwendungsfälle, aber dies ist keiner von ihnen. Um herauszufinden, wo geschnitten werden soll, müssen wir zuerst dieselbe boolesche Analyse durchführen, die wir oben durchgeführt haben. Dadurch müssen wir einen zusätzlichen Schritt ausführen, um dieselbe Aufgabe zu erfüllen.
mask = df['A'] == 'foo'
pos = np.flatnonzero(mask)
df.iloc[pos]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
3. Etikettenindizierung
Die Indizierung von Etiketten kann sehr praktisch sein, aber in diesem Fall erledigen wir wieder mehr Arbeit ohne Nutzen
df.set_index('A', append=True, drop=False).xs('foo', level=1)
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
4. df.query()
API
pd.DataFrame.query
ist eine sehr elegante / intuitive Methode, um diese Aufgabe auszuführen, ist jedoch häufig langsamer. Allerdings , wenn Sie die Aufmerksamkeit auf die Timings unten zahlen, für große Daten, ist die Abfrage sehr effizient. Mehr als der Standardansatz und von ähnlicher Größe wie mein bester Vorschlag.
df.query('A == "foo"')
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Ich bevorzuge die Boolean
mask
Tatsächliche Verbesserungen können vorgenommen werden, indem geändert wird, wie wir unsere erstellen Boolean
mask
.
mask
Alternative 1
Verwenden Sie das zugrunde liegende numpy
Array und verzichten Sie auf den Aufwand für die Erstellung eines anderenpd.Series
mask = df['A'].values == 'foo'
Ich werde am Ende vollständigere Zeittests zeigen, aber werfen Sie einen Blick auf die Leistungssteigerungen, die wir mit dem Beispieldatenrahmen erzielen. Zunächst betrachten wir den Unterschied bei der Erstellung dermask
%timeit mask = df['A'].values == 'foo'
%timeit mask = df['A'] == 'foo'
5.84 µs ± 195 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
166 µs ± 4.45 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Die Auswertung mask
mit dem numpy
Array ist ~ 30 mal schneller. Dies ist teilweise darauf zurückzuführen, dass die numpy
Bewertung häufig schneller erfolgt. Dies ist auch teilweise auf den fehlenden Overhead zurückzuführen, der zum Erstellen eines Index und eines entsprechenden pd.Series
Objekts erforderlich ist .
Als nächstes schauen wir uns den Zeitpunkt für das Schneiden mit dem einen mask
gegen den anderen an.
mask = df['A'].values == 'foo'
%timeit df[mask]
mask = df['A'] == 'foo'
%timeit df[mask]
219 µs ± 12.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
239 µs ± 7.03 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Die Leistungssteigerungen sind nicht so ausgeprägt. Wir werden sehen, ob dies gegenüber robusteren Tests Bestand hat.
mask
Alternative 2
Wir hätten auch den Datenrahmen rekonstruieren können. Bei der Rekonstruktion eines Datenrahmens gibt es eine große Einschränkung - Sie müssen sich dabei um die kümmern dtypes
!
Stattdessen werden df[mask]
wir dies tun
pd.DataFrame(df.values[mask], df.index[mask], df.columns).astype(df.dtypes)
Wenn der Datenrahmen vom gemischten Typ ist, was unser Beispiel ist, dann sind, wenn wir df.values
das resultierende Array erhalten, dtype
object
und folglich alle Spalten des neuen Datenrahmens von dtype
object
. Dies erfordert astype(df.dtypes)
und tötet potenzielle Leistungssteigerungen.
%timeit df[m]
%timeit pd.DataFrame(df.values[mask], df.index[mask], df.columns).astype(df.dtypes)
216 µs ± 10.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
1.43 ms ± 39.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Wenn der Datenrahmen jedoch nicht vom gemischten Typ ist, ist dies eine sehr nützliche Methode.
Gegeben
np.random.seed([3,1415])
d1 = pd.DataFrame(np.random.randint(10, size=(10, 5)), columns=list('ABCDE'))
d1
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
%%timeit
mask = d1['A'].values == 7
d1[mask]
179 µs ± 8.73 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Gegen
%%timeit
mask = d1['A'].values == 7
pd.DataFrame(d1.values[mask], d1.index[mask], d1.columns)
87 µs ± 5.12 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Wir haben die Zeit halbiert.
mask
Alternative 3
@unutbu zeigt uns auch, wie wir pd.Series.isin
jedes Element df['A']
in einer Reihe von Werten berücksichtigen können. Dies ergibt dasselbe, wenn unser Wertesatz ein Satz von einem Wert ist, nämlich 'foo'
. Es wird jedoch auch verallgemeinert, bei Bedarf größere Wertesätze einzuschließen. Es stellt sich heraus, dass dies immer noch ziemlich schnell ist, obwohl es eine allgemeinere Lösung ist. Der einzige wirkliche Verlust liegt in der Intuitivität für diejenigen, die mit dem Konzept nicht vertraut sind.
mask = df['A'].isin(['foo'])
df[mask]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Nach wie vor können wir jedoch numpy
die Leistung verbessern und dabei praktisch nichts opfern. Wir werden verwendennp.in1d
mask = np.in1d(df['A'].values, ['foo'])
df[mask]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Timing
Ich werde andere Konzepte, die in anderen Posts erwähnt werden, auch als Referenz aufnehmen.
Code unten
Jede Spalte in dieser Tabelle repräsentiert einen Datenrahmen unterschiedlicher Länge, über den wir jede Funktion testen. Jede Spalte zeigt die relative Zeit, die benötigt wird, wobei die schnellste Funktion einen Basisindex von hat 1.0
.
res.div(res.min())
10 30 100 300 1000 3000 10000 30000
mask_standard 2.156872 1.850663 2.034149 2.166312 2.164541 3.090372 2.981326 3.131151
mask_standard_loc 1.879035 1.782366 1.988823 2.338112 2.361391 3.036131 2.998112 2.990103
mask_with_values 1.010166 1.000000 1.005113 1.026363 1.028698 1.293741 1.007824 1.016919
mask_with_values_loc 1.196843 1.300228 1.000000 1.000000 1.038989 1.219233 1.037020 1.000000
query 4.997304 4.765554 5.934096 4.500559 2.997924 2.397013 1.680447 1.398190
xs_label 4.124597 4.272363 5.596152 4.295331 4.676591 5.710680 6.032809 8.950255
mask_with_isin 1.674055 1.679935 1.847972 1.724183 1.345111 1.405231 1.253554 1.264760
mask_with_in1d 1.000000 1.083807 1.220493 1.101929 1.000000 1.000000 1.000000 1.144175
Sie werden feststellen, dass die schnellsten Zeiten zwischen mask_with_values
und geteilt zu werden scheinenmask_with_in1d
res.T.plot(loglog=True)
Funktionen
def mask_standard(df):
mask = df['A'] == 'foo'
return df[mask]
def mask_standard_loc(df):
mask = df['A'] == 'foo'
return df.loc[mask]
def mask_with_values(df):
mask = df['A'].values == 'foo'
return df[mask]
def mask_with_values_loc(df):
mask = df['A'].values == 'foo'
return df.loc[mask]
def query(df):
return df.query('A == "foo"')
def xs_label(df):
return df.set_index('A', append=True, drop=False).xs('foo', level=-1)
def mask_with_isin(df):
mask = df['A'].isin(['foo'])
return df[mask]
def mask_with_in1d(df):
mask = np.in1d(df['A'].values, ['foo'])
return df[mask]
Testen
res = pd.DataFrame(
index=[
'mask_standard', 'mask_standard_loc', 'mask_with_values', 'mask_with_values_loc',
'query', 'xs_label', 'mask_with_isin', 'mask_with_in1d'
],
columns=[10, 30, 100, 300, 1000, 3000, 10000, 30000],
dtype=float
)
for j in res.columns:
d = pd.concat([df] * j, ignore_index=True)
for i in res.index:a
stmt = '{}(d)'.format(i)
setp = 'from __main__ import d, {}'.format(i)
res.at[i, j] = timeit(stmt, setp, number=50)
Sonderzeitpunkt
Betrachten Sie den Sonderfall, wenn wir ein einzelnes Nichtobjekt dtype
für den gesamten Datenrahmen haben.
Code unten
spec.div(spec.min())
10 30 100 300 1000 3000 10000 30000
mask_with_values 1.009030 1.000000 1.194276 1.000000 1.236892 1.095343 1.000000 1.000000
mask_with_in1d 1.104638 1.094524 1.156930 1.072094 1.000000 1.000000 1.040043 1.027100
reconstruct 1.000000 1.142838 1.000000 1.355440 1.650270 2.222181 2.294913 3.406735
Es stellt sich heraus, dass sich der Wiederaufbau nach ein paar hundert Reihen nicht lohnt.
spec.T.plot(loglog=True)
Funktionen
np.random.seed([3,1415])
d1 = pd.DataFrame(np.random.randint(10, size=(10, 5)), columns=list('ABCDE'))
def mask_with_values(df):
mask = df['A'].values == 'foo'
return df[mask]
def mask_with_in1d(df):
mask = np.in1d(df['A'].values, ['foo'])
return df[mask]
def reconstruct(df):
v = df.values
mask = np.in1d(df['A'].values, ['foo'])
return pd.DataFrame(v[mask], df.index[mask], df.columns)
spec = pd.DataFrame(
index=['mask_with_values', 'mask_with_in1d', 'reconstruct'],
columns=[10, 30, 100, 300, 1000, 3000, 10000, 30000],
dtype=float
)
Testen
for j in spec.columns:
d = pd.concat([df] * j, ignore_index=True)
for i in spec.index:
stmt = '{}(d)'.format(i)
setp = 'from __main__ import d, {}'.format(i)
spec.at[i, j] = timeit(stmt, setp, number=50)