Panda: filtra le righe di DataFrame con il concatenamento dell'operatore


329

Maggior parte delle operazioni pandaspuò essere realizzato con operatore concatenamento ( groupby, aggregate, apply, ecc), ma l'unico modo che ho trovato per righe filtro avviene tramite normale indicizzazione staffa

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

Ciò non è attraente in quanto richiede l'assegnazione dfa una variabile prima di poter filtrare i suoi valori. C'è qualcosa di più simile al seguente?

df_filtered = df.mask(lambda x: x['column'] == value)

df.querye pd.evalsembrano adatti per questo caso d'uso. Per informazioni sulla pd.eval()famiglia di funzioni, le loro caratteristiche e i casi d'uso, visitare la valutazione delle espressioni dinamiche in panda usando pd.eval () .
cs95,

Risposte:


384

Non sono del tutto sicuro di quello che vuoi, e la tua ultima riga di codice non aiuta neanche, ma comunque:

Il filtro "concatenato" viene eseguito "concatenando" i criteri nell'indice booleano.

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

Se si desidera incatenare i metodi, è possibile aggiungere il proprio metodo maschera e utilizzarlo.

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

2
Bella risposta! Quindi (df.A == 1) & (df.D == 6), "e" è un operatore sovraccarico in Panda?
Shawn,


Questa è davvero una bella soluzione - non sapevo nemmeno che avresti potuto usare metodi di giuria come quello in Python. Una funzione come questa sarebbe davvero bella da avere nei Panda stessi.
naught101

L'unico problema che ho con questo è l'uso di pandas.. Dovresti import pandas as pd.
Daisuke Aramaki,

3
Effettivamente import pandas as pdè una pratica comune ora. Dubito che sia stato quando ho risposto alla domanda.
Wouter Overmeire,

108

I filtri possono essere concatenati utilizzando una query Pandas :

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

I filtri possono anche essere combinati in una singola query:

df_filtered = df.query('a > 0 and 0 < b < 2')

3
Se è necessario fare riferimento a variabili Python nella query, la documentazione dice "È possibile fare riferimento a variabili nell'ambiente anteponendole con un carattere '@' come @a + b". Si noti che sono validi: df.query('a in list([1,2])'), s = set([1,2]); df.query('a in @s').
user3780389

2
D'altra parte, sembra che la valutazione della query fallirà se il nome della tua colonna ha alcuni caratteri speciali: ad esempio "Place.Name".
user3780389,

2
Il concatenamento è ciò per cui è progettata la query.
piRSquared

66

La risposta di @lodagro è fantastica. Lo estenderei generalizzando la funzione maschera come:

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

Quindi puoi fare cose come:

df.mask(lambda x: x[0] < 0).mask(lambda x: x[1] > 0)

8
Una generalizzazione utile! Vorrei che fosse già integrato direttamente in DataFrames!
duckworthd,

24

Dalla versione 0.18.1 il .locmetodo accetta un callable per la selezione. Insieme alle funzioni lambda è possibile creare filtri concatenabili molto flessibili:

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]

Se tutto ciò che stai facendo è filtrare, puoi anche omettere .loc.


16

Lo offro per ulteriori esempi. Questa è la stessa risposta di https://stackoverflow.com/a/28159296/

Aggiungerò altre modifiche per rendere questo post più utile.

pandas.DataFrame.query
queryè stato creato proprio per questo scopo. Considera il frame di datidf

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

Usiamo queryper filtrare tutte le righe doveD > 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

Che noi incateniamo

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

Non è sostanzialmente la stessa risposta di stackoverflow.com/a/28159296 C'è qualcosa che manca in quella risposta che pensi debba essere chiarito?
bscan,

9

Avevo la stessa domanda, tranne per il fatto che volevo combinare i criteri in una condizione OR. Il formato fornito da Wouter Overmeire combina i criteri in una condizione AND in modo che entrambi debbano essere soddisfatti:

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

Ma ho scoperto che, se avvolgi ogni condizione (... == True)e unisci i criteri con una pipe, i criteri vengono combinati in una condizione OR, soddisfatti ogni volta che uno di essi è vero:

df[((df.A==1) == True) | ((df.D==6) == True)]

12
Non df[(df.A==1) | (df.D==6)]sarebbe sufficiente per quello che stai cercando di realizzare?
eenblam,

No, non lo farebbe perché fornisce risultati bolleanni (Vero contro Falso) invece che in quanto è sopra che filtra tutti i dati che soddisfano la condizione. Spero di averlo chiarito.
MGB.py

8

Panda fornisce due alternative alla risposta di Wouter Overmeire che non richiedono alcuna sostituzione. Uno è .loc[.]con un callable, come in

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

l'altro è .pipe(), come in

df_filtered = df.pipe(lambda x: x['column'] == value)

7

La mia risposta è simile alle altre. Se non vuoi creare una nuova funzione, puoi usare ciò che Panda ha già definito per te. Usa il metodo pipe.

df.pipe(lambda d: d[d['column'] == value])

QUESTO è quello che vuoi se vuoi concatenare comandi comea.join(b).pipe(lambda df: df[df.column_to_filter == 'VALUE'])
displayname

4

Se si desidera applicare tutte le maschere booleane comuni nonché una maschera per scopi generici, è possibile inserire quanto segue in un file e assegnarli semplicemente come segue:

pd.DataFrame = apply_masks()

Uso:

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

È un po 'confuso, ma può rendere le cose un po' più pulite se tagli e cambi continuamente set di dati in base ai filtri. C'è anche un filtro per uso generale adattato da Daniel Velkov sopra nella funzione gen_mask che puoi usare con le funzioni lambda o altrimenti se lo desideri.

File da salvare (utilizzo 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

3

Questa soluzione è più sofisticata in termini di implementazione, ma la trovo molto più pulita in termini di utilizzo ed è sicuramente più generale rispetto alle altre proposte.

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

Non è necessario scaricare l'intero repository: salvare il file e fare

from where import where as W

dovrebbe bastare. Quindi lo usi in questo modo:

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

Un esempio di utilizzo leggermente meno stupido:

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

A proposito: anche nel caso in cui tu stia usando solo i colli booleani,

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

può essere molto più efficiente di

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

perché valuta cond2solo dove si cond1trova True.

NOTA BENE: ho prima dato questa risposta altrove perché non l'avevo visto.


2

Voglio solo aggiungere una dimostrazione usando locper filtrare non solo per righe ma anche per colonne e alcuni meriti all'operazione concatenata.

Il codice seguente può filtrare le righe per valore.

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

Modificandolo un po 'puoi anche filtrare le colonne.

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

Quindi perché vogliamo un metodo incatenato? La risposta è che è semplice da leggere se si hanno molte operazioni. Per esempio,

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

2

Questo non è attraente in quanto richiede l'assegnazione dfa una variabile prima di poter filtrare i suoi valori.

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

sembra funzionare: puoi anche annidare l' []operatore. Forse l'hanno aggiunto da quando hai posto la domanda.


1
Questo ha poco senso in una catena perché dfora non fa necessariamente riferimento all'output della parte precedente della catena.
Daan Luttik,

@DaanLuttik: d'accordo, non è incatenamento, ma nidificazione. Meglio per te?
serv-inc,

1

Se imposti le colonne per la ricerca come indici, puoi utilizzare DataFrame.xs()una sezione trasversale. Questo non è versatile come le queryrisposte, ma potrebbe essere utile in alcune situazioni.

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

1

È inoltre possibile sfruttare la libreria numpy per operazioni logiche. È abbastanza veloce.

df[np.logical_and(df['A'] == 1 ,df['B'] == 6)]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.