數據分析之Pandas變形操作總結

↑↑↑關注後"星標"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點贊

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