如何在 Pandas 中正确计算含 NaN 值的加权平均(自动忽略无效权重)

本文详解如何在 pandas dataframe 中对含 nan 的数据进行加权平均计算,确保权重仅作用于非空值,并动态归一化——即分母为对应列中有效值的权重之和,而非全部权重总和。

在实际数据分析中,直接使用 df.mul(weights).sum() / weights.sum() 会错误地将 NaN 对应的权重纳入分母,导致结果偏差(如示例中第2行本应只用权重4参与计算,却仍被除以总权重14)。正确做法是:按元素级对齐掩码,使权重仅保留在非 NaN 数据位置,再分别求加权和与有效权重和

✅ 正确实现步骤

  1. 构造布尔掩码:df.notna() 生成与 df 同形的布尔矩阵,标记每个位置是否为有效值;
  2. 屏蔽无效权重:用 .mul(s, axis=0) 将权重序列 s 沿行广播,并与掩码相乘,使 NaN 位置的权重变为 0;
  3. 计算分子与分母
    • 加权和 = df.mul(masked_weights).sum()(NaN × 0 = 0,不影响求和);
    • 有效权重和 = masked_weights.sum(axis=0)(每列只累加该列非 NaN 行的权重);
  4. 逐列除法:使用 .div() 实现广播除法,得到每列的加权平均。
import pandas as pd
import numpy as np

# 构造示例数据
df = pd.DataFrame({
    1: [100, 150, 175],
    2: [200, 250, 275],
    3: [300, np.nan, 375]
}, index=[1, 2, 3])

s = pd.Series([3, 4, 7], index=[1, 2, 3])

# ✅ 关键代码:动态加权平均(忽略 NaN 对应权重)
weights_masked = df.notna().mul(s, axis=0)  # 形状同 df,NaN 位权重为 0
weighted_sum = df.mul(weights_masked).sum()   # 每列加权和
valid_weight_sum = weights_masked.sum(axis=0) # 每列有效权重和
result = weighted_sum.div(valid_weight_sum)

print(result.round(6))

输出:

1    151.785714
2    251.785714
3    352.500000
dtype: float64

⚠️ 注意事项

  • 权重 s 的索引必须与 df.index 完全一致,否则 .mul(axis=0) 会因对齐失败导致意外 NaN 或广播错误;
  • 若某列全为 NaN,则 valid_weight_sum 为 0,除法将返回 inf 或 NaN,建议前置检查:if (valid_weight_sum == 0).any(): raise ValueError("Empty column detected");
  • 该方法天然支持多列并行计算,无需循环,性能高效,适用于大规模数据场景。

此方案真正实现了「按需加权」:每一列独立计算其有效样本的加权均值,是处理缺失值加权统计的健壮范式。