Vol
的所有值都在12xx左右,而一个值是4000(异常值)。
我想要排除那些具有这样的Vol
列的行。因此,本质上我需要在数据框上设置一个过滤器,以便选择所有某列值在平均值的3个标准差范围内的行。
有什么优雅的方法可以实现这个?
scipy.stats.zscore
}}函数如果您的数据框中有多个列,并且希望删除所有至少有一个列中存在异常值的行,可以使用以下表达式一次性完成:
import pandas as pd
import numpy as np
from scipy import stats
df = pd.DataFrame(np.random.randn(100, 3))
df[(np.abs(stats.zscore(df)) < 3).all(axis=1)]
( < 3).all(axis=1)
检查每一行的所有列值是否都在均值的3个标准差范围内。与上述相同,但指定一个列作为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语句将条件组合起来: 如果需要删除较低和较高的异常值,请使用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
中一样使用布尔索引。
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())]
clip()
函数,在使用 df.SOME_DATA.clip(-3std,+3std)
进行调用时,Jeff
,数据的边界值并不会被删除,而是将超出范围的值分配到 +3std 或 -3std 上。请注意保持原意不变,并尽可能使翻译通俗易懂。 - CT Zhudf_new = df[np.abs(df - df.mean()) <= (3 * df.std())]
。但是,与将其应用于Series或单个列相反,这将使用np.nan
替换异常值并保持DataFrame的形状,因此可能需要插值来填充缺失的值。 - JE_Muc这个答案与@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)]
要筛选DataFrame,仅保留一个列(如'B')在三个标准差范围内的数据:
df[((df['B'] - df['B'].mean()) / df['B'].std()).abs() < standard_deviations]
查看此处了解如何在滚动基础上应用此Z分数:应用于pandas数据框的滚动Z分数
在回答实际问题之前,根据数据的性质,我们应该问另一个非常相关的问题:
想象一系列数值[3, 2, 3, 4, 999]
(其中999
似乎不合适),并分析各种异常值检测方法
问题在于,所讨论的值严重扭曲了我们的度量方式mean
和std
,导致Z-score大约为[-0.5, -0.5, -0.5, -0.5, 2.0]
,保持每个值在平均值的两个标准偏差内。一个非常大的异常值可能会扭曲您对异常值的整体评估。我不鼓励使用这种方法。
更为稳健的方法是此答案,消除数据底部和顶部的1%。但是,这样会消除相对固定的一部分数据,而独立于这些数据是否真的是异常值。您可能会失去很多有效的数据,并且另一方面,如果您的数据中有1%或2%以上的数据是异常值,则仍会保留一些异常值。
这是分位数原理的更稳健版本:消除所有距离数据的中位数超过f
倍四分位距。例如,sklearn
的RobustScaler
使用这种变换。IQR和中位数对异常值具有鲁棒性,因此您可以避免Z-score方法的问题。
在正常分布中,我们大约有iqr=1.35*s
,因此您将把z-score筛选器的z=3
转换为iqr-filter的f=2.22
。这将删除上述示例中的999
。
基本假设是至少您的数据的“中间一半”有效,并且很好地反映了该分布,而您也会搞砸,如果您的分布具有宽尾巴和一个窄的q_25%到q_75%区间。
当然,还有一些花哨的数学方法,例如佩尔斯准则、格鲁布斯检验或迪克森Q检验等,也适用于非正态分布数据。它们中没有一个容易实现,因此不再进一步讨论。
将所有数字列的所有异常值替换为np.nan
在示例数据框上。该方法对Pandas提供的所有数据类型
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 函数:
np.logical_or
应该改为np.logical_and
才能正常工作(选项2)。 - user1259201#------------------------------------------------------------------------------
# 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 阈值=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)
pandas
版本 0.23.0 起,reduce=False
已被弃用。 - RK1reduce=False
替换为result_type='reduce'
。 - Ekaba Bisong对于数据框中的每个系列,您可以使用between
和quantile
来去除异常值。
x = pd.Series(np.random.normal(size=200)) # with outliers
x = x[x.between(x.quantile(.25), x.quantile(.75))] # without outliers
scipy.stats
有trim1()
和trimboth()
方法,可以根据排名和指定的移除数据百分比在单个行中剪裁异常值。
trimboth
对我来说是最容易的。 - wordsforthewise如果你喜欢使用方法链,你可以像这样获取所有数字列的布尔条件:
df.sub(df.mean()).div(df.std()).abs().lt(3)
每列的每个值将根据其是否距离平均值小于三个标准差来转换为True/False
。
le(3)
,因为它正在去除异常值。这样您就可以得到异常值的True
。除此之外+1和这个答案应该更靠前 - Erfan