Pandas 是 python 的一個(gè)數(shù)據(jù)分析包澄者,是基于 NumPy 的一種數(shù)據(jù)分析工具估脆,其中納入了大量庫和一些標(biāo)準(zhǔn)的數(shù)據(jù)模型,提供了快速便捷地處理數(shù)據(jù)的函數(shù)和方法虏等,是高效地操作結(jié)構(gòu)化數(shù)據(jù)集所需的工具民镜,也是使 Python 成為強(qiáng)大而高效的數(shù)據(jù)分析環(huán)境的重要因素之一啡专。
但是相信經(jīng)常使用 Pandas 的同學(xué)在處理結(jié)構(gòu)化數(shù)據(jù)運(yùn)算時(shí)也會(huì)遇到一些麻煩,這些問題要么使得問題解決很復(fù)雜(代碼難寫)制圈,要么使得運(yùn)行極其緩慢(效率低下)们童,下面總結(jié)整理了一些 Pandas 的困難問題進(jìn)行吐槽,如有謬誤歡迎指正鲸鹦,也歡迎大家參加到這次的“Pandas 吐槽大會(huì)”慧库。
切片賦值
切片賦值,指取數(shù)據(jù)中的某個(gè)值或某一塊值馋嗜,修改其中的值齐板,如把第 3 行第 5 列的 x 值修改為 y 值。
使用員工信息數(shù)據(jù)作為案例進(jìn)行介紹,數(shù)據(jù)片段如下:
問題一:將R&D 部門員工的工資改成 20000
Python 代碼
運(yùn)行結(jié)果:
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
可以看到甘磨,報(bào)了這個(gè) SettingWithCopyWarning橡羞,而且修改的值并沒有起作用。相信這個(gè)問題對(duì)于大多數(shù)的 Pandas 用戶并不陌生济舆,那么怎么修改呢卿泽?
就像 SettingWithCopyWarning 中提示的那樣,使用 df.loc[row_indexer,col_indexer] = value 進(jìn)行修改滋觉,這樣不僅可以得到正確的結(jié)果签夭,而且也可以解決報(bào)警的問題。
代碼修改如下:
//修改賦值
import pandas as ? pd
data = ? pd.read_csv('Employees.csv')
data.loc[data['DEPT']=='R&D','SALARY']=20000
print(data)
運(yùn)行結(jié)果:
這才是 Pandas 解決問題的方案椎侠。
討論:問題的實(shí)質(zhì)是我們想通過修改視圖修改源數(shù)據(jù)第租。而 data[data['DEPT']=='R&D']['SALARY']=2000 是將兩個(gè)索引操作鏈接在一起,即直接使用了兩次方括號(hào)的鏈?zhǔn)剿饕?/p>
1.???? data[data['DEPT']=='R&D']
2.???? ['SALARY']=20000
以上兩個(gè)鏈?zhǔn)讲僮饕粋€(gè)接一個(gè)地獨(dú)立執(zhí)行我纪。第一次鏈?zhǔn)讲僮魇菫榱?Get慎宾,返回一個(gè) DataFrame,其中包含所有 DEPT 等于 'R&D' 的行宣羊;第二次鏈?zhǔn)讲僮魇菫榱?Set璧诵,是在這個(gè)新返回的 DataFrame 上運(yùn)行的,并沒有修改原始的 DataFrame仇冯。而此時(shí)使用 loc 函數(shù)獲得原 DataFrame 的視圖,在視圖上賦值就可以修改原始 DataFrame 的值族操。
這種問題還是比較容易發(fā)現(xiàn)的苛坚,下面再來看一種情況:
問題二:修改 R&D 部門 5 號(hào)員工的工資為 19950
問題分析:在實(shí)際的工作中,經(jīng)常把視圖賦值給某個(gè)變量進(jìn)行后續(xù)的計(jì)算色难,直到某一步泼舱,又想修改其中的某行的值,此時(shí)再使用 loc 函數(shù)時(shí)也會(huì)出現(xiàn) SettingWithCopyWarning
Python 代碼:
運(yùn)行結(jié)果:
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
觀察發(fā)現(xiàn)枷莉,即使使用了 loc 函數(shù)娇昙,當(dāng)再次使用 loc 函數(shù)時(shí),還是會(huì)出現(xiàn) SettingWithCopyWarning 的報(bào)警笤妙,其中的原因還是將兩個(gè)索引操作鏈接在一起冒掌,第一次為 get,第二次為 set蹲盘。這次所不同的是賦值結(jié)果起作用了股毫,得到了我們期望的結(jié)果。但我們也不應(yīng)該忽略此 Warning召衔,而是應(yīng)該明確的告訴 Pandas 變量 r_d 是 data 中截取視圖的副本铃诬,然后再使用 loc 函數(shù)修改 5 號(hào)員工的工資。
代碼如下:
運(yùn)行結(jié)果:
討論:var=df.copy() 是明確的告知此 var 是 DataFrame 的副本,此時(shí)再使用 loc 函數(shù)賦值時(shí)趣席,就避免了兩次鏈?zhǔn)剿饕荆簿捅苊饬?SettingWithCopyWarning 的警告。
Pandas 針對(duì) df 的操作冷不防就會(huì)產(chǎn)生視圖宣肚,賦值時(shí)會(huì)錯(cuò)位毒姨,同時(shí)也會(huì)浪費(fèi)時(shí)間。
集合運(yùn)算
常見的集合運(yùn)算钉寝,包括交集弧呐,差集,并集嵌纲,異或集和和集運(yùn)算俘枫,下面來看下 Pandas 兩個(gè)集合間的運(yùn)算。
問題三:求銷售部門的員工與女員工的交集逮走,差集鸠蚪,并集,異或集和和集师溅。
Python 代碼:
討論:Pandas 集合運(yùn)算時(shí)茅信,只能對(duì)著索引進(jìn)行,然后再從原始數(shù)據(jù)中按索引截取得到結(jié)果墓臭。DataFrame 不可以直接進(jìn)行集合運(yùn)算蘸鲸。而且當(dāng)集合數(shù)多于兩個(gè)時(shí),需要通過循環(huán)兩兩計(jì)算得到結(jié)果窿锉,再從原始數(shù)據(jù)按索引截取酌摇。 當(dāng)希望按照某列進(jìn)行集合運(yùn)算時(shí),則還需要把該列轉(zhuǎn)成索引嗡载,計(jì)算完成后還要重置索引窑多,得到結(jié)果。對(duì)于簡單的集合運(yùn)算看起來就很麻煩洼滚,如果 Pandas 能支持集合 (set) 數(shù)據(jù)類型的集合運(yùn)算埂息,通過符號(hào) (&-|^) 進(jìn)行運(yùn)算就好了。
聚合運(yùn)算
Pandas 提供了很多聚合運(yùn)算函數(shù)遥巴,比如求和 sum()千康,平均 mean(),計(jì)數(shù) count()挪哄,方差 var()吧秕,標(biāo)準(zhǔn)差 std() 等等。但遇到稍微特殊一點(diǎn)聚合運(yùn)算時(shí)就有點(diǎn)麻煩迹炼,請(qǐng)看以下兩個(gè)問題砸彬。
問題四:查看所有工資最高的員工的信息
問題分析:首先找到最高工資颠毙,再篩選出等于最高工資的員工。
Python 代碼:
討論:這種方式需要遍歷兩邊數(shù)據(jù)砂碉,計(jì)算最大值時(shí)一遍蛀蜜,過濾時(shí)一遍,效率比較低增蹭。有一種方式可以只遍歷一遍滴某。即找最大值的同時(shí)記錄下最大值員工的索引,然后直接利用索引取數(shù)就可以了滋迈■荩可是 Pandas 的 idxmax() 函數(shù)只返回一個(gè)最大值的索引,不可以返回全部最大值的索引饼灿,因此就只能用上邊的笨方法來解決這個(gè)問題幕侠。
問題五:找到年齡最大的 5 位員工,即 TOPN 問題碍彭。
問題分析:最大值是相當(dāng)于 TOP1晤硕,因此 TOPN 問題也相當(dāng)于聚合運(yùn)算。
Python 代碼:
討論:TOPN 問題并不需要大排序庇忌,只需要維護(hù)一個(gè) N 長度的序列即可舞箍,保持序列中的 N 個(gè)數(shù)總是遍歷過的數(shù)據(jù)中的最大值或者最小值即可。大家都知道大排序的效率是很低的皆疹,而且當(dāng)數(shù)據(jù)量很大時(shí)疏橄,大排序復(fù)雜度和效率又會(huì)進(jìn)一步惡化。但 Pandas 并沒有提供高效的計(jì)算函數(shù)墙基。即使是 nlargest()和 nsmallest() 函數(shù)底層也是大排序后取前五软族。
定位計(jì)算
Pandas 提供了索引功能,用戶可以使用索引進(jìn)行切片等操作残制,但當(dāng)遇到需要計(jì)算指定索引(即位置)比前一行的行就比較麻煩剔氏,如下面這個(gè)問題:
問題六:計(jì)算股價(jià)超過 100 的交易日的當(dāng)日漲幅
問題分析:需要篩選出股價(jià)超過 100 的交易日的交易信息愧怜,將數(shù)據(jù)提前一天,使用相同的索引截取兩份數(shù)據(jù)所踊,計(jì)算兩者的漲幅浊闪。
Python 代碼:
討論:Python 并沒有提供利用位置進(jìn)行相關(guān)計(jì)算的函數(shù)恼布,所以計(jì)算這類問題就略顯麻煩。
分組運(yùn)算
Pandas 提供了豐富的分組運(yùn)算搁宾,既可以按照列名分組折汞,也可以按照指定的數(shù)組分組,既可以對(duì)單列使用多種方式聚合盖腿,也可以對(duì)多列聚合爽待,還可以循環(huán)各組损同,處理分組以后的集合。但有一些常見的分組運(yùn)算使用 Pandas 做起來要么比較繁瑣鸟款,要么效率低下膏燃。
比如按位置分組、值變化分組何什、條件變化分組都需要衍生出一個(gè)數(shù)組作為分組依據(jù)组哩,對(duì)位分組則需要使用 left join 的方式來繞,枚舉分組更是需要多次分組处渣,篩選需要的分組再合并伶贰,這里有一篇文章詳細(xì)介紹了 Pandas 分組運(yùn)算的一些例子
大家可以通過具體的例子體會(huì) Pandas 分組的不便之處。
并行運(yùn)算
Pandas 并不提供并行計(jì)算的方法罐栈,這也是 Pandas 被詬病最多的一方面黍衙,而 Python 所謂的多線程對(duì)于 CPU 而言還是單線程。
大數(shù)據(jù)計(jì)算
Pandas 雖然可以使用分段讀取的方式來獲取數(shù)據(jù)悠瞬,但想要實(shí)現(xiàn)一些復(fù)雜的運(yùn)算们豌,比如排序、分組浅妆、關(guān)聯(lián)等等都會(huì)非常非常麻煩望迎,而且對(duì)程序員的技術(shù)要求也會(huì)很高。詳細(xì)論述可以查看另一篇文檔凌外。