05 pandas数据透视
我们都知道pandas是数据分析的一把好手,其中一个非常重要的功能就是进行Groupby分析,对数据进行汇总求和,求最大值,最小值,平均值等等,那么接下来我们就通过具体的演示,来看看到底如何使用的
一、Groupby的基础使用用法
首先我们调用numpy包来生成一个随机的DataFrame,在这里我们固定随机数种子得到这样一张表,表示每个学生在某个区域中执行任务的花费和产出,spend代表花费,perf代表实际产出,一共生成20条数值:
| rand_seed = 42
np.random.seed(rand_seed) # 保证每次生成的随机数一样
random.seed(rand_seed)
df = pd.DataFrame({
"name":np.random.choice(["John","Doe","Jane"],20),
"area":np.random.choice(["A","B","C"],20),
"spend":np.random.randint(18,60,20),
"perf":np.random.randint(1,100,20)
})
print(df)
|
得到的结果如下:
| name area spend perf
0 Jane A 54 50
1 John A 24 4
2 Jane B 38 2
3 Jane B 26 6
4 John A 56 54
5 John A 35 4
6 Jane A 21 54
7 Doe C 42 93
8 Jane C 31 63
9 Jane C 26 18
10 Jane B 43 90
11 Jane C 19 44
12 John B 37 34
13 Jane B 45 74
14 Doe C 24 62
15 John B 25 14
16 Doe C 52 95
17 Doe C 31 48
18 Doe A 34 15
19 Doe C 53 72
|
1.1 按单列做单条件聚合
我们可以通过以下语法根据单条件进行聚合,下面这句话的意思是根据name列进行分类汇总,汇总列是perf列,方式求和
| # 每个学生的总表现分
print(df.groupby("name")["perf"].sum())
|
得到的结果如图,对每个不同的学生的表现总分进行了加和:
| name
Doe 385
Jane 401
John 110
Name: perf, dtype: int32
|
如果需要统计其他的指标,也可以通过以下方式实现,当然除了以下汇总方式,还有max,min,count 等多种:
| # 根据`area`列进行分类汇总,汇总列是`spend`列,方式是求和
print(df.groupby("area")["spend"].sum())
print(df.groupby("area")["spend"].mean()) # 每个区域的平均花费
print(df.groupby("area")["perf"].mean()) # 每个区域的平均表现分
|
1.2 按单列做多条件聚合
如果需要对单列做多个指标的统计,只需要在后面加上一个agg,然后把需要统计的指标以列表的形式组织即可
| # 每个区域的平均表现分、最大表现分、最小表现分
print(df.groupby("area")["perf"].agg(["mean","max","min"]))
# 如果需要保留小数位数,可以用round函数
# print(round(df.groupby("area")["perf"].agg(["mean","max","min"]),2))
|
输出如下,我们可以非常直观地看出各个指标的具体数值:
| mean max min
area
A 30.166667 54 4
B 36.666667 90 2
C 61.875000 95 18
|
1.3 按多列做单条件聚合
我们可以把需要聚合的字段以列表的形式放在Groupby函数中,然后使用as_index=False 让第一个聚合字段不作为索引使用,这样就可以得到规范的DataFrame
| # 每个区域的每个人的最大花费
print(df.groupby(["area","name"],as_index=False)["spend"].max())
|
输出如下,可以看到第一个字段就是给定的area,而第二个字段就是name:
| area name spend
0 A Doe 34
1 A Jane 54
2 A John 56
3 B Jane 45
4 B John 37
5 C Doe 53
6 C Jane 31
|
如果说我们不设置as_index=False,那么得到的结果就是下面这样子,可以看到第一个字段作为索引被使用,如果我们调用A索引,则会同时打印三个人的信息,因为这里生成的索引是不唯一的:
| area name
A Doe 34
Jane 54
John 56
B Jane 45
John 37
C Doe 53
Jane 31
Name: spend, dtype: int32
|
1.4 按多列做多条件聚合
理解了前面的例子,我们就很好理解这个多列做多条件聚合的事情了,在这里给大家更新一个知识点就是agg后面可以跟一个字典,用于告知需要对哪一列做汇总,汇总方式是什么,比如下面的第二句代码,表示 “每个区域的每个人的最大花费、平均花费、最大表现分、平均表现分”
| # 每个区域的每个人的最大花费、平均花费
print(round(df.groupby(["area","name"],as_index=False)["spend"].agg(["max","mean"]),2))
# 每个区域的每个人的最大花费、平均花费、最大表现分、平均表现分
print(round(df.groupby(["area","name"],as_index=False).agg({"spend":["max","mean"],"perf":["max","mean"]}),2))
|
输出结果如下,可以看到这张汇总表非常直观地展现了各个地区,各个人花费和产出的信息:
| area name spend perf
max mean max mean
0 A Doe 34 34.00 15 15.00
1 A Jane 54 37.50 54 52.00
2 A John 56 38.33 54 20.67
3 B Jane 45 38.00 90 43.00
4 B John 37 31.00 34 24.00
5 C Doe 53 40.40 95 74.00
6 C Jane 31 25.33 63 41.67
|
二、理解多级索引
2.1 列汇总字段唯一
我们通过上面讲到的groupby方法首先进行一个聚合,然后打印一下不同的索引值
| df2 = df.groupby(["area","name"])["spend"].sum()
print(df2)
print(df2.index)
print("="*50)
df2 = df2.reset_index() # 重置索引为数字索引
print(df2)
print(df2.index)
|
可以看到如果我们不重置索引,出来的索引就是一个MultiIndex,是一个元组列表
| area name
A Doe 34
Jane 75
John 115
B Jane 152
John 62
C Doe 202
Jane 76
Name: spend, dtype: int32
MultiIndex([('A', 'Doe'),
('A', 'Jane'),
('A', 'John'),
('B', 'Jane'),
('B', 'John'),
('C', 'Doe'),
('C', 'Jane')],
names=['area', 'name'])
==================================================
area name spend
0 A Doe 34
1 A Jane 75
2 A John 115
3 B Jane 152
4 B John 62
5 C Doe 202
6 C Jane 76
RangeIndex(start=0, stop=7, step=1)
|
所以接下来我们关注如何对多级索引进行取值,在这里需要理解的是( ) 配合的是元组,如果其中填的是两个参数,代表的分别是( 一级索引,二级索引) ,而如果用的是[ ],则代表的是同级索引,指的是不同情况,比如[ "A","C"] 就是同时取出A和C两者的汇总数据,我们具体看代码演示,对df2进行不同方式的取值:
| print(df2.loc["A"]) # 每个区域A的每个人的花费
print(df2.loc[("A","John")]) # 区域A的John的花费
print(df2.loc[["A","B"]]) # 区域A和B的每个人的花费
print(df2.loc["A":"C"]) # 区域A到C的每个人的花费
|
在这里我们只打印print(df2.loc[["A","B"]]) 的结果,如下:
| area name
A Doe 34
Jane 75
John 115
B Jane 152
John 62
Name: spend, dtype: int32
|
2.2 列汇总字段不唯一
如果我们的汇总字段也是多个情况,那又该如何取值呢?比如我们重新生成一个df3,后面的关于列字段的索引,就只可以使用[ ]来表示了,因为只有perf 和 spend 这两种形式
| df3 = round(df.groupby(["area","name"]).agg({"spend":"sum","perf":"mean"}),2)
print(df3.loc["A",["perf"]]) # 区域A的每个人的表现分
print(df3.loc[("A","John"),"spend"]) # 区域A的John的花费
print(df3.loc["A",["perf","spend"]]) # 区域A的每个人的表现分和花费
print(df3.loc[["A","B"],["spend","perf"]]) # 区域A和B的每个人的花费和表现分
print(df3.loc["A":"C","spend"]) # 区域A到C的每个人的花费
|
下方我们只打印print(df3.loc["A",["perf","spend"]])的结果,其他的以此类推
| perf spend
name
Doe 15.00 34
Jane 52.00 75
John 20.67 115
|
三、数据透视
在上面,我们通过groupby 方法实现了多种数据的汇总统计,这次我们生成100行不同的数据,方便透视分析:
| np.random.seed(42)
random.seed(42)
df = pd.DataFrame({
"name":np.random.choice(["John","Doe","Jane"],100),
"area":np.random.choice(["A","B","C","D","E"],100),
"spend":np.random.randint(18,60,100),
"perf":np.random.randint(1,100,100)
})
# 对name和area进行分组,计算每个组合的perf总和
print(df.groupby(["name","area"])["perf"].sum())
|
控制台输出如下结果(非常冗长):
| name area
Doe A 336
B 176
C 659
D 435
E 64
Jane A 414
B 353
C 300
D 434
E 243
John A 349
B 299
C 318
D 183
E 456
Name: perf, dtype: int32
|
3.1 通过unstack整理数据
虽然说上述能够看出对应关系,但是不管是画图还是做分析,这个都不算是很直观,更像是分类汇总得到的结果,并没有进行透视,所以简单看还是一个方向的,因此我们需要将这个表进行整理,透视成更直观的样式,只需要加这样一句代码:
| # 对name和area进行分组,计算每个组合的perf总和
df2 = df.groupby(["name","area"])["perf"].sum()
df2 = df2.unstack() # 对area进行展开,将每个组合的perf总和转换为列
print(df2)
|
注意要用df2重新再接收一下,这样子我们再来看数据,就会变得非常直观了,name在列方向,而area在行方向上:
| area A B C D E
name
Doe 336 176 659 435 64
Jane 414 353 300 434 243
John 349 299 318 183 456
|
如果要还原回原来的样式,那么只需要写df2 = df2.stack()即可,stack和unstack的作用刚好是相反的。
3.2 通过pivot_table透视数据
其实除了上面的方法,我们还可以使用pivot_table方法来对数据进行透视,这个方法有一个优势,就是可以从最原始的数据源出发,整理数据,并做出最后的透视表的形式,可以看到在下面的代码中,我是直接对df(也就是未经groupby的数据直接处理)
| # 对name和area进行分组,计算每个组合的perf总和
df2 = df.pivot_table(index="name",columns="area",values="perf",aggfunc="sum")
print(df2)
|
pivot_table有四个参数,从前往后分别代表聚合行,聚合列,值以及汇总方式,可以看到最后的结果是一模一样的
| area A B C D E
name
Doe 336 176 659 435 64
Jane 414 353 300 434 243
John 349 299 318 183 456
|
希望以上的内容可以让你深刻理解pandas的Groupby使用方法,后续也会继续分享更多实用技巧