Esistono diversi modi per selezionare le righe da un frame di dati Panda:
- Indicizzazione booleana (
df[df['col'] == value
])
- Indicizzazione posizionale (
df.iloc[...]
)
- Indicizzazione etichetta (
df.xs(...)
)
df.query(...)
API
Di seguito vi mostro esempi di ciascuno, con consigli su quando utilizzare determinate tecniche. Supponiamo che il nostro criterio sia colonna'A'
=='foo'
(Nota sulle prestazioni: per ogni tipo di base, possiamo mantenere le cose semplici usando l'API panda o possiamo avventurarci al di fuori dell'API, di solito numpy
, e accelerare le cose.)
Installazione
La prima cosa di cui abbiamo bisogno è identificare una condizione che fungerà da nostro criterio per la selezione delle righe. Inizieremo con il caso del POcolumn_name == some_value
e includeremo alcuni altri casi d'uso comuni.
Prendendo in prestito da @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. Indicizzazione booleana
... L'indicizzazione booleana richiede che il valore reale della 'A'
colonna di ogni riga sia uguale 'foo'
, quindi utilizzare quei valori di verità per identificare quali righe mantenere. In genere, avremmo chiamiamo questa serie, una serie di valori di verità, mask
. Lo faremo anche qui.
mask = df['A'] == 'foo'
Possiamo quindi utilizzare questa maschera per suddividere o indicizzare il frame di dati
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
Questo è uno dei modi più semplici per eseguire questa attività e se le prestazioni o l'intuitività non sono un problema, questo dovrebbe essere il metodo scelto. Tuttavia, se le prestazioni sono un problema, allora potresti prendere in considerazione un modo alternativo di creare il file mask
.
2. Indicizzazione posizionale
L'indicizzazione posizionale ( df.iloc[...]
) ha i suoi casi d'uso, ma questo non è uno di questi. Per identificare dove tagliare, dobbiamo prima eseguire la stessa analisi booleana che abbiamo fatto sopra. Questo ci lascia fare un ulteriore passo per compiere lo stesso compito.
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. Indicizzazione dell'etichetta
L' indicizzazione delle etichette può essere molto utile, ma in questo caso stiamo di nuovo facendo più lavoro senza alcun vantaggio
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
è un modo molto elegante / intuitivo per eseguire questo compito, ma spesso è più lento. Tuttavia , se si presta attenzione ai tempi seguenti, per dati di grandi dimensioni, la query è molto efficiente. Più che un approccio standard e di grandezza simile al mio miglior suggerimento.
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
La mia preferenza è usare il Boolean
mask
È possibile apportare miglioramenti effettivi modificando il modo in cui creiamo il nostro Boolean
mask
.
mask
alternativa 1
Utilizzare l' numpy
array sottostante e rinunciare al sovraccarico di crearne un altropd.Series
mask = df['A'].values == 'foo'
Mostrerò alla fine test più completi sul tempo, ma diamo solo un'occhiata ai miglioramenti delle prestazioni che otteniamo usando il frame di dati di esempio. Innanzitutto, osserviamo la differenza nella creazione dimask
%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)
La valutazione mask
con l' numpy
array è ~ 30 volte più veloce. Ciò è in parte dovuto alla numpy
valutazione spesso più rapida. In parte è anche dovuto alla mancanza di costi generali necessari per costruire un indice e un corrispondentepd.Series
oggetto .
Successivamente, esamineremo i tempi per tagliare con l'uno mask
rispetto all'altro.
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)
I miglioramenti delle prestazioni non sono così pronunciati. Vedremo se questo regge su test più solidi.
mask
alternativa 2
Avremmo potuto ricostruire anche il frame di dati. C'è un grande avvertimento quando si ricostruisce un dataframe: bisogna fare attenzione a dtypes
farlo!
Invece di df[mask]
faremo questo
pd.DataFrame(df.values[mask], df.index[mask], df.columns).astype(df.dtypes)
Se il frame di dati è di tipo misto, come nel nostro esempio, quando otteniamo df.values
l'array risultante è dtype
object
e di conseguenza, tutte le colonne del nuovo frame di dati saranno di dtype
object
. Richiedendo quindi astype(df.dtypes)
e uccidendo qualsiasi potenziale miglioramento delle prestazioni.
%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)
Tuttavia, se il frame di dati non è di tipo misto, questo è un modo molto utile per farlo.
Dato
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)
Contro
%%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)
Dividiamo il tempo a metà.
mask
l'alternativa 3
@unutbu ci mostra anche come utilizzare pd.Series.isin
per tenere conto di ogni elemento df['A']
dell'essere in un insieme di valori. Ciò valuta la stessa cosa se il nostro insieme di valori è un insieme di un valore, vale a dire 'foo'
. Ma si generalizza anche per includere set di valori più grandi, se necessario. Risulta, questo è ancora abbastanza veloce anche se è una soluzione più generale. L'unica vera perdita è nell'intuitività per coloro che non hanno familiarità con il concetto.
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
Tuttavia, come prima, possiamo utilizzare numpy
per migliorare le prestazioni sacrificando praticamente nulla. Useremonp.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
Calendario
Includerò anche altri concetti citati in altri post come riferimento.
Codice sotto
Ogni colonna in questa tabella rappresenta un frame di dati di lunghezza diversa su cui testiamo ciascuna funzione. Ogni colonna mostra il tempo relativo impiegato, con la funzione più veloce dato un indice di base di 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
Noterai che i tempi più veloci sembrano essere condivisi tra mask_with_values
emask_with_in1d
res.T.plot(loglog=True)
funzioni
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]
analisi
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)
Tempi speciali
Osservando il caso speciale quando abbiamo un singolo non oggetto dtype
per l'intero frame di dati.
Codice sotto
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
Si scopre che la ricostruzione non vale la pena oltre alcune centinaia di file.
spec.T.plot(loglog=True)
funzioni
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
)
analisi
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)