基于已有列插入新列是 Pandas 里非常高频的操作。你可以用多种方式做到,但是否需要担心之前的“视图/副本”问题,取决于你是在原 DataFrame 上操作,还是在一个切片副本上操作。

下面先介绍几种主流方法,再说明它们和视图/副本的关系。


一、直接在原 DataFrame 上新增列(就地修改)

1. 直接赋值(最常用)

df['新列'] = df['旧列'] * 2

这会直接修改 df,增加一列。这里没有视图/副本陷阱,因为你是对原始 DataFrame 做赋值。

2. df.insert() —— 指定插入位置

df.insert(loc=2, column='新列', value=df['旧列'] + 10)
  • loc 是列的索引位置(从 0 开始),新列会插入到这个位置。
  • 同样是就地修改原 DataFrame,无返回值(返回 None)。

3. df.eval() —— 字符串表达式

df.eval('新列 = 旧列1 + 旧列2', inplace=True)   # 就地修改
# 或者
new_df = df.eval('新列 = 旧列1 + 旧列2')        # 返回新 DataFrame

适合复杂表达式,且可以高效地对大 DataFrame 操作(使用 numexpr 后端)。如果 inplace=False(默认),返回新对象。


二、返回新 DataFrame 的方法(不修改原数据)

4. df.assign() —— 链式调用友好

new_df = df.assign(新列=df['旧列'] ** 2)
# 可同时创建多列
new_df = df.assign(新列1=..., 新列2=...)
  • assign 永远不会修改原 DataFrame,它返回一个全新的 DataFrame(副本)。
  • 非常适合在管道操作中使用:df.pipe(...).assign(...).groupby(...)

如果你写了 df = df.assign(...),那相当于把原变量重新指向了新对象,原来的 DataFrame 如果没有被其他变量引用,就会被垃圾回收,效果上等同于修改,但语义上仍是创建了新对象。


三、视图/副本问题如何影响新列的添加?

你之前关心的核心是:“我从原 DataFrame 里取了一个子集出来,然后在子集上添加新列,原数据会变吗?”

答案是:通常不会,因为大部分切片操作返回的是副本,除非你拿到了一个视图。

典型陷阱示例:

df = pd.DataFrame({'A': [1,2,3], 'B': [4,5,6]})
 
# 用双层方括号取子集,这是副本!
sub = df[['A']]          # 副本
sub['新列'] = sub['A'] * 10   # 只修改了 sub,不影响原 df
print(df)                # 原 df 没有 '新列' 这一列
 
# 连续切片,可能是视图
sub2 = df.iloc[0:2]      # 如果是视图(同构 dtype)
sub2['新列'] = 99        # 会直接修改原 df!
print(df)
# 可能会看到原 df 的前两行多了一列 '新列',并且后面行为 NaN

这就是“切片影响原数据”在新增列场景的体现:
如果你拿到的切片是视图,那你在这个视图上添加列,就是在原 DataFrame 上添加列。

如何避免这种意外?

  • 永远在需要修改原数据时,直接使用原 DataFrame,而不是通过一个切片变量:
    df.loc[0:2, '新列'] = df.loc[0:2, '旧列'] * 10
  • 如果你想对子集做独立分析,不污染原数据,务必显式复制:
    sub = df.iloc[0:2].copy()
    sub['新列'] = 99    # 这时只会影响 sub
  • 注意链式索引:df['A'][0] = ... 这种写法本身就容易产生副本,如果后面再加列,会在临时对象上加列,然后丢弃,原 df 不受影响。避免链式,统一使用 .loc/.iloc。

四、总结与最佳实践

方法是否修改原 DataFrame适用场景
df['新列'] = ...✅ 是(就地)快速添加单列
df.insert(...)✅ 是(就地)需要控制列的位置
df.eval(..., inplace=True)✅ 是(就地)复杂计算,大型数据
df.assign(...)❌ 返回新对象链式操作,函数式风格

记住两个原则:

  1. 如果你希望原 DataFrame 发生变化,直接在它上面操作,不要通过一个来历不明的切片去操作。
  2. 如果你从一个 DataFrame 里切出了一个子集,准备独立地添加列、修改值,而不希望影响原数据,那就立即 .copy()。

有了这些认知,基于已有列插入新列就完全可控,不会踩坑。