在数据分析与数据清洗过程中,我们经常遇到这样一类需求:数据表中包含两个列,一个用于存储权重,另一个用于记录分类标签。现在需要根据分类标签列进行处理——如果该列存在缺失值(NaN),则该行数据的权重应被视为无效,需将其置零。与此同时,剩余的有效权重需要按比例重新缩放,确保调整后的权重总和保持不变,比如仍然为1。
如果采用逐步手动处理的方法——先求和、再缩放 ——不仅代码冗长,还容易在逻辑上绕晕自己。实际上,Pandas 已经提供了简洁且高效的现成方案,可以一步到位完成这类“条件权重重分配”任务。
核心思路非常清晰,只需三步:首先,将缺失标签对应的权重识别出来并“屏蔽”;接着,将这些权重赋值为零;最后,对非零部分的权重进行归一化处理。
这里推荐使用 Series.mask() 配合 Series.div() 来实现,一句代码即可完成。下面是具体实现:
import pandas as pd
import numpy as np
df = pd.DataFrame({
'Type': ['A', np.nan, 'B', 'C', 'D', np.nan],
'wgt': [0.1, 0.2, 0.3, 0.25, 0.1, 0.05]
})
# 一步到位:将 Type 为 NaN 对应的 wgt 置 0,再按剩余权重总和归一化
s = df['wgt'].mask(df['Type'].isna(), 0)
df['new_wgt'] = s.div(s.sum())
执行结果如下:
Type wgt new_wgt 0 A 0.10 0.133333 1 NaN 0.20 0.000000 2 B 0.30 0.400000 3 C 0.25 0.333333 4 D 0.10 0.133333 5 NaN 0.05 0.000000
可以看到,原本第 1 行和第 5 行的权重分别为 0.2 和 0.05,由于对应的 Type 是 NaN,已被全部置零。原始权重总和为 1.0,处理后仅剩 0.75,剩余的四个有效权重被等比例放大,最终总和依然保持为 1.0。整个操作干净利落,完全满足业务需求。
如果希望代码更加简洁,避免引入中间变量 s,也可以采用链式调用配合 pipe 方法:
df['new_wgt'] = df['wgt'].mask(df['Type'].isna(), 0).pipe(lambda x: x / x.sum())
关于这个操作,有几个关键点值得注意:
mask(condition, other)函数的作用是:当condition(条件)为真时,将原值替换为other中的值。此处将缺失标签位置替换为 0,语义清晰且易于理解。isna()是 Pandas 中检测缺失值的标准方法,能够同时识别None和np.nan,不存在兼容性问题。- 使用
div(s.sum())而不是直接/ s.sum(),可以更好地处理索引对齐,在复杂的数据结构下更加安全可靠。 - 需要特别注意的是,此方法的核心是先置零,然后对所有有效权重做线性缩放。因此,如果原始权重列的总和本身不是 1(例如为 0.8),经过处理后新权重列的总和依然为 0.8。如果你希望强制归一化到 1,必须确保输入的权重列已经归一化过,或者显式地除以原始权重的总和。
总体而言,这套组合拳兼具代码可读性、执行性能与逻辑健壮性。在 Pandas 处理“条件权重重分配”这类数据预处理任务时,你很难找到比它更优雅的解法了。
