不具有SettingWithCopyWarning的不可预测的pandas切片分配行为

max*_*max 15 python pandas chained-assignment

它是众所周知的(和理解)分配到切片时,大熊猫的行为本质上是不可预测的.但我常常被警告SettingWithCopy警告.

为什么警告不是在以下两个代码片段中生成的,哪些技术可以减少无意中编写此类代码的可能性?

# 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
Run Code Online (Sandbox Code Playgroud)

我认为解释是,当父数据帧仍可从当前上下文访问时,pandas仅生成警告.(这将是检测算法的一个弱点,正如我之前的例子所示.)

在下一个片段中,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
Run Code Online (Sandbox Code Playgroud)

编辑:

在对此进行调查时,我发现了另一个缺失警告的情况:

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
Run Code Online (Sandbox Code Playgroud)

即使几乎相同的示例确实触发警告:

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
Run Code Online (Sandbox Code Playgroud)

更新:我在这里回答@firelynx的答案,因为很难将它放在评论中.

在答案中,@ firelynx说第一个代码片段没有警告,因为我正在使用整个数据帧.但即使我参与其中,我仍然没有得到警告:

# 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
Run Code Online (Sandbox Code Playgroud)

fir*_*ynx 2

一步一步解释你在做什么

您创建的数据框不是视图

data = pd.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c']})
data._is_view
False
Run Code Online (Sandbox Code Playgroud)

new_data 也不是视图,因为您正在获取所有列

new_data = data[['a', 'b']]
new_data._is_view
False
Run Code Online (Sandbox Code Playgroud)

现在您正在将数据分配为Series 'a'

data = data['a']
type(data)
pandas.core.series.Series
Run Code Online (Sandbox Code Playgroud)

哪个是视图

data._is_view
True
Run Code Online (Sandbox Code Playgroud)

现在您更新非复制中的值new_data

new_data.loc[0, 'a'] = 100 # no warning, doesn't propagate to data
Run Code Online (Sandbox Code Playgroud)

这不应该发出警告。它是整个数据框。

Series创建的将自身标记为视图,但它不是 DataFrame,并且不表现为 DataFrame 视图。

避免编写这样的代码

Series 与 Dataframe 问题在 pandas 中是一个非常常见的问题[如果您已经使用 pandas 一段时间,则不需要引用]

问题实际上是你应该一直写作

data[['a']]不是data['a']

左边创建一个数据框视图,右边创建一个系列。

有些人可能会争辩说永远不要写,而是data['a']data.a。因此,您可以向data['a']代码环境添加警告。

这是行不通的。首先,使用data.a语法会导致认知失调。

数据框是列的集合。在Python中,我们使用运算符访问集合的成员[]。我们通过操作符访问属性.。对于任何一个 Python 程序员来说,改变这些都会导致认知失调。特别是当你开始做类似的事情del data.a并发现它不起作用时。请参阅此答案以获得更广泛的解释

干净的代码来救援

很难看出data[['a']]和之间的区别data['a']

这是一种气味。我们两者都不应该做。

使用干净代码原则和Python禅宗“显式优于隐式”的正确方法

这是:

columns = ['a']
data[columns]
Run Code Online (Sandbox Code Playgroud)

这可能不是那么令人难以置信,但看看下面的例子:

data[['ad', 'cpc', 'roi']]
Run Code Online (Sandbox Code Playgroud)

这是什么意思?这些列是什么?你在这里得到什么数据?

这是任何人在阅读这行代码时首先想到的问题。

怎么解决呢?不说评论。

ad_performance_columns = ['ad', 'cpc', 'roi']
data[ad_performance_columns]
Run Code Online (Sandbox Code Playgroud)

更明确总是更好。

如需更多信息,请考虑购买一本关于干净代码的书。也许这个