DEBUG.WIKI
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
RackNerd;$21.99/年 · 1GB KVM
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

pandas ffill 后修改结果,原表也跟着变了

pandas 3.1.0 的 Arrow 列没有缺失值时可能复用同一个扩展数组对象,绕过写时复制跟踪。结果写入前显式 copy(deep=True),并检查两边修改都不会串到另一方。

把 filled = df.ffill() 的结果改了一格,原来的 df 也变了;反过来修改原表,结果又跟着变。若受影响列使用 PyArrow 存储,并且该列本身没有缺失值,要检查 pandas 的填充路径,而不只是查链式赋值。

pandas 3.1.0 中,Arrow 扩展数组在无需填充时会直接返回自身。外层为填充结果建立新 block 时,没有为这条路径保留对应的共享引用跟踪,于是两个 pandas 对象可能持有同一个可替换底层数组的扩展数组包装对象。Arrow 数据缓冲区不可变,并不能保证共享包装对象的两个 pandas 视图永远互不影响。

在结果交给可变步骤前建立独立副本

已知会受影响的 int64[pyarrow] 列,可以在填充后显式复制结果,再交给后续修改:

import pandas as pd

source = pd.Series([1, 2, 3], dtype="int64[pyarrow]")
filled = source.bfill(limit=1).copy(deep=True)
filled.iloc[0] = 100

对于这个示例,原 source 应仍保留 [1, 2, 3]。DataFrame 同样可在 ffill() 后调用 .copy(deep=True),让后面的修改作用于独立结果。复制应发生在任何一边被写入之前;如果原表已经受到污染,再复制只会复制受影响的值,需要从保留的输入重新生成。

这里的 deep=True 是 pandas 对数据副本的约定。Arrow 本身可以继续共享不可变缓冲区,关键是不能再共享会被赋值操作替换内部数组的同一个包装对象。不要通过修改 _pa_array 或私有 block 来修补生产数据。

没有缺失值的列也要检查

DataFrame 里只有数值列有空值,不意味着只有它会经过填充。默认字符串使用 Arrow 存储的环境中,完整的姓名列也可能进入“无需填充”分支,并在后续改名时影响原表。先检查实际 dtype 与字符串存储方式,不能仅看 df.isna().any() 后忽略没有空值的列。

这一路径也涉及 bfill(),limit 或 limit_area 不会让没有缺失值的数组自动改走另一条分支。已报告环境包括 pandas 3.0.6 和 3.1.0;这不表示所有 NumPy 列、Python 存储字符串或全部 pandas 历史版本都受影响。

这也不同于普通链式赋值没有改到父表:链式赋值需要先确定正确的修改目标,而这里原本要求独立的填充结果意外影响了输入。将赋值改成 source.loc[...] 会直接修改输入,不能用来恢复独立性。

核对时要用两份干净输入,分别做“改结果看原表”和“改原表看结果”,同时包含无缺失列与真正需要填充的列。除了值,还要检查 dtype、索引和应有的填充结果。含 Python 可变对象的 object 列另有深层对象共享规则,不应把这个 Arrow 方案推广成任意嵌套对象的递归复制保证。