pandas进阶系列根据datawhale远昊大佬的joyful pandas教程写一些自己的心得和补充,本文部分引用了原教程,并参考了《利用Python进行数据分析》、pandas官网
另注:本文是对joyful pandas教程的延伸,完整理解需先阅读joyful pandas教程第五章
目前的进度:学完了内容,做了一个练一练和ex1前两问,都有解决过程,28号早上补完剩余的习题
import numpy as np
import pandas as pd
pivot
是一种典型的长表变宽表的函数,首先来看一个例子:下表存储了张三和李四的语文和数学分数,现在想要把语文和数学分数作为列来展示。
在上面的边际汇总例子中,行或列的汇总为新表中行元素或者列元素的平均值,而总体的汇总为新表中四个元素的平均值。这种关系一定成立吗?若不成立,请给出一个例子来说明。
不一定成立,题目中原本给的数据构造的比较均匀,每人每科刚好都有两个数据,因此造成了边际表的值是均值聚合后的表的行列的均值的假象。
实际上边际表的值还是按照原表的值计算的,下面我更改了原数据(删除了一行数据使其不均匀),就可以看到汇总后的表和原表值不同
df = pd.DataFrame({
'Name':['San Zhang', 'San Zhang',
'San Zhang', 'San Zhang',
'Si Li', 'Si Li', 'Si Li', ],
'Subject':['Chinese', 'Chinese', 'Math', 'Math',
'Chinese', 'Chinese', 'Math'],
'Grade':[80, 90, 100, 90, 70, 80, 85]})
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 |
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 |
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 | 85 |
此外,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 | 85 | 95.000000 | 90.000000 |
Si Li | 75 | 85.000000 | 78.333333 |
All | 80 | 91.666667 | 85.000000 |
可以看到,上面这个表中(75+85)/2 != 78.3 因此,边际表是按原表计算的,不是按聚合后的表计算的
长宽表只是数据呈现方式的差异,但其包含的信息量是等价的,前面提到了利用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
one-hot编码
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 |
现有一份关于美国非法药物的数据集,其中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 |
第一问:我的思路是首先可以确定是长变宽,所以要用pivot,利用前面的带颜色的分割图,在结果图中也分色块展示一下,目标的结果如图所示,所以就确定了参数分别应该选哪几列。
tmp = df.pivot(index=['State', 'COUNTY', 'SubstanceName'],
columns='YYYY',
values='DrugReports')
tmp = tmp.reset_index().rename_axis(columns={
'YYYY':''})
tmp.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 |
tmp.columns[-8:]
Index([2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017], dtype='object', name='')
第二问:这里发现一个神奇的事,上一问的2010,2011这些列,竟然不是字符串表示的,而是数字表示的
tmp.melt(id_vars=['State', 'COUNTY', 'SubstanceName'],
value_vars=list(range(2010, 2018)),
var_name='YYYY',
value_name='DrugReports').dropna()
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 |
15 | KY | ALLEN | Methadone | 2010 | 4.0 |
17 | KY | ALLEN | Oxycodone | 2010 | 15.0 |
... | ... | ... | ... | ... | ... |
49702 | WV | WOOD | Hydrocodone | 2017 | 8.0 |
49704 | WV | WOOD | Isobutyryl fentanyl | 2017 | 3.0 |
49707 | WV | WOOD | Oxycodone | 2017 | 1.0 |
49708 | WV | WOOD | Tramadol | 2017 | 3.0 |
49709 | WV | WYOMING | Buprenorphine | 2017 | 1.0 |
24062 rows × 5 columns