Pandas 是 python 的一個數據分析包,是基於 NumPy 的一種數據分析工具,其中納入了大量庫和一些標準的數據模型,提供了快速便捷地處理數據的函數和方法,是高效地操作結構化數據集所需的工具,也是使 Python 成爲強大而高效的數據分析環境的重要因素之一。
但是相信經常使用 Pandas 的同學在處理結構化數據運算時也會遇到一些麻煩,這些問題要麼使得問題解決很複雜(代碼難寫),要麼使得運行極其緩慢(效率低下),下面總結整理了一些 Pandas 的困難問題進行吐槽,如有謬誤歡迎指正,也歡迎大家參加到這次的“Pandas 吐槽大會”。
切片賦值
切片賦值,指取數據中的某個值或某一塊值,修改其中的值,如把第 3 行第 5 列的 x 值修改爲 y 值。
使用員工信息數據作爲案例進行介紹,數據片段如下:
EID | NAME | SURNAME | GENDER | STATE | BIRTHDAY | HIREDATE | DEPT | SALARY |
1 | Rebecca | Moore | F | California | 1974/11/20 | 2005/3/11 | R&D | 7000 |
2 | Ashley | Wilson | F | New York | 1980/7/19 | 2008/3/16 | Finance | 11000 |
3 | Rachel | Johnson | F | New Mexico | 1970/12/17 | 2010/12/1 | Sales | 9000 |
4 | Emily | Smith | F | Texas | 1985/3/7 | 2006/8/15 | HR | 7000 |
5 | Ashley | Smith | F | Texas | 1975/5/13 | 2004/7/30 | R&D | 16000 |
… | … | … | … | … | … | … | … | … |
問題一:將R&D 部門員工的工資改成 20000
Python 代碼
import pandas as pd data = pd.read_csv('Employees.csv') data[data['DEPT']=='R&D']['SALARY']=20000 print(data) |
導入 Pandas 讀取數據 找到 R&D 部門,修改工資值 |
運行結果:
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
可以看到,報了這個 SettingWithCopyWarning,而且修改的值並沒有起作用。相信這個問題對於大多數的 Pandas 用戶並不陌生,那麼怎麼修改呢?
就像 SettingWithCopyWarning 中提示的那樣,使用 df.loc[row_indexer,col_indexer] = value 進行修改,這樣不僅可以得到正確的結果,而且也可以解決報警的問題。
代碼修改如下:
import pandas as pd data = pd.read_csv('Employees.csv') data.loc[data['DEPT']=='R&D','SALARY']=20000 print(data) |
修改賦值 |
運行結果:
這纔是 Pandas 解決問題的方案。
討論:問題的實質是我們想通過修改視圖修改源數據。而 data[data['DEPT']=='R&D']['SALARY']=2000 是將兩個索引操作鏈接在一起,即直接使用了兩次方括號的鏈式索引。
1. data[data['DEPT']=='R&D']
2. ['SALARY']=20000
以上兩個鏈式操作一個接一個地獨立執行。第一次鏈式操作是爲了 Get,返回一個 DataFrame,其中包含所有 DEPT 等於 'R&D' 的行;第二次鏈式操作是爲了 Set,是在這個新返回的 DataFrame 上運行的,並沒有修改原始的 DataFrame。而此時使用 loc 函數獲得原 DataFrame 的視圖,在視圖上賦值就可以修改原始 DataFrame 的值。
這種問題還是比較容易發現的,下面再來看一種情況:
問題二:修改 R&D 部門 5 號員工的工資爲 19950
問題分析:在實際的工作中,經常把視圖賦值給某個變量進行後續的計算,直到某一步,又想修改其中的某行的值,此時再使用 loc 函數時也會出現 SettingWithCopyWarning
Python 代碼:
import pandas as pd data = pd.read_csv('Employees.csv') r_d = data.loc[data['DEPT']=='R&D'] ''' ... n行代碼運算 ...
''' r_d.loc[r_d['EID']==5,'SALARY']=19950 print(r_d) |
獲取視圖並賦值給變量 r_d
修改 5 號員工的工資 |
運行結果:
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
觀察發現,即使使用了 loc 函數,當再次使用 loc 函數時,還是會出現 SettingWithCopyWarning 的報警,其中的原因還是將兩個索引操作鏈接在一起,第一次爲 get,第二次爲 set。這次所不同的是賦值結果起作用了,得到了我們期望的結果。但我們也不應該忽略此 Warning,而是應該明確的告訴 Pandas 變量 r_d 是 data 中截取視圖的副本,然後再使用 loc 函數修改 5 號員工的工資。
代碼如下:
import pandas as pd data = pd.read_csv('Employees.csv') r_d = data.loc[data['DEPT']=='R&D'].copy() ''' ... n行代碼運算 ...
''' r_d.loc[r_d['EID']==5,'SALARY']=19950 print(r_d) |
獲取視圖並賦值給變量 r_d
修改 5 號員工的工資 |
運行結果:
討論:var=df.copy() 是明確的告知此 var 是 DataFrame 的副本,此時再使用 loc 函數賦值時,就避免了兩次鏈式索引,也就避免了 SettingWithCopyWarning 的警告。
Pandas 針對 df 的操作冷不防就會產生視圖,賦值時會錯位,同時也會浪費時間。
集合運算
常見的集合運算,包括交集,差集,並集,異或集和和集運算,下面來看下 Pandas 兩個集合間的運算。
問題三:求銷售部門的員工與女員工的交集,差集,並集,異或集和和集。
Python 代碼:
import pandas as pd data = pd.read_csv('Employees.csv') sales = data.query('DEPT=="Sales"') femals = data.query('GENDER=="F"') isect_idx = sales.index.intersection(femals.index) diff_idx = sales.index.difference(femals.index) union_idx = sales.index.union(femals.index) symmetric_diff_idx = sales.index.symmetric_difference(femals.index) isect = data.loc[isect_idx] diff = data.loc[diff_idx] union = data.loc[union_idx] symmetric_diff = data.loc[symmetric_diff_idx] union_all = pd.concat([sales,femals]) print(isect,diff,union,symmetric_diff,union_all) |
銷售部門 女員工 交集索引 差集索引 並集索引 異或集索引
交集 差集 並集 異或集 和集
|
討論:Pandas 集合運算時,只能對着索引進行,然後再從原始數據中按索引截取得到結果。DataFrame 不可以直接進行集合運算。而且當集合數多於兩個時,需要通過循環兩兩計算得到結果,再從原始數據按索引截取。 當希望按照某列進行集合運算時,則還需要把該列轉成索引,計算完成後還要重置索引,得到結果。對於簡單的集合運算看起來就很麻煩,如果 Pandas 能支持集合 (set) 數據類型的集合運算,通過符號 (&-|^) 進行運算就好了。
聚合運算
Pandas 提供了很多聚合運算函數,比如求和 sum(),平均 mean(),計數 count(),方差 var(),標準差 std() 等等。但遇到稍微特殊一點聚合運算時就有點麻煩,請看以下兩個問題。
問題四:查看所有工資最高的員工的信息
問題分析:首先找到最高工資,再篩選出等於最高工資的員工。
Python 代碼:
import pandas as pd data = pd.read_csv('Employees.csv') max_salary = data.SALARY.max() max_salary_emp = data.query('SALARY==%d'%max_salary) print(max_salary_emp) |
計算最高工資 找到最高工資的員工 |
討論:這種方式需要遍歷兩邊數據,計算最大值時一遍,過濾時一遍,效率比較低。有一種方式可以只遍歷一遍。即找最大值的同時記錄下最大值員工的索引,然後直接利用索引取數就可以了。可是 Pandas 的 idxmax() 函數只返回一個最大值的索引,不可以返回全部最大值的索引,因此就只能用上邊的笨方法來解決這個問題。
問題五:找到年齡最大的 5 位員工,即 TOPN 問題。
問題分析:最大值是相當於 TOP1,因此 TOPN 問題也相當於聚合運算。
Python 代碼:
import pandas as pd data = pd.read_csv('Employees.csv') data.sort_values('BIRTHDAY',inplace=True) top_5_age_emp = data.head(5) print(top_5_age_emp) |
排序 取前五 |
討論:TOPN 問題並不需要大排序,只需要維護一個 N 長度的序列即可,保持序列中的 N 個數總是遍歷過的數據中的最大值或者最小值即可。大家都知道大排序的效率是很低的,而且當數據量很大時,大排序複雜度和效率又會進一步惡化。但 Pandas 並沒有提供高效的計算函數。即使是 nlargest()和 nsmallest() 函數底層也是大排序後取前五。
定位計算
Pandas 提供了索引功能,用戶可以使用索引進行切片等操作,但當遇到需要計算指定索引(即位置)比前一行的行就比較麻煩,如下面這個問題:
問題六:計算股價超過 100 的交易日的當日漲幅
問題分析:需要篩選出股價超過 100 的交易日的交易信息,將數據提前一天,使用相同的索引截取兩份數據,計算兩者的漲幅。
Python 代碼:
import pandas as pd data = pd.read_csv('Stock_Price.txt',sep='\t') gt100 = data.query('CL>=100') gt100_idx = gt100.index gt100_shift1 = data.shift(1).loc[gt100_idx] raise_per = (gt100.CL/gt100_shift1.CL-1).fillna(0) print(raise_per) |
篩選 記錄索引 數據提前一天,按索引截取 計算漲幅 |
討論:Python 並沒有提供利用位置進行相關計算的函數,所以計算這類問題就略顯麻煩。
分組運算
Pandas 提供了豐富的分組運算,既可以按照列名分組,也可以按照指定的數組分組,既可以對單列使用多種方式聚合,也可以對多列聚合,還可以循環各組,處理分組以後的集合。但有一些常見的分組運算使用 Pandas 做起來要麼比較繁瑣,要麼效率低下。
比如按位置分組、值變化分組、條件變化分組都需要衍生出一個數組作爲分組依據,對位分組則需要使用 left join 的方式來繞,枚舉分組更是需要多次分組,篩選需要的分組再合併,這裏有一篇文章詳細介紹了 Pandas 分組運算的一些例子
大家可以通過具體的例子體會 Pandas 分組的不便之處。
並行運算
Pandas 並不提供並行計算的方法,這也是 Pandas 被詬病最多的一方面,而 Python 所謂的多線程對於 CPU 而言還是單線程。
大數據計算
Pandas 雖然可以使用分段讀取的方式來獲取數據,但想要實現一些複雜的運算,比如排序、分組、關聯等等都會非常非常麻煩,而且對程序員的技術要求也會很高。詳細論述可以查看另一篇文檔。