[英]Detect and exclude outliers in a pandas DataFrame
我有一个只有几列的熊猫数据框。
现在我知道某些行是基于某个列值的异常值。
例如
“Vol”列的所有值都在
12xx
左右,其中一个值为4000
(离群值)。
现在我想排除那些具有Vol
列的行。
所以,基本上我需要在数据框上放置一个过滤器,以便我们选择所有行,其中特定列的值在,比方说,3 个标准偏差范围内。
实现这一目标的优雅方法是什么?
如果您的数据框中有多个列,并且想要删除至少一列中具有异常值的所有行,则以下表达式将一次性完成。
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)]
描述:
zscore
指定一列,例如df[0]
,然后删除.all(axis=1)
。df[(np.abs(stats.zscore(df[0])) < 3)]
对于每个数据框列,您可以通过以下方式获得分位数:
q = df["col"].quantile(0.99)
然后过滤:
df[df["col"] < q]
如果需要删除上下异常值,请将条件与 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)]
像在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())]
这个答案类似于@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”)在三个标准偏差内的 DataFrame:
df[((df['B'] - df['B'].mean()) / df['B'].std()).abs() < standard_deviations]
请参阅此处了解如何滚动应用此 z 分数: Rolling Z-score应用于熊猫数据框
#------------------------------------------------------------------------------
# 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
由于我还没有看到处理数字和非数字属性的答案,所以这里有一个补充答案。
您可能只想删除数值属性上的异常值(分类变量几乎不可能是异常值)。
函数定义
当非数字属性也存在时,我扩展了@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)
# 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)
对于数据框中的每个系列,您可以使用between
和quantile
来删除异常值。
x = pd.Series(np.random.normal(size=200)) # with outliers
x = x[x.between(x.quantile(.25), x.quantile(.75))] # without outliers
在回答实际问题之前,我们应该根据数据的性质询问另一个非常相关的问题:
想象一系列值[3, 2, 3, 4, 999]
(其中999
似乎不适合)并分析异常值检测的各种方法
这里的问题是,有问题的值严重扭曲了我们的测量mean
和std
差,导致不显眼的 z 分数大约为[-0.5, -0.5, -0.5, -0.5, 2.0]
,将每个值保持在两个标准差的范围内意思是。 因此,一个非常大的异常值可能会扭曲您对异常值的整体评估。 我不鼓励这种方法。
这个答案给出了一种更强大的方法,消除了底部和顶部 1% 的数据。 但是,如果这些数据确实是异常值,这将消除与问题无关的固定分数。 您可能会丢失大量有效数据,另一方面,如果您有超过 1% 或 2% 的数据作为异常值,您仍会保留一些异常值。
更强大的分位数原则版本:消除距离数据中位数超过四分位数范围f
倍的所有数据。 例如,这就是sklearn
的RobustScaler所做的。 IQR 和中位数对异常值具有鲁棒性,因此您比 z 分数方法的问题更聪明。
在正态分布中,我们大致有iqr=1.35*s
,因此您可以将 z-score 过滤器的z=3
转换为 iqr-过滤器的f=2.22
。 这将删除上面示例中的999
。
基本假设是您的数据至少“中间一半”是有效的并且与分布非常相似,而如果尾部与您的问题相关,您也会搞砸。
当然,还有一些花哨的数学方法,例如Peirce 准则、 Grubb 检验或Dixon 检验,仅举几例也适用于非正态分布数据。 它们都不容易实现,因此没有进一步解决。
在示例数据框中用np.nan
替换所有数值列的所有异常值。 该方法对 pandas 提供的所有 dtype都具有鲁棒性,并且可以轻松地应用于具有混合类型的数据帧:
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 值的所有行:
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 功能:
scipy.stats
有方法 trim1 trim1()
和trimboth()
根据排名和移除值的引入百分比,将异常值剪切在一行中。
如果你喜欢方法链接,你可以得到所有数字列的布尔条件,如下所示:
df.sub(df.mean()).div(df.std()).abs().lt(3)
每列的每个值都将根据其与平均值的距离是否小于三个标准差而转换为True/False
。
另一种选择是转换数据以减轻异常值的影响。 您可以通过对数据进行微调来做到这一点。
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()
您可以使用布尔掩码:
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
由于我处于数据科学之旅的早期阶段,我正在使用下面的代码处理异常值。
#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
获取第 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
带有数据和 2 个组的完整示例如下:
进口:
from StringIO import StringIO
import pandas as pd
#pandas config
pd.set_option('display.max_rows', 20)
2 组数据示例:G1:第 1 组。 G2:第 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
""")
将文本数据读取到 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)
我更喜欢剪辑而不是丢弃。 以下将在第 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)))
我认为删除和删除异常值在统计上是错误的。 它使数据与原始数据不同。 也使数据形状不均,因此最好的方法是通过对数转换数据来减少或避免异常值的影响。 这对我有用:
np.log(data.iloc[:, :])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.