检测和排除熊猫数据框架中的异常值

我有一个很少列的熊猫数据帧。

现在我知道某些行是基于某个列值的异常值。

例如

列“Vol”的所有值都在12xx附近,其中一个值是4000(离群值)。

现在我想排除那些像这样有Vol列的行。

所以,本质上,我需要在数据帧上放一个过滤器,这样我们就可以选择所有的行,其中某一列的值距离平均值在3个标准差之内。

实现这一点的优雅方式是什么?

508776 次浏览

像在numpy.array中那样使用boolean索引

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

对于一个系列,它是类似的:

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

如果您的数据框架中有多列,并且希望删除至少一列中有异常值的所有行,那么下面的表达式将一次性完成此操作。

df = pd.DataFrame(np.random.randn(100, 3))


import numpy as np
from scipy import stats
df[(np.abs(stats.zscore(df)) < 3).all(axis=1)]

描述:

  • 对于每一列,它首先计算中每个值的z分数 列,相对于列的均值和标准差
  • 然后取绝对z分数,因为方向没有
  • all(axis=1)确保对于每一行,所有列都满足 李约束。< / >
  • 最后,这个条件的结果被用于索引数据帧。

基于单个列筛选其他列

  • zscore指定一个列,例如df[0],然后删除.all(axis=1)
df[(np.abs(stats.zscore(df[0])) < 3)]

这个答案类似于@tanemaki提供的答案,但使用了lambda表达式而不是scipy stats

df = pd.DataFrame(np.random.randn(100, 3), columns=list('ABC'))


standard_deviations = 3
df[df.apply(lambda x: np.abs(x - x.mean()) / x.std() < standard_deviations)
.all(axis=1)]

要过滤只有一个列的数据帧(例如:B)在三个标准差之内:

df[((df['B'] - df['B'].mean()) / df['B'].std()).abs() < standard_deviations]

请看这里如何在滚动的基础上应用这个z分数:滚动z分数在熊猫数据帧中的应用

scipy.stats有方法trim1()trimboth(),根据排名和被移除值的引入百分比,在单行中删除异常值。

对于你的每一个数据帧列,你可以得到分位数:

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

然后用:

df[df["col"] < q]

如果需要移除上下异常值,将condition与and语句结合:

q_low = df["col"].quantile(0.01)
q_hi  = df["col"].quantile(0.99)


df_filtered = df[(df["col"] < q_hi) & (df["col"] > q_low)]

另一种选择是转换数据,以减轻异常值的影响。你可以通过winsorize你的数据来做到这一点。

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

Winsorized data

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

如果你喜欢方法链接,你可以得到所有数值列的布尔条件,如下所示:

df.sub(df.mean()).div(df.std()).abs().lt(3)

每一列的每一个值都将被转换为True/False,基于它与平均值的距离是否小于三个标准差。

下面是一个包含数据和2组的完整示例:

进口:

from StringIO import StringIO
import pandas as pd
#pandas config
pd.set_option('display.max_rows', 20)

有2个组的数据示例:G1:Group 1。G2:第二组:

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

读取文本数据到pandas数据框架:

df = pd.read_csv(TESTDATA, sep=";")

使用标准偏差定义离群值

stds = 1.0
outliers = df[['G1', 'G2', 'Value']].groupby(['G1','G2']).transform(
lambda group: (group - group.mean()).abs().div(group.std())) > stds

定义过滤后的数据值和异常值:

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

打印结果:

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)

去掉离群值的函数

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)
我认为删除和删除异常值在统计上是错误的。 它使数据不同于原始数据。 也使得数据的形状不均匀,因此最好的方法是通过对数据进行对数变换来减少或避免异常值的影响。

np.log(data.iloc[:, :])

我喜欢夹而不喜欢掉。以下将夹在第2和98分频。

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

由于我正处于我的数据科学之旅的早期阶段,我使用下面的代码来处理异常值。

#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

对于数据帧中的每个系列,你可以使用betweenquantile来移除异常值。

x = pd.Series(np.random.normal(size=200)) # with outliers
x = x[x.between(x.quantile(.25), x.quantile(.75))] # without outliers

把98和2百分位作为离群值的极限

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

你可以使用布尔掩码:

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

输出:

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

由于我还没有看到处理数值非数值属性的答案,这里是一个补充答案。

您可能只希望删除数值属性上的异常值(类别变量几乎不可能是异常值)。

函数定义

我扩展了@tanemaki的建议,当非数值属性也存在时处理数据:

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)

使用

drop_numerical_outliers(df)

例子

想象一个数据集df,其中包含一些关于房屋的值:小巷、土地轮廓、销售价格……例句:数据文档

首先,你想要在散点图上可视化数据(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

在回答实际问题之前,我们应该问另一个非常相关的问题,这取决于你的数据的性质:

什么是异常值?

想象一下[3, 2, 3, 4, 999]的值系列(其中999似乎不适合),并分析各种异常值检测的方法

z分数

这里的问题是,有问题的值严重扭曲了我们的测量meanstd,导致大致[-0.5, -0.5, -0.5, -0.5, 2.0]的不明显的z分数,使每个值保持在平均值的两个标准偏差内。因此,一个非常大的离群值可能会扭曲你对离群值的整个评估。我反对这种方法。

分位数过滤器

一个更健壮的方法是这个答案,消除底部和顶部1%的数据。然而,如果这些数据真的是异常值,这就排除了一个与问题无关的固定分数。您可能会丢失大量有效数据,另一方面,如果您有超过1%或2%的数据作为异常值,则仍然会保留一些异常值。

距中位数的距离

更健壮的分位数原则:从数据的中位数中删除所有大于f乘以< em >四分位范围< / em >的数据。这也是sklearnRobustScaler所使用的转换。IQR和中位数对异常值具有鲁棒性,因此您可以聪明地解决z分数方法的问题。

在正态分布中,我们大致有iqr=1.35*s,所以你可以将z-score过滤器的z=3转换为iqr过滤器的f=2.22。这将删除上面示例中的999

基本的假设是,至少“中间一半”;你的数据是有效的,很像分布,然而,如果你的分布有宽尾和狭窄的q_25%到q_75%区间,你也会搞砸。

高级统计方法

当然,还有一些奇特的数学方法,如皮尔斯的标准Grubb的测试迪克森的Q-test,这里仅举几个也适用于非正态分布数据的方法。它们都不容易实现,因此没有进一步解决。

代码

将示例数据帧上所有数值列的所有异常值替换为np.nan。该方法对pandas提供的所有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_and(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函数: