J'ai un DataFrame df avec 40 colonnes et de nombreux enregistrements.

Df:

User_id | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 | Col7 |...| Col39

Pour chaque colonne, à l'exception de la colonne user_id, je veux vérifier les valeurs aberrantes et supprimer tout l'enregistrement, si une valeur aberrante apparaît.

Pour la détection des valeurs aberrantes sur chaque ligne, j'ai décidé d'utiliser simplement les 5e et 95e centiles (je sais que ce n'est pas la meilleure méthode statistique):

Codez ce que j'ai jusqu'à présent:

P = np.percentile(df.Col1, [5, 95])
new_df = df[(df.Col1 > P[0]) & (df.Col1 < P[1])]

Question : comment appliquer cette approche à toutes les colonnes (sauf User_id) sans le faire à la main? Mon objectif est d'obtenir une trame de données sans enregistrements contenant des valeurs aberrantes.

Je vous remercie!

10
Mi Funk 6 mars 2016 à 17:09

4 réponses

Meilleure réponse

L'ensemble de données initial.

print(df.head())

   Col0  Col1  Col2  Col3  Col4  User_id
0    49    31    93    53    39       44
1    69    13    84    58    24       47
2    41    71     2    43    58       64
3    35    56    69    55    36       67
4    64    24    12    18    99       67

Supprimez d'abord la colonne User_id

filt_df = df.loc[:, df.columns != 'User_id']

Ensuite, calculer les centiles.

low = .05
high = .95
quant_df = filt_df.quantile([low, high])
print(quant_df)

       Col0   Col1  Col2   Col3   Col4
0.05   2.00   3.00   6.9   3.95   4.00
0.95  95.05  89.05  93.0  94.00  97.05

Valeurs de filtrage suivantes basées sur les centiles calculés. Pour ce faire, j'utilise un apply par colonnes et c'est tout!

filt_df = filt_df.apply(lambda x: x[(x>quant_df.loc[low,x.name]) & 
                                    (x < quant_df.loc[high,x.name])], axis=0)

Ramener le User_id.

filt_df = pd.concat([df.loc[:,'User_id'], filt_df], axis=1)

Enfin, les lignes avec des valeurs NaN peuvent être supprimées simplement comme ceci.

filt_df.dropna(inplace=True)
print(filt_df.head())

   User_id  Col0  Col1  Col2  Col3  Col4
1       47    69    13    84    58    24
3       67    35    56    69    55    36
5        9    95    79    44    45    69
6       83    69    41    66    87     6
9       87    50    54    39    53    40

Vérification du résultat

print(filt_df.head())

   User_id  Col0  Col1  Col2  Col3  Col4
0       44    49    31   NaN    53    39
1       47    69    13    84    58    24
2       64    41    71   NaN    43    58
3       67    35    56    69    55    36
4       67    64    24    12    18   NaN

print(filt_df.describe())

          User_id       Col0       Col1       Col2       Col3       Col4
count  100.000000  89.000000  88.000000  88.000000  89.000000  89.000000
mean    48.230000  49.573034  45.659091  52.727273  47.460674  57.157303
std     28.372292  25.672274  23.537149  26.509477  25.823728  26.231876
min      0.000000   3.000000   5.000000   7.000000   4.000000   5.000000
25%     23.000000  29.000000  29.000000  29.500000  24.000000  36.000000
50%     47.000000  50.000000  40.500000  52.500000  49.000000  59.000000
75%     74.250000  69.000000  67.000000  75.000000  70.000000  79.000000
max     99.000000  95.000000  89.000000  92.000000  91.000000  97.000000

Comment générer l'ensemble de données de test

np.random.seed(0)
nb_sample = 100
num_sample = (0,100)

d = dict()
d['User_id'] = np.random.randint(num_sample[0], num_sample[1], nb_sample)
for i in range(5):
    d['Col' + str(i)] = np.random.randint(num_sample[0], num_sample[1], nb_sample)

df = DataFrame.from_dict(d)
20
Romain 6 mars 2016 à 16:20

Ce que vous décrivez est similaire au processus de winsorisation, qui découpe les valeurs (par exemple, aux 5e et 95e centiles) au lieu de les éliminer complètement.

Voici un exemple:

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

4
mgoldwasser 3 mai 2019 à 18:53

Utilisez ce code et ne perdez pas votre temps:

Q1 = df.quantile(0.25)
Q3 = df.quantile(0.75)
IQR = Q3 - Q1

df = df[~((df < (Q1 - 1.5 * IQR)) |(df > (Q3 + 1.5 * IQR))).any(axis=1)]
1
E.Zolduoarrati 17 déc. 2019 à 01:26

Utilisez une jointure interne. Quelque chose comme ça devrait fonctionner

cols = df.columns.tolist()
cols.remove('user_id') #remove user_id from list of columns

P = np.percentile(df[cols[0]], [5, 95])
new_df = df[(df[cols[0] > P[0]) & (df[cols[0]] < P[1])]
for col in cols[1:]:
    P = np.percentile(df[col], [5, 95])
    new_df = new_df.join(df[(df[col] > P[0]]) & (df[col] < P[1])], how='inner')
1
Rishabh Srivastava 6 mars 2016 à 14:18