Pour les données fournies, je souhaite définir les valeurs aberrantes (définies par un niveau de confiance de 95% ou une fonction quantile de 95% ou tout autre élément requis) en tant que valeurs nan. Vous trouverez ci-dessous les données et le code que j'utilise actuellement. Je serais heureux si quelqu'un pouvait m'expliquer davantage.
import numpy as np, matplotlib.pyplot as plt
data = np.random.Rand(1000)+5.0
plt.plot(data)
plt.xlabel('observation number')
plt.ylabel('recorded value')
plt.show()
Le problème avec percentile
est que les points identifiés comme points aberrants sont fonction de la taille de votre échantillon.
Il existe un grand nombre de façons de tester les valeurs aberrantes, et vous devriez réfléchir à la façon dont vous les classifiez. Idéalement, vous devriez utiliser des informations a priori (par exemple, "tout ce qui est au-dessus/au-dessous de cette valeur est irréaliste parce que ...").
Cependant, un critère courant, non trop déraisonnable, consiste à supprimer des points en fonction de leur "écart absolu médian".
Voici une implémentation pour le cas N-dimensionnel (à partir d'un code pour un article ici: https://github.com/joferkington/oost_paper_code/blob/master/utilities.py ):
def is_outlier(points, thresh=3.5):
"""
Returns a boolean array with True if points are outliers and False
otherwise.
Parameters:
-----------
points : An numobservations by numdimensions array of observations
thresh : The modified z-score to use as a threshold. Observations with
a modified z-score (based on the median absolute deviation) greater
than this value will be classified as outliers.
Returns:
--------
mask : A numobservations-length boolean array.
References:
----------
Boris Iglewicz and David Hoaglin (1993), "Volume 16: How to Detect and
Handle Outliers", The ASQC Basic References in Quality Control:
Statistical Techniques, Edward F. Mykytka, Ph.D., Editor.
"""
if len(points.shape) == 1:
points = points[:,None]
median = np.median(points, axis=0)
diff = np.sum((points - median)**2, axis=-1)
diff = np.sqrt(diff)
med_abs_deviation = np.median(diff)
modified_z_score = 0.6745 * diff / med_abs_deviation
return modified_z_score > thresh
Ceci est très similaire à ne de mes réponses précédentes , mais je voulais illustrer l'effet de la taille de l'échantillon en détail.
Comparons un test de valeurs aberrantes basé sur le centile (similaire à la réponse de @ CTZhu) avec un test d'écart médian absolu (MAD) pour diverses tailles d'échantillons:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
def main():
for num in [10, 50, 100, 1000]:
# Generate some data
x = np.random.normal(0, 0.5, num-3)
# Add three outliers...
x = np.r_[x, -3, -10, 12]
plot(x)
plt.show()
def mad_based_outlier(points, thresh=3.5):
if len(points.shape) == 1:
points = points[:,None]
median = np.median(points, axis=0)
diff = np.sum((points - median)**2, axis=-1)
diff = np.sqrt(diff)
med_abs_deviation = np.median(diff)
modified_z_score = 0.6745 * diff / med_abs_deviation
return modified_z_score > thresh
def percentile_based_outlier(data, threshold=95):
diff = (100 - threshold) / 2.0
minval, maxval = np.percentile(data, [diff, 100 - diff])
return (data < minval) | (data > maxval)
def plot(x):
fig, axes = plt.subplots(nrows=2)
for ax, func in Zip(axes, [percentile_based_outlier, mad_based_outlier]):
sns.distplot(x, ax=ax, rug=True, hist=False)
outliers = x[func(x)]
ax.plot(outliers, np.zeros_like(outliers), 'ro', clip_on=False)
kwargs = dict(y=0.95, x=0.05, ha='left', va='top')
axes[0].set_title('Percentile-based Outliers', **kwargs)
axes[1].set_title('MAD-based Outliers', **kwargs)
fig.suptitle('Comparing Outlier Tests with n={}'.format(len(x)), size=14)
main()
Notez que le classificateur basé sur MAD fonctionne correctement quelle que soit la taille de l'échantillon, tandis que le classificateur basé sur les centiles classe plus de points que la taille de l'échantillon est grande, qu'il s'agisse ou non de valeurs aberrantes.
La détection des valeurs aberrantes dans des données unidimensionnelles dépend de leur distribution
1 - Distribution normale :
Un autre moyen est l’intervalle de prédiction si vous voulez un intervalle de confiance des points de données plutôt que la moyenne.
Les valeurs des données sont distribuées de manière aléatoire sur une plage : la moyenne peut ne pas être une représentation fidèle des données, car la moyenne est facilement influencée par des valeurs aberrantes (très petit). ou des valeurs importantes dans l'ensemble de données qui ne sont pas typiques) La médiane est un autre moyen de mesurer le centre d'un ensemble de données numériques.
Écart absolu médian - une méthode qui mesure la distance de tous les points à la médiane en termes de distance médiane http: //www.itl .nist.gov/div898/handbook/eda/section3/eda35h.htm - a une bonne explication, comme expliqué dans la réponse de Joe Kington ci-dessus
2 - Distribution symétrique : à nouveau: l'écart médian absolu est une bonne méthode si le calcul du score z et le seuil sont modifiés en conséquence
Explication: http://eurekastatistics.com/using-the-median-absolute-deviation-to-find-outliers/
3 - Distribution asymétrique: Double MAD - Double écart médian absolu Explication dans le lien ci-dessus
Attacher mon code python pour référence:
def is_outlier_doubleMAD(self,points):
"""
FOR ASSYMMETRIC DISTRIBUTION
Returns : filtered array excluding the outliers
Parameters : the actual data Points array
Calculates median to divide data into 2 halves.(skew conditions handled)
Then those two halves are treated as separate data with calculation same as for symmetric distribution.(first answer)
Only difference being , the thresholds are now the median distance of the right and left median with the actual data median
"""
if len(points.shape) == 1:
points = points[:,None]
median = np.median(points, axis=0)
medianIndex = (points.size/2)
leftData = np.copy(points[0:medianIndex])
rightData = np.copy(points[medianIndex:points.size])
median1 = np.median(leftData, axis=0)
diff1 = np.sum((leftData - median1)**2, axis=-1)
diff1 = np.sqrt(diff1)
median2 = np.median(rightData, axis=0)
diff2 = np.sum((rightData - median2)**2, axis=-1)
diff2 = np.sqrt(diff2)
med_abs_deviation1 = max(np.median(diff1),0.000001)
med_abs_deviation2 = max(np.median(diff2),0.000001)
threshold1 = ((median-median1)/med_abs_deviation1)*3
threshold2 = ((median2-median)/med_abs_deviation2)*3
#if any threshold is 0 -> no outliers
if threshold1==0:
threshold1 = sys.maxint
if threshold2==0:
threshold2 = sys.maxint
#multiplied by a factor so that only the outermost points are removed
modified_z_score1 = 0.6745 * diff1 / med_abs_deviation1
modified_z_score2 = 0.6745 * diff2 / med_abs_deviation2
filtered1 = []
i = 0
for data in modified_z_score1:
if data < threshold1:
filtered1.append(leftData[i])
i += 1
i = 0
filtered2 = []
for data in modified_z_score2:
if data < threshold2:
filtered2.append(rightData[i])
i += 1
filtered = filtered1 + filtered2
return filtered
J'ai adapté le code de http://eurekastatistics.com/using-the-median-absolute-deviation-to-find-outliers et donne les mêmes résultats que ceux de Joe Kington, mais utilise L1. distance au lieu de L2 et supporte les distributions asymétriques. Le code R original n’avait pas de multiplicateur de Joe de 0.6745, j’ai donc ajouté cela pour assurer la cohérence de ce fil. Pas sûr à 100% si c'est nécessaire, mais fait la comparaison pomme à pomme.
def doubleMADsfromMedian(y,thresh=3.5):
# warning: this function does not check for NAs
# nor does it address issues when
# more than 50% of your data have identical values
m = np.median(y)
abs_dev = np.abs(y - m)
left_mad = np.median(abs_dev[y <= m])
right_mad = np.median(abs_dev[y >= m])
y_mad = left_mad * np.ones(len(y))
y_mad[y > m] = right_mad
modified_z_score = 0.6745 * abs_dev / y_mad
modified_z_score[y == m] = 0
return modified_z_score > thresh
Utilisation np.percentile
comme l'a suggéré @Martin:
percentiles = np.percentile(data, [2.5, 97.5])
# or =>, <= for within 95%
data[(percentiles[0]<data) & (percentiles[1]>data)]
# set the outliners to np.nan
data[(percentiles[0]>data) | (percentiles[1]<data)] = np.nan
Eh bien, une solution simple peut également être, en supprimant quelque chose qui en dehors de 2 écarts types (ou 1,96):
import random
def outliers(tmp):
"""tmp is a list of numbers"""
outs = []
mean = sum(tmp)/(1.0*len(tmp))
var = sum((tmp[i] - mean)**2 for i in range(0, len(tmp)))/(1.0*len(tmp))
std = var**0.5
outs = [tmp[i] for i in range(0, len(tmp)) if abs(tmp[i]-mean) > 1.96*std]
return outs
lst = [random.randrange(-10, 55) for _ in range(40)]
print lst
print outliers(lst)