pandas 转成 Arrow 后,原表的 NaT 变成了 1970 年
pandas 的时间列转 Arrow 时可能修改共享的底层缓冲区,让原始 NaT 变为 epoch。转换前建立独立副本并保留原件,不能只在转换结果上补 copy。
调用 df.convert_dtypes(dtype_backend="pyarrow") 后,新表的缺失值看起来正常,原表里的 NaT 却变成 1970-01-01。先停止后续覆盖保存,检查转换前后的原时间列;不要把 epoch 当作文件本来就有的时间。
pandas 3.1.0 有这一明确表现,涉及含 NaT 的 NumPy 时间列转 Arrow;同类 astype 转换也可能进入相关路径。它与默认显示格式无关:原始时间缓冲区中的缺失标记确实可能被改写,而新 Arrow 数组仍用单独掩码显示缺失。
为什么新表正确,原表反而被改了
3.1.0 在为 Arrow 时间戳建立缺失掩码后,取得整数视图,并把缺失位置临时设为零。若这个视图仍与输入共享可写缓冲区,零就写回了输入。零按时间单位解释通常对应 epoch;Arrow 输出则另外携带原先记录的缺失掩码,因此两边表现不一致。
这也说明 Copy-on-Write 不是万能保护。通过原始缓冲区进行的写入,并不一定经过普通 DataFrame 赋值路径。将数据先变成只读以碰巧触发另一分支,也不是稳定的业务修复。
副本必须在转换前建立
需要保留 NumPy 后端原表时,可先建立独立副本,再把这份副本交给转换:
conversion_input = original.copy(deep=True)
arrow_frame = conversion_input.convert_dtypes(dtype_backend="pyarrow")
original 使用尚未受影响的源表。转换可以修改自己的隔离输入,但不应再触碰需要保留的原时间缓冲;验收时仍要对照原表。对于 NumPy 日期时间列,深复制用于隔离数值存储;它不是任意 object 列内部 Python 对象的递归复制保证。
不要先转换再对结果 .copy()。原表若已在转换过程中被改写,事后复制不会把 epoch 还原成 NaT。deep=False 也不适合作为这项原始缓冲写入的隔离措施。
大表复制会增加内存。若无法承受,先按既有分批策略隔离转换输入,或暂缓后端迁移;不要为了省内存继续覆盖唯一原件。保存数据来源、字段单位、时区与转换前缺失位置,才有条件判断影响范围。
从可信原件恢复,不批量替换 epoch
原表已经改变时,应从保存的原始数据或转换前快照恢复。将所有 1970-01-01 改成 NaT 会误删真实 epoch 时间,而且不能覆盖所有时区、单位的显示形式。
核对输出行数还不够,应逐列比较转换前后原表的缺失掩码和有效时间值,再检查 Arrow 输出相同位置的缺失含义。覆盖有 NaT、没有 NaT、实际使用的时间单位及时区。更新后仍须确认转换没有修改输入,不能仅看新表输出正确。


