Python 为什么盲目使用df.copy()来修复CopyWarning设置是个坏主意
关于可怕的Python 为什么盲目使用df.copy()来修复CopyWarning设置是个坏主意,python,pandas,chained-assignment,Python,Pandas,Chained Assignment,关于可怕的设置和copywarning 我很清楚这是怎么发生的。(注意我说的好,不是很好) 当数据帧df通过存储在is\u copy中的属性“附加”到另一个数据帧时,就会发生这种情况 这里有一个例子 df = pd.DataFrame([[1]]) d1 = df[:] d1.is_copy <weakref at 0x1115a4188; to 'DataFrame' at 0x1119bb0f0> 我见过像@Jeff这样的开发人员,我不记得还有谁,警告一下。引用带有Cop
设置和copywarning
我很清楚这是怎么发生的。(注意我说的好,不是很好)
当数据帧df
通过存储在is\u copy
中的属性“附加”到另一个数据帧时,就会发生这种情况
这里有一个例子
df = pd.DataFrame([[1]])
d1 = df[:]
d1.is_copy
<weakref at 0x1115a4188; to 'DataFrame' at 0x1119bb0f0>
我见过像@Jeff这样的开发人员,我不记得还有谁,警告一下。引用带有CopyWarning的设置有一个目的
问题
好的,下面是一个具体的例子来说明为什么通过将副本
分配回原始文件来忽略警告是个坏主意
我会定义“坏主意”来澄清
坏主意
将代码放置到生产中,这将是一个强烈的坏主意< /强>,这将导致在星期六晚上的一个电话中说你的代码坏了,需要修复。
现在如何使用df=df.copy()
绕过设置CopyWarning
来获取此类电话。我想把它说清楚,因为这是一个混乱的来源,我正试图找到清晰。我想看看爆炸的边缘案例 编辑:
在我们的评论交流之后,通过阅读一点(我甚至发现),我可能会这样做,但在这个代码示例中:
有时,出现以下情况时,会出现设置with copy
警告:
没有明显的链接索引正在进行。这些就是
设置WithCopy旨在捕捉!熊猫可能正试图
警告您已执行此操作:
def do_something(df):
foo = df[['bar', 'baz']] # Is foo a view? A copy? Nobody knows!
# ... many lines here ...
foo['quux'] = value # We don't know whether this will modify df or not!
return foo
对于有经验的用户/开发人员来说,这可能是一个容易避免的问题,但熊猫不仅仅是针对有经验的用户/开发人员
在星期日的午夜,你可能不会接到一个电话,但是如果你不早点收到它,它可能会在很长一段时间内损坏你的数据完整性。
另外,正如所述,您将要执行的最耗时和最复杂的数据操作将是在一个拷贝上进行的,该拷贝将在使用前被丢弃,您将花费数小时尝试调试它
注意:所有这些都是假设性的,因为文档中的定义是基于(不幸)事件概率的假设SettingWithCopy
是一个新的用户友好警告,用于警告新用户其代码可能存在的随机和不需要的行为
从2014年开始就有了
在这种情况下,导致警告的代码如下所示:
from pandas import DataFrame
# create example dataframe:
df = DataFrame ({'column1':['a', 'a', 'a'], 'column2': [4,8,9] })
df
# assign string to 'column1':
df['column1'] = df['column1'] + 'b'
df
# it works just fine - no warnings
#now remove one line from dataframe df:
df = df [df['column2']!=8]
df
# adding string to 'column1' gives warning:
df['column1'] = df['column1'] + 'c'
df
并就此事发表一些评论:
实际上,您正在设置副本
你可能不在乎;主要是针对以下情况:
df['foo'][0] = 123...
设置副本(因此对用户不可见)
(用户)
此操作,使df现在指向原始文件的副本
df = df [df['column2']!=8]
如果您不关心“原始”帧,则可以
如果您希望
df['column1'] = df['columns'] + 'c'
将实际设置原始帧(它们在这里都称为“df”)
这是令人困惑的)那么你会感到惊讶。
及
(此警告主要针对新用户,以避免设置副本)
最后,他得出结论:
复制通常并不重要,除非您尝试设置
用链子锁着他们
从上面我们可以得出以下结论:
SettingWithCopyWarning
具有一定的意义,在某些情况下(如jreback所示),此警告很重要,可以避免出现复杂情况
该警告主要是为新用户提供一个“安全网”,让他们注意自己正在做的事情,并提醒他们可能会在连锁操作中导致意外行为。因此,更高级的用户可以打开警告(来自jreback的回答):
或者你可以:
df.is_copy = False
更新:
TL;DR:我认为如何使用CopyWarning处理设置取决于用途。如果希望避免修改df
,那么使用df.copy()
是安全的,警告是多余的。如果要修改df
,则使用.copy()
表示方法错误,需要遵守警告
免责声明:我没有像其他回答者那样与熊猫专家进行私人/个人交流。所以这个答案是基于熊猫官方文档,一个典型的用户会基于什么,以及我自己的经验
SettingWithCopyWarning
不是真正的问题,它警告真正的问题。用户需要了解并解决实际问题,而不是绕过警告
真正的问题是,索引一个数据帧可能会返回一个副本,那么修改这个副本不会改变原始数据帧。警告要求用户检查并避免该逻辑错误。例如:
import pandas as pd, numpy as np
np.random.seed(7) # reproducibility
df = pd.DataFrame(np.random.randint(1, 10, (3,3)), columns=['a', 'b', 'c'])
print(df)
a b c
0 5 7 4
1 4 8 8
2 8 9 9
# Setting with chained indexing: not work & warning.
df[df.a>4]['b'] = 1
print(df)
a b c
0 5 7 4
1 4 8 8
2 8 9 9
# Setting with chained indexing: *may* work in some cases & no warning, but don't rely on it, should always avoid chained indexing.
df['b'][df.a>4] = 2
print(df)
a b c
0 5 2 4
1 4 8 8
2 8 2 9
# Setting using .loc[]: guarantee to work.
df.loc[df.a>4, 'b'] = 3
print(df)
a b c
0 5 3 4
1 4 8 8
2 8 3 9
关于绕过警告的错误方式:
df1 = df[df.a>4]['b']
df1.is_copy = None
df1[0] = -1 # no warning because you trick pandas, but will not work for assignment
print(df)
a b c
0 5 7 4
1 4 8 8
2 8 9 9
df1 = df[df.a>4]['b']
df1 = df1.copy()
df1[0] = -1 # no warning because df1 is a separate dataframe now, but will not work for assignment
print(df)
a b c
0 5 7 4
1 4 8 8
2 8 9 9
因此,将df1.is_copy
设置为False
或None
只是一种绕过警告的方法,而不是解决分配时的实际问题。设置df1=df1.copy()
也会以另一种更错误的方式绕过警告,因为df1
不是df的weakref
,而是一个完全独立的数据帧。因此,如果用户想要更改df
中的值,他们将不会收到任何警告,而是收到一个逻辑错误。没有经验的用户将无法理解为什么df
在被分配新值后没有变化。这就是为什么完全避免这些方法是明智的
如果用户只想处理数据的副本,即严格不修改原始的df
,那么显式调用.copy()
是完全正确的。但我
import pandas as pd, numpy as np
np.random.seed(7) # reproducibility
df = pd.DataFrame(np.random.randint(1, 10, (3,3)), columns=['a', 'b', 'c'])
print(df)
a b c
0 5 7 4
1 4 8 8
2 8 9 9
# Setting with chained indexing: not work & warning.
df[df.a>4]['b'] = 1
print(df)
a b c
0 5 7 4
1 4 8 8
2 8 9 9
# Setting with chained indexing: *may* work in some cases & no warning, but don't rely on it, should always avoid chained indexing.
df['b'][df.a>4] = 2
print(df)
a b c
0 5 2 4
1 4 8 8
2 8 2 9
# Setting using .loc[]: guarantee to work.
df.loc[df.a>4, 'b'] = 3
print(df)
a b c
0 5 3 4
1 4 8 8
2 8 3 9
df1 = df[df.a>4]['b']
df1.is_copy = None
df1[0] = -1 # no warning because you trick pandas, but will not work for assignment
print(df)
a b c
0 5 7 4
1 4 8 8
2 8 9 9
df1 = df[df.a>4]['b']
df1 = df1.copy()
df1[0] = -1 # no warning because df1 is a separate dataframe now, but will not work for assignment
print(df)
a b c
0 5 7 4
1 4 8 8
2 8 9 9
x = pd.DataFrame(list(zip(range(4), range(4))), columns=['a', 'b'])
print(x)
a b
0 0 0
1 1 1
2 2 2
3 3 3
q = x.loc[:, 'a']
q += 2
print(x) # checking x again, wow! it changed!
a b
0 2 0
1 3 1
2 4 2
3 5 3
x = pd.DataFrame(list(zip(range(4), range(4))), columns=['a', 'b'])
print(x)
a b
0 0 0
1 1 1
2 2 2
3 3 3
q = x.loc[:, 'a'].copy()
q += 2
print(x) # oh, x did not change because q is a copy now
a b
0 0 0
1 1 1
2 2 2
3 3 3
@profile
def foo():
df = pd.DataFrame(np.random.randn(2 * 10 ** 7))
d1 = df[:]
d1 = d1.copy()
if __name__ == '__main__':
foo()
> python -m memory_profiler demo.py
Filename: demo.py
Line # Mem usage Increment Line Contents
================================================
4 61.195 MiB 0.000 MiB @profile
5 def foo():
6 213.828 MiB 152.633 MiB df = pd.DataFrame(np.random.randn(2 * 10 ** 7))
7
8 213.863 MiB 0.035 MiB d1 = df[:]
9 366.457 MiB 152.594 MiB d1 = d1.copy()
> mprof run -T 0.001 demo.py
Line # Mem usage Increment Line Contents
================================================
7 62.9 MiB 0.0 MiB @profile
8 def foo():
9 215.5 MiB 152.6 MiB df = pd.DataFrame(np.random.randn(2 * 10 ** 7))
10 215.5 MiB 0.0 MiB df = df.copy()