第五章 变形
Datawhale开源内容链接: https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch5.html
1、长宽表介绍。
这里的长与宽都是针对于某一个特征
来说的。比如,将性别
作为列名且该列的元素都是性别的值时,这就称为关于性别的长表。但将性别的unique值
作为列名且该列元素是其他索引的值时,则称为关于性别的宽表。
以上的长表与宽表,从信息角度上看,是等价的。但在呈现的方式上有所不同。所以本章节主要学习这两种表之间的变换–变形函数。
2、长表变宽表—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
df.pivot(index='Name', columns='Subject', values='Grade')
变形过程图解:
⚠️注意:利用pivot进行变形操作需要满足唯一性
的要求,即由于在新表中的行列索引对应了唯一的value,因此index与columns对应的两个列的组合必须唯一。
将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
【题目】:
将测试类型和科目联合组成的四个类别(期中语文、期末语文、期中数学、期末数学)转到列索引,并且同时统计成绩和排名。
【思考】
从上图可以看出,我最初是将Examination
作为了外层行索引,返回的值中关于Mid的两个值并没有合并,这是因为原数据中Examination就是交叉着排列的,但Subject
是顺序排列的。所以将Subject设置为外层索引的时候,就可以自动折叠合并啦。
pivot_table可以通过聚合操作将相同行列组合对应的多个值变为一个值。
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
## 张三和李四都参加了两次语文考试和数学考试,最后的成绩是两次考试分数的平均值。
df.pivot_table(index = 'Name',
columns = 'Subject',
values = 'Grade',
aggfunc = 'mean')
3、宽表变长表。
df = pd.DataFrame({'Class':[1,2],
'Name':['San Zhang', 'Si Li'],
'Chinese':[80, 90],
'Math':[80,75]})
df
## 将subject以列索引的形式存储,将其压缩至一个列中
df_melted = df.melt(id_vars = ['Class','Name'],
value_vars = ['Chinese', 'Math'],
var_name = 'Subject',
value_name = 'Grade')
df_melted
变形过程图解:
wide_to_long
与melt只能压缩同一层含义的列元素不同的是,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
pd.wide_to_long(df,
stubnames=['Chinese','Math'],
i = ['Class', 'Name'],
j = 'Examination',
sep = '_',
suffix = '.+')
【题目】:
将Grade扩充为语文和数学两列,只把期中期末压缩:
变形过程图解:
【补充】:
修改列名的两种方法:
1、直接替换之前的全部列名:
df.columns = ['A','B']
2、只替换对应列的列名:
res.rename(columns={'Subjec':'Subject'},inplace=True)
注意:需要添加inplace=True
,否则只是暂时修改,并没有改DataFrame里面的值。
将pivot多列操作的结果,利用wide_to_long函数转换为原来的形态。
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
pivot_multi = df.pivot(index=['Class','Name'], columns=['Subject', 'Examination'], values=['Grade','rank'])
pivot_multi
res = pivot_multi.copy()
res
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 = '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
4、索引的变形
unstack函数的作用是:把行索引转为列索引。
stack函数的作用是:把列索引的层压入行索引。
【总结】:
上面介绍的所有函数中,除了带有聚合效果的pivot_table
以外,其他函数在变性前后并不会带来values
个数的变化,只是这些值在呈现的形式上发生了变化。
values的个数是否产生了变化,这是分组聚合与变形函数的最大区别。
5、其他变形函数
crosstab
可以统计元素组合出现的频数,即count操作。
explode
能够将某一列的元素进行纵向的展开,被展开的单元格必须存储list, turple, Series, np.ndarray
中的一种类型。
get_dummies
的作用是把类别特征转为指示变量。例如,对年级一列转为指示变量,属于某一个年级的对应位置标记为1,否则为0。这个函数在特征构建时经常使用。
练习题1: 美国非法药物数据集
1、将数据转化为下面图的格式。
【我的解答】:
这个没什么难度,其实就是把YYYY这一列的值转换为列索引。直接使用pivot即可,不过我最初是把重置索引和重命名分开来做的,但是后面的操作还是会基于原始的pivot,所以就把它们写为一句了。
df = pd.read_csv('joyful-pandas-master/data/drugs.csv')
pivot1 = df.pivot(index=['State','COUNTY','SubstanceName'], columns='YYYY', values='DrugReports').reset_index().rename_axis(columns={'YYYY':''})
pivot1
2、将第1问的结果恢复为原表。
【我的解答】:
df_melted = pivot1.melt(id_vars = ['State','COUNTY','SubstanceName'],
value_vars = pivot1.columns[-8:],
var_name = 'YYYY',
value_name = 'DrugReports')
df_melted.dropna(subset=['DrugReports'])
我最初没有使用dropna去掉空值,导致保留了很多不必要的行。
3、按 State 分别统计每年的报告数量总和,其中 State, YYYY 分别为列索引和行索引,要求分别使用 pivot_table 函数与 groupby+unstack 两种不同的策略实现,并体会它们之间的联系。
pivot_table:
res = df.pivot_table(index='YYYY', columns='State',values='DrugReports', aggfunc='sum')
res
groupby+unstack:
res = df.groupby(['State', 'YYYY'])['DrugReports'].sum().to_frame().unstack(0).droplevel(0,axis=1)
res