
本文介绍如何使用 pandas `transform` 方法直接在原数据框中高效计算每组内某列唯一值的平均数,并将结果广播回原始行,避免显式去重、新建 dataframe 和冗余合并操作。
在数据分析实践中,常需基于业务逻辑对“去重后”的数值进行聚合(如:每位用户各品类下出现过的独立计数值的均值),再将该统计量广播到原始明细表的每一行,用于后续归一化、特征工程或指标对比。传统做法(如先 drop_duplicates 再 merge)虽可行,但存在冗余计算、内存开销大、代码可读性差等问题。
核心优化思路是:不真正删除重复行,而是在 groupby 时对每组内 counts 的唯一值先行去重,再求均值——这可通过 groupby(...).apply(lambda x: x.unique().mean()) 实现;但更简洁、向量化且符合 transform 语义的方式,是利用 groupby(...).transform() 配合自定义聚合函数。
然而需注意:原始问题描述与示例代码存在关键偏差——其目标实为 “每个 user_id 组内 counts 的平均值” 和 “每个 (user_id, Category) 组内 counts 的平均值”,而非严格意义上的“unique values 的平均”。观察示例输出:
- Overall_average__Unique_Counts = 1.83 实为全部 counts 值(1,2,2,2,2,2,3,3,3)的均值 ≈ 2.09?但实际是 df1_unique['counts'].mean() = (3+2+1+2+1+2+2+2)/8 = 1.875 ≈ 1.83(四舍五入),即对去重后组合 (user_id, Category, counts) 的 counts 求全局均值;
- Categorywise_average_Unique_counts 中 ABC→2.00 来自 {3,2,1} 去重后均值?但 ABC 对应 counts=[3,3,3,2,2,1] → 唯一值 {1,2,3} → 均值=2;XYZ 对应 [2,1,2,2,2] → 唯一值 {1,2} → 均值=1.5,但输出为 1.67,说明实际计算的是 df1_unique.groupby('Category')['counts'].mean(),即仅对去重后的样本求均值,而非每组内动态去重。
因此,若严格按“每组内 counts 的唯一值均值”需求,正确解法如下:
import pandas as pd
df1 = pd.DataFrame({
'user_id': ['A','A','A', 'B','B','B', 'D','D','D', 'E','E'],
'Category': ['ABC','ABC','ABC','ABC','ABC','ABC','XYZ','XYZ','XYZ','XYZ','XYZ'],
'counts': [3,3,3,2,2,1,2,1,2,2,2]
})
# 方案1:严格实现“每 user_id 组内 counts 唯一值的平均值”
df1["Overall_average__Unique_Counts"] = (
df1.groupby('user_id')['counts']
.apply(lambda x: x.unique().mean())
.reindex(df1['user_id'])
.values
)
# 方案2:严格实现“每 (user_id, Category) 组内 counts 唯一值的平均值”
df1["Categorywise_average_Unique_counts"] = (
df1.groupby(['user_id', 'Category'])['counts']
.apply(lambda x: x.unique().mean())
.reindex(df1.set_index(['user_id', 'Category']).index)
.values
)
print(df1.round(2))✅ 输出验证(关键行):user_id='A' → counts 唯一值 {3} → 均值=3.00 user_id='B' → counts 唯一值 {1,2} → 均值=1.50 (user_id='B', Category='ABC') → counts 唯一值 {1,2} → 均值=1.50 (user_id='D', Category='XYZ') → counts 唯一值 {1,2} → 均值=1.50
但若业务本质只需“全局唯一值均值”和“Category 分组唯一值均值”(即静态去重一次后聚合),则推荐更高效的向量化写法:
# 预先提取去重后的基准数据
df_unique = df1.drop_duplicates(['user_id', 'Category', 'counts'])
# 全局唯一 counts 均值(广播到所有行)
df1["Overall_average__Unique_Counts"] = df_unique['counts'].mean()
# Category 分组唯一 counts 均值(需映射回原表)
cat_unique_mean = df_unique.groupby('Category')['counts'].mean()
df1["Categorywise_average_Unique_counts"] = df1['Category'].map(cat_unique_mean)✅ 优势:无 apply 循环,纯向量化,性能更优;逻辑清晰,易于维护。
重要注意事项:
- transform 本身不支持直接对每组内去重后序列聚合(因 transform 要求返回与组等长的数组),故严格场景需用 apply + reindex 或预计算 + map;
- drop_duplicates 的 keep 参数影响结果,确保去重逻辑符合业务(如 keep='first' 保留首条,不影响均值计算);
- 若数据量极大,优先采用 map 查表方案而非 apply,避免 Python 层循环开销;
- 所有方法均不修改原始索引,保证结果与原表行顺序严格对齐。
综上,根据实际需求选择策略:追求绝对语义准确选 apply + unique,追求性能与简洁性选 drop_duplicates + map。二者均比原始 merge 方案更优雅、高效、易读。










