最灵活方式是groupby后用apply调用自定义函数,函数接收每组DataFrame并返回标量;需用np.average(..., weights=...)处理NaN和空组,避免手算;agg+lambda适合单列快速计算,但须用df.loc[x.index,'weight']精确对齐索引。

用 apply 调用自定义函数计算分组加权平均
直接在 groupby 后用 apply 是最灵活的方式,尤其当权重列和值列不固定、或需额外校验逻辑时。关键在于函数必须接收一个 DataFrame(每组子集),返回标量。
常见错误是函数里没处理空组或权重为负/零的情况,导致 NaN 或除零异常。
- 函数参数名不重要,但内部要明确区分值列(如
'score')和权重列(如'weight') - 务必用
np.average(..., weights=...),别手写sum(value * weight) / sum(weight)—— 后者对NaN更敏感,且不自动跳过NaN权重 - 加
dropna=False在groupby前,避免某组全为NaN时被静默丢弃
import numpy as np
def weighted_avg(group):
values = group['score']
weights = group['weight']
# 自动忽略 values 或 weights 中的 NaN
return np.average(values, weights=weights)
<p>df.groupby('category').apply(weighted_avg)</p>用 agg + 匿名函数快速实现单列加权平均
如果只算一列的加权平均,且权重列固定,agg 比 apply 更快、更简洁。但它不能像 apply 那样访问整组 DataFrame,所以得靠 lambda x: 把当前列(x)和权重列(通过索引对齐)一起传入 np.average。
立即学习“Python免费学习笔记(深入)”;
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
容易踩的坑是忘记权重列和当前列长度一致——若分组后索引错位,df.loc[x.index, 'weight'] 会取错行。
- 必须用
df.loc[x.index, 'weight']精确对齐,不能直接写df['weight'] -
np.average的weights参数不接受Series索引不匹配的情况,会静默返回NaN - 如果权重列含负数,
np.average会报ZeroDivisionError,建议提前过滤或用np.where替换负权重为 0
df.groupby('category')['score'].agg(
lambda x: np.average(x, weights=df.loc[x.index, 'weight'])
)
为什么不用 groupby().sum() / groupby().sum() 手动计算
手动分子分母分别 sum 看似直观,但实际隐患很多:权重列有 NaN 时,两处 sum 会各自 drop 不同的行,导致分子分母对不上;若某组权重全为 NaN,分母变 0,结果全是 inf 或 NaN;而且无法利用 np.average 内置的 returned=True 选项获取有效样本数。
-
np.average对NaN权重和NaN值默认同步忽略,行为可预测 - 手动计算无法复用
min_count参数控制最小有效样本要求 - 性能上,
np.average是 C 实现,比 Python 循环或两次sum快得多
多列加权平均的批量写法
若需同时对 'score' 和 'rating' 做加权平均,且共用同一权重列 'weight',别重复写两个 agg。用字典传入多个键值对,每个值都是带权重的 lambda,但注意:每个 lambda 中的 x 是不同列,索引相同,所以仍可用 df.loc[x.index, 'weight']。
这里最容易被忽略的是——如果某列本身含大量 NaN,而权重列非空,np.average 会按该列的 NaN 位置过滤权重,但你可能期望用“原始权重”做归一化。此时必须显式对齐并预处理。
df.groupby('category').agg({
'score': lambda x: np.average(x, weights=df.loc[x.index, 'weight']),
'rating': lambda x: np.average(x, weights=df.loc[x.index, 'weight'])
})

















