Rileva ed esclude gli outlier nel frame di dati Pandas


198

Ho un frame di dati Panda con poche colonne.

Ora so che alcune righe sono valori anomali basati su un determinato valore di colonna.

Per esempio

la colonna 'Vol' ha tutti i valori intorno 12xxe un valore è 4000(anomalo).

Ora vorrei escludere quelle righe che hanno una Volcolonna come questa.

Quindi, in sostanza, devo inserire un filtro nel frame di dati in modo tale da selezionare tutte le righe in cui i valori di una determinata colonna sono, per esempio, all'interno di 3 deviazioni standard dalla media.

Qual è un modo elegante per raggiungere questo obiettivo?

Risposte:


214

Se hai più colonne nel tuo frame di dati e desideri rimuovere tutte le righe che presentano valori anomali in almeno una colonna, la seguente espressione lo farebbe in un colpo solo.

df = pd.DataFrame(np.random.randn(100, 3))

from scipy import stats
df[(np.abs(stats.zscore(df)) < 3).all(axis=1)]

descrizione:

  • Per ogni colonna, prima calcola il punteggio Z di ciascun valore nella colonna, relativamente alla media della colonna e alla deviazione standard.
  • Quindi prende l'assoluto del punteggio Z perché la direzione non ha importanza, solo se è inferiore alla soglia.
  • all (axis = 1) garantisce che per ogni riga, tutte le colonne soddisfino il vincolo.
  • Infine, il risultato di questa condizione viene utilizzato per indicizzare il frame di dati.

6
Puoi spiegare cosa sta facendo questo codice? E forse fornire un'idea su come potrei rimuovere tutte le righe che hanno un valore anomalo in una singola colonna specificata? Sarebbe utile Grazie.
samthebrand,

17
Per ogni colonna, prima calcola il punteggio Z di ciascun valore nella colonna, relativamente alla media della colonna e alla deviazione standard. Quindi prende l'assoluto del punteggio Z perché la direzione non ha importanza, solo se è al di sotto della soglia. .all (axis = 1) garantisce che per ogni riga, tutte le colonne soddisfino il vincolo. Infine, il risultato di questa condizione viene utilizzato per indicizzare il frame di dati.
rafaelvalle,

4
Come gestiresti la situazione quando ci sono Null / Nans nelle colonne. Come possiamo averli ignorati?
asimo

6
come gestiamo le colonne str per questa soluzione? Se alcune colonne non sono numeriche e vogliamo rimuovere valori anomali in base a tutte le colonne numeriche.
ssp

6
Errore di Got: "TypeError: tipo di operando non supportato (s) per /: 'str' e 'int'"
SAK

142

Usa l' booleanindicizzazione come faresti innumpy.array

df = pd.DataFrame({'Data':np.random.normal(size=200)})
# example dataset of normally distributed data. 

df[np.abs(df.Data-df.Data.mean()) <= (3*df.Data.std())]
# keep only the ones that are within +3 to -3 standard deviations in the column 'Data'.

df[~(np.abs(df.Data-df.Data.mean()) > (3*df.Data.std()))]
# or if you prefer the other way around

Per una serie è simile:

S = pd.Series(np.random.normal(size=200))
S[~((S-S.mean()).abs() > 3*S.std())]

6
il loro è un DataFrame.abs()FYI, ancheDataFrame.clip()
Jeff

7
Nel caso di clip()Jeff, i contorni non vengono rimossi: df.SOME_DATA.clip(-3std,+3std)assegna gli outliner a + 3 ° o -3 °
CT Zhu

1
È quasi lo stesso, @AMM
CT Zhu,

1
Come possiamo fare la stessa cosa se il nostro frame di dati Panda ha 100 colonne?
DreamerP

1
Fantastico, grazie per la risposta @CTZhu. @DreamerP si può solo applicare a tutto il dataframe con: df_new = df[np.abs(df - df.mean()) <= (3 * df.std())]. Ma a differenza di applicarlo ad una Serie o colonna singola, questo sostituirà con valori anomali np.nane mantenere la forma del dataframe, così interpolazione potrebbe essere necessaria per riempire i valori mancanti.
Scotty1-

94

Per ciascuna colonna del tuo frame di dati, puoi ottenere quantile con:

q = df["col"].quantile(0.99)

e quindi filtrare con:

df[df["col"] < q]

Se è necessario rimuovere gli outlier inferiori e superiori, combinare la condizione con un'istruzione AND:

q_low = df["col"].quantile(0.01)
q_hi  = df["col"].quantile(0.99)

df_filtered = df[(df["col"] < q_hi) & (df["col"] > q_low)]

3
Questo articolo fornisce una buona panoramica delle tecniche di rimozione di valori anomali machinelearningmastery.com/...
user6903745

2
questo potrebbe rimuovere i valori anomali solo dal limite superiore .. non inferiore?
indolentdeveloper

1
@indolentdeveloper hai ragione, basta invertire la disuguaglianza per rimuovere valori anomali inferiori o combinarli con un operatore OR.
user6903745

4
L'idea del commento era di aggiornare le risposte;). Dal momento che qualcuno può perdere questo punto.
indolentdeveloper

@ user6903745 Dichiarazione AND o "OR"?
AB

38

Questa risposta è simile a quella fornita da @tanemaki, ma utilizza lambdaun'espressione invece di scipy stats.

df = pd.DataFrame(np.random.randn(100, 3), columns=list('ABC'))

df[df.apply(lambda x: np.abs(x - x.mean()) / x.std() < 3).all(axis=1)]

Per filtrare il DataFrame in cui solo UNA colonna (ad esempio "B") si trova all'interno di tre deviazioni standard:

df[((df.B - df.B.mean()) / df.B.std()).abs() < 3]

Vedi qui per come applicare questo punteggio z su base continuativa: punteggio Z continuo applicato al frame di dati Panda


22
#------------------------------------------------------------------------------
# accept a dataframe, remove outliers, return cleaned data in a new dataframe
# see http://www.itl.nist.gov/div898/handbook/prc/section1/prc16.htm
#------------------------------------------------------------------------------
def remove_outlier(df_in, col_name):
    q1 = df_in[col_name].quantile(0.25)
    q3 = df_in[col_name].quantile(0.75)
    iqr = q3-q1 #Interquartile range
    fence_low  = q1-1.5*iqr
    fence_high = q3+1.5*iqr
    df_out = df_in.loc[(df_in[col_name] > fence_low) & (df_in[col_name] < fence_high)]
    return df_out

Ricevo l'errore "ValueError: impossibile indicizzare con chiave multidimensionale" nella riga "df_out = df_in.loc [(df_in [nome_col]> fence_low) & (df_in [nome_col.] <Fence_high)]" Aiuterai
Imran Ahmad Ghazali

18

Per ogni serie nel frame di dati, è possibile utilizzare betweene quantilerimuovere valori anomali.

x = pd.Series(np.random.normal(size=200)) # with outliers
x = x[x.between(x.quantile(.25), x.quantile(.75))] # without outliers

3
Qui stai selezionando solo i dati all'interno dell'intervallo interquartile (IQR), ma tieni presente che al di fuori di questo intervallo possono esserci valori che non sono anomali.
BCArg

2
Scegliere ad es. 0.1 e 0.9 sarebbe abbastanza sicuro credo. Usare tra e quantili come questo è una sintassi piuttosto carina.
PascalVKooten

18

Da quando non ho visto una risposta che si occupi di valori numerici e non numerici tratti di attributi , ecco una risposta complementare.

È possibile che si desideri eliminare gli outlier solo sugli attributi numerici (le variabili categoriche difficilmente possono essere outlier).

Definizione della funzione

Ho esteso il suggerimento di @ tanemaki per gestire i dati quando sono presenti anche attributi non numerici:

from scipy import stats

def drop_numerical_outliers(df, z_thresh=3):
    # Constrains will contain `True` or `False` depending on if it is a value below the threshold.
    constrains = df.select_dtypes(include=[np.number]) \
        .apply(lambda x: np.abs(stats.zscore(x)) < z_thresh, reduce=False) \
        .all(axis=1)
    # Drop (inplace) values set to be rejected
    df.drop(df.index[~constrains], inplace=True)

uso

drop_numerical_outliers(df)

Esempio

Immagina un set dfdi dati con alcuni valori sulle case: vicolo, contorno del terreno, prezzo di vendita, ... Ad esempio: Documentazione dei dati

Innanzitutto, si desidera visualizzare i dati su un grafico a dispersione (con z-score Thresh = 3):

# Plot data before dropping those greater than z-score 3. 
# The scatterAreaVsPrice function's definition has been removed for readability's sake.
scatterAreaVsPrice(df)

Prima: Gr Liv Area vs SalePrice

# Drop the outliers on every attributes
drop_numerical_outliers(train_df)

# Plot the result. All outliers were dropped. Note that the red points are not
# the same outliers from the first plot, but the new computed outliers based on the new data-frame.
scatterAreaVsPrice(train_df)

After - Gr Liv Area vs. SalePrice


2
Ottima soluzione! Come heads-up reduce=Falseè stato deprecato dalla pandasversione 0.23.0
RK1

Sostituto result_type='reduce'di reduce=False.
Ekaba Bisong,

8

scipy.statsha metodi trim1()e trimboth()per tagliare gli outlier in una singola riga, secondo la classifica e una percentuale introdotta di valori rimossi.


1
trimbothè stato più facile per me.
parole per il

6

Un'altra opzione è quella di trasformare i tuoi dati in modo da mitigare l'effetto degli outlier. Puoi farlo salvando i tuoi dati.

import pandas as pd
from scipy.stats import mstats
%matplotlib inline

test_data = pd.Series(range(30))
test_data.plot()

Dati originali

# Truncate values to the 5th and 95th percentiles
transformed_test_data = pd.Series(mstats.winsorize(test_data, limits=[0.05, 0.05])) 
transformed_test_data.plot()

Dati Winsorized


6

Se ti piace il concatenamento dei metodi, puoi ottenere la tua condizione booleana per tutte le colonne numeriche come questa:

df.sub(df.mean()).div(df.std()).abs().lt(3)

Ogni valore di ogni colonna verrà convertito in True/Falsebase al fatto che sia meno di tre deviazioni standard dalla media o meno.


Questo dovrebbe essere le(3)sin dalla sua rimozione dei valori anomali. In questo modo ottieni Truegli outlier. Oltre a quel +1 e questa risposta dovrebbe essere più in alto
Erfan,

2

Puoi usare la maschera booleana:

import pandas as pd

def remove_outliers(df, q=0.05):
    upper = df.quantile(1-q)
    lower = df.quantile(q)
    mask = (df < upper) & (df > lower)
    return mask

t = pd.DataFrame({'train': [1,1,2,3,4,5,6,7,8,9,9],
                  'y': [1,0,0,1,1,0,0,1,1,1,0]})

mask = remove_outliers(t['train'], 0.1)

print(t[mask])

produzione:

   train  y
2      2  0
3      3  1
4      4  1
5      5  0
6      6  0
7      7  1
8      8  1

1

Dato che sono in una fase molto precoce del mio viaggio nella scienza dei dati, sto trattando i valori anomali con il codice seguente.

#Outlier Treatment

def outlier_detect(df):
    for i in df.describe().columns:
        Q1=df.describe().at['25%',i]
        Q3=df.describe().at['75%',i]
        IQR=Q3 - Q1
        LTV=Q1 - 1.5 * IQR
        UTV=Q3 + 1.5 * IQR
        x=np.array(df[i])
        p=[]
        for j in x:
            if j < LTV or j>UTV:
                p.append(df[i].median())
            else:
                p.append(j)
        df[i]=p
    return df

1

Ottieni il 98 ° e il 2 ° percentile come limiti dei nostri valori anomali

upper_limit = np.percentile(X_train.logerror.values, 98) 
lower_limit = np.percentile(X_train.logerror.values, 2) # Filter the outliers from the dataframe
data[‘target’].loc[X_train[‘target’]>upper_limit] = upper_limit data[‘target’].loc[X_train[‘target’]<lower_limit] = lower_limit

0

segue un esempio completo con dati e 2 gruppi:

importazioni:

from StringIO import StringIO
import pandas as pd
#pandas config
pd.set_option('display.max_rows', 20)

Esempio di dati con 2 gruppi: G1: Gruppo 1. G2: Gruppo 2:

TESTDATA = StringIO("""G1;G2;Value
1;A;1.6
1;A;5.1
1;A;7.1
1;A;8.1

1;B;21.1
1;B;22.1
1;B;24.1
1;B;30.6

2;A;40.6
2;A;51.1
2;A;52.1
2;A;60.6

2;B;80.1
2;B;70.6
2;B;90.6
2;B;85.1
""")

Leggi i dati di testo sul frame di dati Panda:

df = pd.read_csv(TESTDATA, sep=";")

Definire i valori anomali utilizzando le deviazioni standard

stds = 1.0
outliers = df[['G1', 'G2', 'Value']].groupby(['G1','G2']).transform(
           lambda group: (group - group.mean()).abs().div(group.std())) > stds

Definire i valori dei dati filtrati e i valori anomali:

dfv = df[outliers.Value == False]
dfo = df[outliers.Value == True]

Stampa il risultato:

print '\n'*5, 'All values with decimal 1 are non-outliers. In the other hand, all values with 6 in the decimal are.'
print '\nDef DATA:\n%s\n\nFiltred Values with %s stds:\n%s\n\nOutliers:\n%s' %(df, stds, dfv, dfo)

0

La mia funzione per far cadere gli outlier

def drop_outliers(df, field_name):
    distance = 1.5 * (np.percentile(df[field_name], 75) - np.percentile(df[field_name], 25))
    df.drop(df[df[field_name] > distance + np.percentile(df[field_name], 75)].index, inplace=True)
    df.drop(df[df[field_name] < np.percentile(df[field_name], 25) - distance].index, inplace=True)

0

Preferisco tagliare piuttosto che lasciar cadere. quanto segue verrà agganciato sul posto al 2 ° e al 98 ° pecentiles.

df_list = list(df)
minPercentile = 0.02
maxPercentile = 0.98

for _ in range(numCols):
    df[df_list[_]] = df[df_list[_]].clip((df[df_list[_]].quantile(minPercentile)),(df[df_list[_]].quantile(maxPercentile)))

-2

L'eliminazione e l'eliminazione di valori anomali credo statisticamente sia errata. Rende i dati diversi dai dati originali. Inoltre, rende i dati in una forma diseguale e quindi il modo migliore è ridurre o evitare l'effetto dei valori anomali con il log trasformando i dati. Questo ha funzionato per me:

np.log(data.iloc[:, :])

3
Non è possibile fare ipotesi sul perché l'OP voglia fare qualcosa.
RajeshM,
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.