sno*_*vik 11 python group-by pivot-table dataframe pandas
pivot_table我在与 的性能上挣扎groupby
一方面我有:
%time df.groupby(['INDEX', 'COLUMN']).agg({'VALUE':['sum','size']}).unstack(level='COLUMN')
CPU times: user 2.28 s, sys: 29.8 ms, total: 2.31 s
Wall time: 2.36 s
Run Code Online (Sandbox Code Playgroud)
另一方面我得到:
%time pd.pivot_table(df, index='INDEX', columns='COLUMN', values='VALUE', aggfunc=[len, np.sum], fill_value=0)
CPU times: user 1min 51s, sys: 1.57 s, total: 1min 53s
Wall time: 1min 54s
Run Code Online (Sandbox Code Playgroud)
这些本质上是相同的,但我得到了 60 倍的性能差异。这是为什么?
我的样本中有 800k 行,其中大约 400k 是唯一的INDEX,并且COLUMN有 16 个唯一值。
TL;DR: 无论传递给它什么,都会pivot_table循环aggfuncgroupby,同时检查 cython 优化的实现是否可用,如果不可用则循环。
如果我们查看 的源代码,pivot_table()它的实现方式是,当您将聚合器函数(又名 aggfuncs)列表传递给它时,对于func()列表中的每个函数,groupby().func().unstack()都会被调用,并且稍后会连接生成的数据帧列表。同时,groupby().agg()尝试首先调用 cython 优化的方法并使用循环作为最后的手段。
因此,如果 aggfuncs 中的函数都是 cython 优化的(例如\'sum\'或 )\'size\',则执行速度将比aggfuncs 中函数的数量groupby().agg()快很多倍。pivot_table()特别是,对于单个聚合器函数,它们的执行效果大致相同(尽管我认为pivot_table()仍然会稍微慢一些,因为它具有较大的开销)。
但是,如果函数列表未经过 cython 优化,则由于两者都在循环中调用每个函数,因此它们的执行效果大致相同。NB在拨打电话时只groupby().agg().unstack()拨打一次电话unstack()pivot_table()len(aggfuncs)多次;那么自然,pivot_table()也会稍微慢一些。
代码演示如下:
\ndef groupby_unstack(funcs):\n return df.groupby([\'INDEX\', \'COLUMN\'])[\'VALUE\'].agg(funcs).unstack(level=\'COLUMN\', fill_value=0)\n\ndef pivot_table_(funcs):\n return df.pivot_table(index=\'INDEX\', columns=\'COLUMN\', values=\'VALUE\', aggfunc=funcs, fill_value=0)\n\ndef get_df(k):\n return pd.DataFrame({\'INDEX\': np.random.default_rng().choice(k // 2, size=k), \n \'COLUMN\': np.random.default_rng().choice(16, size=k), \n \'VALUE\': np.random.rand(k).round(2)})\nRun Code Online (Sandbox Code Playgroud)\n从下面的基准测试可以看出,随着聚合器函数数量的增加groupby().agg().unstack(), 和之间的性能差距pivot_table()也随之增大。对于单个聚合器函数,它们的执行大致相同,但对于两个函数,pivot_table()速度大约慢两倍,对于三个函数,速度大约慢三倍,等等。
df = get_df(800_000)\n\ncython_funcs1 = [\'sum\', \'size\']\n\n%timeit groupby_unstack(cython_funcs1)\n# 1.41 s \xc2\xb1 35.7 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\n%timeit pivot_table_(cython_funcs1)\n# 3.51 s \xc2\xb1 263 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\n\ncython_funcs2 = [\'sum\', \'size\', \'mean\']\n\n%timeit groupby_unstack(cython_funcs2)\n# 1.63 s \xc2\xb1 16.6 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\n%timeit pivot_table_(cython_funcs2)\n# 5.08 s \xc2\xb1 57 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\n\ncython_funcs3 = [\'median\']\n\n%timeit groupby_unstack(cython_funcs3)\n# 1.17 s \xc2\xb1 92.6 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\n%timeit pivot_table_(cython_funcs3)\n# 1.84 s \xc2\xb1 70.7 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\nRun Code Online (Sandbox Code Playgroud)\n对于非 cython 优化的函数,groupby().agg().unstack()以及pivot_table()对于多个聚合器函数也执行大致相同的操作,因为两者都在底层函数列表上循环。
df = get_df(80_000)\n\nfuncs = [lambda x: list(x.mode()), lambda x: x.nunique()**2]\n\n%timeit groupby_unstack(funcs)\n# 26.6 s \xc2\xb1 5.99 s per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\n%timeit pivot_table_(funcs)\n# 27.2 s \xc2\xb1 6.46 s per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\nRun Code Online (Sandbox Code Playgroud)\n