【问题标题】:How to make pandas groupby not lazy?如何让 pandas groupby 不偷懒?
【发布时间】:2020-04-05 11:18:54
【问题描述】:

本教程中提到 pandas groupby 对象是惰性的。

它本质上是懒惰的。它实际上并没有做任何操作来产生一个 有用的结果,直到你说出来。

还值得一提的是 .groupby() 确实做了一些,但不是全部, 通过为每个构建一个 Grouping 类实例来进行拆分工作 您传递的密钥。但是,BaseGrouper 的许多方法 持有这些分组的类被懒惰地调用,而不是在 init(),并且许多还使用缓存属性设计。

所以我做了一些测试以确保 groupby 真的很懒。

df=pd.DataFrame(np.random.randint(1,10,size=(1000000,4)))

然后

%timeit gg=df.groupby(1)
35.6 µs ± 110 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

这几乎不需要时间。和

%timeit res=gg.get_group(1)
2.76 ms ± 8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

时间要长得多,只是比

快一点
%timeit res=df[df[1]==1]
6.87 ms ± 16.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

另一方面,如果我们首先提取组

%timeit gdict=df.groupby(1).groups
15.7 ms ± 35.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

那么获取组不需要时间

%timeit gdict[1]
29.8 ns ± 0.0989 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

所以我的问题是

  1. 为什么pandas 把groupby 设计成懒惰的?在实际应用中,我想我几乎总是需要对组对象做很多进一步的操作。如果组对象一开始在拆分数据帧时很懒惰,那么每次进行get_group等操作时都会浪费时间。
  2. 我也不明白“.groupby() 确实通过为您传递的每个键构建一个 Grouping 类实例来完成部分但不是全部的拆分工作”,这是什么意思?
  3. 是否可以让 groupby 对象不懒惰?

【问题讨论】:

    标签: python pandas group-by


    【解决方案1】:

    你需要一个更大的基准:

    import numpy as np, pandas as pd
    df=pd.DataFrame(np.random.randint(1,10,size=(100000000,4))) #3GB data
    gg=df.groupby(1)
    %time _ = gg.get_group(1) #first call slow
    %time _ = gg.get_group(1) #fast
    %time _ = gg.get_group(2) #other group lookup is also fast 
    %timeit _ = gg.get_group(1) #gives wrong result
    

    Groupby 是懒惰的,它不会立即计算 groups。它会在对他们的第一次请求时这样做。或者,当您使用 IPython 并在光标下点击 gg 时。如果您跟踪进程的内存消耗,可以看到它。或者您可以在 IPython 案例中感受到它。

    很难猜测幕后发生了什么,但get_group 似乎有自己的缓存,而groupssummin 等方法共享一个。可能会尝试最小化不同用例的内存使用量。无论如何,第一次使用后,懒惰就消失了。

    最后的测试是错误的。 gg.groups 包含 indexex,而不是组本身:

    %timeit df.loc[gdict[1]]  #It is actually the slowest
    1.23 s ± 26.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    %timeit df[df[1]==1]
    928 ms ± 23.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    %timeit gg.get_group(1)
    510 ms ± 30.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    从字典中检索项目确实快了数千倍,但是您将以空间换取速度。

    如果您绝对确定需要多次在同一组上运行函数,您可以尝试对列上的数据框进行排序并保存组切片。

    %time df = df.sort_values(1,ignore_index=True)
    #Wall time: 10.3 s
    %time ids = df[1].diff().to_numpy().nonzero()[0]
    #Wall time: 1.88 s
    %time gl = {df[1][v] : slice(v,ids[i+1] if (i+1)<len(ids) else None) for i,v in enumerate(ids)}
    #Wall time: 112 µs
    %timeit df[gl[1]]
    #12.1 µs ± 208 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    

    对于某些用例,排序数据可能是最快的。

    %timeit {k:df[v].sum() for k,v in gl.items()}
    1.16 s ± 42.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    %timeit gg.sum()
    2.73 s ± 29.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    %timeit {x: gg.get_group(x).sum() for x in range(1,10)}
    4.23 s ± 61.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    【讨论】:

      猜你喜欢
      • 2012-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-23
      • 2017-08-27
      • 1970-01-01
      • 1970-01-01
      • 2011-03-14
      相关资源
      最近更新 更多