piR*_*red 25 python pandas chained-assignment
关于可怕的问题,有无数的问题 SettingWithCopyWarning
我已经很好地理解了它是如何产生的.(注意我说好,不好)
当数据df帧通过存储的属性"附加"到另一个数据帧时,就会发生这种情况is_copy.
这是一个例子
df = pd.DataFrame([[1]])
d1 = df[:]
d1.is_copy
<weakref at 0x1115a4188; to 'DataFrame' at 0x1119bb0f0>
Run Code Online (Sandbox Code Playgroud)
我们可以将该属性设置为None或
d1 = d1.copy()
Run Code Online (Sandbox Code Playgroud)
我见过像@Jeff这样的开发者,我不记得还有谁,警告这样做.引用SettingWithCopyWarning有目的.
问题
好的,那么什么是一个具体的例子,说明为什么通过分配copy回原始来忽略警告是一个坏主意.
我会定义"坏主意"以澄清.
坏主意
这是一个坏主意来放置代码投入生产,这将导致越来越在星期六晚上说你的代码被打破,需要固定的中间一个电话.
现在如何使用df = df.copy()以绕过SettingWithCopyWarning导致获得那种电话.我想要它拼写出来,因为这是一个混乱的来源,我试图找到清晰度.我想看到爆炸的边缘情况!
Ste*_*n G 13
这是我的2分,这是一个非常简单的例子,为什么警告很重要.
所以假设我正在创建一个这样的df
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
Run Code Online (Sandbox Code Playgroud)
现在我想基于原始子集创建一个新的数据框并修改它,如下所示:
q = x.loc[:, 'a']
Run Code Online (Sandbox Code Playgroud)
现在这是原始片段,无论我做什么都会影响x:
q += 2
print(x) # checking x again, wow! it changed!
a b
0 2 0
1 3 1
2 4 2
3 5 3
Run Code Online (Sandbox Code Playgroud)
这就是警告告诉你的.您正在处理切片,因此您在其上执行的所有操作都将反映在原始DataFrame上
现在使用.copy(),它不会是原始的一部分,所以在q上做一个操作不会影响x:
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
Run Code Online (Sandbox Code Playgroud)
和顺便说一句,副本只是意味着q它将成为记忆中的新对象.切片在内存中共享相同的原始对象
imo,使用.copy()非常安全.作为示例df.loc[:, 'a']返回切片但df.loc[df.index, 'a']返回副本.杰夫告诉我,这是一个意外的行为,:或者df.index应该和.loc []中的索引器具有相同的行为,但是.copy()在两者上使用将返回一个副本,更好的是安全.所以使用.copy(),如果你不想影响原始数据帧.
现在使用.copy()返回DataFrame的深层复制,这是一种非常安全的方法,不接听您正在谈论的电话.
但使用df.is_copy = None,只是一个不会复制任何非常糟糕的想法的技巧,你仍然会在原始DataFrame的一片上工作
人们往往不知道的另一件事:
df[columns] 可能会返回一个视图.
df.loc[indexer, columns]也可能会返回一个视图,但几乎总是不会在实践中.
在这里强调可能
虽然其他答案提供了关于为什么不应该忽略警告的良好信息,但我认为您的原始问题尚未得到解答.
@thn指出使用copy()完全取决于手头的情况.如果您希望保留原始数据,则使用.copy(),否则不会.如果您正在使用copy()规避,SettingWithCopyWarning则忽略了您可能会在软件中引入逻辑错误的事实.只要你绝对确定这是你想要做的,你就没事了.
但是,当.copy()盲目使用时,您可能会遇到另一个问题,这个问题不再是特定的熊猫,而是每次复制数据时都会发生.
我稍微修改了您的示例代码,以使问题更加明显:
@profile
def foo():
df = pd.DataFrame(np.random.randn(2 * 10 ** 7))
d1 = df[:]
d1 = d1.copy()
if __name__ == '__main__':
foo()
Run Code Online (Sandbox Code Playgroud)
当使用memory_profile时,可以清楚地看到.copy()我们的内存消耗增加了一倍:
> 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()
Run Code Online (Sandbox Code Playgroud)
这涉及这样的事实,即仍然存在df指向原始数据帧的reference().因此,df垃圾收集器不会清理它并保存在内存中.
在生产系统中使用此代码时,MemoryError根据所处理数据的大小和可用内存,可能会也可能不会.
总而言之,.copy() 盲目使用并不是一个明智的想法.这不仅仅是因为您可能会在软件中引入逻辑错误,还因为它可能会暴露运行时的危险,例如a MemoryError.
编辑:
即使您正在执行df = df.copy(),并且您可以确保没有其他对原始的引用df,仍然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()
Run Code Online (Sandbox Code Playgroud)
但是如果你可以看到内存消耗随着时间的推移,在1.6秒内,两个数据帧都在内存中:
| 归档时间: |
|
| 查看次数: |
2975 次 |
| 最近记录: |