直接用 df['col_a'] - df['col_b'] 即可,pandas series 支持原生算术运算并自动对齐索引;需确保两列类型为数值型且长度一致,字符串需先用 pd.to_numeric(..., errors='coerce') 转换,空值默认保留 nan,如需填 0 则用 fillna(0);时间列相减得 timedelta,须通过 .dt.total_seconds() 等访问属性;链式操作中应使用 assign() 避免 settingwithcopywarning。

直接用 df['col_a'] - df['col_b'] 就行,别绕弯
绝大多数情况下,两列数值相减不需要调用函数——Pandas 的 Series 支持原生算术运算,自动对齐索引。只要两列长度一致、类型可计算(比如都是 float64 或 int64),直接写减法表达式最稳。
常见错误现象:TypeError: unsupported operand type(s) for -: 'str' and 'str' 或结果全是 NaN。前者说明列是字符串(比如带百分号或逗号),后者大概率是某列含空值或类型为 object 但实际混了非数字内容。
- 先检查类型:
df[['col_a', 'col_b']].dtypes - 有空值?减法本身会保留
NaN,不用额外处理;但如果要填 0 再算,得写成(df['col_a'].fillna(0) - df['col_b'].fillna(0)) - 字符串列?必须先转数值:
pd.to_numeric(df['col_a'], errors='coerce'),errors='coerce'把转不了的变NaN,比astype(float)安全
sub() 方法适合带参数控制的场景
sub() 是 Series 和 DataFrame 的内置方法,和 - 运算符行为基本一致,但它多一个 fill_value 参数——这才是它存在的理由。
使用场景:当两列长度不等(比如一列是原始数据,另一列是按日期对齐的基准值,但缺失某些日期),你希望缺失位置按某个默认值参与运算,而不是直接产出 NaN。
- 例如:
df['col_a'].sub(df['col_b'], fill_value=0),表示如果col_b某行是NaN,就当它是 0 来减 -
fill_value不影响非缺失值,只补缺失对齐位置 - 注意:
sub()默认按索引对齐,不是按位置;如果索引乱序或重复,结果可能和直觉不符
时间列相减会得到 Timedelta,别直接当数字用
如果两列是 datetime64 类型(比如 df['end_time'] 和 df['start_time']),相减结果是 Timedelta 类型,不是秒数或天数——这是最容易忽略的类型陷阱。
常见错误现象:想算“用了多少小时”,结果得到 Timedelta('2 days 03:45:12'),然后直接传给 int() 或做除法报错。
- 转成秒数:
(df['end_time'] - df['start_time']).dt.total_seconds() - 转成天数(浮点):
(df['end_time'] - df['start_time']).dt.days只取整数天;要小数天得用.dt.total_seconds() / (24 * 3600) - 别漏掉
.dt——这是访问Timedelta属性的必需前缀,漏了会报AttributeError
性能敏感时避免链式赋值,改用 assign()
如果你在做管道式处理(比如 df.query(...).sort_values(...).xxx),又想顺手加一列差值,别写 df['diff'] = df.a - df.b ——这会触发 SettingWithCopyWarning,且在链式中可能修改原视图而非副本。
正确做法是统一用 assign(),它返回新 DataFrame,语义清晰、无副作用。
- 示例:
df.query('x > 0').assign(diff=lambda x: x['a'] - x['b']) - lambda 中的
x指当前中间结果,安全引用 - 多个列一起算?
.assign(diff=..., ratio=...)一次性完成,比连续assign更快
复杂点在于:差值逻辑如果依赖前面刚生成的列(比如先 assign(a_clean=...),再算 a_clean - b),就必须拆成两步 assign,不能塞在一个里——lambda 作用域只看到输入 DataFrame,看不到同级定义的新列。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











