Pandas切片操作:很容易忽視的SettingWithCopyWarning

jpld發表於2020-05-06

Pandas是一個強大的分析結構化資料的工具集,主要用於資料探勘和資料分析,同時也提供資料清洗功能。

很多初學者在資料的選取,修改和切片時經常面臨一些困惑。這是因為Pandas提供了太多方法可以做同樣的事情,方法選擇不當,可能導致一些意想不到的錯誤。

Pandas切片

Pandas資料訪問方式包括:df[] ,.at,.iat,.loc,.iloc(之前有ix方法,pandas1.0之後已被移除)

  • df[] :直接索引
  • at/iat:通過標籤或行號獲取某個數值的具體位置。
  • loc:通過標籤選取資料,即通過index和columns的值進行選取。loc方法有兩個引數,按順序控制行列選取,範圍包括start和end。
  • iloc:通過行號選取資料,即通過資料所在的自然行列數為選取資料。iloc方法也有兩個引數,按順序控制行列選取。

它們之間的區別不是文字重點,大家可以新建一個dataframe練習一下,本文我們主要來一個錯誤示範,然後給大家提一些合理的建議。

錯誤示範

新建一個DataFrame

df = pd.DataFrame(
{'x':[1,5,4,3,4,5],
'y':[.1,.5,.4,.3,.4,.5],
'w':[11,15,14,13,14,15]})

   x    y   w
0  1  0.1  11
1  5  0.5  15
2  4  0.4  14
3  3  0.3  13
4  4  0.4  14
5  5  0.5  15

假設我們要查詢與“x”列對應的所有DataFrame元素都大於3,並根據此更改將所有對應的“ y”值更改為50。
我們來先試一個看起來毫無問題的方法

df[df['x']>3]['y']=50
執行之後,df沒有任何變化,Warning如下:

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

根據提示資訊,我們使用loc方法

df.loc[df['x']>3,'y']=50
   x     y   w
0  1   0.1  11
1  5  50.0  15
2  4  50.0  14
3  3   0.3  13
4  4  50.0  14
5  5  50.0  15

得到預期結果√
這是為什麼呢?這裡我們就遇到了所謂的“連結索引”,具體原因是使用了兩個索引器,例如: df[][]
df[df['x']>3] 導致Pandas建立原始DataFrame的單獨副本
df[df['x']>3]['y'] = 50 將新值分配給“ y”列,但在此臨時建立的副本上,而不是原始DataFrame上。

反轉切片的順序時,即先呼叫列,然後再呼叫我們要滿足的條件,便得到了預期的結果:

df['y'][df['x']>3]=50

   x     y   w
0  1   0.1  11
1  5  50.0  15
2  4  50.0  14
3  3   0.3  13
4  4  50.0  14
5  5  50.0  15

但是同樣會給出一個Warning:
A value is trying to be set on a copy of a slice from a DataFrame

SettingWithCopyWarning 是一個警告 Warning,而不是錯誤 Error。
這是因為,當我們從DataFrame中僅選擇一列時,Pandas會建立一個檢視,而不是副本。關於檢視和副本的區別,下圖最為形象:

df[]方法會建立檢視

df 
   x    y   w
0  1  0.1  11
1  5  0.5  15
2  4  0.4  14
3  3  0.3  13
4  4  0.4  14
5  5  0.5  15

z = df['y'] # view of column 'y'
z[z>=0.5] = 30

z
0     0.1
1    30.0
2     0.4
3     0.3
4     0.4
5    30.0

df
   x     y   w
0  1   0.1  11
1  5  30.0  15
2  4   0.4  14
3  3   0.3  13
4  4   0.4  14
5  5  30.0  15

當我們建立了檢視後,pandas就會出現warning,因為它不知道我們是否只想更改y系列(通過z)或原始值df。
如果我們要提取“z”作為獨立物件怎麼辦?pandas提供了copy()方法,當我們將命令更新為以下所示的命令時:

z = df['y'].copy()

我們將在記憶體中建立一個具有其自己地址的全新物件,並且對“z”進行的任何更新df都將不受影響。
實際上有兩個要點,可以使我們在使用切片和資料操作時免受任何有害影響:

  • 避免連結索引。始終選擇.loc/ .iloc(或.at/ .iat)方法;
  • 使用copy() 建立獨立的物件,並保護原始資源免遭不當操縱。

參考

https://www.jianshu.com/p/199a653e9668
https://www.kdnuggets.com/2020/04/stop-hurting-pandas.html

相關文章