在 Pandas 里,DataFrame 的切片操作既可能返回原数据的视图(view),也可能返回一个独立的副本(copy)。如果你把切片结果赋给一个变量,再修改这个变量,只有“视图”才会影响原 DataFrame;如果是“副本”,修改就不会影响原数据。

另外,有一种特殊情况是直接在切片表达式上赋值(例如 df.iloc[0:2, 0] = 100),这总是会修改原 DataFrame —— 因为这本质上是对原数据的赋值操作,和返回视图还是副本无关。下面主要讨论把切片保存为新变量,再修改新变量的情况。


什么时候会影响原数据(返回视图)

当切片返回视图时,新变量和原 DataFrame 共享同一块内存,对新变量的修改会立刻反映到原 DataFrame 上。常见情况:

1. 单列的简单选择(同构数据类型)

如果 DataFrame 的所有列数据类型一致(例如全部是 float64),df['A'] 通常返回视图。

import pandas as pd
import numpy as np
 
df = pd.DataFrame(np.random.rand(5, 3), columns=list('ABC'))
s = df['A']            # 视图
s[0] = 999
print(df.loc[0, 'A'])  # 999.0,原数据被修改

如果列的数据类型不一致(比如既有 float 又有 int),Pandas 内部会为不同 dtype 的列创建不同的数据块,此时取单列可能返回副本,修改就不会影响原数据。

2. 连续的行切片或连续的行列切片(单一数据块)

通过 .iloc 或 .loc 取连续的行/列(且选取的数据在内存中连续)时,可能是视图。

df = pd.DataFrame(np.random.rand(10, 5))
sub = df.iloc[0:3, 0:2]   # 连续切片,很可能是视图
sub.iloc[0,0] = 888
print(df.iloc[0,0])        # 888.0,原数据被修改

注意:是否真的是视图还取决于数据的内部存储布局(如是否发生过转置、类型是否单一等),Pandas 不保证连续切片一定返回视图,只是常见情况如此。


什么时候不会影响原数据(返回副本)

以下操作明确返回副本,修改返回的变量完全不会影响原 DataFrame。

1. 使用列表或数组进行“花式索引”

用列表/数组指定行或列时,Pandas 会强制返回副本。

  • 选列:df[['A', 'B']](注意双层方括号)
  • 选行:df.iloc[[0, 2, 4]]
df = pd.DataFrame(np.random.rand(5, 3), columns=list('ABC'))
sub = df[['A', 'B']]       # 副本
sub.iloc[0,0] = 777
print(df.loc[0, 'A'])      # 原数据不变(仍为随机数)

2. 布尔索引

df[df['A'] > 0.5] 返回的总是副本。

df = pd.DataFrame({'A': [0.2, 0.8, 0.4], 'B': [1,2,3]})
sub = df[df['A'] > 0.5]    # 副本
sub.iloc[0,0] = 100
print(df)                  # 原数据不变

3. 链式索引(Chained indexing)

形如 df['A'][0] = ...,或者先取一部分再取一部分,实际上是先产生中间对象,再对中间对象进行索引/赋值。由于中间对象很可能是副本,所以赋值往往不会影响原 DataFrame,并且会触发 SettingWithCopyWarning。

df = pd.DataFrame({'A': [1,2,3]})
df['A'][0] = 999           # 可能不会修改原 df,并可能抛出警告

4. 数据类型不一致导致的隐性复制

如前所述,对混合 dtype 的 DataFrame 取单列,或执行某些会改变数据类型的操作时,Pandas 为了保证数据块的同质性,会产生副本。


如何准确判断是视图还是副本?

可以通过检查底层 NumPy 数组的 .base 属性来判断:

  • 如果 values.base 不是 None,说明它是视图,和某个原始数组共享内存。
  • 如果 .base 是 None,说明它是独立的副本。
df = pd.DataFrame(np.random.rand(5, 3))
s = df['A']
print(s.values.base is df.values)   # True → 视图,会相互影响
s2 = df[['A']]                      # 注意:双层括号返回DataFrame
print(s2.values.base is None)       # True → 副本,互不影响

最佳实践:不要依赖“自动判断”

因为视图和副本的规则会受内部优化、数据类型、操作链等多种因素影响,官方建议永远不要在代码中依赖“猜”到的结果。

  • 如果要修改原数据,直接使用 .loc[]、.iloc[] 的赋值操作:
    df.loc[0:3, 'A'] = new_values
  • 如果只是取出数据进行独立操作,不希望影响原数据,显式调用 .copy():
    subset = df[['A', 'B']].copy()
  • 避免链式索引(如 df['A'][0]),改用 .loc[0, 'A']。

总结一张表

操作通常返回修改后是否影响原 DataFrame
df['A'](同构 dtype)视图✅ 是(可能)
df['A'](混合 dtype)副本❌ 否
df.iloc[0:3, 0:2](连续切片)视图(常见)✅ 是
df[['A', 'B']](列表索引列)副本❌ 否
df.iloc[[0, 2]](花式索引行)副本❌ 否
df[df['A'] > 0](布尔索引)副本❌ 否
df.iloc[0:3] = 100(直接赋值)—✅ 总是修改原数据

理解视图/副本机制,能帮你避免很多难以排查的“幽灵般”的数据篡改 bug。