Pandas中正确计算分组样本合并均值与标准差的方法
正确计算分组后的合并均值与标准差
你提到的直接用groupby.agg({'Mean':'mean', 'Dev':'mean'})确实是数学错误的,因为每个子样本的大小(n)不同,均值需要加权平均,而标准差(方差)不能直接取平均,得结合每个子样本的方差、均值与合并均值的偏差来计算。
一、数学原理
1. 合并均值(CombinedMean)
合并均值是所有子样本的加权算术平均,权重为每个子样本的样本量n:
$$\text{CombinedMean} = \frac{\sum(n_i \times \text{Mean}_i)}{\sum(n_i)}$$
2. 合并标准差(CombinedDev)
标准差的计算需要先算合并方差,步骤如下:
- 计算每个子样本的方差:$\text{Var}_i = \text{Dev}_i^2$(假设你的
Dev是标准差,如果是方差则跳过这一步) - 计算组内平方和:$\sum(n_i \times \text{Var}_i)$
- 计算组间平方和:$\sum(n_i \times (\text{Mean}_i - \text{CombinedMean})^2)$
- 合并方差:$\text{CombinedVar} = \frac{\text{组内平方和} + \text{组间平方和}}{\sum(n_i)}$(如果是样本标准差则分母用$\sum(n_i) - k$,$k$是分组内子样本的数量;你的示例结果用的是总体标准差,分母是总样本量)
- 合并标准差:$\text{CombinedDev} = \sqrt{\text{CombinedVar}}$
二、Pandas实现方案
我们可以用groupby.apply()来实现自定义的分组计算,这样能同时处理均值和标准差的正确计算:
import pandas as pd import numpy as np # 构造示例数据 df = pd.DataFrame({ 'Start': ['abc', 'abc', 'abc', 'abc', 'ijk', 'ijk', 'ijk'], 'End': ['x', 'x', 'y', 'y', 'x', 'x', 'z'], 'n': [54, 45, 14, 16, 25, 25, 7], 'Mean': [47, 42, 50, 30, 20, 20, 10], 'Dev': [5, 4, 10, 20, 5, 5, 2] }) def calculate_combined_stats(group): total_n = group['n'].sum() # 计算合并均值 combined_mean = (group['n'] * group['Mean']).sum() / total_n # 计算合并方差(匹配示例的总体标准差逻辑) var_i = group['Dev'] ** 2 within_ss = (group['n'] * var_i).sum() between_ss = (group['n'] * (group['Mean'] - combined_mean) ** 2).sum() combined_var = (within_ss + between_ss) / total_n combined_dev = np.sqrt(combined_var) # 整理结果并保留一位小数 return pd.Series({ 'N': total_n, 'CombinedMean': round(combined_mean, 1), 'CombinedDev': round(combined_dev, 1) }) # 按Start和End分组计算 result = df.groupby(['Start', 'End']).apply(calculate_combined_stats).reset_index() print(result)
运行这段代码后,输出结果和你期望的完全一致:
Start End N CombinedMean CombinedDev 0 abc x 99 44.7 5.2 1 abc y 30 39.3 19.0 2 ijk x 50 20.0 5.0 3 ijk z 7 10.0 2.0
补充说明
- 如果你的
Dev是样本标准差(而非总体标准差),那么合并方差的分母应该用total_n - len(group)(即总样本量减去分组内子样本的数量),这样计算出来的是无偏的样本合并标准差。 - 用
apply()而不是agg()是因为我们需要先计算合并均值,再用它来计算合并方差,这是一个有依赖的步骤,agg()无法直接处理这种顺序关联的计算。
内容的提问来源于stack exchange,提问作者TylerNG




