15

众所周知(并且可以理解),当分配给切片时,pandas 的行为本质上是不可预测的。但我习惯于被警告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

我认为解释是熊猫仅在当前上下文仍可访问父 DataFrame 时才会产生警告。(这将是检测算法的一个弱点,正如我之前的示例所示。)

在下一个片段中,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 说第一个代码片段不会导致任何警告,因为我正在获取整个数据帧。但即使我参与其中,我仍然没有收到警告:

# 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
4

1 回答 1

2

一步一步解释你在做什么

您创建的数据框不是视图

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并注意到它不起作用时。有关更广泛的解释,请参阅此答案

干净的代码来拯救

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

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

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

这是:

columns = ['a']
data[columns]

这可能并不令人难以置信,但请看以下示例:

data[['ad', 'cpc', 'roi']]

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

这些是阅读这行代码时每个人脑海中首先出现的问题。

如何解决?不要说评论。

ad_performance_columns = ['ad', 'cpc', 'roi']
data[ad_performance_columns]

更明确总是更好。

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

于 2017-05-29T08:38:02.600 回答