↑↑↑關注後"星標"Datawhale
每日干貨 & 每月組隊學習,不錯過
Datawhale乾貨
作者:耿遠昊,Datawhale成員,華東師範大學
pandas 是一個強大的分析結構化數據的工具集;它的使用基礎是Numpy(提供高性能的矩陣運算);用於數據挖掘和數據分析,同時也提供數據清洗功能。
Pandas做分析數據,可以分爲索引、分組、變形及合併四種操作。前邊已經介紹過索引操作、分組操作,現在接着對Pandas中的變形操作進行介紹,涉及知識點提綱如下圖:
本文目錄
1. 透視表
1.1. pivot
1.2. pivot_table
1.3. crosstab(交叉表)
2. 其他變形方法
2.1. melt函數
2.2. 壓縮與展開
3. 啞變量與因子化
3.1. Dummy Variable(啞變量)
3.2. factorize方法
在詳細講解每個模塊之前,首先讀入數據:
import numpy as np
import pandas as pd
df = pd.read_csv('joyful-pandas-master/data/table.csv')
df.head()
透視表
1. pivot
一般狀態下,數據在DataFrame會以壓縮(stacked)狀態存放,例如上面的Gender,兩個類別被疊在一列中,pivot函數可將某一列作爲新的cols:
df.pivot(index='ID',columns='Gender',values='Height').head() # 設行列名,變成一個新的DataFrame
然而pivot函數具有很強的侷限性,除了功能上較少之外,還不允許values中出現重複的行列索引對(pair),例如下面的語句就會報錯:
# df.pivot(index='School',columns='Gender',values='Height').head()
# ValueError: Index contains duplicate entries, cannot reshape
因此,更多的時候會選擇使用強大的pivot_table函數。
2. pivot_table
pd.pivot_table(df,index='ID',columns='Gender',values='Height').head()
但是在速度上,由於功能更多,自然是比不上原來的pivot函數的。
%timeit df.pivot(index='ID',columns='Gender',values='Height')
%timeit pd.pivot_table(df,index='ID',columns='Gender',values='Height')
Pandas中提供了各種選項,下面介紹常用參數:
① aggfunc:對組內進行聚合統計,可傳入各類函數,默認爲'mean'
pd.pivot_table(df,index='School',columns='Gender',values='Height',aggfunc=['mean','sum']).head()
② margins:彙總邊際狀態
pd.pivot_table(df,index='School',columns='Gender',values='Height',aggfunc=['mean','sum'],margins=True).head()
#margins_name可以設置名字,默認爲'All'
③ 行、列、值都可以爲多級
pd.pivot_table(df,index=['School','Class'], columns=['Gender','Address'], values=['Height','Weight'])
3. crosstab(交叉表)
交叉表是一種特殊的透視表,典型的用途如分組統計,如現在想要統計關於街道和性別分組的頻數:
pd.crosstab(index=df['Address'],columns=df['Gender'])
交叉表的功能也很強大(但目前還不支持多級分組),下面說明一些重要參數:
① values和aggfunc:分組對某些數據進行聚合操作,這兩個參數必須成對出現
pd.crosstab(index=df['Address'],columns=df['Gender'], values=np.random.randint(1,20,df.shape[0]), aggfunc='min')
默認參數如下:
pd.crosstab(index=df['Address'],columns=df['Gender'],values=1,aggfunc='count')
② 除了邊際參數margins外,還引入了normalize參數(求百分比),可選'all','index','columns'參數值,也就是對全體、行或列求百分比。
pd.crosstab(index=df['Address'],columns=df['Gender'],normalize='all',margins=True)
其他變形方法
1. melt
melt函數可以認爲是pivot函數的逆操作,將unstacked狀態的數據,壓縮成stacked,使“寬”的DataFrame變“窄”
df_m = df[['ID','Gender','Math']]
df_m.head()
pivoted = df.pivot(index='ID',columns='Gender',values='Math')
pivoted.head()
melt函數中的id_vars表示需要保留的列,value_vars表示需要stack的一組列,value_name是value_vars對應的值的列名。
詳細可以看:https://pandas.pydata.org/docs/reference/api/pandas.melt.html
result = pivoted.reset_index().melt(id_vars=['ID'],value_vars=['F','M'],value_name='Math').dropna().set_index('ID').sort_index()
result.head()
result.equals(df_m.set_index('ID'))
True
2. 壓縮與展開
1). stack:這是最基礎的變形函數,總共只有兩個參數:level和dropna
df_s = pd.pivot_table(df,index=['Class','ID'],columns='Gender',values=['Height','Weight'])
df_s.groupby('Class').head(2)
df_stacked = df_s.stack() # 默認將列往行壓縮,從後往前。
df_stacked.groupby('Class').head(2)
當將所有列壓入行之後,就變成Series了,比如下一個例子:
ddd = df_stacked.stack()
ddd.groupby('Class').head(2)
結論:stack函數可以看做將橫向的索引放到縱向,因此功能類似與melt,參數level可指定變化的列索引是哪一層(或哪幾層,需要列表)
df_stacked = df_s.stack(level=0) # 這裏將第一層橫向索引放到縱向。這個參數默認爲level,所以也可以省略。
# df_stacked = df_s.stack(0)
df_stacked.groupby('Class').head(2)
這裏說一下dropna參數,默認是True。這個參數是用來刪除缺失值的,這個例子不是很好,展示不出刪除缺失值,但是可以看下面分享的鏈接,有一個例子比較明顯的展示了dropna是怎麼刪除缺失值的。
如果dropna=True,那麼就是會將缺失值刪除,若dropna=False,則會保留缺失值。
qwe = df_s.stack(level=0, dropna = True)
qwe.groupby('Class').head(2)
參考學習:https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.stack.html?highlight=stack#pandas.DataFrame.stack
2). unstack:stack的逆函數,功能上類似於pivot_table。
表達式:DataFrame.unstack(self,level = -1,fill_value = None)
df_stacked.head()
結論:這個unstack就是相當於stack的反向操作,將列索引變爲行索引。默認是從右邊索引開始變。
下面說一下參數:對於level就是轉移行索引,默認是-1,也就上面說的從右往左轉移。第二個參數fill_value也很容易猜到,前面stack的dropna是刪除缺失值,這裏的fill_value就是將出現的缺失值補充成NaN,默認爲None。
參考學習:https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.unstack.html#pandas.DataFrame.unstack
result = df_stacked.unstack().swaplevel(1,0,axis=1).sort_index(axis=1)
# 這裏swaplevel是交換這兩個列索引的位置,sort_index是將列索引分組。可以將上面的這兩個函數去掉再運行,就能發現出差別了。
result.groupby('Class').head(2
result.equals(df_s)
True
啞變量與因子化
1. Dummy Variable(啞變量)
這裏主要介紹get_dummies函數,其功能主要是進行one-hot編碼:
df_d = df[['Class','Gender','Weight']]
df_d.head()
現在希望將上面的表格前兩列轉化爲啞變量,並加入第三列Weight數值:
pd.get_dummies(df_d[['Class','Gender']]).join(df_d['Weight']).head()
可選prefix參數添加前綴,prefix_sep添加分隔符,示例如下:
df_pp = df_d[['Class','Gender']]
pd.get_dummies(df_pp, prefix=['C','G'], prefix_sep='*').join(df_d['Weight']).head()
這裏prefix的默認是原前綴,prefixsep默認爲' '。
參考學習:https://pandas.pydata.org/docs/reference/api/pandas.get_dummies.html?highlight=get_dummi
2. factorize方法
該方法主要用於自然數編碼,並且缺失值會被記做-1,其中sort參數表示是否排序後賦值,默認爲False。
codes, uniques = pd.factorize(['b', None, 'a', 'c', 'b'], sort=True)
display(codes)
display(uniques)
codes是對元素進行編碼,None爲-1。uniques得到列表的唯一元素s。
參考學習:https://pandas.pydata.org/docs/reference/api/pandas.factorize.html?highlight=factori#pandas.factorize
問題與練習
問 題
問題1:上面提到的變形函數,請總結它們各自的使用特點。
melt/crosstab/pivot/pivot_table/stack/unstack
1)首先我們講 pivot、pivot_tabel,這兩個變形函數都是對某列的元素變成列索引,功能很強大,可以同時計算平均值、總和等等數據,但是前者有一定的侷限性。
2)其次說一下crosstab,這個函數可以計算頻數,也可以計算百分比,功能也較爲強大。
3)最後看這個melt、stack和unstack。這些函數主要就是用來變換行列索引,功能比較侷限,其中stack的功能就是將行索引變成列索引,然後melt和unstack的功能類似,和stack的功能恰恰相反。
這裏說的比較寬泛,還有很多參數會影響這些功能的使用,詳細的就看上面的代碼和鏈接吧。
問題2:變形函數和多級索引是什麼關係?哪些變形函數會使得索引維數變化?具體如何變化?
一般我們使用變形函數,會是變換行列索引,那麼這裏就會遇到這個多級索引的問題,到底換哪一個索引,怎麼選擇索引就值得我們來探討。
從我們所學的來看,能使用多級索引的變形函數是pivot_tabel,這個函數功能很強大,行列和值都可以多級。那麼面對這個多級索引,我們要變化維數,就要使用stack和unstack這些函數了。在這些函數中有專門的參數來代表我們要換的那一行列索引的位置level,從而實現選擇索引。
問題3:請舉出一個除了上文提過的關於啞變量方法的例子。
下面我們改變df_d中的元素。
df_d = df[['School','Gender','Height']]
df_d.head()
pd.get_dummies(df_d[['School','Gender']]).join(df_d['Height']).head()
pd.get_dummies(df_d[['School','Gender']], prefix=['S','G'], prefix_sep='%').join(df_d['Height']).head()
問題4:使用完stack後立即使用unstack一定能保證變化結果與原始表完全一致嗎?
不一定。這兩個變形函數都是有參數的,我們如果不考慮參數,遇到多級索引就很有可能不會一致。但是我們要是考慮參數,換的行正好對應換的列,然後通過參數找出,再換回來,再通過swaplevel和sort_index等函數進行修正,就可以做到一致。
問題5:透視表中涉及了三個函數,請分別使用它們完成相同的目標(任務自定)並比較哪個速度最快。
%timeit df.pivot(index='ID',columns='Gender',values='Height')
%timeit pd.pivot_table(df,index='ID',columns='Gender',values='Height')
%timeit pd.crosstab(index=df['ID'],columns=df['Gender'], values=df['Height'], aggfunc='min')
最快的還是pivot函數。
問題6:既然melt起到了unstack的功能,爲什麼再設計unstack函數?
雖然說melt和unstack很像,但是使用起來卻十分的複雜,參數太多了,需要我們自己填寫的東西很多。而這個unstack的參數就兩個,level和fill_value,簡單快捷,使用很方便。所以設計unstack函數應該是爲了更方便的完成任務吧。
練 習
練習1:有一份關於美國10年至17年的非法藥物數據集,列分別記錄了年份、州(5個)、縣、藥物類型、報告數量,請解決下列問題:
pd.read_csv('data/Drugs.csv').head()
(a) 現在請你將數據錶轉化成如下形態,每行需要顯示每種藥物在每個地區的10年至17年的變化情況,且前三列需要排序:
df = pd.read_csv('joyful-pandas-master/data/Drugs.csv',index_col=['State','COUNTY']).sort_index()
df.head()
#result = pivoted.reset_index().melt(id_vars=['ID'],value_vars=['F','M'],value_name='Math').dropna().set_index('ID').sort_index()
df_result = pd.pivot_table(df,index=['State','COUNTY','SubstanceName'],columns='YYYY',values='DrugReports',
fill_value='-').sort_index().reset_index().rename_axis(columns={'YYYY':''})
df_result.head()
(b) 現在請將(a)中的結果恢復到原數據表,並通過equal函數檢驗初始表與新的結果是否一致(返回True)
result_melted = result.melt(id_vars=result.columns[:3],value_vars=result.columns[-8:]
,var_name='YYYY',value_name='DrugReports').query('DrugReports != "-"')
result2 = result_melted.sort_values(by=['State','COUNTY','YYYY','SubstanceName']).reset_index().drop(columns='index')
#下面其實無關緊要,只是交換兩個列再改一下類型(因爲‘-’所以type變成object了)
cols = list(result2.columns)
a, b = cols.index('SubstanceName'), cols.index('YYYY')
cols[b], cols[a] = cols[a], cols[b]
result2 = result2[cols].astype({'DrugReports':'int','YYYY':'int'})
result2
df_tidy = df.reset_index().sort_values(by=result2.columns[:4].tolist()).reset_index().drop(columns='index')
df_tidy
df_tidy.equals(result2)
False
練習2:現有一份關於某地區地震情況的數據集,請解決如下問題:
pd.read_csv('joyful-pandas-master/data/Earthquake.csv').head()
(a) 現在請你將數據錶轉化成如下形態,將方向列展開,並將距離、深度和烈度三個屬性壓縮:
df = pd.read_csv('joyful-pandas-master/data/Earthquake.csv')
df = df.sort_values(by=df.columns.tolist()[:3]).sort_index(axis=1).reset_index().drop(columns='index')
df.head()
result = pd.pivot_table(df,index=['日期','時間','維度','經度'],columns='方向',values=['烈度','深度','距離'],
fill_value='-').stack(level=0).rename_axis(index={None:'地震參數'})
result.head(6)
(b) 現在請將(a)中的結果恢復到原數據表,並通過equal函數檢驗初始表與新的結果是否一致(返回True)
df_result = result.unstack().stack(0)[(~(result.unstack().stack(0)=='-')).any(1)].reset_index()
df_result.columns.name=None
df_result = df_result.sort_index(axis=1).astype({'深度':'float64','烈度':'float64','距離':'float64'}).sort_index()
df_result.head()
df_result.astype({'深度':'float64','烈度':'float64','距離':'float64'},copy=False).dtypes
df.equals(df_result)
True
本文電子版 後臺回覆 變形 獲取
“在看,爲Pandas點贊↓