TL;DR: pivot_table
循环aggfunc
无论传递给它什么groupby
首先检查 cython 优化的实现是否可用,如果不可用则循环。
如果我们窥视源代码 of pivot_table()
,它的实现方式是,当您向它传递聚合器函数列表(又名 aggfuncs)时,对于每个func()
在列表中,groupby().func().unstack()
被调用,并且稍后将生成的数据帧列表连接起来。同时,groupby().agg()尝试首先调用 cython 优化的方法并使用循环作为最后的手段。
因此,如果 aggfuncs 中的函数都是 cython 优化的,例如'sum'
or 'size'
, groupby().agg()
执行速度将比pivot_table()
作为 aggfuncs 中函数的数量。特别是,对于单个聚合器函数,它们的执行效果大致相同(尽管,我想pivot_table()
仍然会稍微慢一些,因为它有更大的开销)。
但是,如果函数列表未经过 cython 优化,则由于两者都在循环中调用每个函数,因此它们的执行效果大致相同。注意:groupby().agg().unstack()
打电话给unstack()
只有一次pivot_table()
拨打电话len(aggfuncs)
次数;如此自然地,pivot_table()
也会稍微慢一些。
代码演示如下:
Setup:
def groupby_unstack(funcs):
return df.groupby(['INDEX', 'COLUMN'])['VALUE'].agg(funcs).unstack(level='COLUMN', fill_value=0)
def pivot_table_(funcs):
return df.pivot_table(index='INDEX', columns='COLUMN', values='VALUE', aggfunc=funcs, fill_value=0)
def get_df(k):
return pd.DataFrame({'INDEX': np.random.default_rng().choice(k // 2, size=k),
'COLUMN': np.random.default_rng().choice(16, size=k),
'VALUE': np.random.rand(k).round(2)})
Cython 优化的函数
从下面的基准测试可以看出,两者的性能差距groupby().agg().unstack()
and pivot_table()
随着聚合函数数量的增加而增加。对于单个聚合器函数,它们的执行大致相同,但对于两个函数,pivot_table()
大约慢两倍,对于三个函数,大约慢三倍等等。
df = get_df(800_000)
cython_funcs1 = ['sum', 'size']
%timeit groupby_unstack(cython_funcs1)
# 1.41 s ± 35.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit pivot_table_(cython_funcs1)
# 3.51 s ± 263 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cython_funcs2 = ['sum', 'size', 'mean']
%timeit groupby_unstack(cython_funcs2)
# 1.63 s ± 16.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit pivot_table_(cython_funcs2)
# 5.08 s ± 57 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cython_funcs3 = ['median']
%timeit groupby_unstack(cython_funcs3)
# 1.17 s ± 92.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit pivot_table_(cython_funcs3)
# 1.84 s ± 70.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
非 cython 优化的函数
对于非 cython 优化的函数,groupby().agg().unstack()
and pivot_table()
即使对于多个聚合器函数,其执行也大致相同,因为两者都在底层函数列表上循环。
df = get_df(80_000)
funcs = [lambda x: list(x.mode()), lambda x: x.nunique()**2]
%timeit groupby_unstack(funcs)
# 26.6 s ± 5.99 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit pivot_table_(funcs)
# 27.2 s ± 6.46 s per loop (mean ± std. dev. of 7 runs, 1 loop each)