Elimina le righe da un DataFrame panda basato su un'espressione condizionale che coinvolge len (stringa) che fornisce KeyError


303

Ho un DataFrame Panda e voglio eliminare righe da esso in cui la lunghezza della stringa in una colonna particolare è maggiore di 2.

Mi aspetto di poterlo fare (per questa risposta ):

df[(len(df['column name']) < 2)]

ma ho appena ricevuto l'errore:

KeyError: u'no item named False'

Che cosa sto facendo di sbagliato?

(Nota: so che posso usare df.dropna()per sbarazzarmi di righe che ne contengono NaN, ma non ho visto come rimuovere le righe in base a un'espressione condizionale.)

Risposte:


168

Quando lo fai len(df['column name'])ottieni solo un numero, ovvero il numero di righe nel DataFrame (ovvero la lunghezza della colonna stessa). Se si desidera applicare lena ciascun elemento nella colonna, utilizzare df['column name'].map(len). Allora prova

df[df['column name'].map(len) < 2]

3
Ho trovato un modo usando una comprensione della lista: df[[(len(x) < 2) for x in df['column name']]]ma la tua è molto più bella. Grazie per l'aiuto!
sjs,

13
Nel caso in cui qualcuno abbia bisogno di un confronto più complesso, è sempre possibile utilizzare una lambda. df[df['column name'].map(lambda x: str(x)!=".")]
4lberto,

1
Per qualche ragione, nessuna delle altre opzioni ha funzionato per me, tranne quella pubblicata da @ 4lberto. Sono su pandas 0.23.4e python 3.6
goelakash il

1
Vorrei aggiungere .copy()a alla fine, nel caso in cui si desideri modificare successivamente questo frame di dati (ad esempio, l'assegnazione di nuove colonne aumenterebbe l'avviso "Un valore sta cercando di essere impostato su una copia di una porzione da un DataFrame".
PlasmaBinturong

807

Per rispondere direttamente al titolo originale di questa domanda "Come eliminare le righe da un DataFrame di Panda basato su un'espressione condizionale" (che capisco non è necessariamente il problema del PO ma potrebbe aiutare altri utenti a trovare questa domanda) un modo per farlo è usare il metodo di rilascio :

df = df.drop(some labels)

df = df.drop(df[<some boolean condition>].index)

Esempio

Per rimuovere tutte le righe in cui la colonna "punteggio" è <50:

df = df.drop(df[df.score < 50].index)

Versione sul posto (come indicato nei commenti)

df.drop(df[df.score < 50].index, inplace=True)

Condizioni multiple

(vedi l'indicizzazione booleana )

Gli operatori sono: |per or, &per ande ~per not. Questi devono essere raggruppati usando le parentesi.

Per rimuovere tutte le righe in cui la colonna "punteggio" è <50 e> 20

df = df.drop(df[(df.score < 50) & (df.score > 20)].index)


32
Voglio solo sottolineare che la funzione di rilascio supporta la sostituzione sul posto. Vale a dire ,. la tua soluzione è la stessa di df.drop (df [df.score <50] .index, inplace = True). Tuttavia non conosceva il trucco "indice". Mi ha aiutato molto
Quickbeam2k1,

9
Voglio solo sottolineare che prima di utilizzare questo trucco dell'indice è necessario essere sicuri che i valori dell'indice siano univoci (o di chiamata reset_index()). L'ho scoperto nel modo più difficile quando il mio frame di dati ha lasciato cadere molte righe.
Jay,

3
come faccio a eliminare tutte le righe in cui il tipo di colonna è str? Voglio mantenere solo i tipi di colonne dell'elenco. Ho provato test = df.drop(df[df['col1'].dtype == str].index)ma ottengo l'errore KeyError: False che ho anche provato df.drop(df[df.col1.dtype == str].index)e df.drop(df[type(df.cleaned_norm_email) == str].index)ma nulla sembra funzionare? Qualcuno può consigliare. Grazie! @User
PyRsquared

1
Questa è una vecchia domanda ma ... @ il pesce sfidato in modo acquatico è molto più veloce di questo. Nota che calcoli df[(df.score < 50) & (df.score > 20)]come parte della tua risposta. Se lo facessi invertendo df = df[(df.score >= 50) | (df.score <= 20)], otterrai la tua risposta molto più velocemente.
Roobie Nuby,

1
@RoobieNuby - non sono la stessa condizione.
Nguai al

106

È possibile assegnare il DataFramea una versione filtrata di se stesso:

df = df[df.score > 50]

Questo è più veloce di drop:

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test[test.x < 0]
# 54.5 ms ± 2.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test.drop(test[test.x > 0].index, inplace=True)
# 201 ms ± 17.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test.drop(test[test.x > 0].index)
# 194 ms ± 7.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Come posso verificare l'utilizzo o la condizione di più colonne?
Piyush S. Wanare,


9

Espanderò sulla soluzione generica di @ User per fornire dropun'alternativa gratuita. Questo è per le persone dirette qui in base al titolo della domanda (non al problema di OP)

Supponi di voler eliminare tutte le righe con valori negativi. Una soluzione di rivestimento è: -

df = df[(df > 0).all(axis=1)]

Spiegazione dettagliata: -

Generiamo un frame di dati di distribuzione normale casuale 5x5

np.random.seed(0)
df = pd.DataFrame(np.random.randn(5,5), columns=list('ABCDE'))
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
1 -0.977278  0.950088 -0.151357 -0.103219  0.410599
2  0.144044  1.454274  0.761038  0.121675  0.443863
3  0.333674  1.494079 -0.205158  0.313068 -0.854096
4 -2.552990  0.653619  0.864436 -0.742165  2.269755

Lascia che la condizione elimini i negativi. Un booleano df che soddisfa la condizione: -

df > 0
      A     B      C      D      E
0   True  True   True   True   True
1  False  True  False  False   True
2   True  True   True   True   True
3   True  True  False   True  False
4  False  True   True  False   True

Una serie booleana per tutte le righe che soddisfano la condizione Nota se qualsiasi elemento nella riga non riesce la condizione la riga è contrassegnata come falsa

(df > 0).all(axis=1)
0     True
1    False
2     True
3    False
4    False
dtype: bool

Infine filtra le righe dal frame di dati in base alla condizione

df[(df > 0).all(axis=1)]
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
2  0.144044  1.454274  0.761038  0.121675  0.443863

È possibile assegnare di nuovo a df effettivamente eliminare vs filtro ing fatto sopra
df = df[(df > 0).all(axis=1)]

Questo può essere facilmente esteso per filtrare le righe contenenti NaN (voci non numeriche): -
df = df[(~df.isnull()).all(axis=1)]

Questo può anche essere semplificato per casi come: Elimina tutte le righe in cui la colonna E è negativa

df = df[(df.E>0)]

Vorrei concludere con alcune statistiche di profilazione sul perché la dropsoluzione di @ User è più lenta della filtrazione basata su colonne non elaborate: -

%timeit df_new = df[(df.E>0)]
345 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit dft.drop(dft[dft.E < 0].index, inplace=True)
890 µs ± 94.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Una colonna è fondamentalmente un Seriesesempio una NumPymatrice, può essere indicizzato senza alcun costo. Per le persone interessate a come l'organizzazione della memoria sottostante gioca nella velocità di esecuzione ecco un ottimo collegamento su Speeding up Panda :


6

In Panda puoi fare str.lencon il tuo confine e usare il risultato booleano per filtrarlo.

df[df['column name'].str.len().lt(2)]

3

Se si desidera eliminare righe di frame di dati sulla base di una condizione complicata sul valore della colonna, scrivere ciò nel modo mostrato sopra può essere complicato. Ho la seguente soluzione più semplice che funziona sempre. Supponiamo che tu voglia eliminare la colonna con 'header' quindi prendi prima quella colonna in un elenco.

text_data = df['name'].tolist()

ora applica una funzione su ogni elemento dell'elenco e inseriscilo in una serie di panda:

text_length = pd.Series([func(t) for t in text_data])

nel mio caso stavo solo cercando di ottenere il numero di token:

text_length = pd.Series([len(t.split()) for t in text_data])

ora aggiungi una colonna aggiuntiva con le serie sopra nel frame di dati:

df = df.assign(text_length = text_length .values)

ora possiamo applicare la condizione sulla nuova colonna come:

df = df[df.text_length  >  10]
def pass_filter(df, label, length, pass_type):

    text_data = df[label].tolist()

    text_length = pd.Series([len(t.split()) for t in text_data])

    df = df.assign(text_length = text_length .values)

    if pass_type == 'high':
        df = df[df.text_length  >  length]

    if pass_type == 'low':
        df = df[df.text_length  <  length]

    df = df.drop(columns=['text_length'])

    return df
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.