检测并排除Pandas数据帧中的异常值

AMM*_*AMM 174 python filtering outliers dataframe pandas

我有一个包含很少列的pandas数据帧.

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

例如列 - 'Vol'的所有值都大约为12xx,一个值为4000(异常值).

现在我想排除那些有'Vol'列的行.因此,基本上我需要在数据框上放置一个过滤器,以便我们选择所有行,其中某列的值在与平均值相差3个标准差的范围内.

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

小智 173

如果数据框中有多个列,并且想要删除至少有一列中具有异常值的所有行,则以下表达式将一次性执行此操作.

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

from scipy import stats
df[(np.abs(stats.zscore(df)) < 3).all(axis=1)]
Run Code Online (Sandbox Code Playgroud)

描述:

  • 对于每列,首先计算列中每个值的Z分数,相对于列平均值和标准偏差.
  • 然后取Z分的绝对值,因为方向无关紧要,只有当它低于阈值时.
  • all(axis = 1)确保对于每一行,所有列都满足约束.
  • 最后,此条件的结果用于索引数据帧.

  • 对于每列,首先计算列中每个值的Z分数,相对于列平均值和标准偏差.然后取Z分的绝对值,因为方向无关紧要,只有当它低于阈值时..all(axis = 1)确保对于每一行,所有列都满足约束.最后,此条件的结果用于索引数据帧. (14认同)
  • 你能解释一下这段代码在做什么吗?并且可能提供一个想法,我如何删除在单个指定列中具有异常值的所有行?会有所帮助.谢谢. (6认同)
  • 当列中有Nulls / Nans时,您将如何处理这种情况。我们如何让它们被忽略? (4认同)
  • 我们该如何处理str列?如果某些列为非数字列,我们希望基于所有数字列删除离群值。 (4认同)
  • 得到错误:“ TypeError:/:'str'和'int'不支持的操作数类型” (3认同)
  • 如果整个列具有相同的值,则此操作会失败 - 在这些情况下,zscore 返回 NaN,因此 &lt; 3 检查对每一行都返回 False,因此它会删除每条记录。 (2认同)

CT *_*Zhu 131

boolean您可以像使用索引一样使用索引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
Run Code Online (Sandbox Code Playgroud)

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

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

  • 对于`clip()`,Jeff,不删除轮廓:`df.SOME_DATA.clip(-3std,+ 3std)`将轮廓分配给+ 3std或-3std (6认同)
  • 他们是``DataFrame.abs()``FYI,也是``DataFrame.clip()`` (4认同)
  • 太棒了,感谢@CTZhu 的回答。@DreamerP你可以将它应用到整个DataFrame:`df_new = df[np.abs(df - df.mean()) &lt;= (3 * df.std())]`。**但是**与将其应用于系列或单列相比,这将用“np.nan”替换异常值并保持数据帧的形状,因此可能需要插值来填充缺失值。 (3认同)

use*_*745 75

对于每个dataframe列,您可以获得分位数:

q = df["col"].quantile(0.99)
Run Code Online (Sandbox Code Playgroud)

然后过滤:

df[df["col"] < q]
Run Code Online (Sandbox Code Playgroud)

  • 评论的想法是更新答案;)。因为有人可能会忽略这一点。 (4认同)
  • 本文很好地概述了异常值消除技术https://machinelearningmastery.com/how-to-use-statistics-to-identify-outliers-in-data/ (3认同)
  • 这可能仅从上限中删除异常值..而不是下限? (3认同)
  • @indolentdeveloper你是对的,只需反转不等式即可删除较低的异常值,或将它们与 OR 运算符结合起来。 (3认同)

asc*_*ter 54

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

什么是异常值?

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

Z 分数

这里的问题是,所讨论的值严重扭曲了我们的测量meanstd导致不明显的 z 分数大致为[-0.5, -0.5, -0.5, -0.5, 2.0],将每个值保持在平均值的两个标准差之内。因此,一个非常大的异常值可能会扭曲您对异常值的整体评估。我不鼓励这种方法。

分位数过滤器

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

IQR-距中位数的距离

分位数原则的更强大版本:消除所有距数据中位数超过四分位数范围f倍的数据。这也是例如使用的转换。IQR 和中位数对于异常值具有鲁棒性,因此您可以智取 z 分数方法的问题。sklearnRobustScaler

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

基本假设是,至少数据的“中间一半”是有效的,并且与分布非常相似,而如果您的分布具有宽尾部和狭窄的 q_25% 到 q_75% 区间,那么您也会搞砸。

先进的统计方法

当然,还有一些奇特的数学方法,例如Peirce 准则Grubb 检验Dixon 检验,仅举几个也适用于非正态分布数据的方法。它们都不容易实现,因此没有进一步解决。

代码

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)
Run Code Online (Sandbox Code Playgroud)

要删除至少包含一个 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
Run Code Online (Sandbox Code Playgroud)

使用 pandas 1.3 函数:


Ale*_*der 27

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

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

df[df.apply(lambda x: np.abs(x - x.mean()) / x.std() < 3).all(axis=1)]
Run Code Online (Sandbox Code Playgroud)

要过滤DataFrame,其中只有一列(例如'B')在三个标准差内:

df[((df.B - df.B.mean()) / df.B.std()).abs() < 3]
Run Code Online (Sandbox Code Playgroud)


小智 20

#------------------------------------------------------------------------------
# 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
Run Code Online (Sandbox Code Playgroud)


Jef*_*dez 16

对于数据框中的每个系列,您可以使用betweenquantile删除异常值.

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

  • 在这里,您仅选择四分位数范围(IQR)内的数据,但请记住,此范围之外的值可能不是异常值。 (2认同)
  • 我认为选择 0.1 和 0.9 是相当安全的。像这样使用 Between 和分位数是一个很好的语法。 (2认同)

Key*_*r00 10

由于我还没有看到涉及数字非数字属性的答案,因此这里是一个补充答案。

您可能只想将离群值放在数字属性上(分类变量几乎不可能是离群值)。

功能定义

我扩展了@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)
Run Code Online (Sandbox Code Playgroud)

用法

drop_numerical_outliers(df)
Run Code Online (Sandbox Code Playgroud)

想象一个数据集df,其中包含有关房屋的一些值:胡同,土地轮廓,售价,...例如:数据文档

首先,您要可视化散点图上的数据(z分数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)
Run Code Online (Sandbox Code Playgroud)

之前-Gr Liv Area与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)
Run Code Online (Sandbox Code Playgroud)

售后-Gr Liv地区对售价

  • 很好的解决方案!注意,自 `pandas` 版本 0.23.0 以来,`reduce=False` 已被弃用 (3认同)
  • @KeyMaker00我真的很想使用它,但出现以下错误:ValueError:对象类型系列没有名为 1 的轴 (3认同)
  • 将 `result_type='reduce'` 替换为 `reduce=False`。 (2认同)

Ted*_*rou 8

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

df.sub(df.mean()).div(df.std()).abs().lt(3)
Run Code Online (Sandbox Code Playgroud)

每列的每个值将True/False根据其与平均值的距离是否小于三个标准差来转换。


小智 7

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


mgo*_*ser 7

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

import pandas as pd
from scipy.stats import mstats
%matplotlib inline

test_data = pd.Series(range(30))
test_data.plot()
Run Code Online (Sandbox Code Playgroud)

原始数据

# 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()
Run Code Online (Sandbox Code Playgroud)

Winsorized 数据


Man*_*dos 7

您可以使用布尔掩码:

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])
Run Code Online (Sandbox Code Playgroud)

输出:

   train  y
2      2  0
3      3  1
4      4  1
5      5  0
6      6  0
7      7  1
8      8  1
Run Code Online (Sandbox Code Playgroud)