pandas mask 原地更新却改了相反的行:检查扩展数组替换值
DataFrame.mask 使用 inplace=True、替换值为扩展数组 Series 时,可能按 where 的方向更新。说明 pandas 3.1.0 修正及返回结果赋值的处理。
同一张表、同一个布尔条件,df.mask(cond, other, axis=0) 替换的是条件为真的行,加上 inplace=True 后却换成了条件为假的行。如果 other 是包含可空整数等扩展数组的 Series,先检查这条原地更新路径,不要为了迁就错误结果把业务条件取反。
这类问题的关键是原地与返回新对象两种调用结果不一致。mask 应在条件为真处替换;where 才是在条件为假处替换。pandas 3.1.0 已修正扩展数组替换值进入递归处理时的条件方向,采用旧版本时,需用同一输入核对两条调用路径,不能将问题范围扩展为全部旧版本。
用同一份输入比较两种更新方式
下面的最小示例保留了触发条件:二维 DataFrame、按行对齐的扩展数组 Series,以及既有真值也有假值的条件。
import numpy as np
import pandas as pd
original = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
replacement = pd.Series(pd.array([7, pd.NA, 9]))
cond = np.array([[True, True], [False, False], [True, True]])
expected = original.mask(cond, replacement, axis=0)
actual = original.copy()
actual.mask(cond, replacement, axis=0, inplace=True)
pd.testing.assert_frame_equal(actual, expected)
预期第 0 行变成两个 7,第 2 行变成两个 9,第 1 行仍是 2 和 5。不要只检查“数据有变化”:相反方向的替换也能产生一张看似合理的表。真实任务还应核对替换后 dtype、缺失值和索引。
axis=0 表示替换 Series 按行索引对齐。示例的两边索引恰好都是 0、1、2;业务索引不同,必须先确认 Series 标签与目标行匹配。这里的修正不能替代标签对齐检查。
暂时不能调整依赖时,接住非原地结果
受影响环境可把对同一 DataFrame 的原地调用改为显式接收结果:
df = df.mask(cond, replacement, axis=0)
这行替换原调用,要求 df、cond、replacement 已按应用上下文定义。它让变量 df 指向新结果,不会把所有持有旧 DataFrame 引用的对象一并更新。如果其他组件依赖原对象被原地修改,应一起调整数据传递,不能只改一行后假设对象身份不变。
也不要通过去掉可空 dtype、用零替代 pd.NA,或将 mask 换成相反条件来绕开问题。这些改动会改变数据语义,且在采用修正版后可能再次把正确条件翻转。
验收保留未命中的行
沿项目锁文件和部署流程使用包含修正的正式版本后,用相同输入比较两种调用。除了命中行,还要确认未命中行保持原值,并覆盖含缺失值、非默认索引的替换 Series。原地调用返回 None 是接口约定,不代表更新成功与否。
若两种调用已经一致、但行值仍不符合业务预期,应继续检查条件生成和索引对齐。保留处理前的数据;已经写入数据库或导出文件的错误结果,需要按输入重算,升级依赖不会恢复那些输出。


