Pandas 不擅長的結構化數據運算

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 雖然可以使用分段讀取的方式來獲取數據,但想要實現一些複雜的運算,比如排序、分組、關聯等等都會非常非常麻煩,而且對程序員的技術要求也會很高。詳細論述可以查看另一篇文檔。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章