python - 如何让 pandas groupby 不偷懒?
问题描述
本教程中提到 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)
所以我的问题是
- 为什么熊猫设计
groupby
如此懒惰?在实际应用中,我想我几乎总是需要对组对象做很多进一步的操作。如果组对象一开始在拆分数据帧时很懒惰,那么每次执行诸如此类的操作时都会浪费时间get_group
。 - 我也不明白“.groupby() 确实通过为您传递的每个键构建一个 Grouping 类实例来完成一些但不是全部的拆分工作”,这是什么意思?
- 是否可以使 groupby 对象不懒惰?
解决方案
你需要一个更大的基准:
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
似乎有自己的缓存,而groups
和方法类似sum
或min
共享一个。可能会尝试最小化不同用例的内存使用量。无论如何,在第一次使用之后,懒惰就消失了。
最后的测试是错误的。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)
推荐阅读
- c# - 初学者项目 - Pong - “Ball Script”有点问题:(
- regex - 在正则表达式中使一些信息可选
- java - POST 方法 Volley 错误的意外响应代码 500
- azure - Azure devops 允许 Azure VM 的 IP 规则
- java - 了解 Log4J2 异常(抛出)输出
- scala - 根据 spark 中的日期范围填充数据
- java - android中的sqlLite没有将我的数据插入到表中
- swift - Firebase/Firestore - 数据库有不安全的规则?
- playframework - 自定义每个请求的 PLAY_SESSION cookie maxAge
- javascript - 将数组转换为数组数组