在大熊猫DataFrame中按组删除异常值的更快方法[重复]

Posted

技术标签:

【中文标题】在大熊猫DataFrame中按组删除异常值的更快方法[重复]【英文标题】:Faster way to remove outliers by group in large pandas DataFrame [duplicate] 【发布时间】:2015-02-10 00:21:56 【问题描述】:

我有一个相对较大的 DataFrame 对象(大约一百万行,数百列),我想按组裁剪每一列中的异常值。我的意思是“按组剪裁每列的异常值” - 计算组中每列的 5% 和 95% 分位数,并剪裁此分位数范围之外的值。

这是我目前使用的设置:

def winsorize_series(s):
    q = s.quantile([0.05, 0.95])
    if isinstance(q, pd.Series) and len(q) == 2:
        s[s < q.iloc[0]] = q.iloc[0]
        s[s > q.iloc[1]] = q.iloc[1]
    return s

def winsorize_df(df):
    return df.apply(winsorize_series, axis=0)

然后,我的 DataFrame 名为 features 并由 DATE 索引,我可以这样做

grouped = features.groupby(level='DATE')
result = grouped.apply(winsorize_df)

这可行,只是速度很慢,可能是由于嵌套的apply 调用:每个组一个,然后每个组中的每一列一个。我尝试通过一次计算所有列的分位数来摆脱第二个apply,但试图用不同的值对每列设置阈值时遇到了困难。有没有更快的方法来完成这个过程?

【问题讨论】:

【参考方案1】:

您可以考虑使用winsorize function in scipy.stats.mstats。但请注意,它返回的值与 winsorize_series 略有不同:

In [126]: winsorize_series(pd.Series(range(20), dtype='float'))[0]
Out[126]: 0.95000000000000007

In [127]: mstats.winsorize(pd.Series(range(20), dtype='float'), limits=[0.05, 0.05])[0]
Out[127]: 1.0

使用 mstats.winsorize 而不是 winsorize_series 可能(取决于 N、M、P)快 1.5 倍:

import numpy as np
import pandas as pd
from scipy.stats import mstats

def using_mstats_df(df):
    return df.apply(using_mstats, axis=0)

def using_mstats(s):
    return mstats.winsorize(s, limits=[0.05, 0.05])

N, M, P = 10**5, 10, 10**2
dates = pd.date_range('2001-01-01', periods=N//P, freq='D').repeat(P)
df = pd.DataFrame(np.random.random((N, M))
                  , index=dates)
df.index.names = ['DATE']
grouped = df.groupby(level='DATE')

In [122]: %timeit result = grouped.apply(winsorize_df)
1 loops, best of 3: 17.8 s per loop

In [123]: %timeit mstats_result = grouped.apply(using_mstats_df)
1 loops, best of 3: 11.2 s per loop

【讨论】:

谢谢,这是一个很好的指针,我没有意识到 scipy 有一个 winsorize 函数。但是,我认为如果有一种方法可以在 DataFrame 上进行批量操作而不必逐列操作,则可以实现更大幅度的加速,类似于批量标准化或标准化的方式,例如,***.com/questions/12525722/normalize-data-in-pandas 每组的日期是否相同? 分组操作是按日期,所以每组只有一个日期。你的意思是问每个组的行数是否相同?答案是否定的,每个日期可以(并且通常确实)有不同的行数。 @YT 正如您在 OP 中提到的那样,pandas 现在有一个 .clip() 函数应该适合您,尤其是与 .quantile() 结合使用时。 查看我刚刚发布的这个问题,然后回答,使用@Zhang18 建议的clip() 和quantile() 处理缺失值:***.com/questions/50612095/…【参考方案2】:

这是一个不使用 scipy.stats.mstats 的解决方案:

def clip_series(s, lower, upper):
   clipped = s.clip(lower=s.quantile(lower), upper=s.quantile(upper), axis=1)
   return clipped

# Manage list of features to be winsorized
feature_list = list(features.columns)

for f in feature_list:
   features[f] = clip_series(features[f], 0.05, 0.95)

【讨论】:

你能添加一个简短的描述吗?【参考方案3】:

我找到了一种相当简单的方法来让它工作,使用 pandas 中的转换方法。

from scipy.stats import mstats

def winsorize_series(group):
    return mstats.winsorize(group, limits=[lower_lim,upper_lim])

grouped = features.groupby(level='DATE')
result = grouped.transform(winsorize_series)

【讨论】:

【参考方案4】:

解决这个问题的好方法是矢量化。为此,我喜欢使用np.where

import pandas as pd
import numpy as np
from scipy.stats import mstats
import timeit

data = pd.Series(range(20), dtype='float')

def WinsorizeCustom(data):
    quantiles = data.quantile([0.05, 0.95])
    q_05 = quantiles.loc[0.05]
    q_95 = quantiles.loc[0.95]

    out = np.where(data.values <= q_05,q_05, 
                                      np.where(data >= q_95, q_95, data)
                  )
    return out

为了比较,我将 scipy 中的函数包装在一个函数中:

def WinsorizeStats(data):
    out = mstats.winsorize(data, limits=[0.05, 0.05])
    return out

但是正如你所看到的,尽管我的函数非常快,但它仍然远离 Scipy 实现:

%timeit WinsorizeCustom(data)
#1000 loops, best of 3: 842 µs per loop

%timeit WinsorizeStats(data)
#1000 loops, best of 3: 212 µs per loop

如果您有兴趣阅读更多关于加速 pandas 代码的信息,我建议您使用 Optimization Pandas for speed 和 From Python to Numpy。

【讨论】:

【参考方案5】:

有一个二维数组,其中行作为观察值,列作为特征。 并且,要求省略具有任何异常特征值的完整行。

data = np.array([[1, 8, 13, 113, 401],
                 [2, 8, 15, 119, 402],
                 [1, 9, 14, 117, 399],
                 [100, 7, 12, 110, 409],
                 [4, 70, 11, 111, 404]
                 ])

是否有任何 API 或函数可以做到这一点?

【讨论】:

以上是关于在大熊猫DataFrame中按组删除异常值的更快方法[重复]的主要内容,如果未能解决你的问题,请参考以下文章

熊猫中按组的唯一性索引

在 Pandas 数据框中按组过滤具有最小值的行 [重复]

熊猫:按组计算唯一的日期时间值会给出奇怪的值

当日期不唯一时,在熊猫中按日期分组后计数观察值

按组的每个出现值构建计数列

在熊猫数据框中按日期和计数值分组