【问题标题】:Fastest alternative to pd.groupby.filter when filtering on group size按组大小过滤时 pd.groupby.filter 的最快替代方案
【发布时间】:2021-03-31 16:06:09
【问题描述】:

我有一个非常庞大的 DataFrame,其中包含数百万行和大约 20-30 列,其中包含各种类型的数据,例如。字符串、数字、日期等

df

index           t1      num1    float1  ...          str2  
0       2014-10-21      3456     0.000  ...  ayzkcxtoScUy             
1       2014-10-21      2453     0.000  ...  jZygJWtxyVnS             
...            ...       ...       ...  ...           ...           
n-1     2020-11-06    708735   670.818  ...  UWVhmKCfmzVj             
n       2020-11-06     70630   670.817  ...  EvhreYZotqVS             

让我们说它很疯狂,但我需要每一行都有它的所有值。我现在想对某些列进行分组,并根据组大小从原始 DataFrame df 中消除组和行。特别是,我想消除所有大小为 1 的组。

第一种天真的方法

我搜索并尝试使用这个答案:How to select rows in Pandas dataframe where value appears more than once

lst = ["t1", "str1", "num1", "str2", "num2"]
df = df.groupby(lst).filter(lambda x: len(x.index) > 1).reset_index(drop=True)

这确实按预期工作。我的 DataFrame df 现在从大小为 1 的组中出现的所有行中过滤出来。问题是使用 filter 方法与我的 DataFrame 的尺寸相关的时间,这需要太长时间。从这个角度来看,对这些样本列进行分组将产生大约 165,000 个组和 250 万行 DataFrame,其中大约三分之一的组的大小为 1。我不得不中止这个脚本的执行,因为它需要年龄。我进一步尝试使用来自此链接How do I improve the performance of pandas GroupBy filter operation? 的灵感,但无法使其与map 一起使用,因为我在 DataFrame 上而不是在 Series 上进行分组。使用transform 方法,性能变差

旁注

进一步调查,我发现在具有datetime64[ns, UTC] 和/或datetime64[ns] 列的DataFrame 上使用filter 时出现问题。我使用Del df[x] 删除了所有这三个列,这将过滤方法的性能提高了大约三分之一。仍然不够,但足以在此处提及它,尤其是当我需要这些列并且不能只删除它们时。

第二种“聪明”方法

然后,我尝试使用来自链接1.value_counts() 对我的数据进行巧妙的索引来规避groupby、过滤或转换的使用。

vc = df[lst].value_counts()
vc_index = vc.index[vc.gt(1)]
df = data[data[lst].isin(vc_index)]

我正在获取值计数 vc 以定位计数为 1 的所有索引,然后创建一个 MultiIndex new_index 仅包含所需的索引(即 count > 1)。之后,我尝试使用链接1 中的.isin() 过滤我的df,它将df 的所有值设置为NaN/NaT。我被困在这里 - 我不确定我在这里做错了什么。

df

index      t1      num1    float1  ...    str2  
0         NaT       NaN       NaN  ...     NaN             
1         NaT       NaN       NaN  ...     NaN             
...       ...       ...       ...  ...     ...           
n-1       NaT       NaN       NaN  ...     NaN             
n         NaT       NaN       NaN  ...     NaN             

在另一次尝试中,我尝试使用pd.index.difference() 方法

vc = data[lst].value_counts()
df = data.set_index(keys=lst)
df.index = df.index.difference(other=vc.index[vc.gt(1)])

但这只是给了我一个TypeError: '<' not supported between instances of 'float' and 'str'

老实说,自从两天以来我一直在处理这个问题,我有点不知道什么是最好的。我什至考虑过并行化(也许使用 Dask?),但我不确定如何使用它,因为我从未使用过它。非常感谢您的帮助。

【问题讨论】:

    标签: python pandas performance dataframe numpy


    【解决方案1】:

    对于这个特定用例(计数 > 1 的组),duplicated 更快:

    df[df.duplicated(lst, keep=False)]
    # 231 ms ± 10.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    另一个选项,不是那么快,但比filter 快得多,并且通常工作是groupby().transform('size')

    df[df.groupby(lst)['t1'].transform('size')>1]
    # 554 ms ± 108 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    相比:

    df.groupby(lst).filter(lambda x: len(x) > 1)
    # CPU times: user 38.8 s, sys: 482 ms, total: 39.3 s
    # Wall time: 39.4 s
    

    【讨论】:

    • 谢谢!你的回答很好用! ?
    【解决方案2】:

    @Quang Hoang 的解决方案效果很好。我用我的数据集做的一些基准测试:

    (rs = df 的行,ngrps = df.groupby(lst).ngroups

    method   100k rs/82.488 ngrps  200k rs/164.466 ngrps  400k rs/331.351 ngrps  800k rs/672.905 ngrps  1.600k rs/1.351.525 ngrps
    
    duplicated     0:00:00.031236         0:00:00.078112         0:00:00.181825         0:00:00.331095             0:00:00.683959
    transform      0:00:00.062507         0:00:00.109386         0:00:00.261506         0:00:00.528166             0:00:01.029606
    filter         0:00:09.039214         0:00:18.422355         0:00:37.372117         0:01:15.531945             0:02:32.075144
    

    使用重复可以很好地扩展,但请注意:如果列表内的列中有 NaN 值(您要在其上分组,在我的示例中为 lst),重复的不会丢弃它们

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-27
      • 2018-10-15
      • 1970-01-01
      • 2017-08-23
      • 1970-01-01
      • 2018-12-11
      相关资源
      最近更新 更多