Pandas有个“坑”
import pandas as pd
df = pd.DataFrame({
"A": [1, -1, 2, -2],
"B": [0, 0, 0, 0]
})
subset = df[df["A"] > 0]
subset["B"] = 1
print(df)
有些版本会输出:
A B
0 1 1
1 -1 0
2 2 1
3 -2 0
原始 df 被改了。
但你如果换一种写法、换个 Pandas 版本、或者数据稍微复杂一点,又可能看到:
A B
0 1 0
1 -1 0
2 2 0
3 -2 0
df 又没被改。
坑点:这就是问题的核心:同一段代码,结果不稳定。
为什么会这样?
关键在这一行:
subset = df[df["A"] > 0]
在老 Pandas 里:
有时它返回的是 视图(view)
有时返回的是 副本(copy)
且无法通过代码判断是哪一种…
于是当你写:
subset["B"] = 1
就会出现两种情况:
|
|
|
|---|---|
|
|
|
|
|
|
这也是为什么 Pandas 会给你一个SettingWithCopyWarning但这个警告本身并不能保证你代码是安全的。
SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy subset[“B”] = 1
更危险的写法!!
链式写法:
df[df["A"] > 0]["B"] = 1
危险的原因:
df[df["A"] > 0] 可能是视图,也可能是副本
你又立刻在它上面赋值
Pandas自己都不敢保证结果对不对
所以你会看到:
有时生效??
有时完全没效果??
有时只给你一个警告??
那“正确姿势”到底是什么?
只记一句话就够了:
你想改原 df,就在原 df 上改。
栗子🌰:
df.loc[df["A"] > 0, "B"] = 1
这个写法的好处:
没有中间对象
不存在视图 / 副本歧义
Pandas3.0的写时复制(Copy-on-Write)
针对上面的 “坑”,即将发布的Pandas3.0终于选择去 “填”了…
Pandas3.0表示:
只要你不是明确在改原表,就不要偷偷改原表。
也就是说:
你从 DataFrame 派生出来的新对象
在“看起来”就是一个独立副本
对它的修改,不会再悄悄影响原数据!
至于底层是不是复用了内存,那是 Pandas 自己的事,用户不需要操心。


评论0