跳转至

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())
得到的结果如图,对每个不同的学生的表现总分进行了加和:

1
2
3
4
5
name
Doe     385
Jane    401
John    110
Name: perf, dtype: int32
如果需要统计其他的指标,也可以通过以下方式实现,当然除了以下汇总方式,还有maxmincount 等多种:

1
2
3
4
# 根据`area`列进行分类汇总,汇总列是`spend`列,方式是求和
print(df.groupby("area")["spend"].sum())
print(df.groupby("area")["spend"].mean()) # 每个区域的平均花费
print(df.groupby("area")["perf"].mean()) # 每个区域的平均表现分

1.2 按单列做多条件聚合

如果需要对单列做多个指标的统计,只需要在后面加上一个agg,然后把需要统计的指标以列表的形式组织即可

1
2
3
4
# 每个区域的平均表现分、最大表现分、最小表现分
print(df.groupby("area")["perf"].agg(["mean","max","min"])) 
# 如果需要保留小数位数,可以用round函数
# print(round(df.groupby("area")["perf"].agg(["mean","max","min"]),2)) 
输出如下,我们可以非常直观地看出各个指标的具体数值:

1
2
3
4
5
           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
1
2
3
4
5
6
7
8
  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索引,则会同时打印三个人的信息,因为这里生成的索引是不唯一的:

1
2
3
4
5
6
7
8
9
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后面可以跟一个字典,用于告知需要对哪一列做汇总,汇总方式是什么,比如下面的第二句代码,表示 “每个区域的每个人的最大花费、平均花费、最大表现分、平均表现分

1
2
3
4
# 每个区域的每个人的最大花费、平均花费
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))
输出结果如下,可以看到这张汇总表非常直观地展现了各个地区,各个人花费和产出的信息:

1
2
3
4
5
6
7
8
9
  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方法首先进行一个聚合,然后打印一下不同的索引值

1
2
3
4
5
6
7
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进行不同方式的取值:

1
2
3
4
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"]]) 的结果,如下:

1
2
3
4
5
6
7
area  name
A     Doe      34
      Jane     75
      John    115
B     Jane    152
      John     62
Name: spend, dtype: int32

2.2 列汇总字段不唯一

如果我们的汇总字段也是多个情况,那又该如何取值呢?比如我们重新生成一个df3,后面的关于列字段的索引,就只可以使用[ ]来表示了,因为只有perfspend 这两种形式

1
2
3
4
5
6
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"]])的结果,其他的以此类推

1
2
3
4
5
       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整理数据

虽然说上述能够看出对应关系,但是不管是画图还是做分析,这个都不算是很直观,更像是分类汇总得到的结果,并没有进行透视,所以简单看还是一个方向的,因此我们需要将这个表进行整理,透视成更直观的样式,只需要加这样一句代码:

1
2
3
4
# 对name和area进行分组,计算每个组合的perf总和
df2 = df.groupby(["name","area"])["perf"].sum()
df2 = df2.unstack() # 对area进行展开,将每个组合的perf总和转换为列
print(df2)

注意要用df2重新再接收一下,这样子我们再来看数据,就会变得非常直观了,name在列方向,而area在行方向上:

1
2
3
4
5
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()即可,stackunstack的作用刚好是相反的。

3.2 通过pivot_table透视数据

其实除了上面的方法,我们还可以使用pivot_table方法来对数据进行透视,这个方法有一个优势,就是可以从最原始的数据源出发,整理数据,并做出最后的透视表的形式,可以看到在下面的代码中,我是直接对df(也就是未经groupby的数据直接处理)

1
2
3
# 对name和area进行分组,计算每个组合的perf总和
df2 = df.pivot_table(index="name",columns="area",values="perf",aggfunc="sum")
print(df2)
pivot_table有四个参数,从前往后分别代表聚合行,聚合列,值以及汇总方式,可以看到最后的结果是一模一样的

1
2
3
4
5
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
希望以上的内容可以让你深刻理解pandasGroupby使用方法,后续也会继续分享更多实用技巧