没有 SettingWithCopyWarning 的不可预测的 pandas 切片分配行为
Posted
技术标签:
【中文标题】没有 SettingWithCopyWarning 的不可预测的 pandas 切片分配行为【英文标题】:Unpredictable pandas slice assignment behavior with no SettingWithCopyWarning 【发布时间】:2017-01-12 07:29:29 【问题描述】:well known(和understandable)在分配给切片时,pandas 的行为本质上是不可预测的。但是我已经习惯了被SettingWithCopy
警告警告。
为什么以下两个代码sn-ps都没有产生警告,哪些技术可以减少无意中编写此类代码的机会?
# pandas 0.18.1, python 3.5.1
import pandas as pd
data = pd.DataFrame('a': [1, 2, 3], 'b': ['a', 'b', 'c'])
new_data = data[['a', 'b']]
data = data['a']
new_data.loc[0, 'a'] = 100 # no warning, doesn't propagate to data
data[0] == 1
True
data = pd.DataFrame('a': [1, 2, 3], 'b': ['a', 'b', 'c'])
new_data = data['a']
data = data['a']
new_data.loc[0] = 100 # no warning, propagates to data
data[0] == 100
True
我认为解释是,pandas 仅在当前上下文仍可访问父 DataFrame 时才会产生警告。 (正如我之前的示例所示,这将是检测算法的一个弱点。)
在下一个 sn-p 中,AFAIK 无法再访问原始的两列 DataFrame,但 pandas 警告机制设法触发(幸运的是):
data = pd.DataFrame('a': [1, 2, 3], 'b': ['a', 'b', 'c'])
new_data = data['a']
data = data[['a']]
new_data.loc[0] = 100 # warning, so we're safe
编辑:
在调查此问题时,我发现了另一个缺少警告的情况:
data = pd.DataFrame('a': [1, 2, 3], 'b': ['a', 'b', 'c'])
data = data.groupby('a')
new_data = data.filter(lambda g: len(g)==1)
new_data.loc[0, 'a'] = 100 # no warning, does not propagate to data
assert data.filter(lambda g: True).loc[0, 'a'] == 1
即使几乎相同的示例确实触发了警告:
data = pd.DataFrame('a': [1, 2, 2], 'b': ['a', 'b', 'c'])
data = data.groupby('a')
new_data = data.filter(lambda g: len(g)==1)
new_data.loc[0, 'a'] = 100 # warning, does not propagate to data
assert data.filter(lambda g: True).loc[0, 'a'] == 1
更新:我在这里回复@firelynx 的答案,因为很难将其放在评论中。
在答案中,@firelynx 说第一个代码 sn-p 不会导致任何警告,因为我正在获取整个数据帧。但即使我参与其中,我仍然没有收到警告:
# pandas 0.18.1, python 3.5.1
import pandas as pd
data = pd.DataFrame('a': [1, 2, 3], 'b': ['a', 'b', 'c'], c: range(3))
new_data = data[['a', 'b']]
data = data['a']
new_data.loc[0, 'a'] = 100 # no warning, doesn't propagate to data
data[0] == 1
True
【问题讨论】:
关于你的第二个例子,没有副本。new_data
和 data
是同一个对象。分配不会“传播”;它只出现在一个有两个名称指向它的对象中。更一般地说,我不认为在任何特定情况下(尤其是更复杂的情况,如您添加的 groupby
示例)会或不会引发 SettingWithCopyWarning 。这只是防止最容易捕获的错误的粗略保护措施。
@BrenBarn 你能澄清一下new_data
和data
是同一个对象 的意思吗? new_data
的类型为 DataFrame
,data
的类型为 DataFrameGroupBy
。
我指的是您的 second 示例(您的第一个代码块中的第二个代码 sn-p),其中设置了 data
和 new_data
到data['a']
。
@BrenBarn 我明白了,是的。鉴于 pandas 有点假设程序员在分配给切片时尝试修改父 DataFrame
,因此这个示例问题最少 - 毕竟,父被正确修改并且没有发出警告。真正的问题是没有修改父级时没有警告。
对于不占用 PC 内存的数据集,与花费 15 分钟的程序员时间试图确定它是否是副本相比,CPU/内存效率的损失是微不足道的或不。 ;-)
【参考方案1】:
一步一步解释你在做什么
您创建的数据框不是视图
data = pd.DataFrame('a': [1, 2, 3], 'b': ['a', 'b', 'c'])
data._is_view
False
new_data 也不是视图,因为您正在获取所有列
new_data = data[['a', 'b']]
new_data._is_view
False
现在您将数据分配为Series 'a'
data = data['a']
type(data)
pandas.core.series.Series
什么是视图
data._is_view
True
现在您更新非副本new_data
中的值
new_data.loc[0, 'a'] = 100 # no warning, doesn't propagate to data
这不应该给出警告。它是整个数据框。
您创建的 Series
将自身标记为视图,但它不是 DataFrame,也不会像 DataFrame 视图那样工作。
避免编写这样的代码
Series 与 Dataframe 问题在 pandas 中非常常见[如果您已经使用 pandas 一段时间,则不需要引用]
问题在于你应该一直在写作
data[['a']]
不是data['a']
左侧创建一个数据框视图,右侧创建一个系列。
有些人可能会争辩说永远不要写data['a']
,而是写data.a
。因此,您可以为 data['a']
代码向您的环境添加警告。
这不起作用。首先使用data.a
语法会导致认知失调。
数据框是列的集合。在 python 中,我们使用[]
操作符访问集合的成员。我们通过.
操作符访问属性。切换这些会导致任何 Python 程序员的认知失调。尤其是当您开始执行 del data.a
之类的操作并注意到它不起作用时。 See this answer for more extensive explaination
清理代码以进行救援
很难看出data[['a']]
和data['a']
之间的区别
这是一种气味。我们应该都不做。
使用干净代码原则和python之禅的正确方法“显式胜于隐式”
这是:
columns = ['a']
data[columns]
这可能并不令人难以置信,但请看以下示例:
data[['ad', 'cpc', 'roi']]
这是什么意思?这些列是什么?你在这里得到什么数据?
这些是阅读这行代码时每个人脑海中首先出现的问题。
如何解决?不要发表评论。
ad_performance_columns = ['ad', 'cpc', 'roi']
data[ad_performance_columns]
越明确越好。
如需了解更多信息,请考虑购买一本关于简洁代码的书。也许this one
【讨论】:
在底部查看我的更新。我不明白您对为什么客户端不应在我刚刚添加的代码 sn-p 中收到警告的解释。而且我看不出您的干净代码方法会如何阻止某人在最后一个 sn-p 中编写代码。以上是关于没有 SettingWithCopyWarning 的不可预测的 pandas 切片分配行为的主要内容,如果未能解决你的问题,请参考以下文章
没有 SettingWithCopyWarning 的不可预测的 pandas 切片分配行为
我绕过了 SettingWithCopyWarning,感觉像是错误的方式并且计算效率低下,有没有更好的方法?
pandas DataFrame 警告(SettingWithCopyWarning)
当一行DataFrame是字符串时,SettingWithCopyWarning