pandas 05-變形

Gocara發表於2020-12-27

Pandas 05-變形

import numpy as np
import pandas as pd

一、長寬表的變形

什麼是長表?什麼是寬表?這個概念是對於某一個特徵而言的。例如:一個表中把性別儲存在某一個列中,那麼它就是關於性別的長表;如果把性別作為列名,列中的元素是某一其他的相關特徵數值,那麼這個表是關於性別的寬表。下面的兩張表就分別是關於性別的長表和寬表:

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

顯然這兩張表從資訊上是完全等價的,它們包含相同的身高統計數值,只是這些數值的呈現方式不同,而其呈現方式主要又與性別一列選擇的佈局模式有關,即到底是以 l o n g \color{red}{long} long的狀態儲存還是以 w i d e \color{red}{wide} wide的狀態儲存。因此,pandas針對此類長寬表的變形操作設計了一些有關的變形函式。

1. pivot

pivot是一種典型的長表變寬表的函式,首先來看一個例子:下表儲存了張三和李四的語文和數學分數,現在想要把語文和數學分數作為列來展示。

df = pd.DataFrame({'Class':[1,1,2,2],
                   'Name':['San Zhang','San Zhang','Si Li','Si Li'],
                   'Subject':['Chinese','Math','Chinese','Math'],
                   'Grade':[80,75,90,85]})
df
ClassNameSubjectGrade
01San ZhangChinese80
11San ZhangMath75
22Si LiChinese90
32Si LiMath85

對於一個基本的長變寬的操作而言,最重要的有三個要素,分別是變形後的行索引、需要轉到列索引的列,以及這些列和行索引對應的數值,它們分別對應了pivot方法中的index, columns, values引數。新生成表的列索引是columns對應列的unique值,而新表的行索引是index對應列的unique值,而values對應了想要展示的數值列。

df.pivot(index='Name', columns='Subject', values='Grade')
SubjectChineseMath
Name
San Zhang8075
Si Li9085

通過顏色的標記,更容易地能夠理解其變形的過程:
在這裡插入圖片描述

利用pivot進行變形操作需要滿足唯一性的要求,即由於在新表中的行列索引對應了唯一的value,因此原表中的indexcolumns對應兩個列的行組合必須唯一。例如,現在把原表中第二行張三的數學改為語文就會報錯,這是由於NameSubject的組合中兩次出現("San Zhang", "Chinese"),從而最後不能夠確定到底變形後應該是填寫80分還是75分。

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

pandas1.1.0開始,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, 2]})
df
ClassNameExaminationSubjectGraderank
01San ZhangMidChinese8010
11San ZhangFinalChinese7515
22Si LiMidChinese8521
32Si LiFinalChinese6515
41San ZhangMidMath9020
51San ZhangFinalMath857
62Si LiMidMath926
72Si LiFinalMath882

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

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

根據唯一性原則,新表的行索引等價於對index中的多列使用drop_duplicates,而列索引的長度為values中的元素個數乘以columns的唯一組合數量(與index類似) 。從下面的示意圖中能夠比較容易地理解相應的操作:
在這裡插入圖片描述

2. pivot_table

pivot的使用依賴於唯一性條件,那如果不滿足唯一性條件,那麼必須通過聚合操作使得相同行列組合對應的多個值變為一個值。例如,張三和李四都參加了兩次語文考試和數學考試,按照學院規定,最後的成績是兩次考試分數的平均值,此時就無法通過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, 85, 95]})
df
NameSubjectGrade
0San ZhangChinese80
1San ZhangChinese90
2San ZhangMath100
3San ZhangMath90
4Si LiChinese70
5Si LiChinese80
6Si LiMath85
7Si LiMath95

pandas中提供了pivot_table來實現,其中的aggfunc引數就是使用的聚合函式。上述場景可以如下寫出:

df.pivot_table(index = 'Name',
               columns = 'Subject',
               values = 'Grade',
               aggfunc = 'mean')
SubjectChineseMath
Name
San Zhang8595
Si Li7590

這裡傳入aggfunc包含了上一章中介紹的所有合法聚合字串,此外還可以傳入以序列為輸入標量為輸出的聚合函式來實現自定義操作,上述功能可以等價寫出:

df.pivot_table(index = 'Name',
               columns = 'Subject',
               values = 'Grade',
               aggfunc = lambda x:x.mean())
SubjectChineseMath
Name
San Zhang8595
Si Li7590

此外,pivot_table具有邊際彙總的功能,可以通過設定margins=True來實現,其中邊際的聚合方式與aggfunc中給出的聚合方法一致。下面就分別統計了語文均分和數學均分、張三均分和李四均分,以及總體所有分數的均分:

df.pivot_table(index = 'Name',
               columns = 'Subject',
               values = 'Grade',
               aggfunc='mean',
               margins=True)
SubjectChineseMathAll
Name
San Zhang77.5087.5082.5
Si Li75.0090.0082.5
All76.2588.7582.5

【練一練 - 01】

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

My solution :

  • 不一定成立
  • 依題意我們想到可能有三種方式得到最終的彙總結果
  • 方式1 : 用原表中的資料彙總
df['Grade'].mean()
82.5
  • 方式2 : 設定marginsFalse , 手動將pivot後的結果彙總
df.pivot_table(index = 'Name',
              columns = 'Subject',
              values = 'Grade',
              aggfunc = 'mean',
              margins = False).mean().mean()
82.5
  • 方式3 : 設定marginsTrue , 此時會多一行一列都為All的彙總結果 , 取出['All','All']對應的彙總結果
df.pivot_table(index = 'Name',
               columns = 'Subject',
               values = 'Grade',
               aggfunc = 'mean',
               margins = True).loc['All','All']
82.5
  • 我們發現這三種方式的彙總結果一致 , 再來嘗試其他聚合函式
  • 將上一章提到的很多種聚合函式copy過來用split分割後遍歷
  • 把上述三種方式中的聚合函式分別對應位置格式化為aggfuncs裡遍歷到的聚合函式
  • 列印遍歷到的聚合函式名
  • 嘗試對比方式2方式3得出的結果是否一致
  • 嘗試對比方式1方式3得出的結果是否一致
aggfuncs = 'max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod'.split('/')

for func in aggfuncs:
    method_1 = f'''df['Grade'].{func}()'''
    method_2 = f'''df.pivot_table(index = 'Name',
                              columns = 'Subject',
                              values = 'Grade',
                              aggfunc = '{func}',
                              margins = False).{func}().{func}()'''
    method_3 = f'''df.pivot_table(index = 'Name',
                              columns = 'Subject',
                              values = 'Grade',
                              aggfunc = '{func}',
                              margins = True).loc['All','All']'''
    
    print(f'{func} :', end='\t')
    try:
        print(eval(method_2) == eval(method_3), end='\t')
    except Exception as e:
        print(e, end='\t')
        
    try:
        print(eval(method_1) == eval(method_3))
    except Exception as e:
        print(e)
max :	True	True
min :	True	True
mean :	True	True
median :	False	True
count :	False	True
all :	True	True
any :	True	True
idxmax :	reduction operation 'argmax' not allowed for this dtype	True
idxmin :	reduction operation 'argmin' not allowed for this dtype	True
mad :	False	True
nunique :	False	True
skew :	No objects to concatenate	No objects to concatenate
quantile :	False	True
sum :	True	True
std :	False	True
var :	False	True
sem :	False	True
size :	'numpy.int32' object is not callable	'int' object is not callable
prod :	True	True
  • 可以看到第一列是使用的聚合函式名
  • 中間一列是方式2方式3的對比 , 其中所有的False值對應的聚合函式都可以作為不成立的例子 , 但結果為True的卻不能說明一定成立 , 是因為資料可能具有巧合性得出了True的結果 , 因此結果為True對應的聚合函式要單獨用數學或邏輯推匯出是否真的為True
  • 第三列是方式1方式3的對比 , 其中除了異常值全為True , 這說明margins引數的彙總是按原表資料彙總的 , 而非基於pivot後的表資料再彙總

【END】

3. melt

長寬表只是資料呈現方式的差異,但其包含的資訊量是等價的,前面提到了利用pivot把長錶轉為寬表,那麼就可以通過相應的逆操作把寬錶轉為長表,melt函式就起到了這樣的作用。在下面的例子中,Subject以列索引的形式儲存,現在想要將其壓縮到一個列中。

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

melt的主要引數和壓縮的過程如下圖所示:

在這裡插入圖片描述

前面提到了meltpivot是一組互逆過程,那麼就一定可以通過pivot操作把df_melted轉回df的形式:

df_unmelted = df_melted.pivot(index = ['Class', 'Name'],
                              columns='Subject',
                              values='Grade')
df_unmelted # 下面需要恢復索引,並且重新命名列索引名稱
SubjectChineseMath
ClassName
1San Zhang8080
2Si Li9075
df_unmelted = df_unmelted.reset_index().rename_axis(columns={'Subject':''})
df_unmelted.equals(df)
True

4. wide_to_long

melt方法中,在列索引中被壓縮的一組值對應的列元素只能代表同一層次的含義,即values_name。現在如果列中包含了交叉類別,比如期中期末的類別和語文數學的類別,那麼想要把values_name對應的Grade擴充為兩列分別對應語文分數和數學分數,只把期中期末的資訊壓縮,這種需求下就要使用wide_to_long函式來完成。

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
df = pd.DataFrame({'Class':[1,2],'Name':['San Zhang', 'Si Li'],
                   'Chinese_Mid':[80, 75],
                   'Chinese_Final':[80, 75]})
df
ClassNameChinese_MidChinese_Final
01San Zhang8080
12Si Li7575
pd.wide_to_long(df,
                stubnames=['Chinese'],
                i = ['Class', 'Name'],
                j = 'Examination',
                sep = '_',
                suffix = '.+')
Chinese
ClassNameExamination
1San ZhangMid80
Final80
2Si LiMid75
Final75
pd.wide_to_long(df,
                stubnames=['Chinese', 'Math'],
                i = ['Class', 'Name'],
                j = 'Examination',
                sep = '_',
                suffix = '.+')
ChineseMath
ClassNameExamination
1San ZhangMid80NaN
Final80NaN
2Si LiMid75NaN
Final75NaN

具體的變換過程由下圖進行展示,屬相同概念的元素使用了一致的顏色標出:

在這裡插入圖片描述

下面給出一個比較複雜的案例,把之前在pivot一節中多列操作的結果(產生了多級索引),利用wide_to_long函式,將其轉為原來的形態。其中,使用了第八章的str.split函式,目前暫時只需將其理解為對序列按照某個分隔符進行拆分即可。

res = pivot_multi.copy()
res.columns = res.columns.map(lambda x:'_'.join(x))
res = res.reset_index()
res = pd.wide_to_long(res, stubnames=['Grade', 'rank'],
                           i = ['Class', 'Name'],
                           j = 'Examination_Subject',
                           sep = '_',
                           suffix = '.+')
res = res.reset_index()
res[['Examination', 'Subject']] = res['Examination_Subject'].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 LiFinalMath882

二、索引的變形

1. stack與unstack

在第二章中提到了利用swaplevel或者reorder_levels進行索引內部的層交換,下面就要討論 行 列 索 引 之 間 \color{red}{行列索引之間} 的交換,由於這種交換帶來了DataFrame維度上的變化,因此屬於變形操作。在第一節中提到的4種變形函式與其不同之處在於,它們都屬於某一列或幾列 元 素 \color{red}{元素} 列 索 引 \color{red}{列索引} 之間的轉換,而不是索引之間的轉換。

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

類似於pivot中的唯一性要求,在unstack中必須保證 被 轉 為 列 索 引 的 行 索 引 層 \color{red}{被轉為列索引的行索引層} 被 保 留 的 行 索 引 層 \color{red}{被保留的行索引層} 構成的組合是唯一的,例如把前兩個列索引改成相同的破壞唯一性,那麼就會報錯:

my_index = df.index.to_list()
my_index[1] = my_index[0]
df.index = pd.Index(my_index)
df
col_1col_2
Acatbig1.01.0
big1.01.0
Bcatbig1.01.0
dogsmall1.01.0
try:
    df.unstack()
except Exception as e:
    Err_Msg = e
Err_Msg
ValueError('Index contains duplicate entries, cannot reshape')

unstack相反,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

2. 聚合與變形的關係

在上面介紹的所有函式中,除了帶有聚合效果的pivot_table以外,所有的函式在變形前後並不會帶來values個數的改變,只是這些值在呈現的形式上發生了變化。在上一章討論的分組聚合操作,由於生成了新的行列索引,因此必然也屬於某種特殊的變形操作,但由於聚合之後把原來的多個值變為了一個值,因此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

【練一練 - 02】

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

My solution :

  • 同上一題一樣 , 遍歷aggfuncs , 將func傳入對應位置 , 用timeit魔法指令測試200次均值 , 可以發現pivot_table的效能更優
aggfuncs = 'max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod'.split('/')

for func in aggfuncs:
    pivot_table = f'''df.pivot_table(index = 'School',
                                     columns = 'Transfer',
                                     values = 'Height',
                                     aggfunc = '{func}')'''
    crosstab = f'''pd.crosstab(index = df.School, 
                               columns = df.Transfer, 
                               values = df.Height, 
                               aggfunc = '{func}')'''
    print(f'{func}\t\tpivot_table:')
    try:
        %timeit -n 200 eval(pivot_table)
    except Exception as e:
        print(e)
    print(f'{func}\t\tcrosstab:')
    try:
        %timeit -n 200 eval(crosstab)
    except Exception as e:
        print(e)
    print('-------------------------------------------------------------------------')
max		pivot_table:
7.58 ms ± 611 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
max		crosstab:
7.89 ms ± 468 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
min		pivot_table:
7.67 ms ± 473 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
min		crosstab:
7.83 ms ± 230 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
mean		pivot_table:
7.3 ms ± 507 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
mean		crosstab:
7.82 ms ± 716 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
median		pivot_table:
7.3 ms ± 416 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
median		crosstab:
7.86 ms ± 450 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
count		pivot_table:
7.2 ms ± 373 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
count		crosstab:
7.72 ms ± 602 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
all		pivot_table:
8.56 ms ± 613 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
all		crosstab:
8.93 ms ± 832 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
any		pivot_table:
7.96 ms ± 201 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
any		crosstab:
8.78 ms ± 280 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
idxmax		pivot_table:
12.2 ms ± 583 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
idxmax		crosstab:
12.6 ms ± 291 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
idxmin		pivot_table:
12.3 ms ± 336 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
idxmin		crosstab:
14.3 ms ± 1.76 ms per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
mad		pivot_table:
19 ms ± 1.75 ms per loop (mean ± std. dev. of 7 runs, 200 loops each)
mad		crosstab:
18.2 ms ± 908 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
nunique		pivot_table:
9.07 ms ± 991 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
nunique		crosstab:
9.32 ms ± 717 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
skew		pivot_table:
12.2 ms ± 722 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
skew		crosstab:
12.6 ms ± 485 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
quantile		pivot_table:
7.74 ms ± 572 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
quantile		crosstab:
8.42 ms ± 511 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
sum		pivot_table:
8.2 ms ± 524 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
sum		crosstab:
8.67 ms ± 870 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
std		pivot_table:
8.22 ms ± 775 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
std		crosstab:
8.57 ms ± 430 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
var		pivot_table:
7.48 ms ± 454 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
var		crosstab:
8.24 ms ± 1.16 ms per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
sem		pivot_table:
10 ms ± 1.03 ms per loop (mean ± std. dev. of 7 runs, 200 loops each)
sem		crosstab:
9.65 ms ± 625 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------
size		pivot_table:
4.01 ms ± 390 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
size		crosstab:
'__dummy__'
-------------------------------------------------------------------------
prod		pivot_table:
7.73 ms ± 346 µs per loop (mean ± std. dev. of 7 runs, 200 loops each)
prod		crosstab:
8.88 ms ± 1.24 ms per loop (mean ± std. dev. of 7 runs, 200 loops each)
-------------------------------------------------------------------------

【END】

2. 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

3. get_dummies

get_dummies是用於特徵構建的重要函式之一,其作用是把類別特徵轉為指示變數。例如,對年級一列轉為指示變數,屬於某一個年級的對應列標記為1,否則為0:

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

四、練習

Ex1:美國非法藥物資料集

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

df = pd.read_csv('../data/drugs.csv').sort_values(['State','COUNTY','SubstanceName'],ignore_index=True)
df.head(3)
YYYYStateCOUNTYSubstanceNameDrugReports
02011KYADAIRBuprenorphine3
12012KYADAIRBuprenorphine5
22013KYADAIRBuprenorphine4
  1. 將資料轉為如下的形式:

在這裡插入圖片描述

  1. 將第1問中的結果恢復為原表。
  2. State分別統計每年的報告數量總和,其中State, YYYY分別為列索引和行索引,要求分別使用pivot_table函式與groupby+unstack兩種不同的策略實現,並體會它們之間的聯絡。

My solution :

  1. 觀察表中State,COUNTY,SubstanceName這三列被去重了 , YYYY列的值被移到了column上 , DrugReports單列填充一個值 , 應該為pivot變形 , 按引數填好值變形後重置索引 , 再將多餘的YYYY名字去掉
df1 = df.pivot(index = ['State','COUNTY','SubstanceName'],
               columns = 'YYYY',
               values = 'DrugReports').reset_index().rename_axis(columns={'YYYY':''})
df1.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
  1. meltpivot後的表逆變換回去 , 並設定好對應的引數 , 但由於pivot後的值不是一一對應的 , 變換後會多出很多NaN , 需追加去除NaN的行
df2 = df1.melt(id_vars = ['State','COUNTY','SubstanceName'],
               value_vars = range(2010,2018),
               var_name = 'YYYY',
               value_name = 'DrugReports').dropna(subset = ['DrugReports'])
df2.head(3)
StateCOUNTYSubstanceNameYYYYDrugReports
4KYADAIRHydrocodone20106.0
6KYADAIRMethadone20101.0
13KYALLENHydrocodone201010.0
  • 將變換後的列名順序與原df變一致後按題幹中的變換操作一次
  • 此時還沒有與原df完全相等 , 檢視二者info資訊
df2 = df2[df.columns].sort_values(['State','COUNTY','SubstanceName'],ignore_index=True)
df2.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 24062 entries, 0 to 24061
Data columns (total 5 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   YYYY           24062 non-null  object 
 1   State          24062 non-null  object 
 2   COUNTY         24062 non-null  object 
 3   SubstanceName  24062 non-null  object 
 4   DrugReports    24062 non-null  float64
dtypes: float64(1), object(4)
memory usage: 940.0+ KB
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 24062 entries, 0 to 24061
Data columns (total 5 columns):
 #   Column         Non-Null Count  Dtype 
---  ------         --------------  ----- 
 0   YYYY           24062 non-null  int64 
 1   State          24062 non-null  object
 2   COUNTY         24062 non-null  object
 3   SubstanceName  24062 non-null  object
 4   DrugReports    24062 non-null  int64 
dtypes: int64(2), object(3)
memory usage: 940.0+ KB
  • 除了兩個Dtype不一樣 , 其他都一樣 , 把兩個不一樣的Dtype重新設定後與原df相等
df2.astype({'YYYY':'int64','DrugReports':'int64'}).equals(df)
True
  1. 按要求先用pivot_table填入對應引數 , 用sum對每年的DrugReports求和
df.pivot_table(index = 'YYYY', columns = 'State', values = 'DrugReports', aggfunc = 'sum').head()
StateKYOHPAVAWV
YYYY
201010453197071981486852890
201110289203301998767493271
201210722231451995978313376
2013111482684620409116754046
201411081308602490490373280
  • groupby+unstack實現
df.groupby(['State', 'YYYY'])['DrugReports'].sum().to_frame().unstack(0).droplevel(0,axis=1).head()
StateKYOHPAVAWV
YYYY
201010453197071981486852890
201110289203301998767493271
201210722231451995978313376
2013111482684620409116754046
201411081308602490490373280

Ex2:特殊的wide_to_long方法

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

df = pd.DataFrame({'Class':[1,2],
                   'Name':['San Zhang', 'Si Li'],
                   'Chinese':[80, 90],
                   'Math':[80, 75]})
df
ClassNameChineseMath
01San Zhang8080
12Si Li9075

My solution :

  • 經嘗試wide_to_long引數stubnames不可與原columns名完全一樣 , 而是原columns從前向後的正則匹配一部分 , 並且這個引數的值會當做變形後的列名 , 因此先起個字首Score供其正則匹配
df.rename(columns={'Chinese':'ScoreChinese','Math':'ScoreMath'}, inplace=True)
df
ClassNameScoreChineseScoreMath
01San Zhang8080
12Si Li9075
  • wide_to_long傳入df , 設定index列為ClassName , 正則匹配字首為自定義的Score , 匹配剩下的部分用j引數設定為Subject , 再重置索引即可
pd.wide_to_long(df,
                stubnames=['Score'],
                i=['Class', 'Name'],
                j='Subject',
                suffix='.+').reset_index()
ClassNameSubjectScore
01San ZhangChinese80
11San ZhangMath80
22Si LiChinese90
32Si LiMath75

相關文章