如果未定义索引操作是返回视图还是副本,熊猫的观点是啥?

Posted

技术标签:

【中文标题】如果未定义索引操作是返回视图还是副本,熊猫的观点是啥?【英文标题】:What is the point of views in pandas if it is undefined whether an indexing operation returns a view or a copy?如果未定义索引操作是返回视图还是副本,熊猫的观点是什么? 【发布时间】:2016-04-25 09:12:21 【问题描述】:

我已从 R 切换到 pandas。当我做类似的事情时,我经常会收到 SettingWithCopyWarnings

df_a = pd.DataFrame('col1': [1,2,3,4])    

# Filtering step, which may or may not return a view
df_b = df_a[df_a['col1'] > 1]

# Add a new column to df_b
df_b['new_col'] = 2 * df_b['col1']

# SettingWithCopyWarning!!

我想我理解问题所在,但我很乐意了解我做错了什么。在给定的示例中,未定义 df_b 是否是 df_a 上的视图。因此,分配给df_b 的效果尚不清楚:它会影响df_a 吗?该问题可以通过过滤时显式复制来解决:

df_a = pd.DataFrame('col1': [1,2,3,4])    

# Filtering step, definitely a copy now
df_b = df_a[df_a['col1'] > 1].copy()

# Add a new column to df_b
df_b['new_col'] = 2 * df_b['col1']

# No Warning now

我认为我缺少一些东西:如果我们永远无法确定是否创建视图,那么视图有什么用?来自熊猫文档 (http://pandas-docs.github.io/pandas-docs-travis/indexing.html?highlight=view#indexing-view-versus-copy)

除了简单的情况外,很难预测 [getitem] 会返回视图还是副本(这取决于数组的内存布局,pandas 对此不做任何保证)

对于不同的索引方法,可以找到类似的警告。

我发现在我的代码中散布 .copy() 调用非常麻烦且容易出错。我是否使用错误的样式来操作我的 DataFrames?还是性能提升如此之高,以至于证明了明显的尴尬是合理的?

【问题讨论】:

您可以通过以下分配安全地禁用此新警告。 pd.options.mode.chained_assignment = None 嗯,也许可以帮助重置索引df_b = df_a[df_a['col1'] > 1].reset_index(drop=True) @GeorgePetrov 我强烈建议不要禁用它!出现警告是有充分理由的——如果有的话,我实际上建议您将其提升为异常而不是警告。 【参考方案1】:

我同意这有点好笑。我目前的做法是为我想做的任何事情寻找一种“功能性”的方法(根据我的经验,这些方法几乎总是存在,除了重命名列和系列)。有时它使代码更优雅,有时它变得更糟(我不喜欢assignlambda),但至少我不必担心可变性。

所以对于索引,您可以使用query,而不是使用切片表示法,默认情况下会返回一个副本:

In [5]: df_a.query('col1 > 1')
Out[5]:
   col1
1     2
2     3
3     4

我在this blog post. 中对此进行了一些扩展

编辑: 正如在 cmets 中提出的,我对 query 默认返回一个副本似乎是错误的,但是如果您使用 assign 样式,那么 assign 将创建一个在返回结果之前复制,一切顺利:

df_b = (df_a.query('col1 > 1')
            .assign(newcol = 2*df_a['col1']))

【讨论】:

为什么序列:df_b = df_a.query('col1 > 1') 后跟df_b['new_col'] = 2 * df_b['col1'] 仍然给出 SettingWithCopyWarning? @maxymoo:这回答了我问题的第二部分:避免 SettingWithCopy 问题的编程风格。谢谢!我真的很喜欢你的博文!你能回答screenpaver的问题吗?我认为您博客文章中的大多数建议都非常有效,但是 .query() 似乎并非在所有情况下都返回副本!那么我可以在方法链中进行过滤吗? 好的,谢谢。或者这也可以(无需查询):df_b = df_a[df_a.col1>1].assign(newcol = 2*df_a['col1'])【参考方案2】:

好问题!

简短的回答是:这是 pandas 中正在修复的缺陷。

您可以找到关于 the problem here 本质的更长讨论,但主要的收获是我们现在正在转向“写时复制”行为,在这种行为中,任何时候切片,都会得到新副本,您永远不必考虑视图。修复很快就会通过这个refactoring project. 我实际上尝试直接修复它(see here),但它在当前架构中是不可行的。

事实上,我们会将视图保留在后台 - 当它们可以提供时,它们会使 pandas 超级高效和快速 - 但我们最终会对用户隐藏它们,因此,从用户的角度来看,如果您对 DataFrame 进行切片、索引或剪切,您得到的实际上将是一个新副本。

(这是通过在用户只读取数据时创建视图来实现的,但每当使用赋值操作时,视图将在赋值发生之前转换为副本。)

最好的猜测是修复将在一年内完成 - 同时,我担心一些 .copy() 可能是必要的,抱歉!

【讨论】:

以上是关于如果未定义索引操作是返回视图还是副本,熊猫的观点是啥?的主要内容,如果未能解决你的问题,请参考以下文章

熊猫:将新列添加到作为索引列副本的数据框

Numpy | 20 副本和视图

熊猫如何通过数据框列值获取行索引

如何用Python字符串进行切片操作?

Numpy常用概念-对象的副本和视图向量化广播机制

多租户应用程序的轨道覆盖/范围视图