Pandas 05-變形
import numpy as np
import pandas as pd
一、長寬表的變形
什麼是長表?什麼是寬表?這個概念是對於某一個特徵而言的。例如:一個表中把性別儲存在某一個列中,那麼它就是關於性別的長表;如果把性別作為列名,列中的元素是某一其他的相關特徵數值,那麼這個表是關於性別的寬表。下面的兩張表就分別是關於性別的長表和寬表:
pd. DataFrame( { 'Gender' : [ 'F' , 'F' , 'M' , 'M' ] , 'Height' : [ 163 , 160 , 175 , 180 ] } )
Gender Height 0 F 163 1 F 160 2 M 175 3 M 180
pd. DataFrame( { 'Height: F' : [ 163 , 160 ] , 'Height: M' : [ 175 , 180 ] } )
Height: F Height: M 0 163 175 1 160 180
顯然這兩張表從資訊上是完全等價的,它們包含相同的身高統計數值,只是這些數值的呈現方式不同,而其呈現方式主要又與性別一列選擇的佈局模式有關,即到底是以
l
o
n
g
\color{red}{long}
l o n g 的狀態儲存還是以
w
i
d
e
\color{red}{wide}
w i d e 的狀態儲存。因此,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
Class Name Subject Grade 0 1 San Zhang Chinese 80 1 1 San Zhang Math 75 2 2 Si Li Chinese 90 3 2 Si Li Math 85
對於一個基本的長變寬的操作而言,最重要的有三個要素,分別是變形後的行索引、需要轉到列索引的列,以及這些列和行索引對應的數值,它們分別對應了pivot
方法中的index, columns, values
引數。新生成表的列索引是columns
對應列的unique
值,而新表的行索引是index
對應列的unique
值,而values
對應了想要展示的數值列。
df. pivot( index= 'Name' , columns= 'Subject' , values= 'Grade' )
Subject Chinese Math Name San Zhang 80 75 Si Li 90 85
通過顏色的標記,更容易地能夠理解其變形的過程:
利用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:
print ( e)
Index contains duplicate entries, cannot reshape
pandas
從1.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
Class Name Examination Subject Grade rank 0 1 San Zhang Mid Chinese 80 10 1 1 San Zhang Final Chinese 75 15 2 2 Si Li Mid Chinese 85 21 3 2 Si Li Final Chinese 65 15 4 1 San Zhang Mid Math 90 20 5 1 San Zhang Final Math 85 7 6 2 Si Li Mid Math 92 6 7 2 Si Li Final Math 88 2
現在想要把測試型別和科目聯合組成的四個類別(期中語文、期末語文、期中數學、期末數學)轉到列索引,並且同時統計成績和排名:
pivot_multi = df. pivot( index = [ 'Class' , 'Name' ] ,
columns = [ 'Subject' , 'Examination' ] ,
values = [ 'Grade' , 'rank' ] )
pivot_multi
Grade rank Subject Chinese Math Chinese Math Examination Mid Final Mid Final Mid Final Mid Final Class Name 1 San Zhang 80 75 90 85 10 15 20 7 2 Si Li 85 65 92 88 21 15 6 2
根據唯一性原則,新表的行索引等價於對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
Name Subject Grade 0 San Zhang Chinese 80 1 San Zhang Chinese 90 2 San Zhang Math 100 3 San Zhang Math 90 4 Si Li Chinese 70 5 Si Li Chinese 80 6 Si Li Math 85 7 Si Li Math 95
pandas
中提供了pivot_table
來實現,其中的aggfunc
引數就是使用的聚合函式。上述場景可以如下寫出:
df. pivot_table( index = 'Name' ,
columns = 'Subject' ,
values = 'Grade' ,
aggfunc = 'mean' )
Subject Chinese Math Name San Zhang 85 95 Si Li 75 90
這裡傳入aggfunc
包含了上一章中介紹的所有合法聚合字串,此外還可以傳入以序列為輸入標量為輸出的聚合函式來實現自定義操作,上述功能可以等價寫出:
df. pivot_table( index = 'Name' ,
columns = 'Subject' ,
values = 'Grade' ,
aggfunc = lambda x: x. mean( ) )
Subject Chinese Math Name San Zhang 85 95 Si Li 75 90
此外,pivot_table
具有邊際彙總的功能,可以通過設定margins=True
來實現,其中邊際的聚合方式與aggfunc
中給出的聚合方法一致。下面就分別統計了語文均分和數學均分、張三均分和李四均分,以及總體所有分數的均分:
df. pivot_table( index = 'Name' ,
columns = 'Subject' ,
values = 'Grade' ,
aggfunc= 'mean' ,
margins= True )
Subject Chinese Math All Name San Zhang 77.50 87.50 82.5 Si Li 75.00 90.00 82.5 All 76.25 88.75 82.5
【練一練 - 01】
在上面的邊際彙總例子中,行或列的彙總為新表中行元素或者列元素的平均值,而總體的彙總為新表中四個元素的平均值。這種關係一定成立嗎?若不成立,請給出一個例子來說明。
My solution :
不一定成立 依題意我們想到可能有三種方式得到最終的彙總結果 方式1
: 用原表中的資料彙總
df[ 'Grade' ] . mean( )
82.5
方式2
: 設定margins
為False
, 手動將pivot
後的結果彙總
df. pivot_table( index = 'Name' ,
columns = 'Subject' ,
values = 'Grade' ,
aggfunc = 'mean' ,
margins = False ) . mean( ) . mean( )
82.5
方式3
: 設定margins
為True
, 此時會多一行一列都為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
Class Name Chinese Math 0 1 San Zhang 80 80 1 2 Si Li 90 75
df_melted = df. melt( id_vars = [ 'Class' , 'Name' ] ,
value_vars = [ 'Chinese' , 'Math' ] ,
var_name = 'Subject' ,
value_name = 'Grade' )
df_melted
Class Name Subject Grade 0 1 San Zhang Chinese 80 1 2 Si Li Chinese 90 2 1 San Zhang Math 80 3 2 Si Li Math 75
melt
的主要引數和壓縮的過程如下圖所示:
前面提到了melt
和pivot
是一組互逆過程,那麼就一定可以通過pivot
操作把df_melted
轉回df
的形式:
df_unmelted = df_melted. pivot( index = [ 'Class' , 'Name' ] ,
columns= 'Subject' ,
values= 'Grade' )
df_unmelted
Subject Chinese Math Class Name 1 San Zhang 80 80 2 Si Li 90 75
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
Class Name Chinese_Mid Math_Mid Chinese_Final Math_Final 0 1 San Zhang 80 90 80 90 1 2 Si Li 75 85 75 85
df = pd. DataFrame( { 'Class' : [ 1 , 2 ] , 'Name' : [ 'San Zhang' , 'Si Li' ] ,
'Chinese_Mid' : [ 80 , 75 ] ,
'Chinese_Final' : [ 80 , 75 ] } )
df
Class Name Chinese_Mid Chinese_Final 0 1 San Zhang 80 80 1 2 Si Li 75 75
pd. wide_to_long( df,
stubnames= [ 'Chinese' ] ,
i = [ 'Class' , 'Name' ] ,
j = 'Examination' ,
sep = '_' ,
suffix = '.+' )
Chinese Class Name Examination 1 San Zhang Mid 80 Final 80 2 Si Li Mid 75 Final 75
pd. wide_to_long( df,
stubnames= [ 'Chinese' , 'Math' ] ,
i = [ 'Class' , 'Name' ] ,
j = 'Examination' ,
sep = '_' ,
suffix = '.+' )
Chinese Math Class Name Examination 1 San Zhang Mid 80 NaN Final 80 NaN 2 Si Li Mid 75 NaN Final 75 NaN
具體的變換過程由下圖進行展示,屬相同概念的元素使用了一致的顏色標出:
下面給出一個比較複雜的案例,把之前在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
Class Name Examination Subject Grade rank 0 1 San Zhang Mid Chinese 80 10 1 1 San Zhang Final Chinese 75 15 2 2 Si Li Mid Chinese 85 21 3 2 Si Li Final Chinese 65 15 4 1 San Zhang Mid Math 90 20 5 1 San Zhang Final Math 85 7 6 2 Si Li Mid Math 92 6 7 2 Si Li Final Math 88 2
二、索引的變形
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_1 col_2 A cat big 1.0 1.0 dog small 1.0 1.0 B cat big 1.0 1.0 dog small 1.0 1.0
df. unstack( )
col_1 col_2 big small big small A cat 1.0 NaN 1.0 NaN dog NaN 1.0 NaN 1.0 B cat 1.0 NaN 1.0 NaN dog NaN 1.0 NaN 1.0
unstack
的主要引數是移動的層號,預設轉化最內層,移動到列索引的最內層,同時支援同時轉化多個層:
df. unstack( 2 )
col_1 col_2 big small big small A cat 1.0 NaN 1.0 NaN dog NaN 1.0 NaN 1.0 B cat 1.0 NaN 1.0 NaN dog NaN 1.0 NaN 1.0
df. unstack( [ 0 , 2 ] )
col_1 col_2 A B A B big small big small big small big small cat 1.0 NaN 1.0 NaN 1.0 NaN 1.0 NaN dog NaN 1.0 NaN 1.0 NaN 1.0 NaN 1.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_1 col_2 A cat big 1.0 1.0 big 1.0 1.0 B cat big 1.0 1.0 dog small 1.0 1.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
A B cat dog cat dog big small big small index_1 1.0 1.0 1.0 1.0 index_2 1.0 1.0 1.0 1.0
df. stack( )
A B cat dog cat dog index_1 big 1.0 NaN 1.0 NaN small NaN 1.0 NaN 1.0 index_2 big 1.0 NaN 1.0 NaN small NaN 1.0 NaN 1.0
df. stack( [ 1 , 2 ] )
A B index_1 cat big 1.0 1.0 dog small 1.0 1.0 index_2 cat big 1.0 1.0 dog small 1.0 1.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)
Transfer N Y School Fudan University 38 1 Peking University 28 2 Shanghai Jiao Tong University 53 0 Tsinghua University 62 4
這等價於如下crosstab
的如下寫法,這裡的aggfunc
即聚合引數:
pd. crosstab( index = df. School, columns = df. Transfer, values = [ 0 ] * df. shape[ 0 ] , aggfunc = 'count' )
Transfer N Y School Fudan University 38.0 1.0 Peking University 28.0 2.0 Shanghai Jiao Tong University 53.0 NaN Tsinghua University 62.0 4.0
同樣,可以利用pivot_table
進行等價操作,由於這裡統計的是組合的頻數,因此values
引數無論傳入哪一個列都不會影響最後的結果:
df. pivot_table( index = 'School' ,
columns = 'Transfer' ,
values = 'Name' ,
aggfunc = 'count' )
Transfer N Y School Fudan University 38.0 1.0 Peking University 28.0 2.0 Shanghai Jiao Tong University 53.0 NaN Tsinghua University 62.0 4.0
從上面可以看出這兩個函式的區別在於,crosstab
的對應位置傳入的是具體的序列,而pivot_table
傳入的是被呼叫表對應的名字,若傳入序列對應的值則會報錯。
除了預設狀態下的count
統計,所有的聚合字串和返回標量的自定義函式都是可用的,例如統計對應組合的身高均值:
pd. crosstab( index = df. School, columns = df. Transfer, values = df. Height, aggfunc = 'mean' )
Transfer N Y School Fudan University 162.043750 177.20 Peking University 163.429630 162.40 Shanghai Jiao Tong University 163.953846 NaN Tsinghua University 163.253571 164.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
A B 0 [1, 2] 1 1 my_str 1 2 {1, 2} 1 3 0 3 1 4 dtype: int64 1
df_ex. explode( 'A' )
A B 0 1 1 0 2 1 1 my_str 1 2 {1, 2} 1 3 3 1 3 4 1
3. get_dummies
get_dummies
是用於特徵構建的重要函式之一,其作用是把類別特徵轉為指示變數。例如,對年級一列轉為指示變數,屬於某一個年級的對應列標記為1,否則為0:
pd. get_dummies( df. Grade) . head( )
Freshman Junior Senior Sophomore 0 1 0 0 0 1 1 0 0 0 2 0 0 1 0 3 0 0 0 1 4 0 0 0 1
四、練習
Ex1:美國非法藥物資料集
現有一份關於美國非法藥物的資料集,其中SubstanceName, DrugReports
分別指藥物名稱和報告數量:
df = pd. read_csv( '../data/drugs.csv' ) . sort_values( [ 'State' , 'COUNTY' , 'SubstanceName' ] , ignore_index= True )
df. head( 3 )
YYYY State COUNTY SubstanceName DrugReports 0 2011 KY ADAIR Buprenorphine 3 1 2012 KY ADAIR Buprenorphine 5 2 2013 KY ADAIR Buprenorphine 4
將資料轉為如下的形式:
將第1
問中的結果恢復為原表。 按State
分別統計每年的報告數量總和,其中State, YYYY
分別為列索引和行索引,要求分別使用pivot_table
函式與groupby+unstack
兩種不同的策略實現,並體會它們之間的聯絡。
My solution :
觀察表中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( )
State COUNTY SubstanceName 2010 2011 2012 2013 2014 2015 2016 2017 0 KY ADAIR Buprenorphine NaN 3.0 5.0 4.0 27.0 5.0 7.0 10.0 1 KY ADAIR Codeine NaN NaN 1.0 NaN NaN NaN NaN 1.0 2 KY ADAIR Fentanyl NaN NaN 1.0 NaN NaN NaN NaN NaN 3 KY ADAIR Heroin NaN NaN 1.0 2.0 NaN 1.0 NaN 2.0 4 KY ADAIR Hydrocodone 6.0 9.0 10.0 10.0 9.0 7.0 11.0 3.0
用melt
將pivot
後的表逆變換回去 , 並設定好對應的引數 , 但由於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 )
State COUNTY SubstanceName YYYY DrugReports 4 KY ADAIR Hydrocodone 2010 6.0 6 KY ADAIR Methadone 2010 1.0 13 KY ALLEN Hydrocodone 2010 10.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
按要求先用pivot_table
填入對應引數 , 用sum
對每年的DrugReports
求和
df. pivot_table( index = 'YYYY' , columns = 'State' , values = 'DrugReports' , aggfunc = 'sum' ) . head( )
State KY OH PA VA WV YYYY 2010 10453 19707 19814 8685 2890 2011 10289 20330 19987 6749 3271 2012 10722 23145 19959 7831 3376 2013 11148 26846 20409 11675 4046 2014 11081 30860 24904 9037 3280
df. groupby( [ 'State' , 'YYYY' ] ) [ 'DrugReports' ] . sum ( ) . to_frame( ) . unstack( 0 ) . droplevel( 0 , axis= 1 ) . head( )
State KY OH PA VA WV YYYY 2010 10453 19707 19814 8685 2890 2011 10289 20330 19987 6749 3271 2012 10722 23145 19959 7831 3376 2013 11148 26846 20409 11675 4046 2014 11081 30860 24904 9037 3280
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
Class Name Chinese Math 0 1 San Zhang 80 80 1 2 Si Li 90 75
My solution :
經嘗試wide_to_long
引數stubnames
不可與原columns
名完全一樣 , 而是原columns
從前向後的正則匹配一部分 , 並且這個引數的值會當做變形後的列名 , 因此先起個字首Score
供其正則匹配
df. rename( columns= { 'Chinese' : 'ScoreChinese' , 'Math' : 'ScoreMath' } , inplace= True )
df
Class Name ScoreChinese ScoreMath 0 1 San Zhang 80 80 1 2 Si Li 90 75
用wide_to_long
傳入df
, 設定index
列為Class
和Name
, 正則匹配字首為自定義的Score
, 匹配剩下的部分用j
引數設定為Subject
, 再重置索引即可
pd. wide_to_long( df,
stubnames= [ 'Score' ] ,
i= [ 'Class' , 'Name' ] ,
j= 'Subject' ,
suffix= '.+' ) . reset_index( )
Class Name Subject Score 0 1 San Zhang Chinese 80 1 1 San Zhang Math 80 2 2 Si Li Chinese 90 3 2 Si Li Math 75