python Pandas SettingwithCopy 警告解决方案
原文鏈接:https://www.dataquest.io/blog/settingwithcopywarning/
原文標(biāo)題:Understanding SettingwithCopyWarning in pandas
原文發(fā)布時(shí)間:5 JULY 2017(需要注意時(shí)效性,文中有一些方法已經(jīng)棄用,比如?ix)
作者:Benjamin Pryke
譯者:Ivy Lee
學(xué)習(xí) Python 數(shù)據(jù)分析的同學(xué)總是遇到這個(gè)警告,查詢中文資料,一般只能找到個(gè)別的解決辦法,不一定適用于自己遇到的情況。查到的最常見解決辦法就是直接設(shè)置為不顯示警告。這實(shí)際上并不能解決問題,搜索資料發(fā)現(xiàn)這篇英文講解SettingWithCopyWarning原理非常系統(tǒng)的文章,翻譯了一下,分享給大家。
太長(zhǎng)不看
一、解決方案:學(xué)會(huì)識(shí)別鏈?zhǔn)剿饕?#xff0c;不惜一切代價(jià)避免使用鏈?zhǔn)剿饕?br />注意:如果你看不懂這里的解決方案,請(qǐng)閱讀此文的前半部分,直到真正理解如何去做1. 如果要更改原始數(shù)據(jù),請(qǐng)使用單一賦值操作(loc): data.loc[data.bidder == 'parakeet2004', 'bidderrate'] = 100 2. 如果想要一個(gè)副本,請(qǐng)確保強(qiáng)制讓 Pandas 創(chuàng)建副本: winners = data.loc[data.bid == data.price].copy() winners.loc[304, 'bidder'] = 'therealname' 二、強(qiáng)烈不推薦直接關(guān)閉警告,不過還是提供一下關(guān)閉警告的設(shè)置方法: pd.set_option('mode.chained_assignment', None) 三、深度解析底層代碼和歷史演變(可選閱讀)
?
以下是正文部分:
SettingWithCopyWarning?是人們?cè)趯W(xué)習(xí) Pandas 時(shí)遇到的最常見的障礙之一。搜索引擎可以搜索到 Stack Overflow 上的問答、GitHub issues 和一些論壇帖子,分別提供了該警告在某些特定情況下的含義。會(huì)有這么多人同樣遇到這個(gè)警告并不奇怪:有很多方法可以索引 Pandas 數(shù)據(jù)結(jié)構(gòu),每種數(shù)據(jù)結(jié)構(gòu)都有各自的細(xì)微差別,甚至 Pandas 本身并不能保證兩行代碼的運(yùn)行結(jié)果看起來完全相同。
本指南包含了生成警告的原因及解決方案,其中還包括一些底層細(xì)節(jié),讓你更好地了解代碼內(nèi)部的運(yùn)行機(jī)制,最后提供了有關(guān)該話題的一些歷史情況,解釋代碼底層以這樣的方式運(yùn)行的原因。
為了探索?SettingWithCopyWarning,我們將使用 eBay 3 天拍賣出售的 Xbox 的價(jià)格數(shù)據(jù)集,該數(shù)據(jù)集出自?Modelling Online Auctions?一書。先來了解下數(shù)據(jù)的基本結(jié)構(gòu):
import Pandas as pddata = pd.read_csv('xbox-3-day-auctions.csv') data.head()如你所見,數(shù)據(jù)集的每一行都是某一次 eBay Xbox 出價(jià)信息。下面是對(duì)數(shù)據(jù)集中每列的簡(jiǎn)要說明:
- auctionid?- 每次拍賣的唯一標(biāo)識(shí)符
- bid?- 本次拍賣出價(jià)
- bidtime?- 拍賣的時(shí)長(zhǎng),以天為單位,從投標(biāo)開始累計(jì)
- bidder?- 投標(biāo)人的 eBay 用戶名
- bidderrate?- 投標(biāo)人的 eBay 用戶評(píng)級(jí)
- openbid?- 賣方為拍賣設(shè)定的開標(biāo)價(jià)
- price?- 拍賣結(jié)束時(shí)的中標(biāo)價(jià)
什么是 SettingWithCopyWarning?
首先要理解的是,SettingWithCopyWarning?是一個(gè)警告 Warning,而不是錯(cuò)誤 Error。
錯(cuò)誤表明某些內(nèi)容是“壞掉”的,例如無效語法(invalid syntax)或嘗試引用未定義的變量;警告的作用是提醒編程人員,他們的代碼可能存在潛在的錯(cuò)誤或問題,但是這些操作在該編程語言中依然合法。在這種情況下,警告很可能表明一個(gè)嚴(yán)重但不容易意識(shí)到的錯(cuò)誤。
SettingWithCopyWarning?告訴你,你的操作可能沒有按預(yù)期運(yùn)行,需要檢查結(jié)果以確保沒有出錯(cuò)。
如果代碼確實(shí)按預(yù)期工作,那么我們會(huì)很容易忽略該警告,但是?SettingWithCopyWarning不應(yīng)該被忽略。在進(jìn)行下一步操作之前,我們需要花點(diǎn)時(shí)間了解這一警告顯示的原因。
要了解?SettingWithCopyWarning,首先要知道,Pandas 中的某些操作會(huì)返回?cái)?shù)據(jù)的視圖(View),某些操作會(huì)返回?cái)?shù)據(jù)的副本(Copy)。
?
View VS Copy
如上所示,左側(cè)的視圖?df2?只是原始數(shù)據(jù)?df1?一個(gè)子集,而右側(cè)的副本創(chuàng)建了一個(gè)新的對(duì)象?df2。
當(dāng)我們嘗試對(duì)數(shù)據(jù)集進(jìn)行更改時(shí),這可能會(huì)出現(xiàn)問題:
修改視圖或副本
根據(jù)需求,我們可能想要修改原始?df1(左),也可能想要修改?df2(右)。警告提醒我們,代碼可能并沒有符合需求,修改到的可能并不是我們想要修改的那個(gè)數(shù)據(jù)集。
稍后會(huì)深入研究這個(gè)問題,但是現(xiàn)在先來了解一下,警告出現(xiàn)的兩個(gè)主要原因以及對(duì)應(yīng)的解決方案。
鏈?zhǔn)劫x值(Chained Assignment)
當(dāng) Pandas 檢測(cè)到鏈?zhǔn)劫x值(Chained Assignment)時(shí)會(huì)生成警告。為了方便后續(xù)的解釋,先來解釋一些術(shù)語:
- 賦值(Assignment) - 設(shè)置某些變量值的操作,例如?data = pd.read_csv('xbox-3-day-auctions.csv')?,有時(shí)會(huì)將這個(gè)操作稱之為?設(shè)置(Set)
- 訪問(Access) - 返回某些值的操作,具體參照下方的索引和鏈?zhǔn)剿饕纠S袝r(shí)會(huì)將這個(gè)操作稱之為?獲取(Get)
- 索引(Indexing) - 任何引用數(shù)據(jù)子集的賦值或訪問方法,例如?data[1:5]
- 鏈?zhǔn)剿饕?#xff08;Chaining) - 連續(xù)使用多個(gè)索引操作,例如data[1:5][1:3]
鏈?zhǔn)劫x值是鏈?zhǔn)剿饕唾x值的組合。先快速瀏覽一下之前加載的數(shù)據(jù)集,稍后將詳細(xì)介紹。在這個(gè)例子中,假設(shè)我們了解到用戶'parakeet2004'的bidderrate值不正確,需要修改這個(gè)bidderrate值,那么先來查看一下用戶'parakeet2004'的當(dāng)前值:
data[data.bidder == 'parakeet2004']有三行數(shù)據(jù)需要更新bidderrate字段,繼續(xù)操作:
data[data.bidder == 'parakeet2004']['bidderrate'] = 100/Library/Frameworks/Python.framework/Versions/36/lib/python3.6/ipykernel/__main__.py:1:SettingWithCopyWarning: A value is trying to be set on a copy of a slice from aDataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation:http://Pandas.pydata.org/Pandas-docs/stable/indexinghtml#indexing-view-versus-copyif __name__ == '__main__':神奇!我們“創(chuàng)造”出了SettingWithCopyWarning!
檢查一下用戶'parakeet2004'的相關(guān)值,可以看到值沒有按預(yù)期改變:
data[data.bidder == 'parakeet2004']這次警告是因?yàn)閷蓚€(gè)索引操作鏈接在一起,直接使用了兩次方括號(hào)的鏈?zhǔn)剿饕容^容易理解。但如果使用其他訪問方法,例如.bidderrate、.loc[]、.iloc[]、.ix[],也會(huì)如此,這次的鏈?zhǔn)讲僮饔?#xff1a;
- data[data.bidder == 'parakeet2004']
- ['bidderrate'] = 100
以上兩個(gè)鏈?zhǔn)讲僮饕粋€(gè)接一個(gè)地獨(dú)立執(zhí)行。第一次鏈?zhǔn)讲僮魇菫榱?Get,返回一個(gè) DataFrame,其中包含所有?bidder?等于?'parakeet2004'?的行;第二次鏈?zhǔn)讲僮魇菫榱?Set,是在這個(gè)新返回的 DataFrame 上運(yùn)行的,并沒有修改原始的 DataFrame。
這種情況對(duì)應(yīng)的解決方案很簡(jiǎn)單:使用?loc?將兩次鏈?zhǔn)讲僮鹘M合成一步操作,確保 Pandas 進(jìn)行 Set 的是原始 DataFrame。Pandas 始終確保下面這樣的非鏈?zhǔn)?Set 操作起作用:
# 設(shè)置新值 data.loc[data.bidder == 'parakeet2004', 'bidderrate'] = 100 # 檢查結(jié)果 data[data.bidder == 'parakeet2004']['bidderrate']6 100 7 100 8 100 Name: bidderrate, dtype: int64這就是警告的文本(Try using .loc[row_indexer,col_indexer] = value instead)中建議的操作,在這種情況下完美適用。
隱蔽的鏈?zhǔn)讲僮?#xff08;Hidden chaining)
現(xiàn)在來看遇到SettingWithCopyWarning的第二種常見方式。創(chuàng)建一個(gè)新的 DataFrame 來探索中標(biāo)者數(shù)據(jù),因?yàn)楝F(xiàn)在已經(jīng)學(xué)習(xí)了鏈?zhǔn)劫x值的內(nèi)容,請(qǐng)注意使用?loc:
winners = data.loc[data.bid == data.price] winners.head()winners變量可能會(huì)被用來編寫一些后續(xù)代碼:
mean_win_time = winners.bidtime.mean() ... # 20 lines of code mode_open_bid = winners.openbid.mode()我們?cè)谂既婚g發(fā)現(xiàn)了一個(gè)數(shù)據(jù)錯(cuò)誤:標(biāo)記為304的行中缺少了bidder值:
winners.loc[304, 'bidder']nan對(duì)這個(gè)例子來說,假設(shè)我們已知該投標(biāo)人的真實(shí)用戶名,并據(jù)此更新數(shù)據(jù):
winners.loc[304, 'bidder'] = 'therealname'/Library/Frameworks/Python.framework/Versions/36/lib/python3.6/Pandas/core/indexing.py:517:SettingWithCopyWarning: A value is trying to be set on a copy of a slice from aDataFrame. 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-copyself.obj[item] = sSettingWithCopyWarning又出現(xiàn)啦!但是這次使用了loc,為什么還會(huì)出現(xiàn)?來看代碼的結(jié)果:
print(winners.loc[304, 'bidder'])therealname代碼確實(shí)起了預(yù)期的作用,為什么仍然出現(xiàn)警告?
鏈?zhǔn)剿饕赡茉谝恍写a內(nèi)發(fā)生,也可能跨越兩行代碼。因?yàn)?winners?變量是作為 Get 操作的輸出創(chuàng)建的(data.loc[data.bid == data.price]),它可能是原始 DataFrame 的副本,也可能不是,除非檢查,否則我們不能確認(rèn)。對(duì)?winners?進(jìn)行索引時(shí),實(shí)際上使用的就是鏈?zhǔn)剿饕?/p>
這意味著當(dāng)我們嘗試修改?winners?時(shí),可能也修改了?data。
在實(shí)際的代碼中,相關(guān)的兩行鏈?zhǔn)剿饕a之間,可能相距很多行其他代碼,追蹤問題可能會(huì)更困難,但大致情況是與示例類似的。
這種情況下的警告解決方案是:創(chuàng)建新 DataFrame 時(shí)明確告知 Pandas 創(chuàng)建一個(gè)副本:
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就這么簡(jiǎn)單!
竅門就是,學(xué)會(huì)識(shí)別鏈?zhǔn)剿饕?#xff0c;不惜一切代價(jià)避免使用鏈?zhǔn)剿饕H绻脑紨?shù)據(jù),請(qǐng)使用單一賦值操作。如果你想要一個(gè)副本,請(qǐng)確保你強(qiáng)制讓 Pandas 創(chuàng)建副本。這樣既可以節(jié)省時(shí)間,也可以使代碼保持邏輯嚴(yán)密。
另外請(qǐng)注意,即使?SettingWithCopyWarning?只在你進(jìn)行 Set 時(shí)才會(huì)發(fā)生,但在進(jìn)行 Get 操作時(shí),最好也避免使用鏈?zhǔn)剿饕f準(zhǔn)讲僮鞔a效率較低,而且只要稍后進(jìn)行賦值,就會(huì)導(dǎo)致問題。
處理 SettingWithCopyWarning 的提示和技巧
在進(jìn)行下面更深入的分析之前,讓我們看看SettingWithCopyWarning的更多細(xì)節(jié)。
關(guān)閉警告
如果不討論如何明確地控制?SettingWithCopy?警告設(shè)置,本文則不夠完整。Pandas 的?mode.chained_assignment?選項(xiàng)可以采用以下幾個(gè)值之一:
- 'raise'?- 拋出異常(exception)而不是警告
- 'warn'?- 生成警告(默認(rèn))
- None?- 完全關(guān)閉警告
例如,如果要關(guān)閉警告:
pd.set_option('mode.chained_assignment', None) data[data.bidder == 'parakeet2004']['bidderrate'] = 100這樣沒有給出任何提示或警告,除非完全了解代碼的運(yùn)行情況,否則請(qǐng)不要嘗試。只要你對(duì)想要實(shí)現(xiàn)的代碼功能有任何一丁點(diǎn)疑問,不要關(guān)閉警告。有些開發(fā)者非常重視SettingWithCopy甚至選擇將其提升為異常,如下所示:
pd.set_option('mode.chained_assignment', 'raise') data[data.bidder == 'parakeet2004']['bidderrate'] = 100--------------------------------------------------------------------------- SettingWithCopyError Traceback (most recent call last) <ipython-input-13-80e3669cab86> in <module>()1 pd.set_option('mode.chained_assignment', 'raise')----> 2 data[data.bidder == 'parakeet2004']['bidderrate'] = 100 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/Pandas/core/frame.py in __setitem__(self, key, value)2427 else:2428 # set column-> 2429 self._set_item(key, value) 2430 2431 def _setitem_slice(self, key, value):/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/Pandas/core/frame.py in _set_item(self, key, value)2500 # value exception to occur first2501 if len(self):-> 2502 self._check_setitem_copy() 2503 2504 def insert(self, loc, column, value, allow_duplicates=False):/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/Pandas/core/generic.py in _check_setitem_copy(self, stacklevel, t, force)1758 1759 if value == 'raise':-> 1760 raise SettingWithCopyError(t) 1761 elif value == 'warn':1762 warnings.warn(t, SettingWithCopyWarning, stacklevel=stacklevel)SettingWithCopyError: 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如果你正與缺乏經(jīng)驗(yàn)的 Pandas 開發(fā)人員合作開發(fā)項(xiàng)目,或者正在開發(fā)需要高度嚴(yán)謹(jǐn)?shù)捻?xiàng)目,這可能特別有用。
更精確使用此設(shè)置的方法是使用?上下文管理器 context manager?。
# resets the option we set in the previous code segment pd.reset_option('mode.chained_assignment')with pd.option_context('mode.chained_assignment', None):data[data.bidder == 'parakeet2004']['bidderrate'] = 100如你所見,這種方法可以實(shí)現(xiàn)針對(duì)性的警告設(shè)置,而不影響整個(gè)環(huán)境。
is_copy 屬性
避免警告的另一個(gè)技巧是修改 Pandas 用于解釋SettingWithCopy的工具之一。每個(gè) DataFrame 都有一個(gè)is_copy屬性,默認(rèn)情況下為None,但如果它是副本,則會(huì)使用weakref引用原始 DataFrame 。通過將is_copy設(shè)置為None,可以避免生成警告。
winners = data.loc[data.bid == data.price] winners.is_copy = Nonewinners.loc[304, 'bidder'] = 'therealname'但是請(qǐng)注意,這并不會(huì)奇跡般地解決問題,反而會(huì)使錯(cuò)誤檢測(cè)變得更加困難。
單類型 VS 多類型對(duì)象
值得強(qiáng)調(diào)的另一點(diǎn)是單類型對(duì)象和多類型對(duì)象之間的差異。如果 DataFrame 所有列都具有相同的 dtype,則它是單類型的,例如:
import numpy as npsingle_dtype_df = pd.DataFrame(np.random.rand(5,2), columns=list('AB')) print(single_dtype_df.dtypes) single_dtype_dfA float64 B float64 dtype: object如果 DataFrame 的列不是全部具有相同的 dtype,那么它是多類型的,例如:
multiple_dtype_df = pd.DataFrame({'A': np.random.rand(5),'B': list('abcde')}) print(multiple_dtype_df.dtypes) multiple_dtype_dfA float64 B object dtype: object由于下面歷史部分中所述的原因,對(duì)多類型對(duì)象的索引 Get 操作將始終返回副本。而為了提高效率,索引器對(duì)單類型對(duì)象的操作幾乎總是返回一個(gè)視圖,需要注意的是,這取決于對(duì)象的內(nèi)存布局,并不能完全保證。
誤報(bào)
誤報(bào),即無意中報(bào)告鏈?zhǔn)劫x值的情況,曾經(jīng)在早期版本的 Pandas 中比較常見,但此后大部分都被解決了。為了完整起見,在本文中包含一些已修復(fù)的誤報(bào)示例也是有用的。如果你在使用早期版本的 Pandas 時(shí)遇到以下任何情況,則可以安全地忽略或抑制警告(或通過升級(jí) Pandas 版本完全避免警告!)
使用當(dāng)前列的值,將新列添加到 DataFrame 會(huì)生成警告,但這已得到修復(fù)。
data['bidtime_hours'] = data.bidtime.map(lambda x: x * 24) data.head(2)在一個(gè) DataFrame 切片上使用apply方法進(jìn)行 Set 時(shí),也會(huì)出現(xiàn)誤報(bào),不過這也已得到修復(fù)。
data.loc[:, 'bidtime_hours'] = data.bidtime.apply(lambda x: x * 24) data.head(2)直到 0.17.0 版本前,DataFrame.sample方法中存在一個(gè)錯(cuò)誤,導(dǎo)致SettingWithCopy警告誤報(bào)。現(xiàn)在,sample方法每次都會(huì)返回一個(gè)副本。
sample = data.sample(2) sample.loc[:, 'price'] = 120 sample.head()鏈?zhǔn)劫x值深度解析
讓我們重用之前的例子:試圖更新data中bidder值為'parakeet2004'的所有行的bidderrate字段。
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-copyif __name__ == '__main__':Pandas 用?SettingWithCopyWarning?告訴我們的是,代碼的行為是模棱兩可的,要理解原因和警告的措辭,以下概念將會(huì)有所幫助。
之前簡(jiǎn)要了解了視圖(View)和副本(Copy)。有兩種方法可以訪問 DataFrame 的子集:可以創(chuàng)建對(duì)內(nèi)存中原始數(shù)據(jù)的引用(視圖),也可以將子集復(fù)制到新的較小的 DataFrame 中(副本)。視圖是查看?原始?數(shù)據(jù)特定部分的一種方式;副本是將該數(shù)據(jù)?復(fù)制?到內(nèi)存中的新位置。正如之前的圖表所示,修改視圖將修改原始變量,而修改副本則不會(huì)。
由于某些原因(本文稍后介紹),Pandas 中 Get 操作的輸出無法保證。索引 Pandas 數(shù)據(jù)結(jié)構(gòu)時(shí),視圖或副本都可能被返回,也就是說:對(duì)某一 DataFrame 進(jìn)行 Get 操作返回一個(gè)新的 DataFrame,新的數(shù)據(jù)可能是:
- 來自原始對(duì)象的數(shù)據(jù)副本
- 沒有復(fù)制,而是直接對(duì)原始對(duì)象的引用
因?yàn)椴淮_定返回的對(duì)象是什么,而且每種可能性都有非常不同后續(xù)影響,所以忽略警告就是“玩火”。
為了更清楚地解釋視圖、副本和其中的歧義,我們創(chuàng)建一個(gè)簡(jiǎn)單的 DataFrame 并對(duì)其進(jìn)行索引:
df1 = pd.DataFrame(np.arange(6).reshape((3,2)), columns=list('AB')) df1將?df1?的子集賦值給?df2:
df2 = df1.loc[:1] df2根據(jù)剛才學(xué)到的知識(shí),我們知道?df2?可能是?df1?的視圖或?df1?子集的副本。
在解決問題之前,我們還需要再看一下鏈?zhǔn)剿饕U(kuò)展一下?'parakeet2004'?示例,將兩個(gè)索引操作鏈接在一起:
data[data.bidder == 'parakeet2004'] __intermediate__['bidderrate'] = 100__intermediate__表示第一個(gè)調(diào)用的輸出,對(duì)我們是完全不可見的。請(qǐng)記住,如果我們使用了屬性訪問(.+列名形式的訪問),會(huì)得到相同的有問題的結(jié)果:
data[data.bidder == 'parakeet2004'].bidderrate = 100這同樣適用于任何其他形式的鏈?zhǔn)秸{(diào)用,因?yàn)槲覀冋谏芍虚g對(duì)象?。
在底層代碼中,鏈?zhǔn)剿饕馕吨鴮?duì)?__getitem__?或?__setitem__?進(jìn)行多次調(diào)用以完成單個(gè)操作。這些是?特殊的 Python 方法,通過在實(shí)現(xiàn)它們類的實(shí)例上使用方括號(hào),可以調(diào)用這些方法,這是一種語法糖。下面看一下 Python 解釋器如何執(zhí)行示例中的內(nèi)容。
# Our code data[data.bidder == 'parakeet2004']['bidderrate'] = 100 # Code executed data.__getitem__(data.__getitem__('bidder') == 'parakeet2004').__setitem__('bidderrate', 100)你可能已經(jīng)意識(shí)到,SettingWithCopyWarning?是由此鏈?zhǔn)?__setitem__?調(diào)用生成的。可以自己嘗試一下 - 上面這些代碼的功能相同。為清楚起見,請(qǐng)注意第二個(gè)?__getitem__?調(diào)用(對(duì)?bidder?列)是嵌套的,而不是鏈?zhǔn)絾栴}的所有部分。
通常,如上面所述,Pandas 不保證 Get 操作是返回視圖還是副本。如果示例中返回了一個(gè)視圖,則鏈?zhǔn)劫x值中的第二個(gè)表達(dá)式將是對(duì)原始對(duì)象?__setitem__?的調(diào)用。但是,如果返回一個(gè)副本,那么將被修改的是副本 - 原始對(duì)象不會(huì)被修改。
這就是警告中 “a value is trying to be set on a copy of a slice from a DataFrame” 的含義。由于沒有對(duì)此副本的引用,它最終將被回收?。SettingWithCopyWarning?讓我們知道 Pandas 無法確定第一個(gè)?__getitem__?調(diào)用是否返回了視圖或副本,因此不清楚該賦值是否更改了原始對(duì)象。換一種說法就是:“我們是否正在修改原始數(shù)據(jù)?”這一問題的答案是未知的。
如果確實(shí)想要修改原始文件,警告建議的解決方案是使用?loc?將這兩個(gè)單獨(dú)的鏈?zhǔn)讲僮鬓D(zhuǎn)換為單個(gè)賦值操作。這樣代碼中沒有了鏈?zhǔn)剿饕?#xff0c;就不會(huì)再收到警告。修改后的代碼及其擴(kuò)展版本如下所示:
# Our code data.loc[data.bidder == 'parakeet2004', 'bidderrate'] = 100 # Code executed data.loc.__setitem__((data.__getitem__('bidder') == 'parakeet2004', 'bidderrate'), 100)DataFrame 的loc屬性保證是原始 DataFrame 本身,具有擴(kuò)展的索引功能。
假陰性(False negatives)
使用loc并沒有結(jié)束問題,因?yàn)槭褂胠oc的 Get 操作仍然可以返回一個(gè)視圖或副本,下面是個(gè)有點(diǎn)復(fù)雜的例子。
data.loc[data.bidder == 'parakeet2004', ('bidderrate', 'bid')]這次拉出了兩列而不是一列。下面嘗試 Set 所有的bid值。
data.loc[data.bidder == 'parakeet2004', ('bidderrate', 'bid')]['bid'] = 5.0 data.loc[data.bidder == 'parakeet2004', ('bidderrate', 'bid')]沒有效果,也沒有警告!我們?cè)谇衅母北旧?Set 了一個(gè)值,但是 Pandas 沒有檢測(cè)到它 - 這就是假陰性。這是因?yàn)?#xff0c;使用?loc?之后并不意味著可以再次使用鏈?zhǔn)劫x值。這個(gè)特定的 bug,有一個(gè)未解決的?GitHub issue?。
正確的解決方法如下:
data.loc[data.bidder == 'parakeet2004', 'bid'] = 5.0 data.loc[data.bidder == 'parakeet2004', ('bidderrate', 'bid')]你可能懷疑,是否真的有人會(huì)在實(shí)踐中遇到這樣的問題。其實(shí)這比你想象的更容易出現(xiàn)。當(dāng)我們像下一節(jié)中這樣做:將 DataFrame 查詢的結(jié)果賦值給變量。
隱藏的鏈?zhǔn)剿饕?/strong>
再看一下之前隱藏的鏈?zhǔn)剿饕纠?#xff0c;我們?cè)噲D設(shè)置winners變量中,標(biāo)記為304行的bidder字段。
winners = data.loc[data.bid == data.price] winners.loc[304, 'bidder'] = 'therealname'/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/Pandas/core/indexing.py:517: 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-copyself.obj[item] = s盡管使用了?loc,還是得到了?SettingWithCopyWarning?。這可能令人非常困惑,因?yàn)榫嫘畔⒔ㄗh的方法,我們已經(jīng)做過了。
不過,想一下?winners?變量究竟是什么?由于我們通過?data.loc[data.bid == data.price]?將它初始化,無法知道它是原始?data?的視圖還是副本(因?yàn)?Get 操作返回視圖或副本)。將初始化與生成警告的行組合在一起可以清楚地表明我們的錯(cuò)誤。
data.loc[data.bid == data.price].loc[304, 'bidder'] = 'therealname'/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/Pandas/core/indexing.py:517: 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-copyself.obj[item] = s再次使用了鏈?zhǔn)劫x值,只是這次它被分在了兩行代碼中。思考這個(gè)問題的另一種方法是,問一個(gè)問題:“這個(gè)操作會(huì)修改一個(gè)對(duì)象,還是兩個(gè)對(duì)象?”在示例中,答案是未知的:如果?winners?是副本,那么只有?winners?受到影響,但如果是視圖,則?winners?和?data?都將被更新。這種情況可能發(fā)生在腳本或代碼庫(kù)中相距很遠(yuǎn)的行之間,這使問題很難被追根溯源。
此處警告的意圖是提醒,自以為代碼將修改原始 DataFrame,實(shí)際沒有修改成功,或者說我們將修改副本而不是原始數(shù)據(jù)。深入研究 Pandas GitHub repo 中的 issue,可以看到開發(fā)人員自己對(duì)這個(gè)問題的解釋。
如何解決這個(gè)問題在很大程度上取決于自己的意圖。如果想要使用原始數(shù)據(jù)的副本,解決方案就是強(qiáng)制 Pandas 制作副本。
winners = data.loc[data.bid == data.price].copy() winners.loc[304, 'bidder'] = 'therealname' print(data.loc[304, 'bidder']) # Original print(winners.loc[304, 'bidder']) # Copynan therealname另一方面,如果需要更新原始 DataFrame,那么應(yīng)該使用原始 DataFrame 而不是重新賦值一些具有未知行為的其他變量。之前的代碼可以修改為:
# Finding the winners winner_mask = data.bid == data.price# Taking a peek data.loc[winner_mask].head()# Doing analysis mean_win_time = data.loc[winner_mask, 'bidtime'].mean() ... # 20 lines of code mode_open_bid = data.loc[winner_mask, 'openbid'].mode()# Updating the username data.loc[304, 'bidder'] = 'therealname'?
在更復(fù)雜的情況下,例如修改 DataFrame 子集的子集,不要使用鏈?zhǔn)剿饕?#xff0c;可以在原始 DataFrame 上通過loc進(jìn)行修改。例如,可以更改上面的新winner_mask變量或創(chuàng)建一個(gè)選擇中標(biāo)者子集的新變量,如下所示:
high_winner_mask = winner_mask & (data.price > 150) data.loc[high_winner_mask].head()這種技術(shù)會(huì)使未來的代碼庫(kù)維護(hù)和擴(kuò)展地更加穩(wěn)健。
歷史
你可能想知道為什么要造成這么混亂的現(xiàn)狀,為什么不明確指定索引方法是返回視圖還是副本,來完全避免?SettingWithCopy?問題。要理解這個(gè)問題,必須研究 Pandas 的過去。
Pandas 確定返回一個(gè)視圖還是一個(gè)副本的邏輯,源于它對(duì) NumPy 庫(kù)的使用,這是 Pandas 庫(kù)的基礎(chǔ)。視圖實(shí)際上是通過 NumPy 進(jìn)入 Pandas 的詞庫(kù)的。實(shí)際上,視圖在 NumPy 中很有用,因?yàn)樗鼈兡軌蚩深A(yù)測(cè)地返回。由于 NumPy 數(shù)組是單一類型的,因此 Pandas 嘗試使用最合適的?dtype?來最小化內(nèi)存處理需求。因此,包含單個(gè) dtype 的 DataFrame 切片可以作為單個(gè) NumPy 數(shù)組的視圖返回,這是一種高效處理方法。但是,多類型的切片不能以相同的方式存儲(chǔ)在 NumPy 中。Pandas 兼顧多種索引功能,并且保持高效地使用其 NumPy 內(nèi)核的能力。
最終,Pandas 中的索引被設(shè)計(jì)為有用且通用的方式,其核心并不完全與底層 NumPy 數(shù)組的功能相結(jié)合。隨著時(shí)間的推移,這些設(shè)計(jì)和功能元素之間的相互作用,導(dǎo)致了一組復(fù)雜的規(guī)則,這些規(guī)則決定了返回視圖還是副本。經(jīng)驗(yàn)豐富的 Pandas 開發(fā)者通常都很滿意 Pandas 的做法,因?yàn)樗麄兛梢暂p松地瀏覽其索引行為。
不幸的是,對(duì)于 Pandas 的新手來說,鏈?zhǔn)剿饕龓缀醪豢杀苊?#xff0c;因?yàn)?Get 操作返回的就是可索引的 Pandas 對(duì)象。此外,用 Pandas 的核心開發(fā)人員之一?Jeff Reback 的話來說,“從語言的角度來看,直接檢測(cè)鏈?zhǔn)剿饕遣豢赡艿?#xff0c;必須經(jīng)過推斷才能了解”(It is simply not possible from a language perspective to detect chain indexing directly; it has to be inferred)。
因此,在 2013 年底的?0.13.0 版本中引入了警告,作為許多開發(fā)者遇到鏈?zhǔn)劫x值導(dǎo)致的無聲失敗的解決方案。
在 0.12 版本之前,ix 索引器是最受歡迎的(在 Pandas 術(shù)語中,“索引器”比如?ix,loc?和?iloc,是一種簡(jiǎn)單的結(jié)構(gòu),允許使用方括號(hào)來索引對(duì)象,就像數(shù)組一樣,但具有一些特殊的用法)。但是大約在 2013 年 ,Pandas 項(xiàng)目開始意識(shí)到日益增加的新手用戶的重要性,有動(dòng)力開始提高新手用戶的使用體驗(yàn)。自從此版本發(fā)布以來,loc?和?iloc索引器因其更明確的性質(zhì)和更易于解釋的用法而受到青睞。(譯者注:pandas v0.23.3 (July 7, 2018),其中?ix?方法已經(jīng)被棄用)
?
Google Trends: Pandas
SettingWithCopyWarning?在推出后持續(xù)改進(jìn),多年來在許多 GitHub issue 中得到了熱烈的討論?,甚至還在不斷更新?,但是要理解它,仍然是成為 Pandas 專家的關(guān)鍵。
總結(jié)
SettingWithCopyWarning?的基礎(chǔ)復(fù)雜性是 Pandas 庫(kù)中為數(shù)不多的坑。這個(gè)警告的源頭深深嵌在庫(kù)的底層中,不應(yīng)被忽視。Jeff Reback?自己的話?,“Their are no cases that I am aware that you should actually ignore this warning. ……If you do certain types of indexing it will never work, others it will work. You are really playing with fire.”
幸運(yùn)的是,解決警告只需要識(shí)別鏈?zhǔn)劫x值并將其修復(fù)——看完本文你唯一需要理解的
總結(jié)
以上是生活随笔為你收集整理的python Pandas SettingwithCopy 警告解决方案的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ArcGIS Clip(裁剪)时出现0
- 下一篇: python pandas加速包