Détecter et exclure les valeurs aberrantes dans la trame de données Pandas

198

J'ai un cadre de données pandas avec quelques colonnes.

Maintenant, je sais que certaines lignes sont des valeurs aberrantes basées sur une certaine valeur de colonne.

Par exemple

la colonne 'Vol' a toutes les valeurs autour 12xxet une valeur est 4000(aberrante).

Maintenant, je voudrais exclure les lignes qui ont une Volcolonne comme celle-ci.

Donc, essentiellement, je dois mettre un filtre sur le bloc de données de telle sorte que nous sélectionnons toutes les lignes où les valeurs d'une certaine colonne sont à l'intérieur, disons, de 3 écarts-types de la moyenne.

Quelle est une manière élégante d'y parvenir?

AMM
la source

Réponses:

214

Si vous avez plusieurs colonnes dans votre trame de données et que vous souhaitez supprimer toutes les lignes qui ont des valeurs aberrantes dans au moins une colonne, l'expression suivante le ferait en une seule fois.

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

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

la description:

  • Pour chaque colonne, il calcule d'abord le score Z de chaque valeur de la colonne, par rapport à la moyenne et à l'écart-type de la colonne.
  • Ensuite, il prend l'absolu de Z-score car la direction n'a pas d'importance, seulement si elle est inférieure au seuil.
  • all (axe = 1) garantit que pour chaque ligne, toutes les colonnes satisfont à la contrainte.
  • Enfin, le résultat de cette condition est utilisé pour indexer la trame de données.
tanemaki
la source
6
Pouvez-vous expliquer ce que fait ce code? Et peut-être fournir une idée de la façon dont je pourrais supprimer toutes les lignes qui ont une valeur aberrante dans une seule colonne spécifiée? Serait utile. Merci.
samthebrand
17
Pour chaque colonne, il calcule d'abord le score Z de chaque valeur de la colonne, par rapport à la moyenne et à l'écart-type de la colonne. Il prend alors l'absolu du score Z car la direction n'a pas d'importance, seulement si elle est inférieure au seuil. .all (axe = 1) garantit que pour chaque ligne, toutes les colonnes satisfont à la contrainte. Enfin, le résultat de cette condition est utilisé pour indexer la trame de données.
rafaelvalle
4
Comment géreriez-vous la situation quand il y a des Nulls / Nans dans les colonnes. Comment pouvons-nous les ignorer?
asimo
6
comment traitons-nous les colonnes str pour cette solution? Si certaines colonnes ne sont pas numériques et que nous voulons supprimer les valeurs aberrantes basées sur toutes les colonnes numériques.
ssp
6
Erreur: "TypeError: type (s) d'opérande non pris en charge pour /: 'str' et 'int'"
sak
143

Utilisez l' booleanindexation comme vous le feriez dansnumpy.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

Pour une série, c'est similaire:

S = pd.Series(np.random.normal(size=200))
S[~((S-S.mean()).abs() > 3*S.std())]
CT Zhu
la source
6
leur est un DataFrame.abs()FYI, aussiDataFrame.clip()
Jeff
7
Dans le cas de clip()Jeff, les contours ne sont pas supprimés: df.SOME_DATA.clip(-3std,+3std)affectez les contours à + 3std ou -3std
CT Zhu
1
C'est presque la même chose, @AMM
CT Zhu
1
Comment pouvons-nous faire la même chose si notre bloc de données pandas comprend 100 colonnes?
DreamerP
1
Génial, merci pour cette réponse @CTZhu. @DreamerP vous pouvez simplement l' appliquer à l'ensemble dataframe avec: df_new = df[np.abs(df - df.mean()) <= (3 * df.std())]. Mais contrairement à l'appliquer à une série ou à une seule colonne, cela remplacera les valeurs aberrantes avec np.nanet conservera la forme du DataFrame, donc une interpolation peut être nécessaire pour remplir les valeurs manquantes.
Scotty1-
94

Pour chacune de vos colonnes de trame de données, vous pouvez obtenir un quantile avec:

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

puis filtrer avec:

df[df["col"] < q]

Si vous devez supprimer les valeurs aberrantes inférieures et supérieures, combinez la condition avec une instruction 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)]
user6903745
la source
3
Cet article donne un très bon aperçu des techniques d'élimination des valeurs aberrantes machinelearningmastery.com/…
user6903745
2
cela pourrait supprimer les valeurs aberrantes uniquement de la limite supérieure .. pas inférieure?
indolentdeveloper
1
@indolentdeveloper vous avez raison, inversez simplement l'inégalité pour supprimer les valeurs aberrantes inférieures ou combinez-les avec un opérateur OR.
user6903745
4
L'idée du commentaire était de mettre à jour les réponses;). Puisque quelqu'un peut manquer ce point.
indolentdeveloper
@ user6903745 ET instruction ou "OU"?
AB
38

Cette réponse est similaire à celle fournie par @tanemaki, mais utilise une lambdaexpression à la place de 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)]

Pour filtrer le DataFrame où UNE seule colonne (par exemple «B») se trouve dans les trois écarts-types:

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

Voir ici pour savoir comment appliquer ce z-score sur une base continue: Rolling Z-score appliqué aux pandas dataframe

Alexandre
la source
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
user2708149
la source
J'obtiens l'erreur "ValueError: Impossible d'indexer avec une clé multidimensionnelle" dans la ligne "df_out = df_in.loc [(df_in [col_name]> fence_low) & (df_in [col_name] <fence_high)]" Allez-vous aider
Imran Ahmad Ghazali
18

Pour chaque série de la trame de données, vous pouvez utiliser betweenet quantilepour supprimer les valeurs aberrantes.

x = pd.Series(np.random.normal(size=200)) # with outliers
x = x[x.between(x.quantile(.25), x.quantile(.75))] # without outliers
Jeff Hernandez
la source
3
Ici, vous sélectionnez uniquement des données dans la plage interquartile (IQR), mais gardez à l'esprit qu'il peut y avoir des valeurs en dehors de cette plage qui ne sont pas aberrantes.
BCArg
2
Choisir par exemple 0,1 et 0,9 serait assez sûr je pense. Utiliser entre et les quantiles comme celui-ci est une jolie syntaxe.
PascalVKooten
18

Depuis que je n'ai pas vu de réponse qui traite de numérique et non numérique attributs , voici une réponse complémentaire.

Vous souhaiterez peut-être supprimer les valeurs aberrantes uniquement sur les attributs numériques (les variables catégorielles peuvent difficilement être des valeurs aberrantes).

Définition de fonction

J'ai étendu la suggestion de @ tanemaki pour gérer les données lorsque des attributs non numériques sont également présents:

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)

Usage

drop_numerical_outliers(df)

Exemple

Imaginez un ensemble dfde données avec quelques valeurs sur les maisons: ruelle, contour du terrain, prix de vente, ... Par exemple: Documentation des données

Tout d'abord, vous souhaitez visualiser les données sur un graphique à nuages ​​de points (avec 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)

Avant - Gr Liv Area Versus 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)

Après - Gr Liv Area Versus SalePrice

KeyMaker00
la source
2
Excellente solution! Un avertissement reduce=Falseest obsolète depuis la pandasversion 0.23.0
RK1
Remplacer result_type='reduce'par reduce=False.
Ekaba Bisong
8

scipy.statsa des méthodes trim1()et trimboth()de couper les valeurs aberrantes sur une seule ligne, selon le classement et un pourcentage introduit de valeurs supprimées.

Oleg N. Osychenko
la source
1
trimbothétait plus facile pour moi.
wordsforhewise
6

Une autre option consiste à transformer vos données afin d'atténuer l'effet des valeurs aberrantes. Vous pouvez le faire en gagnantorisant vos données.

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

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

Données d'origine

# 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()

Données gagnées

mgoldwasser
la source
6

Si vous aimez le chaînage de méthodes, vous pouvez obtenir votre condition booléenne pour toutes les colonnes numériques comme ceci:

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

Chaque valeur de chaque colonne sera convertie en True/Falsefonction de si elle est inférieure ou non à trois écarts-types de la moyenne.

Ted Petrou
la source
Cela devrait être le(3)depuis sa suppression des valeurs aberrantes. De cette façon, vous obtenez Truepour les valeurs aberrantes. En plus de ce +1 et cette réponse devrait être plus élevée
Erfan
2

Vous pouvez utiliser un masque booléen:

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

production:

   train  y
2      2  0
3      3  1
4      4  1
5      5  0
6      6  0
7      7  1
8      8  1
Manualmsdos
la source
1

Comme je suis à un stade très précoce de mon parcours en science des données, je traite les valeurs aberrantes avec le code ci-dessous.

#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
Arun Gupta
la source
1

Obtenez le 98e et le 2e centile comme limites de nos valeurs aberrantes

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
Dheeraj
la source
0

un exemple complet avec des données et 2 groupes suit:

Importations:

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

Exemple de données avec 2 groupes: G1: Groupe 1. G2: Groupe 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
""")

Lire les données texte dans le cadre de données pandas:

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

Définir les valeurs aberrantes à l'aide des écarts-types

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

Définissez les valeurs de données filtrées et les valeurs aberrantes:

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

Imprimez le résultat:

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)
Wagner Cipriano
la source
0

Ma fonction pour supprimer les valeurs aberrantes

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)
hommes lumineux
la source
0

Je préfère couper plutôt que laisser tomber. les éléments suivants seront clipsés aux 2e et 98e pécentiles.

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)))
tnf
la source
-2

Supprimer et supprimer les valeurs aberrantes, je crois, est erroné statistiquement. Cela rend les données différentes des données d'origine. Rend également les données de forme inégale et, par conséquent, la meilleure façon est de réduire ou d'éviter l'effet des valeurs aberrantes en transformant les données en journal. Cela a fonctionné pour moi:

np.log(data.iloc[:, :])
Ezekiel Ohene Asare
la source
3
Ne peut pas faire d'hypothèses sur la raison pour laquelle le PO souhaite faire quelque chose.
RajeshM