pandas DataFrame避免链式赋值
在运行以下Python代码时,Pandas抛出SettingWithCopyWarning警告:
row_data = df_pred.loc[key] row_data['col'] = new_value
df_pred是一个数据框,根据索引从数据框中获取一行,然后对该行的一个字段进行赋值,警告的详细内容如下:
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
该警告的意思是:在DataFrame的一个切片上的copy上进行赋值操作。出现警告是因为该赋值操作可能不会影响到原始的数据框。
从代码上来理解:row_data 是原始数据框的一个切片(df_loc[key]),该切片可能是原始数据框的一个视图(View),也可能是原始数据框的一个副本(Copy)。如果row_data是原始数据框的一个视图,对row_data进行数据修改不会影响到df_pred。建议使用df_pred.loc[row_indx,col_index] 方式,该方式肯定修改原始数据框。
一,深拷贝和浅拷贝
在使用DataFrame修改数据时,要知道深拷贝和浅拷贝的区别。图例描述了浅拷贝和深拷贝的区别,假设B复制了A,当修改B时,如果A也跟着变了,说明这是浅拷贝;如果A没变,那就是深拷贝。
也就是说,浅拷贝是创建了对象的一个引用,而深拷贝是创建了对象的一个独立的实体。
二,数据的视图和副本
在pandas中,浅拷贝也称作数据的视图(View),深拷贝也称作数据的副本(Copy),Pandas 中的某些操作可以返回数据的视图(View),而某些其他操作将返回数据的副本(Copy)。
SettingWithCopyWarning
警告暗示:赋值操作可能没有按照预期执行,你应该检查结果以确保结果没有出错。
如果你的代码执行的结果符合预期,那么你可以忽略警告,但这并不是良好的编码实践,SettingWithCopyWarning
警告不应该被忽略。在采取下一步行动之前,花点时间了解以下为什么Pandas会抛出这一警告。
三,警告出现的原因
直接抛出结论,警告出现的原因只有一个:
出现警告的原因是链式赋值
当 Pandas 检测到链式赋值(Chained assignment)时会生成警告,链式赋值是指通过使用链式索引来赋值。链式索引(Chaining)是指连续使用多个索引,例如data[1:5][1:3]。
使用链式索引赋值的两种方式:
- 第一种是显式的,索引是连续的,例如,data.iloc[1][3] = value
- 第二种是隐式的,例如,先定义 df = data.iloc[1],再使用 df[3] = value 也属于链式赋值
链式赋值可以在一行中进行,也可以跨越多行,只要Pandas检测到链式赋值,Python就会抛出SettingWithCopyWarning警告。
1,显式的链式赋值
举个例子,使用链式索引对bidder列进行赋值:
data[data.bidder == 'parakeet2004']['bidderrate'] = 100
生成警告是因为我们把两个索引操作链接在一起,代码中直接使用了两次中括号,所以这种格式比较容易理解。但如果我们使用其他访问方法,例如 .bidderrate
、.loc[]
、.iloc[]
、.ix[]
,也是如此,我们的链式操作一个接一个独立执行:
data[data.bidder == 'parakeet2004'] ['bidderrate'] = 100
第一次是访问操作(get),返回一个 DataFrame
,其包含所有 bidder
等于 'parakeet2004'
的行。第二个是赋值操作(set),是在这个新的 DataFrame
上运行的,我们压根没有在原始 DataFrame
上运行。
这个解决方案很简单:使用 loc
将链式操作组合到一个操作中,以便 Pandas 可以确保 set 操作是在原始 DataFrame上执行
。Pandas 会始终确保下面这样的非链式 set 操作起作用。
data.loc[data.bidder == 'parakeet2004', 'bidderrate'] = 100
2,隐式的链式赋值
举个例子,使用原始数据框的一个变量,对变量赋值,这是隐式的链式赋值:
winners = data.loc[data.bid == data.price] winners.loc[304, 'bidder'] = 'therealname'
隐式的链式索引可能跨越多行代码发生,也可能在一行代码内发生。因为 winners
是作为 get 操作的输出创建的(data.loc[data.bid == data.price]
),它可能是原始 DataFrame
的副本,也可能不是,但除非我们检查,否则我们不能确认。当我们对 winners
进行索引时,我们实际上使用的是链式索引。这意味着当我们尝试修改 winners
时,我们可能也修改了 data
。
在实际的代码中,这些行可能会跨越很大的距离,因此追踪问题可能会更困难,但情况是与示例类似的。
为了防止这种情况下的警告,解决方案是在创建新 DataFrame 时明确告知 Pandas 这是创建的一个副本,对副本的修改不会影响到原始对象:
winners = data.loc[data.bid == data.price].copy() winners.loc[304, 'bidder'] = 'therealname'
敲门就是,学会识别链式索引,不惜一切代价避免使用链式索引。如果要更改原始数据,请使用单一索引赋值操作。如果你想要一个副本,请确保显式调用copy()函数强制让 Pandas 创建副本。
提一个中肯的建议,即使SettingWithCopyWarning警告
只在set 时才会发生,但在进行 get 操作时,最好也避免使用链式索引。不仅因为链式操作较慢,而且因为链式索引始终是一个潜在的问题,只要你稍后进行赋值操作,就可能不会影响到原始对象,这可能不是你的预期操作,所以,请不惜一切代价避免使用链式索引。
参考文档: