在 Pandas DataFrame 子集(副本)上设置值很慢
Posted
技术标签:
【中文标题】在 Pandas DataFrame 子集(副本)上设置值很慢【英文标题】:Setting values on Pandas DataFrame subset (copy) is slow 【发布时间】:2016-11-04 10:21:33 【问题描述】:import timeit
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.rand(10, 10))
dft = df[[True, False] * 5]
# df = dft
dft2 = dft.copy()
new_data = np.random.rand(5, 10)
print(timeit.timeit('dft.loc[:, :] = new_data', setup='from __main__ import dft, new_data', number=100))
print(timeit.timeit('dft2.loc[:, :] = new_data', setup='from __main__ import dft2, new_data', number=100))
在我的笔记本电脑上,dft
(原始子集)中的设置值比dft2
(dft
的深层副本)中的设置值慢大约 160 倍。
为什么会这样?
编辑:移除关于代理对象的推测。
作为c。皮革建议,这可能是因为在副本 (dft
) 与原始数据帧 (dft2
) 上设置值时的代码路径不同。
额外问题:删除对原始 DataFrame df
的引用(通过取消注释 df = dft
行),在我的笔记本电脑上将速度因子降低到大约 2。知道为什么会这样吗?
【问题讨论】:
在底层,df[[True, False] * 5]
调用 Dataframe.__getitem__()
当索引器是一个列表时调用 Dataframe._getitem_array()
。这反过来又调用Dataframe.take()
,它有一个属性is_copy。我发现如果我运行df.take([0,2,4,6,8], is_copy=True)
,我的速度会比df.take([0,2,4,6,8], is_copy=False)
慢,在您的示例中 is_copy=True 产生与 dft 相同的运行时间,而 is_copy=False 产生与 dft2 相同的运行时间。因此,由于这个 is_copy 属性,可能在Dataframe.__setitem__
期间出现减速。
然而,is_copy 属性的实际用途非常模糊,可能需要深入了解__setitem__
。我认为您对返回的数组作为视图/代理的感觉很好,我认为这与此属性有关。
谢谢@c.leather。想知道这些检查是什么。
【参考方案1】:
这并不是关于 SO 的新问题。 This 和 this 是相关帖子。 This is the link to the current docs 解释它。
@c.leather 的 cmets 走在了正确的轨道上。问题是dft
是一个视图,而不是数据框df
的副本,如链接文章中所述。但是 pandas 无法知道它是否真的是副本以及操作是否安全,因此需要进行大量检查以确保执行分配是安全的,而这可以通过简单地避免制作副本。
这是一个相关问题,Github 有完整的讨论。我看过很多建议,我最喜欢的一个是文档应该鼓励df[[True,False] * 5].copy()
成语,可以称之为slice & copy 成语。
我找不到确切的检查,并且在 github 问题上,仅通过一些开发人员发布的一些推文提到了这种性能细微差别,并指出了这种行为。也许更多参与熊猫开发的人可以添加更多输入。
【讨论】:
问题不在于视图与复制,而在于速度差异的原因。我认为我对代理对象的猜测具有误导性(并且正在删除它)。感谢 github 页面的链接!以上是关于在 Pandas DataFrame 子集(副本)上设置值很慢的主要内容,如果未能解决你的问题,请参考以下文章
如何更新 MultiIndex pandas DataFrame 的子集
Pandas DataFrame:SettingWithCopyWarning:试图在 DataFrame 的切片副本上设置一个值 [重复]