第五章 变形

参考:https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch5.html#id2

一、长宽表的变形(元素和列索引的转换)

长表:把性别Gender存在列名中(long状态)

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

宽表:如果把性别作为列名,列中的元素是某一其他的相关特征数值,那么这个表是关于性别的宽表(wide状态)

pd.DataFrame({'Height: F':[163, 160], 'Height: M':[175, 180]})

Height: F Height: M
0 163 175
1 160 180

变形函数:

分类 函数 例子 备注
长表转宽表 pivot df.pivot(index='Name', columns='Subject', values='Grade') 三个要素,分别是变形后的行索引、需要转到列索引的列,以及这些列和行索引对应的数值,它们分别对应了pivot方法中的index, columns, values参数。
行列组合需要唯一
pivot_multi = df.pivot(index = ['Class', 'Name'],columns = ['Subject','Examination'],values = ['Grade','rank']) 参数可以传入列表,形成多级索引;仍然需要组合的唯一性
pivot_table df.pivot_table(index = 'Name',
columns = 'Subject',
values = 'Grade',
aggfunc = 'mean')
aggfunc 参数可以使用的聚合函数,不需要组合的唯一性,因为如果相同会被聚合起来
df.pivot_table(index = 'Name',
columns = 'Subject',
values = 'Grade',
aggfunc='mean',
margins=True)
pivot_table具有边际汇总的功能
宽表转长表 melt df_melted = df.melt(id_vars = ['Class', 'Name'],
value_vars = ['Chinese', 'Math'],
var_name = 'Subject',
value_name = 'Grade')
和pivot是互逆操作;只能合并一个元素
wide_to_long pd.wide_to_long(df,
stubnames=['Chinese', 'Math'],
i = ['Class', 'Name'],
j='Examination',
sep='_',
suffix='.+')
../_images/ch5_wtl.png

可以合并一部分元素

【练一练】

在上面的边际汇总例子中,行或列的汇总为新表中行元素或者列元素的平均值,而总体的汇总为新表中四个元素的平均值。这种关系一定成立吗?若不成立,请给出一个例子来说明。

下面的代码会报错,size不能作为汇总函数

df.groupby(['Subject','Name']).size()
df.pivot_table(index = 'Name',
               columns = 'Subject',
               values = 'Grade',
               aggfunc='size',
               margins=True)

二、索引的变形(行列索引之间的交换)

函数 例子 备注
unstack df.unstack() 行索引转为列索引
df.unstack([0,2]) 主要参数是移动的层号,默认转化最内层,移动到列索引的最内层,同时支持同时转化多个层;必须保证 被转为列索引的行索引层 和 **被保留的行索引层 **构成的组合是唯一的,否则会报错
stack df.stack([1, 2]) 列索引的层压入行索引

三、其他变形函数

函数 例子 备注
crosstab pd.crosstab(index = df.School, columns = df.Transfer) 不是一个值得推荐使用的函数;在默认状态下,可以统计元素组合出现的频数,即count操作
pd.crosstab(index = df.School, columns = df.Transfer, values = [0]*df.shape[0], aggfunc = 'count') 也可以做其他的聚合操作,values传入要进行agg计算的类似序列的数据
explode df_ex = pd.DataFrame({'A': [[1, 2], 'my_str', {1, 2}, pd.Series([3, 4])],
'B': 1})
df_ex.explode('A')
对某一列的元素进行纵向的展开,被展开的单元格必须存储list, tuple, Series, np.ndarray中的一种类型。
get_dummies pd.get_dummies(df.Grade).head() 把类别特征转为指示变量;传入series或dataframe

练一练

前面提到了 crosstab 的性能劣于 pivot_table ,请选用多个聚合方法进行验证。

1分别对learn_pandas表进行pivot_table和crosstab的转换,计算count和mean:

import datetime
beg_time = datetime.datetime.now()
df.pivot_table(index = 'School',
               columns = 'Transfer',
               values = 'Name',
               aggfunc = 'count')
end_time = datetime.datetime.now()

print ("df.pivot_table:count", end_time - beg_time)
beg_time = datetime.datetime.now()
pd.crosstab(index = df.School, columns = df.Transfer, values = df.Height, aggfunc = 'count')
end_time = datetime.datetime.now()
print ("pd.crosstab:count", end_time - beg_time)

beg_time = datetime.datetime.now()
df.pivot_table(index = 'School',
               columns = 'Transfer',
               values = 'Name',
               aggfunc = 'sum')
end_time = datetime.datetime.now()

print ("df.pivot_table:sum", end_time - beg_time)
beg_time = datetime.datetime.now()
pd.crosstab(index = df.School, columns = df.Transfer, values = df.Height, aggfunc = 'sum')
end_time = datetime.datetime.now()
print ("pd.crosstab:sum", end_time - beg_time)

结果:似乎没有很慢?

'''
df.pivot_table:count 0:00:00.011967
pd.crosstab:count 0:00:00.009974
df.pivot_table:sum 0:00:00.013121
pd.crosstab:sum 0:00:00.010976
'''

2.是不是数据量不够大,换一个大点的表试试:(31569条数据house_info.xls)

import datetime
print("index数量",df2.groupby(['city','rooms']).ngroups)
beg_time = datetime.datetime.now()
df2 = pd.read_excel("../data/house_info.xls")
df2.pivot_table(index = 'city',
               columns = 'rooms',
               values = 'area',
               aggfunc = 'count')
end_time = datetime.datetime.now()

print ("df.pivot_table:count", end_time - beg_time)
beg_time = datetime.datetime.now()
pd.crosstab(index = df2.city, columns = df2.rooms, values = df2.area, aggfunc = 'count')
end_time = datetime.datetime.now()
print ("pd.crosstab:count", end_time - beg_time)

beg_time = datetime.datetime.now()
df2 = pd.read_excel("../data/house_info.xls")
df2['test'] = df2.index.values
df2.pivot_table(index = 'city',
               columns = 'rooms',
               values = 'test',
               aggfunc = 'mean')
end_time = datetime.datetime.now()

print ("df.pivot_table:mean", end_time - beg_time)
beg_time = datetime.datetime.now()
pd.crosstab(index = df2.city, columns = df2.rooms, values = df2.test, aggfunc = 'mean')
end_time = datetime.datetime.now()
print ("pd.crosstab:mean", end_time - beg_time)

结果:crosstab还是更快一点

index数量 4336
df.pivot_table:count 0:00:00.885630
pd.crosstab:count 0:00:00.017953
df.pivot_table:mean 0:00:00.568480
pd.crosstab:mean 0:00:00.013963        

3.换成3万条纯数字的,index数量为669:

import datetime
from string import ascii_uppercase
data = np.random.randint(0, 26, size=(3000, 10))
cols = list(ascii_uppercase[:10])
np.random.seed(2)
df3 = pd.DataFrame(data, columns=cols)
print("index数量", df3.groupby(['A', 'B'])['C'].ngroups)
beg_time = datetime.datetime.now()
df3.groupby(['A', 'B'])['C'].count().unstack(fill_value=0)
end_time = datetime.datetime.now()

print ("groupby:count", end_time - beg_time)
beg_time = datetime.datetime.now()
df3.pivot_table(values='C', index='A', columns='B', aggfunc='count', fill_value=0)
end_time = datetime.datetime.now()
print ("pivot_table:count", end_time - beg_time)


beg_time = datetime.datetime.now()
pd.crosstab(index=df3.A, columns=df3.B)
end_time = datetime.datetime.now()
print ("crosstab:count", end_time - beg_time)

结果:crosstab确实是最慢的

index数量 669
groupby:count 0:00:00.002988
pivot_table:count 0:00:00.011968
crosstab:count 0:00:00.015994

4.换成3万条纯字母的,index数量为676:

import datetime
from string import ascii_uppercase
data = np.random.choice(list(ascii_uppercase[:]), size=(30000, 10))
cols = list(ascii_uppercase[:10])
np.random.seed(2)

df3 = pd.DataFrame(data, columns=cols)
print("index数量",df3.groupby(['A', 'B'])['C'].ngroups)
beg_time = datetime.datetime.now()
df3.groupby(['A', 'B'])['C'].count().unstack(fill_value=0)
end_time = datetime.datetime.now()

print ("groupby:count", end_time - beg_time)
beg_time = datetime.datetime.now()
df3.pivot_table(values='C', index='A', columns='B', aggfunc='count', fill_value=0)
end_time = datetime.datetime.now()
print ("pivot_table:count", end_time - beg_time)


beg_time = datetime.datetime.now()
pd.crosstab(index=df3.A, columns=df3.B)
end_time = datetime.datetime.now()
print ("crosstab:count", end_time - beg_time)

结果:两者差不多

index数量 676
#crosstab慢于pivot_table
groupby:count 0:00:00.006970
pivot_table:count 0:00:00.013941
crosstab:count 0:00:00.016956
#测试多次的其他输出:crosstab快于pivot_table
index数量 676
groupby:count 0:00:00.016970
pivot_table:count 0:00:00.030903
crosstab:count 0:00:00.024946

结论:在index数量类似的情况下,crosstab处理字符串的速度和pivot_table差不多(甚至有的时候要快),而crosstab处理数字的速度始终比pivot_table慢,随着index数量的增长,差距越大

四、练习

Ex1:美国非法药物数据集

现有一份关于美国非法药物的数据集,其中 SubstanceName, DrugReports 分别指药物名称和报告数量:

In [63]: df = pd.read_csv('data/drugs.csv').sort_values([
   ....:      'State','COUNTY','SubstanceName'],ignore_index=True)
   ....: 

In [64]: df.head(3)
Out[64]: 
   YYYY State COUNTY  SubstanceName  DrugReports
0  2011    KY  ADAIR  Buprenorphine            3
1  2012    KY  ADAIR  Buprenorphine            5
2  2013    KY  ADAIR  Buprenorphine            4
  1. 将数据转为如下的形式:
../_images/Ex5_1.png
df2 = df.pivot_table(index=['State','COUNTY','SubstanceName'],columns=['YYYY'], values='DrugReports').reset_index().rename_axis(columns={'YYYY':''})
----------------------------
answer:
res = df.pivot(index=['State','COUNTY','SubstanceName'], columns='YYYY', values='DrugReports').reset_index().rename_axis(columns={'YYYY':''})
  1. 将第1问中的结果恢复为原表。

注意最后要排序

df2.melt(id_vars=['State','COUNTY','SubstanceName'],
         value_vars=[2010,2011,2012,2013,2014,2015,2016,2017],
         var_name='YYYY',
         value_name='DrugReports')
answer:
res_melted = res.melt(id_vars = ['State','COUNTY','SubstanceName'],
                      value_vars = res.columns[-8:],
                      var_name = 'YYYY',
                      value_name = 'DrugReports').dropna(
                      subset=['DrugReports'])
res_melted = res_melted[df.columns].sort_values(['State','COUNTY','SubstanceName'],ignore_index=True).astype({'YYYY':'int64', 'DrugReports':'int64'})
res_melted.equals(df)    
  1. State 分别统计每年的报告数量总和,其中 State, YYYY 分别为列索引和行索引,要求分别使用 pivot_table 函数与 groupby+unstack 两种不同的策略实现,并体会它们之间的联系。
df.pivot_table(values=['DrugReports'],index=['YYYY'],columns=['State'],aggfunc='sum')
df.groupby(['State','YYYY'])['DrugReports'].agg('sum').unstack([0])
answer:
res = df.groupby(['State', 'YYYY'])['DrugReports'].sum().to_frame().unstack(0).droplevel(0,axis=1)

Ex2:特殊的wide_to_long方法

从功能上看, melt 方法应当属于 wide_to_long 的一种特殊情况,即 stubnames 只有一类。请使用 wide_to_long 生成 melt 一节中的 df_melted 。(提示:对列名增加适当的前缀)

df = df.rename(columns={'Chinese':'Grade_Chinese','Math':'Grade_Math'})                   
pd.wide_to_long(df, 
                stubnames=['Grade'],
                i=['Class','Name'],
                j="Subject",
                sep='_',
                suffix='.+').reset_index().sort_values('Subject')

你可能感兴趣的:(第五章 变形)