在回答实际问题之前,我们应该根据您的数据的性质询问另一个非常相关的问题:
什么是异常值?
想象一系列值[3, 2, 3, 4, 999](其中999 似乎不适合)并分析各种异常值检测方法
Z 分数
这里的问题是,有问题的值严重扭曲了我们的度量 mean 和 std,导致不显眼的 z 分数大约为 [-0.5, -0.5, -0.5, -0.5, 2.0],使每个值都保持在平均值的两个标准差内。因此,一个非常大的异常值可能会扭曲您对异常值的整体评估。我不鼓励这种方法。
分位数过滤器
一种更强大的方法是this answer,消除了底部和顶部 1% 的数据。但是,如果这些数据确实是异常值,这将消除与问题无关的固定分数。您可能会丢失大量有效数据,另一方面,如果您有超过 1 % 或 2 % 的数据作为异常值,您仍会保留一些异常值。
IQR 距离中位数
更强大的分位数原则版本:消除所有与数据中位数相差超过f 乘以interquartile range 的数据。例如,sklearn 的 RobustScaler 就是这样做的。 IQR 和中位数对异常值具有鲁棒性,因此您比 z-score 方法的问题更聪明。
在正态分布中,我们大致有 iqr=1.35*s,因此您可以将 z-score 过滤器的 z=3 转换为 iqr 过滤器的 f=2.22。这将删除上面示例中的999。
基本假设是您的数据至少“中间一半”是有效的并且与分布非常相似,而如果尾部与您的问题相关,您也会搞砸。
高级统计方法
当然,还有一些花哨的数学方法,例如 Peirce criterion、Grubb's test 或 Dixon's Q-test,仅举几个也适用于非正态分布数据的方法。它们都不容易实现,因此没有进一步解决。
代码
在示例数据框中将所有数值列的所有异常值替换为 np.nan。该方法对 pandas 提供的all dtypes 具有鲁棒性,可以轻松应用于混合类型的数据帧:
import pandas as pd
import numpy as np
# sample data of all dtypes in pandas (column 'a' has an outlier) # dtype:
df = pd.DataFrame({'a': list(np.random.rand(8)) + [123456, np.nan], # float64
'b': [0,1,2,3,np.nan,5,6,np.nan,8,9], # int64
'c': [np.nan] + list("qwertzuio"), # object
'd': [pd.to_datetime(_) for _ in range(10)], # datetime64[ns]
'e': [pd.Timedelta(_) for _ in range(10)], # timedelta[ns]
'f': [True] * 5 + [False] * 5, # bool
'g': pd.Series(list("abcbabbcaa"), dtype="category")}) # category
cols = df.select_dtypes('number').columns # limits to a (float), b (int) and e (timedelta)
df_sub = df.loc[:, cols]
# OPTION 1: z-score filter: z-score < 3
lim = np.abs((df_sub - df_sub.mean()) / df_sub.std(ddof=0)) < 3
# OPTION 2: quantile filter: discard 1% upper / lower values
lim = np.logical_or(df_sub < df_sub.quantile(0.99, numeric_only=False),
df_sub > df_sub.quantile(0.01, numeric_only=False))
# OPTION 3: iqr filter: within 2.22 IQR (equiv. to z-score < 3)
iqr = df_sub.quantile(0.75, numeric_only=False) - df_sub.quantile(0.25, numeric_only=False)
lim = np.abs((df_sub - df_sub.median()) / iqr) < 2.22
# replace outliers with nan
df.loc[:, cols] = df_sub.where(lim, np.nan)
删除包含至少一个 nan-value 的所有行:
df.dropna(subset=cols, inplace=True) # drop rows with NaN in numerical columns
# or
df.dropna(inplace=True) # drop rows with NaN in any column
使用 pandas 1.3 函数: