第4章 变形
import numpy as np
import pandas as pd
df = pd.read_csv(r'D:\86151\桌面\Datawhale\pandas\joyful-pandas-master\data\table.csv')
df.head()
| Unnamed: 0 | School | Class | ID | Gender | Address | Height | Weight | Math | Physics |
---|
0 | 0 | S_1 | C_1 | 1101 | M | street_1 | 173 | 63 | 34.0 | A+ |
---|
1 | 1 | S_1 | C_1 | 1102 | F | street_2 | 192 | 73 | 32.5 | B+ |
---|
2 | 2 | S_1 | C_1 | 1103 | M | street_2 | 186 | 82 | 87.2 | B+ |
---|
3 | 3 | S_1 | C_1 | 1104 | F | street_2 | 167 | 81 | 80.4 | B- |
---|
4 | 4 | S_1 | C_1 | 1105 | F | street_4 | 159 | 64 | 84.8 | B+ |
---|
一、透视表
1. pivot
一般状态下,数据在DataFrame会以压缩(stacked)状态存放,例如上面的Gender,两个类别被叠在一列中,pivot函数可将某一列作为新的cols:
df.pivot(index='ID',columns='Gender',values='Height').head()
Gender | F | M |
---|
ID | | |
---|
1101 | NaN | 173.0 |
---|
1102 | 192.0 | NaN |
---|
1103 | NaN | 186.0 |
---|
1104 | 167.0 | NaN |
---|
1105 | 159.0 | NaN |
---|
然而pivot函数具有很强的局限性,除了功能上较少之外,还不允许values中出现重复的行列索引对(pair),例如下面的语句就会报错:
因此,更多的时候会选择使用强大的pivot_table函数
2. pivot_table
首先,再现上面的操作:
pd.pivot_table(df,index='ID',columns='Gender',values='Height').head()
Gender | F | M |
---|
ID | | |
---|
1101 | NaN | 173.0 |
---|
1102 | 192.0 | NaN |
---|
1103 | NaN | 186.0 |
---|
1104 | 167.0 | NaN |
---|
1105 | 159.0 | NaN |
---|
由于功能更多,速度上自然是比不上原来的pivot函数:
%timeit df.pivot(index='ID',columns='Gender',values='Height')
%timeit pd.pivot_table(df,index='ID',columns='Gender',values='Height')
2.28 ms ± 74.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
9.77 ms ± 498 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Pandas中提供了各种选项,下面介绍常用参数:
① aggfunc:对组内进行聚合统计,可传入各类函数,默认为’mean’
pd.pivot_table(df,index='School',columns='Gender',values='Height',aggfunc=['mean','sum']).head()
| mean | sum |
---|
Gender | F | M | F | M |
---|
School | | | | |
---|
S_1 | 173.125000 | 178.714286 | 1385 | 1251 |
---|
S_2 | 173.727273 | 172.000000 | 1911 | 1548 |
---|
② margins:汇总边际状态
pd.pivot_table(df,index='School',columns='Gender',values='Height',aggfunc=['mean','sum'],margins=True).head()
| mean | sum |
---|
Gender | F | M | All | F | M | All |
---|
School | | | | | | |
---|
S_1 | 173.125000 | 178.714286 | 175.733333 | 1385 | 1251 | 2636 |
---|
S_2 | 173.727273 | 172.000000 | 172.950000 | 1911 | 1548 | 3459 |
---|
All | 173.473684 | 174.937500 | 174.142857 | 3296 | 2799 | 6095 |
---|
③ 行、列、值都可以为多级
pd.pivot_table(df,index=['School','Class'],
columns=['Gender','Address'],
values=['Height','Weight'])
| | Height | ... | Weight |
---|
| Gender | F | M | ... | F | M |
---|
| Address | street_1 | street_2 | street_4 | street_5 | street_6 | street_7 | street_1 | street_2 | street_4 | street_5 | ... | street_4 | street_5 | street_6 | street_7 | street_1 | street_2 | street_4 | street_5 | street_6 | street_7 |
---|
School | Class | | | | | | | | | | | | | | | | | | | | | |
---|
S_1 | C_1 | NaN | 179.5 | 159.0 | NaN | NaN | NaN | 173.0 | 186.0 | NaN | NaN | ... | 64.0 | NaN | NaN | NaN | 63.0 | 82.0 | NaN | NaN | NaN | NaN |
---|
C_2 | NaN | NaN | 176.0 | 162.0 | 167.0 | NaN | NaN | NaN | NaN | 188.0 | ... | 94.0 | 63.0 | 63.0 | NaN | NaN | NaN | NaN | 68.0 | 53.0 | NaN |
---|
C_3 | 175.0 | NaN | NaN | 187.0 | NaN | NaN | NaN | 195.0 | 161.0 | NaN | ... | NaN | 69.0 | NaN | NaN | NaN | 70.0 | 68.0 | NaN | NaN | 82.0 |
---|
S_2 | C_1 | NaN | NaN | NaN | 159.0 | 161.0 | NaN | NaN | NaN | 163.5 | NaN | ... | NaN | 97.0 | 61.0 | NaN | NaN | NaN | 71.0 | NaN | NaN | 84.0 |
---|
C_2 | NaN | NaN | NaN | NaN | NaN | 188.5 | 175.0 | NaN | 155.0 | 193.0 | ... | NaN | NaN | NaN | 76.5 | 74.0 | NaN | 91.0 | 100.0 | NaN | NaN |
---|
C_3 | NaN | NaN | 157.0 | NaN | 164.0 | 190.0 | NaN | NaN | 187.0 | 171.0 | ... | 78.0 | NaN | 81.0 | 99.0 | NaN | NaN | 73.0 | 88.0 | NaN | NaN |
---|
C_4 | NaN | 176.0 | NaN | NaN | 175.5 | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | 57.0 | NaN | NaN | NaN | NaN | NaN | NaN | 82.0 |
---|
7 rows × 24 columns
3. crosstab(交叉表)
交叉表是一种特殊的透视表,典型的用途如分组统计,如现在想要统计关于街道和性别分组的频数:
pd.crosstab(index=df['Address'],columns=df['Gender'])
Gender | F | M |
---|
Address | | |
---|
street_1 | 1 | 2 |
---|
street_2 | 4 | 2 |
---|
street_4 | 3 | 5 |
---|
street_5 | 3 | 3 |
---|
street_6 | 5 | 1 |
---|
street_7 | 3 | 3 |
---|
交叉表的功能也很强大(但目前还不支持多级分组),下面说明一些重要参数:
① values和aggfunc:分组对某些数据进行聚合操作,这两个参数必须成对出现
pd.crosstab(index=df['Address'],columns=df['Gender'],
values=np.random.randint(1,20,df.shape[0]),aggfunc='min')
Gender | F | M |
---|
Address | | |
---|
street_1 | 6 | 4 |
---|
street_2 | 10 | 5 |
---|
street_4 | 6 | 2 |
---|
street_5 | 10 | 8 |
---|
street_6 | 9 | 4 |
---|
street_7 | 8 | 4 |
---|
② 除了边际参数margins外,还引入了normalize参数,可选’all’,‘index’,'columns’参数值
pd.crosstab(index=df['Address'],columns=df['Gender'],normalize='all',margins=True)
Gender | F | M | All |
---|
Address | | | |
---|
street_1 | 0.028571 | 0.057143 | 0.085714 |
---|
street_2 | 0.114286 | 0.057143 | 0.171429 |
---|
street_4 | 0.085714 | 0.142857 | 0.228571 |
---|
street_5 | 0.085714 | 0.085714 | 0.171429 |
---|
street_6 | 0.142857 | 0.028571 | 0.171429 |
---|
street_7 | 0.085714 | 0.085714 | 0.171429 |
---|
All | 0.542857 | 0.457143 | 1.000000 |
---|
二、其他变形方法
1. melt
melt函数可以认为是pivot函数的逆操作,将unstacked状态的数据,压缩成stacked,使“宽”的DataFrame变“窄”
df_m = df[['ID','Gender','Math']]
df_m.head()
| ID | Gender | Math |
---|
0 | 1101 | M | 34.0 |
---|
1 | 1102 | F | 32.5 |
---|
2 | 1103 | M | 87.2 |
---|
3 | 1104 | F | 80.4 |
---|
4 | 1105 | F | 84.8 |
---|
df.pivot(index='ID',columns='Gender',values='Math').head()
Gender | F | M |
---|
ID | | |
---|
1101 | NaN | 34.0 |
---|
1102 | 32.5 | NaN |
---|
1103 | NaN | 87.2 |
---|
1104 | 80.4 | NaN |
---|
1105 | 84.8 | NaN |
---|
melt函数中的id_vars表示需要保留的列,value_vars表示需要stack的一组列
pivoted = df.pivot(index='ID',columns='Gender',values='Math')
result = pivoted.reset_index().melt(id_vars=['ID'],value_vars=['F','M'],value_name='Math')\
.dropna().set_index('ID').sort_index()
result.head()
| Gender | Math |
---|
ID | | |
---|
1101 | M | 34.0 |
---|
1102 | F | 32.5 |
---|
1103 | M | 87.2 |
---|
1104 | F | 80.4 |
---|
1105 | F | 84.8 |
---|
2. 压缩与展开
(1)stack:这是最基础的变形函数,总共只有两个参数:level和dropna
df_s = pd.pivot_table(df,index=['Class','ID'],columns='Gender',values=['Height','Weight'])
df_s.groupby('Class').head(2)
| | Height | Weight |
---|
| Gender | F | M | F | M |
---|
Class | ID | | | | |
---|
C_1 | 1101 | NaN | 173.0 | NaN | 63.0 |
---|
1102 | 192.0 | NaN | 73.0 | NaN |
---|
C_2 | 1201 | NaN | 188.0 | NaN | 68.0 |
---|
1202 | 176.0 | NaN | 94.0 | NaN |
---|
C_3 | 1301 | NaN | 161.0 | NaN | 68.0 |
---|
1302 | 175.0 | NaN | 57.0 | NaN |
---|
C_4 | 2401 | 192.0 | NaN | 62.0 | NaN |
---|
2402 | NaN | 166.0 | NaN | 82.0 |
---|
df_stacked = df_s.stack()
df_stacked.groupby('Class').head(2)
| | | Height | Weight |
---|
Class | ID | Gender | | |
---|
C_1 | 1101 | M | 173.0 | 63.0 |
---|
1102 | F | 192.0 | 73.0 |
---|
C_2 | 1201 | M | 188.0 | 68.0 |
---|
1202 | F | 176.0 | 94.0 |
---|
C_3 | 1301 | M | 161.0 | 68.0 |
---|
1302 | F | 175.0 | 57.0 |
---|
C_4 | 2401 | F | 192.0 | 62.0 |
---|
2402 | M | 166.0 | 82.0 |
---|
stack函数可以看做将横向的索引放到纵向,因此功能类似与melt,参数level可指定变化的列索引是哪一层(或哪几层,需要列表)
df_stacked = df_s.stack(0)
df_stacked.groupby('Class').head(2)
| | Gender | F | M |
---|
Class | ID | | | |
---|
C_1 | 1101 | Height | NaN | 173.0 |
---|
Weight | NaN | 63.0 |
---|
C_2 | 1201 | Height | NaN | 188.0 |
---|
Weight | NaN | 68.0 |
---|
C_3 | 1301 | Height | NaN | 161.0 |
---|
Weight | NaN | 68.0 |
---|
C_4 | 2401 | Height | 192.0 | NaN |
---|
Weight | 62.0 | NaN |
---|
(2) unstack:stack的逆函数,功能上类似于pivot_table
df_stacked.head()
| | Gender | F | M |
---|
Class | ID | | | |
---|
C_1 | 1101 | Height | NaN | 173.0 |
---|
Weight | NaN | 63.0 |
---|
1102 | Height | 192.0 | NaN |
---|
Weight | 73.0 | NaN |
---|
1103 | Height | NaN | 186.0 |
---|
result = df_stacked.unstack().swaplevel(1,0,axis=1).sort_index(axis=1)
result.head()
| | Height | Weight |
---|
| Gender | F | M | F | M |
---|
Class | ID | | | | |
---|
C_1 | 1101 | NaN | 173.0 | NaN | 63.0 |
---|
1102 | 192.0 | NaN | 73.0 | NaN |
---|
1103 | NaN | 186.0 | NaN | 82.0 |
---|
1104 | 167.0 | NaN | 81.0 | NaN |
---|
1105 | 159.0 | NaN | 64.0 | NaN |
---|
三、哑变量与因子化
1. Dummy Variable(哑变量)
这里主要介绍get_dummies函数,其功能主要是进行one-hot编码:
df_d = df[['Class','Gender','Weight']]
df_d.head()
| Class | Gender | Weight |
---|
0 | C_1 | M | 63 |
---|
1 | C_1 | F | 73 |
---|
2 | C_1 | M | 82 |
---|
3 | C_1 | F | 81 |
---|
4 | C_1 | F | 64 |
---|
现在希望将上面的表格前两列转化为哑变量,并加入第三列Weight数值:
pd.get_dummies(df_d[['Class','Gender']]).join(df_d['Weight']).head()
| Class_C_1 | Class_C_2 | Class_C_3 | Class_C_4 | Gender_F | Gender_M | Weight |
---|
0 | 1 | 0 | 0 | 0 | 0 | 1 | 63 |
---|
1 | 1 | 0 | 0 | 0 | 1 | 0 | 73 |
---|
2 | 1 | 0 | 0 | 0 | 0 | 1 | 82 |
---|
3 | 1 | 0 | 0 | 0 | 1 | 0 | 81 |
---|
4 | 1 | 0 | 0 | 0 | 1 | 0 | 64 |
---|
2. factorize方法
该方法主要用于自然数编码,并且缺失值会被记做-1,其中sort参数表示是否排序后赋值
codes, uniques = pd.factorize(['b', None, 'a', 'c', 'b'], sort=True)
display(codes)
display(uniques)
array([ 1, -1, 0, 2, 1])
array(['a', 'b', 'c'], dtype=object)
四、问题与练习
推荐博客:
哑变量详解
同打卡文章
1. 问题
【问题一】 上面提到了许多变形函数,如melt/crosstab/pivot/pivot_table/stack/unstack函数,请总结它们各自的使用特点。
pivot–>pivot_table(可以多级索引,功能更强大些),crosstab主要侧重交叉展示数据(两个索引组成的交叉表),unstack也是类似函数
上边这些都是把数据“展开”来看,仿佛是透视数据,更方便注重一些数据与一些数据的关联,把一些不容易看成数据之间关联与特点的地方“提出来放大来观察”
melt/stack
感觉就是把表中的数据“压缩”起来,上面说的一点是把横向索引变为纵向索引,这个说明例子很方便我们理解
上述两组函数是互逆操作
【问题二】 变形函数和多级索引是什么关系?哪些变形函数会使得索引维数变化?具体如何变化?
个人理解:通过提取出一些不同的索引来组成新的数据表,对原数据表进行变形,如何变化有些不太明白,感觉大部分维数都变了,如原来的性别由一维索引变为男,女两个维度的索引展示数据?
【问题三】 请举出一个除了上文提过的关于哑变量方法的例子。
##### 哑变量(DummyVariable),也叫虚拟变量,引入哑变量的目的是,将不能够定量处理的变量量化,在线性回归分析中引入哑变量的目的是,可以考察定性因素对因变量的影响,它是人为虚设的变量,通常取值为0或1,来反映某个变量的不同属性。对于有n个分类属性的自变量,通常需要选取1个分类作为参照,因此可以产生n-1个哑变量。
不太了解》。。。。有哪些
【问题四】 使用完stack后立即使用unstack一定能保证变化结果与原始表完全一致吗?
不一定吧?但也不知有什么特殊情况....
【问题五】 透视表中涉及了三个函数,请分别使用它们完成相同的目标(任务自定)并比较哪个速度最快。
unstack()方法是针对索引或者标签的,即将列索引转成最内层的行索引;而pivot()方法则是针对列的值,即指定某列的值作为行索引,指定某列的值作为列索引,然后再指定哪些列作为索引对应的值。因此,总结起来一句话就是:unstack()针对索引进行操作,pivot()针对值进行操作。但实际上,两者在功能往往可以互相实现。
可能unstack更快(猜测)
【问题六】 既然melt起到了stack的功能,为什么再设计stack函数?
肯定不同函数有不同特点,因而解决相应的问题
melt vs stack
2. 练习
【练习一】 继续使用上一章的药物数据集:
pd.read_csv(r'D:\86151\桌面\Datawhale\pandas\joyful-pandas-master\data\table.csv').head()
| Unnamed: 0 | School | Class | ID | Gender | Address | Height | Weight | Math | Physics |
---|
0 | 0 | S_1 | C_1 | 1101 | M | street_1 | 173 | 63 | 34.0 | A+ |
---|
1 | 1 | S_1 | C_1 | 1102 | F | street_2 | 192 | 73 | 32.5 | B+ |
---|
2 | 2 | S_1 | C_1 | 1103 | M | street_2 | 186 | 82 | 87.2 | B+ |
---|
3 | 3 | S_1 | C_1 | 1104 | F | street_2 | 167 | 81 | 80.4 | B- |
---|
4 | 4 | S_1 | C_1 | 1105 | F | street_4 | 159 | 64 | 84.8 | B+ |
---|
(a) 现在请你将数据表转化成如下形态,每行需要显示每种药物在每个地区的10年至17年的变化情况,且前三列需要排序:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z7S6tHnU-1588171897314)(picture/drug_pic.png)]
(b) 现在请将(a)中的结果恢复到原数据表,并通过equal函数检验初始表与新的结果是否一致(返回True)
【练习二】 现有一份关于某地区地震情况的数据集,请解决如下问题:
pd.read_csv('data/Earthquake.csv').head()
| 日期 | 时间 | 维度 | 经度 | 方向 | 距离 | 深度 | 烈度 |
---|
0 | 2003.05.20 | 12:17:44 AM | 39.04 | 40.38 | west | 0.1 | 10.0 | 0.0 |
---|
1 | 2007.08.01 | 12:03:08 AM | 40.79 | 30.09 | west | 0.1 | 5.2 | 4.0 |
---|
2 | 1978.05.07 | 12:41:37 AM | 38.58 | 27.61 | south_west | 0.1 | 0.0 | 0.0 |
---|
3 | 1997.03.22 | 12:31:45 AM | 39.47 | 36.44 | south_west | 0.1 | 10.0 | 0.0 |
---|
4 | 2000.04.02 | 12:57:38 AM | 40.80 | 30.24 | south_west | 0.1 | 7.0 | 0.0 |
---|
(a) 现在请你将数据表转化成如下形态,将方向列展开,并将距离、深度和烈度三个属性压缩:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bzAdzpby-1588171897319)(picture/earthquake_pic.png)]
(b) 现在请将(a)中的结果恢复到原数据表,并通过equal函数检验初始表与新的结果是否一致(返回True)