2015-07-08 45 views
9

Su un dataframe panda, so che posso raggruppare su una o più colonne e quindi filtrare i valori che si verificano più o meno di un dato numero.Panda: filtro dataframe per valori troppo frequenti o troppo rari

Ma voglio farlo su ogni colonna del dataframe. Voglio rimuovere i valori che sono troppo rari (diciamo che si verificano meno del 5% delle volte) o troppo frequenti. Ad esempio, considera un dataframe con le seguenti colonne: city of origin, city of destination, distance, type of transport (air/car/foot), time of day, price-interval.

import pandas as pd 
import string 
import numpy as np 
vals = [(c, np.random.choice(list(string.lowercase), 100, replace=True)) for c in 
    'city of origin', 'city of destination', 'distance, type of transport (air/car/foot)', 'time of day, price-interval'] 
df = pd.DataFrame(dict(vals)) 
>> df.head() 
    city of destination  city of origin distance, type of transport (air/car/foot) time of day, price-interval 
0 f p a n 
1 k b a f 
2 q s n j 
3 h c g u 
4 w d m h 

Se questo è un grande dataframe, ha senso per rimuovere le righe che contengono elementi spuri, ad esempio, se time of day = night verifica solo il 3% del tempo, oppure se foot modalità di trasporto è raro, e così via .

Voglio rimuovere tutti questi valori da tutte le colonne (o da un elenco di colonne). Un'idea che ho è di fare un value_counts su ogni colonna, transform e aggiungere una colonna per ogni value_counts; quindi filtrare in base al fatto che siano sopra o sotto una soglia. Ma penso che ci debba essere un modo migliore per raggiungere questo obiettivo?

+0

Date un'occhiata a sklearn funzione di rilevamento – Moritz

risposta

7

Questa procedura passerà attraverso ciascuna colonna di DataFrame ed eliminerà le righe in cui la data categoria è inferiore a una determinata percentuale di soglia, riducendo il DataFrame su ciascun ciclo.

Questa risposta è simile a quello fornito da @Ami Tavory, ma con alcune differenze:

  • Si normalizza la conta di valore in modo da poter semplicemente utilizzare una soglia percentile.
  • Calcola i conteggi solo una volta per colonna anziché due. Ciò si traduce in un'esecuzione più rapida.

Codice:

threshold = 0.03 
for col in df: 
    counts = df[col].value_counts(normalize=True) 
    df = df.loc[df[col].isin(counts[counts > threshold].index), :] 

tempistica Codice:

df2 = pd.DataFrame(np.random.choice(list(string.lowercase), [1e6, 4], replace=True), 
        columns=list('ABCD')) 

%%timeit df=df2.copy() 
threshold = 0.03 
for col in df: 
    counts = df[col].value_counts(normalize=True) 
    df = df.loc[df[col].isin(counts[counts > threshold].index), :] 

1 loops, best of 3: 485 ms per loop 

%%timeit df=df2.copy() 
m = 0.03 * len(df) 
for c in df: 
    df = df[df[c].isin(df[c].value_counts()[df[c].value_counts() > m].index)] 

1 loops, best of 3: 688 ms per loop 
+0

si dovrebbe modificare 'per col in df' su:' per col in df.columns' – vpk

+0

Altro (non è necessario specificare '.columns'). Grazie per aver sottolineato l'incoerenza. – Alexander

2

Sono nuovo di Python e utilizzo di Panda. Ho trovato la seguente soluzione qui sotto. Forse altre persone potrebbero avere un approccio migliore o più efficiente.

Supponendo che il tuo DataFrame sia DF, puoi usare il seguente codice qui sotto per filtrare tutti i valori poco frequenti. Assicurati di aggiornare la variabile col e bin_freq. DF_Filtered è il tuo nuovo DataFrame filtrato.

# Column you want to filter 
col = 'time of day' 

# Set your frequency to filter out. Currently set to 5% 
bin_freq = float(5)/float(100) 

DF_Filtered = pd.DataFrame() 

for i in DF[col].unique(): 
    counts = DF[DF[col]==i].count()[col] 
    total_counts = DF[col].count() 
    freq = float(counts)/float(total_counts) 

    if freq > bin_freq: 
     DF_Filtered = pd.concat([DF[DF[col]==i],DF_Filtered]) 

print DF_Filtered 
3

vorrei andare con una delle seguenti:

Opzione A

m = 0.03 * len(df) 
df[np.all(
    df.apply(
     lambda c: c.isin(c.value_counts()[c.value_counts() > m].index).as_matrix()), 
    axis=1)] 

Spiegazione:

  • m = 0.03 * len(df) è la soglia (è piacevole togliere la costante dall'espressione complicata)

  • df[np.all(..., axis=1)] conserva le righe in cui è stata ottenuta una condizione su tutte le colonne.

  • df.apply(...).as_matrix applica una funzione a tutte le colonne e crea una matrice dei risultati.

  • c.isin(...) controlli, per ogni articolo di colonna, se è in qualche set.

  • c.value_counts()[c.value_counts() > m].index è l'insieme di tutti i valori in una colonna il cui conteggio è superiore a m.

Opzione B

m = 0.03 * len(df) 
for c in df.columns: 
    df = df[df[c].isin(df[c].value_counts()[df[c].value_counts() > m].index)] 

La spiegazione è simile a quello di cui sopra.


Compromessi:

  • Personalmente, trovo B più leggibile.

  • B crea un nuovo DataFrame per ogni filtraggio di una colonna; per i DataFrames di grandi dimensioni, è probabilmente più costoso.

+0

Ho trovato questo in un altro thread SO (http://stackoverflow.com/a/ 23202269/228177): df_f = df [(np.abs (stats.zscore (df)) <2) .all (asse = 1)] Filtra tutti i valori che sono 2 deviazioni standard di distanza, utilizzando uno z-score test. Anche i tuoi suggerimenti sono simili. Grazie – vpk

+1

Questo è vero - è simile. FWIW, penso che la risposta sia quella che dovresti usare, mentre la risposta qui è una risposta rigorosa alla tua domanda. –

+0

BTW, solo per creare un MWE, come si crea una serie di alfabeti casuali o un insieme di alfabeti? ad es. [A, A, A, B, Z, X, X, A]. Crei serie numeriche per np.random.randn (10), ma c'è qualcosa di simile per le stringhe? – vpk

0

DataFrames supportano clip_lower(threshold, axis=None) e clip_upper(threshold, axis=None), che rimuove tutti i valori sotto o sopra (rispettivamente) una certa soglia.

+0

Siamo spiacenti, ma questo * tronca *, che è 1. completamente diverso e 2. applicabile ai valori numerici. –