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.
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())]
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 chacune de vos colonnes dataframe, vous pouvez obtenir un quantile avec:
q = df["col"].quantile(0.99)
puis filtrez avec:
df[df["col"] < q]
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]
#------------------------------------------------------------------------------
# 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
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
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.
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)
# 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)
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()
# 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()
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.
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)
É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
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
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
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)
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)))