虽然pandas 有很好的语法来与dicts 和NamedAggs 进行聚合,但这些可能会带来巨大的效率成本。原因是因为没有使用在 cython 中优化和/或实现的内置 groupby 方法,任何 .agg(lambda x: ...) 或 .apply(lambda x: ...) 都将采用慢得多的路径。
这意味着您应该坚持使用可以直接引用或通过别名引用的内置函数。只有在万不得已的情况下,您才应该尝试使用lambda:
在这种特殊情况下使用
df.groupby('numbers')[['colors']].agg('nunique', dropna=False)
避免
df.groupby('numbers').agg({'colors': lambda x: x.nunique(dropna=False)})
这个例子表明,虽然输出相同,并且看似很小的变化,但在性能方面会产生巨大的影响,尤其是当组的数量变大时。
import perfplot
import pandas as pd
import numpy as np
def built_in(df):
return df.groupby('numbers')[['colors']].agg('nunique', dropna=False)
def apply(df):
return df.groupby('numbers').agg({'colors': lambda x: x.nunique(dropna=False)})
perfplot.show(
setup=lambda n: pd.DataFrame({'numbers': np.random.randint(0, n//10+1, n),
'colors': np.random.choice([np.NaN] + [*range(100)])}),
kernels=[
lambda df: built_in(df),
lambda df: apply(df)],
labels=['Built-In', 'Apply'],
n_range=[2 ** k for k in range(1, 20)],
equality_check=np.allclose,
xlabel='~N Groups'
)
但是你想做多个聚合并使用不同的列
groupby 的.groupby() 部分并没有真正做那么多;它只是确保映射是正确的。因此,虽然不直观,但与使用 lambda 的更简单的 dict 进行聚合相比,单独与内置聚合并最终连接结果仍然要快得多。
这是一个同样想sum权重列的例子,我们可以看到拆分仍然快很多,尽管需要手动加入
def built_in(df):
return pd.concat([df.groupby('numbers')[['colors']].agg('nunique', dropna=False),
df.groupby('numbers')[['weight']].sum()], axis=1)
def apply(df):
return df.groupby('numbers').agg({'colors': lambda x: x.nunique(dropna=False),
'weight': 'sum'})
perfplot.show(
setup=lambda n: pd.DataFrame({'numbers': np.random.randint(0, n//10+1, n),
'colors': np.random.choice([np.NaN] + [*range(100)]),
'weight': np.random.normal(0,1,n)}),
kernels=[
lambda df: built_in(df),
lambda df: apply(df)],
labels=['Built-In', 'Apply'],
n_range=[2 ** k for k in range(1, 20)],
equality_check=np.allclose,
xlabel='~N Groups'
)