Pandas SettingwithCopyWarning 警告處理

數據處理時,常使用 python 的數據結構及分析工具 Pandas (Python Data Analysis Library) ,在使用時經常遇到一項警告: SettingWithCopyWarning ,這警告是學習 Pandas 時最常見的障礙之一,從 Stack Overflow 的討論串數量就知道自己並不孤單。來回爬了幾次,還是有看沒懂,確認過執行結果如預期,就先把警告忽略。

直到發現這篇 SettingwithCopyWarning- How to Fix This Warning in Pandas,有著清楚定義 + 圖片支援 + 這警告的歷史,耐心讀完後終於瞭解如何避免,在使用 Pandas 時有相當大的幫助,可避免預期外的數據操作,導致結果錯誤,以下摘錄重點。


這警告在說什麼?


這是個警告,而非錯誤,所以程式碼還是會執行。它通知你的操作可能未達到預期的效果,因此應檢查結果以確保沒有犯錯。做決定前,最好花點時間了解發生什麼事情。

首先,瞭解 pandas 中,你的操作,有可能返回 (return) 的是數據的 view 或是 copy


from here


上圖中,左邊 df2 是 df1 的一個子集,是一個 view;而右邊的 df2 是一個新的獨立物件,是一個 copy。

當你試著更改資料時,可能會導致問題:


from here


你可能想修改原始 df1 數據(左),或是只想修改 df2(右邊)。這警告叫你開一下這薛丁格的盒子,確認裡面貓的生死。


警告原因:Chained assignment 及 Hidden chaining


數據操作定義:
  1. 分配 (Assignment,or set) — 設置某些值的操作;如 data = pd.read_csv('xbox-3-day-auctions.csv')
  2. 取得 (Access, or get) — 返回某物值的操作;如下面的索引和鏈接
  3. 索引 (Indexing) — 對數據子集的分配或取得方法; 如 data [1:5]
  4. 鏈接 (Chaining) — 連續使用多個索引操作; 如 data [1:5] [1:3]

Chained assignmen = 4 + 1
data[data.bidder == 'parakeet2004']['bidderrate'] = 100

這時就會跳出警告
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ipykernel/__main__.py:1: 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 insteadSee the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy if __name__ == '__main__':

這情況就照著警告內容,使用 loc,讓 pandas 知道要 set 原始 DataFrame 。

但有時明明已經用了還是跳出警告,可能是你用了 Chained assignment 而沒察覺 (Hidden chaining) 
winners = data.loc[data.bid == data.price]
mean_win_time = winners.bidtime.mean()
... # 20 lines of code
mode_open_bid = winners.openbid.mode()
winners.loc[304, 'bidder'] = 'therealname' # 原本無值,在這想設定為'therealname'

又會跳出警告來!但檢查一下,數據如你所想寫入了
print(winners.loc[304, 'bidder'])
therealname

問題出在 winners ,pandas 不確定是原 DataFrame 的copy 還是 view, 所以對 winners 進行索引時(就算用了 loc),實際上是 chained indexing。

在這情況,修改 winners 時,可能也修改了 data


解決方法:加上.copy()


好消息是,這問題很容易解決,清楚地告訴 pandas 你要的是 copy 還是創造一個新的 dataframe
winners = data.loc[data.bid == data.price].copy()
winners.loc[304, 'bidder'] = 'therealname'
print(winners.loc[304, 'bidder'])
print(data.loc[304, 'bidder'])
therealname
nan

訣竅是學會識別 Chained assignmen ,並不惜一切代價避免。

如果要更改原稿,請單獨使用 set; 如果要複製,請確保強迫 pandas 這樣做, 這會省下找 bug 的時間。

另外,即使僅在 set 時才會發生 SettingWithCopyWarning ,但最好還是避免以 Chained assignment 進行 get。 鍊式操作較慢,如果以後決定添加 set 操作,則會導致問題。

原文後面還有更多細節說明,有興趣可以研究一下。

留言

  1. 其實若 loc 使用正確的話也不會跳警告,安全的寫法為:
    mask = (data.bidder == 'parakeet2004')
    data.loc[mask, 'bidderrate'] = 100

    回覆刪除
    回覆
    1. 抱歉過了許久才發現留言,謝謝你的分享😁

      刪除

張貼留言

這個網誌中的熱門文章

《爸爸,你為什麼愛我?》:一起來想想如何回答孩子。