assign方法本质是返回新dataframe而非原地修改,它总是创建副本并附加新列,不改变原始数据,链式调用依赖其返回值,漏接返回值则操作丢失。

assign 方法的本质是返回新 DataFrame,不是原地修改
assign 从不改变原始 df,它总是返回一个新对象。这点和 df['col'] = ... 或 df.drop(columns=..., inplace=True) 完全不同。如果你写 df.assign(a=df.b + 1) 却没接返回值,那这次赋值就“消失”了。
链式调用能成立,正是因为它每次返回可继续调用的 DataFrame。常见错误是中间某步漏了赋值或没参与链式,比如:
df.assign(new_col=df.x * 2) # ❌ 没接返回值,也没链下去 df.assign(new_col=df.x * 2).head() # ✅ 返回新 df,再调 head
- 所有传给
assign的参数必须是标量、Series 或可广播的函数(如lambda x: x.col1 / x.col2) - 列名重复时,后出现的会覆盖前面的(按参数顺序),但不会报错
- 如果传入的 Series 长度与原 DataFrame 行数不一致,会直接抛
ValueError: Length mismatch
用 lambda 实现跨列计算,避免变量名污染
直接写 df.assign(ratio=df.a / df.b) 看似简单,但如果 df 是链式中间产物(比如刚 filter 过),df.a 可能已不存在;更麻烦的是,你在全局作用域里反复引用 df,容易因变量重命名或作用域变化出错。
正确做法是统一用 lambda x: 接收当前链中传递下来的 DataFrame:
df.query('b > 0') \
.assign(ratio=lambda x: x.a / x.b) \
.assign(is_large=lambda x: x.ratio > 10) \
.sort_values('ratio')
-
lambda x:中的x就是前一步输出的 DataFrame,列名和状态完全确定 - 多个
assign步骤之间可相互依赖:后面步骤能用到前面assign新增的列 - 避免在 lambda 外部提前计算值(如
mean_val = df.c.mean()),否则无法响应链中前序操作(如filter)带来的数据变化
混合标量、Series 和函数参数,注意求值时机
assign 支持三种参数类型混用,但它们的求值时机不同:
- 标量(如
flag=True):立即求值,赋给所有行 - Series(如
new_s=pd.Series([...])):立即求值,长度必须匹配 - 函数(
lambda x: ...):延迟求值,在链执行到该步时才调用,接收当前 DataFrame
典型陷阱是误把本该延迟求值的逻辑写成立即求值:
# ❌ 错误:filter 后行数变少,但 new_col 是 filter 前算的,长度不匹配
tmp = df[df.c > 5].c.cumsum()
df.assign(new_col=tmp).query('c > 5')
<h1>✅ 正确:全部放在链内,用 lambda 延迟求值</h1><p>df.query('c > 5').assign(new_col=lambda x: x.c.cumsum())</p>
性能提醒:assign 不比原地操作快,别为链式牺牲可读性
有人以为链式一定更高效,其实不然。assign 每次都拷贝整个 DataFrame(浅拷贝列,但列内容仍共享),连续多次 assign 会产生多个中间对象,内存开销可能比分步 df['col'] = ... 更大。
真正适合链式的场景是:逻辑清晰、步骤间强依赖、且每步都需保留中间状态用于后续计算。如果只是加一两列又没后续依赖,直接 df['col'] = ... 更直白。
另外,Pandas 2.0+ 对 assign 的 lambda 参数做了优化,但若 lambda 内部调用复杂函数(如 scipy.stats.zscore),仍可能因重复调用拖慢速度——这时不如先算好 Series 再传进去。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











