web-dev-qa-db-fra.com

Détecter et exclure les valeurs aberrantes dans le cadre de données Pandas

J'ai une base de données de pandas avec peu de colonnes.

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

Par exemple, les colonnes - "Vol" a toutes les valeurs autour de 12xx et une valeur est 4000 (valeur aberrante).

Maintenant, je voudrais exclure les lignes qui ont une colonne 'Vol' comme celle-ci . Donc, je dois essentiellement mettre un filtre sur le cadre de données de sorte que nous sélectionnions toutes les lignes où les valeurs d'une certaine colonne sont comprises, par exemple 3 écarts types par rapport à la moyenne.

Quelle est une manière élégante de réaliser ceci. 

111
AMM

Utilisez l'indexation boolean comme vous le feriez dans numpy.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 pareil:

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

Si vous avez plusieurs colonnes dans votre structure de données et souhaitez supprimer toutes les lignes contenant des valeurs aberrantes dans au moins une colonne, l'expression suivante le fera 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 de la colonne et à l’écart type. 
  • Alors, prend l’absolu du Z-score car la direction n’a pas d'importance, seulement si elle est inférieure au seuil.
  • all (axis = 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.
110
tanemaki

Pour chacune de vos colonnes dataframe, vous pouvez obtenir un quantile avec:

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

puis filtrez avec:

df[df["col"] < q]
50
user6903745

Cette réponse est similaire à celle fournie par @tanemaki, mais utilise une expression lambda au lieu 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 cadre de données où une seule colonne (par exemple, "B") se situe à moins de trois écarts-types:

df[((df.B - df.B.mean()) / df.B.std()).abs() < 3]
23
Alexander
#------------------------------------------------------------------------------
# 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
10
user2708149

Pour chaque série dans le cadre de données, vous pouvez utiliser between et quantile pour 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
6
Jeff Hernandez

scipy.stats a les méthodes trim1() et trimboth() pour réduire les valeurs éloignées d'une seule ligne, en fonction du classement et du pourcentage introduit de valeurs supprimées.

6
Oleg N. Osychenko

Comme je n'ai pas vu de réponse qui traite des attributs numériques et non numériques , voici une réponse complémentaire.

Vous voudrez 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 la fonction

J'ai étendu la suggestion de @ tanemaki de 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)

Utilisation

drop_numerical_outliers(df)

Exemple

Imaginez un ensemble de données df avec certaines valeurs relatives aux maisons: allée, contour des terrains, prix de vente, ... E.g: Documentation de données

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

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

After - Gr Liv Area Versus SalePrice

5
KeyMaker00

Une autre option consiste à transformer vos données pour atténuer les effets des valeurs aberrantes. Vous pouvez le faire en optimisant vos données. 

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

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

 Original data

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

 Winsorized data

1
mgoldwasser

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/False en fonction de son écart inférieur de trois écarts types à la moyenne ou non.

1
Ted Petrou

Ma fonction pour abandonner 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)
0
luminousmen

Étant donné que je suis à un stade très précoce de mon aventure dans le domaine de la science des données, je traite les personnes éloignées 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
0
Arun Gupta

Vous pouvez utiliser le 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])

sortie:

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

Obtenez les 98e et 2e percentiles 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
0
Dheeraj

voici un exemple complet avec données et 2 groupes:

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 fichier 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éfinir les valeurs de données filtrées et les valeurs aberrantes:

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

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

Je préfère couper plutôt que de laisser tomber. les éléments suivants seront en place aux 2e et 98e 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)))
0
tnf