Day5-Python變形(DataWhale)

liying_tt發表於2020-12-27

變形

import numpy as np
import pandas as pd

一、長表變寬表

長表:一個表中把性別儲存在某一個列中,它就是關於性別的長表
寬表:把性別作為列名,列中的元素是某一其他的相關特徵數值,這個表是關於性別的寬表

#長表
pd.DataFrame({'Gender':['F','F','M','M'],
             'Heigth':[163,160,175,180]})
GenderHeigth
0F163
1F160
2M175
3M180
#寬表
pd.DataFrame({'Height:F':[163,160],
             'Height:M':[175,180]})
Height:FHeight:M
0163175
1160180

1. pivot

1.pivot是長表變寬表的函式

一個基本的長變寬的操作而言,最重要的有三個要素:

(1)變形後的行索引: index <-- 對應列的unique值

(2)需要轉到列索引的列:columns <-- 對應列的unique值

(3)這些列和行索引對應的數值:values

例:把語文和數學分數作為列來展示

df = pd.DataFrame({'Class':[1,1,2,2,2],
                  'Name':['San Zhang','San Zhang','Si Li','Si Li','Si Li'],
                  'Subject': ['Chinese','Math','Chinese','Math','Science'],
                  'Grade':[80,75,90,85,90]})
df
ClassNameSubjectGrade
01San ZhangChinese80
11San ZhangMath75
22Si LiChinese90
32Si LiMath85
42Si LiScience90
df.pivot(index='Name',columns='Subject',values='Grade')
SubjectChineseMathScience
Name
San Zhang80.075.0NaN
Si Li90.085.090.0

pivot變形過程:
在這裡插入圖片描述利用 pivot 進行變形操作需要滿足唯一性的要求,即由於在新表中的行列索引對應了唯一的 value ,因此原表中的 index 和 columns 對應兩個列的行組合必須唯一。

例如,現在把原表中第二行張三的數學改為語文就會報錯,這是由於 Name 與 Subject 的組合中兩次出現 (“San Zhang”, “Chinese”) ,從而最後不能夠確定到底變形後應該是填寫80分還是75分。

df.loc[1,'Subject'] = 'Chinese'
try:
    df.pivot(index='Name',columns='Subject',values='Grade')
except Exception as e:
        Err_Msg = e
Err_Msg
ValueError('Index contains duplicate entries, cannot reshape')

2.pivot 相關的三個引數允許被設定為列表

df = pd.DataFrame({'Class':[1,1,2,2,1,1,2,2],
                  'Name':['San Zhang','San Zhang','Si Li','Si Li',
                         'San Zhang','San Zhang','Si Li','Si Li'],
                  'Examination':['Mid','Final','Mid','Final',
                                'Mid','Final','Mid','Final'],
                  'Subject': ['Chinese','Chinese','Chinese','Chinese',
                             'Math','Math','Math','Math'],
                  'Grade':[80,75,85,65,90,85,92,88],
                  'rank':[10,15,21,15,20,7,6,21]})
df
ClassNameExaminationSubjectGraderank
01San ZhangMidChinese8010
11San ZhangFinalChinese7515
22Si LiMidChinese8521
32Si LiFinalChinese6515
41San ZhangMidMath9020
51San ZhangFinalMath857
62Si LiMidMath926
72Si LiFinalMath8821

例:把測試型別和科目聯合組成的四個類別(期中語文、期末語文、期中數學、期末數學)轉到列索引,並且同時統計成績和排名

pivot_multi = df.pivot(index = ['Class','Name'],
                      columns = ['Subject','Examination'],
                      values = ['Grade','rank'])
pivot_multi
Graderank
SubjectChineseMathChineseMath
ExaminationMidFinalMidFinalMidFinalMidFinal
ClassName
1San Zhang807590851015207
2Si Li856592882115621

根據唯一性原則,新表的行索引等價於對 index 中的多列使用 drop_duplicates ,而列索引的長度為 values 中的元素個數乘以 columns 的唯一組合數量(與 index 類似)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9RsCn77z-1609072795458)(attachment:image.png
)]

2. pivot_table

pivot 的使用依賴於唯一性條件,那如果不滿足唯一性條件,那麼必須通過聚合操作使得相同行列組合對應的多個值變為一個值

df = pd.DataFrame({'Name':['San Zhang','San Zhang','San Zhang','San Zhang',
                          'Si Li','Si Li','Si Li','Si Li'],
                  'Subject':['Chinese','Chinese','Math','Math',
                            'Chinese','Chinese','Math','Math'],
                  'Grade':[80,90,100,90,70,80,985,95]})
df
NameSubjectGrade
0San ZhangChinese80
1San ZhangChinese90
2San ZhangMath100
3San ZhangMath90
4Si LiChinese70
5Si LiChinese80
6Si LiMath985
7Si LiMath95

(1) 引數

aggfunc: 使用的聚合函式

margins=True: 邊際彙總的功能

df.pivot_table(index='Name',
              columns='Subject',
              values='Grade',
              aggfunc= lambda x:x.mean()) #也可以使用aggfunc='mean'
SubjectChineseMath
Name
San Zhang8595
Si Li75540
df.pivot_table(index='Name',
              columns='Subject',
              values='Grade',
              aggfunc='mean',
              margins=True)
SubjectChineseMathAll
Name
San Zhang8595.090.00
Si Li75540.0307.50
All80317.5198.75

3. 練一練1

在上面的邊際彙總例子中,行或列的彙總為新表中行元素或者列元素的平均值,而總體的彙總為新表中四個元素的平均值。這種關係一定成立嗎?若不成立,請給出一個例子來說明。

思路:我的理解是,邊際彙總的值依據的是給定的函式,它滿足的條件應該是函式的條件,上面的例子因為我們只用了平均值,所以計算的結果也滿足平均值,但是當我們將函式複雜度提高之後,例如下面的例子,我求的是平均值和彙總值的和,我們會發現總體的彙總就不滿足新表的四個元素均值和彙總值的和

df.pivot_table(index='Name',
              columns='Subject',
              values='Grade',
              aggfunc='sum',
              margins=True)
SubjectChineseMathAll
Name
San Zhang170190360
Si Li15010801230
All32012701590
df.pivot_table(index='Name',
              columns='Subject',
              values='Grade',
              aggfunc= lambda x :x.mean()+x.sum(),
              margins=True)
SubjectChineseMathAll
Name
San Zhang255285.0450.00
Si Li2251620.01537.50
All4001587.51788.75

二、寬表變長表

1. melt

(1)引數:
id_vars 不需要被轉換的列名,標識列,不是索引

value_vars 被轉換的列名

var_name “被轉換的列名”,組成的新列的名稱

value_name "被轉換的列名"下的資料,組成的新列的名稱

df = pd.DataFrame({'Class':[1,2],
                  'Name':['San Zhang','Si Li'],
                  'Chinese':[80,90],
                  'Math':[80,75]})
df
ClassNameChineseMath
01San Zhang8080
12Si Li9075
df_melted = df.melt(id_vars = ['Class','Name'],
                   value_vars=['Chinese','Math'],
                   var_name='Subject',
                   value_name='Grade')
df_melted
ClassNameSubjectGrade
01San ZhangChinese80
12Si LiChinese90
21San ZhangMath80
32Si LiMath75

在這裡插入圖片描述
例:使用pivot將df_melted轉換為df

df_unmeltes = df_melted.pivot(index=['Class','Name'],
                             columns='Subject',
                             values='Grade')
df_unmeltes
SubjectChineseMath
ClassName
1San Zhang8080
2Si Li9075
#恢復索引
df_unmeltes = df_unmeltes.reset_index().rename_axis(columns={'Subject':''})
df_unmeltes
ClassNameChineseMath
01San Zhang8080
12Si Li9075
df_unmeltes.equals(df)
True

2. wide_to_long

melt 方法中,在列索引中被壓縮的一組值對應的列元素只能代表同一層次的含義,即 values_name 。如果列中包含了交叉類別,要把 values_name 對應的 Grade 擴充為兩列,使用 wide_to_long 函式來完成

(1)引數:

stubnames:轉換之後的表以其為列,等價於value_name

i: 保持不變的id變數,等價於id_vars

j: 壓縮到行的變數名,等價於var_name

sep: 分隔符

suffix: 正則字尾

df = pd.DataFrame({'Class':[1,2],
                  'Name':['San Zhang','Si Li'],
                  'Chinese_Mid':[80,75],
                  'Math_Mid':[90,85],
                  'Chinese_Final':[80,75],
                  'Math_Final':[90,85]})
df
ClassNameChinese_MidMath_MidChinese_FinalMath_Final
01San Zhang80908090
12Si Li75857585
pd.wide_to_long(df,
               stubnames=['Chinese','Math'],#分隔符之前的資料
               i = ['Class','Name'],
               j = 'Examination',#分隔符之後的資料名稱
               sep = '_',
               suffix = '.+')
ChineseMath
ClassNameExamination
1San ZhangMid8090
Final8090
2Si LiMid7585
Final7585

在這裡插入圖片描述
把之前在 pivot 一節中多列操作的結果(產生了多級索引),利用 wide_to_long 函式,將其轉為原來的形態

res = pivot_multi.copy()
res
Graderank
SubjectChineseMathChineseMath
ExaminationMidFinalMidFinalMidFinalMidFinal
ClassName
1San Zhang807590851015207
2Si Li856592882115621
res.columns = res.columns.map(lambda x: '_'.join(x))
res.head(2)
Grade_Chinese_MidGrade_Chinese_FinalGrade_Math_MidGrade_Math_Finalrank_Chinese_Midrank_Chinese_Finalrank_Math_Midrank_Math_Final
ClassName
1San Zhang807590851015207
2Si Li856592882115621
res = res.reset_index()
res.head(2)
ClassNameGrade_Chinese_MidGrade_Chinese_FinalGrade_Math_MidGrade_Math_Finalrank_Chinese_Midrank_Chinese_Finalrank_Math_Midrank_Math_Final
01San Zhang807590851015207
12Si Li856592882115621
res = pd.wide_to_long(res,
                     stubnames=['Grade','rank'],
                     i = ['Class','Name'],
                     j = 'Subject_Examination',
                     sep = '_',
                     suffix = '.+')
res = res.reset_index()
res[['Subject','Examination']] = res['Subject_Examination'].str.split('_',expand=True)
res = res[['Class','Name','Examination','Subject','Grade','rank']].sort_values('Subject')
res=res.reset_index(drop=True)
res
ClassNameExaminationSubjectGraderank
01San ZhangMidChinese8010
11San ZhangFinalChinese7515
22Si LiMidChinese8521
32Si LiFinalChinese6515
41San ZhangMidMath9020
51San ZhangFinalMath857
62Si LiMidMath926
72Si LiFinalMath8821

三、索引的變形

1. unstack

把行索引轉為列索引

在 unstack 中必須保證 被轉為列索引的行索引層 和 被保留的行索引層 構成的組合是唯一的,

df = pd.DataFrame(np.ones((4,2)),
                 index=pd.Index([('A','cat','big'),
                                ('A','dog','small'),
                                ('B','cat','big'),
                                ('B','dog','small')]),
                 columns=['col_1','col_2'])
df
col_1col_2
Acatbig1.01.0
dogsmall1.01.0
Bcatbig1.01.0
dogsmall1.01.0
df.unstack()
col_1col_2
bigsmallbigsmall
Acat1.0NaN1.0NaN
dogNaN1.0NaN1.0
Bcat1.0NaN1.0NaN
dogNaN1.0NaN1.0

unstack 的主要引數是移動的層號,預設轉化最內層,移動到列索引的最內層,同時支援同時轉化多個層:

df.unstack(2)
col_1col_2
bigsmallbigsmall
Acat1.0NaN1.0NaN
dogNaN1.0NaN1.0
Bcat1.0NaN1.0NaN
dogNaN1.0NaN1.0
df.unstack([0,2])
col_1col_2
ABAB
bigsmallbigsmallbigsmallbigsmall
cat1.0NaN1.0NaN1.0NaN1.0NaN
dogNaN1.0NaN1.0NaN1.0NaN1.0

2. stack

把列索引的層壓入行索引

df = pd.DataFrame(np.ones((4,2)),
                 index=pd.Index([('A','cat','big'),
                                ('A','dog','small'),
                                ('B','cat','big'),
                                ('B','dog','small')]),
                 columns=['index_1','index_2']).T
df
AB
catdogcatdog
bigsmallbigsmall
index_11.01.01.01.0
index_21.01.01.01.0
df.stack()
AB
catdogcatdog
index_1big1.0NaN1.0NaN
smallNaN1.0NaN1.0
index_2big1.0NaN1.0NaN
smallNaN1.0NaN1.0
df.stack([1,2])
AB
index_1catbig1.01.0
dogsmall1.01.0
index_2catbig1.01.0
dogsmall1.01.0

3. 聚合與變形的關係

(1)除了帶有聚合效果的 pivot_table 以外,所有的函式在變形前後並不會帶來 values 個數的改變,只是這些值在呈現的形式上發生了變化。

(2)分組聚合操作,由於生成了新的行列索引,屬於某種特殊的變形操作,但由於聚合之後把原來的多個值變為了一個值,因此 values 的個數產生了變化,這也是分組聚合與變形函式的最大區別。

四、其他變形函式

1. crosstab

crosstab 並不是一個值得推薦使用的函式,因為它能實現的所有功能 pivot_table 都能完成,並且速度更快。在預設狀態下, crosstab 可以統計元素組合出現的頻數,即 count 操作

例:統計 learn_pandas 資料集中學校和轉系情況對應的頻數

df = pd.read_csv('data/learn_pandas.csv')
pd.crosstab(index=df.School, columns=df.Transfer)
TransferNY
School
Fudan University381
Peking University282
Shanghai Jiao Tong University530
Tsinghua University624

這等價於如下 crosstab 的如下寫法,這裡的 aggfunc 即聚合引數:

pd.crosstab(index=df.School, columns=df.Transfer, 
            values=[0]*df.shape[0], aggfunc='count')
TransferNY
School
Fudan University38.01.0
Peking University28.02.0
Shanghai Jiao Tong University53.0NaN
Tsinghua University62.04.0

同樣,可以利用 pivot_table 進行等價操作,由於這裡統計的是組合的頻數,因此 values 引數無論傳入哪一個列都不會影響最後的結果:

df.pivot_table(index='School',
               columns='Transfer',
               values='Name',
               aggfunc='count')
TransferNY
School
Fudan University38.01.0
Peking University28.02.0
Shanghai Jiao Tong University53.0NaN
Tsinghua University62.04.0

從上面可以看出這兩個函式的區別在於, crosstab 的對應位置傳入的是具體的序列,而 pivot_table 傳入的是被呼叫表對應的名字,若傳入序列對應的值則會報錯。

除了預設狀態下的 count 統計,所有的聚合字串和返回標量的自定義函式都是可用的,例如統計對應組合的身高均值

pd.crosstab(index=df.School, columns=df.Transfer,
           values=df.Height, aggfunc='mean')
TransferNY
School
Fudan University162.043750177.20
Peking University163.429630162.40
Shanghai Jiao Tong University163.953846NaN
Tsinghua University163.253571164.55

2. 練一練2

前面提到了 crosstab 的效能劣於 pivot_table ,請選用多個聚合方法進行驗證。

思路:我驗證了mean、sum、cumsum,發現pivot_table的執行效率比crosstab高

import time
start = time.time()

pd.crosstab(index=df.School, columns=df.Transfer,
           values=df.Height, aggfunc='cumsum')

end = time.time()
print(end-start)
0.04938101768493652
start = time.time()

df.pivot_table(index='School',
               columns='Transfer',
               values='Height',
               aggfunc='cumsum')

end = time.time()
print(end-start)
0.008005380630493164

3.explode

explode 引數能夠對某一列的元素進行縱向的展開,被展開的單元格必須儲存 list, tuple, Series, np.ndarray 中的一種型別

df_ex = pd.DataFrame({'A':[[1,2],
                          'my_str',
                           {1,2},
                          pd.Series([3,4])],
                      'B':1})
df_ex
AB
0[1, 2]1
1my_str1
2{1, 2}1
30 3 1 4 dtype: int641
df_ex.explode('A')
AB
011
021
1my_str1
2{1, 2}1
331
341

4. get_dummies

get_dummies 是用於特徵構建的重要函式之一,其作用是把類別特徵轉為指示變數

例:對年級一列轉為指示變數,屬於某一個年級的對應列標記為1,否則為0:

pd.get_dummies(df.Grade).head()
FreshmanJuniorSeniorSophomore
01000
11000
20010
30001
40001

五、練習

1. 美國非法藥物資料集

現有一份關於美國非法藥物的資料集,其中 SubstanceName, DrugReports 分別指藥物名稱和報告數量

df = pd.read_csv('data/Drugs.csv').sort_values([
    'State','COUNTY','SubstanceName'],ignore_index=True)
df.head(2)
YYYYStateCOUNTYSubstanceNameDrugReports
02011KYADAIRBuprenorphine3
12012KYADAIRBuprenorphine5

1.將資料轉為如下的形式

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-BN9lYNXJ-1609072795463)(attachment:image.png)]

思考:其中遇到了一個將index的標題修改為’'的問題,後來使用rename_axis解決

df_1 = df.pivot(index = ['State','COUNTY','SubstanceName'],
                columns = 'YYYY',
               values='DrugReports')
df_1 = df_1.reset_index()
df_1 = df_1.rename_axis(None, axis=1)
df_1.head()
StateCOUNTYSubstanceName20102011201220132014201520162017
0KYADAIRBuprenorphineNaN3.05.04.027.05.07.010.0
1KYADAIRCodeineNaNNaN1.0NaNNaNNaNNaN1.0
2KYADAIRFentanylNaNNaN1.0NaNNaNNaNNaNNaN
3KYADAIRHeroinNaNNaN1.02.0NaN1.0NaN2.0
4KYADAIRHydrocodone6.09.010.010.09.07.011.03.0

2.將第1問中的結果恢復為原表。

思路:還原後,我們會發現列的順序與原來不一致,最後再賦值將列的位置調整一下

date = list(set(df.YYYY))
date
[2016, 2017, 2010, 2011, 2012, 2013, 2014, 2015]
df_2 = df_1.melt(id_vars = ['State','COUNTY','SubstanceName'],
                value_vars = date,
                var_name = 'YYYY',
                value_name = 'DrugReports')
df_2.head()
StateCOUNTYSubstanceNameYYYYDrugReports
0KYADAIRBuprenorphine20167.0
1KYADAIRCodeine2016NaN
2KYADAIRFentanyl2016NaN
3KYADAIRHeroin2016NaN
4KYADAIRHydrocodone201611.0
cols = list(df.columns)
df_2 = df_2.loc[:,cols]
df_2.head(2)
YYYYStateCOUNTYSubstanceNameDrugReports
02016KYADAIRBuprenorphine7.0
12016KYADAIRCodeineNaN

3.按 State 分別統計每年的報告數量總和,其中 State, YYYY 分別為列索引和行索引,要求分別使用 pivot_table 函式與 groupby+unstack 兩種不同的策略實現,並體會它們之間的聯絡

思考:pivot_table可以直接按照行、列的排列將資料彙總,也就是相當於先使用groupby得到含兩個索引的Series,之後通過unstack將Series轉換為DataFrame,也就是pivot_table之後的結果

df_3 = df.pivot_table(index = 'YYYY',
                     columns = 'State',
                     values = 'DrugReports',
                     aggfunc = 'sum')
df_3.head(2)
StateKYOHPAVAWV
YYYY
201010453197071981486852890
201110289203301998767493271
gb_3 = df.groupby(['YYYY','State'])['DrugReports'].sum()
type(gb_3)
pandas.core.series.Series
gb_3.unstack().head(2)
StateKYOHPAVAWV
YYYY
201010453197071981486852890
201110289203301998767493271

2. 特殊的wide_to_long方法

從功能上看, melt 方法應當屬於 wide_to_long 的一種特殊情況,即 stubnames 只有一類。請使用 wide_to_long 生成 melt 一節中的 df_melted 。(提示:對列名增加適當的字首)

思考:在Chinese和Math列加入一個Grade字首,Grade會單獨形成一列,之後的科目名稱會存入到一列,最後去掉索引即可。

df_4 = pd.DataFrame({'Class': [1,2],
                  'Name':['San Zhang','Si Li'],
                  'Chinese':[80,90],
                  'Math':[80,75]})
df_4
ClassNameChineseMath
01San Zhang8080
12Si Li9075
df_4_melted = df_4.melt(id_vars = ['Class','Name'],
                       value_vars = ['Chinese','Math'],
                       var_name = 'Subject',
                       value_name = 'Grade')
df_4_melted
ClassNameSubjectGrade
01San ZhangChinese80
12Si LiChinese90
21San ZhangMath80
32Si LiMath75
df_new = df_4.rename(columns={'Chinese':'Grade_Chinese','Math':'Grade_Math'})
df_new.head(2)
ClassNameGrade_ChineseGrade_Math
01San Zhang8080
12Si Li9075
df_new = pd.wide_to_long(df_new,
               stubnames=['Grade'],
               i = ['Class','Name'],
               j = 'Subject',
               sep = '_',
               suffix = '.+')
df_new.reset_index()
ClassNameSubjectGrade
01San ZhangChinese80
11San ZhangMath80
22Si LiChinese90
32Si LiMath75

相關文章