Python_tips-groupby

万壑松风知客来,摇扇抚琴待留声

简述

Python 的快速发展离不开大量库的开发,因为有了这些库,Python 贴上了 简单、强大、高效、上手快 的标签。但同时也带来了一些问题:技巧太多、方法参数太多,这往往导致在实际开发中容易忘记、混淆或用错知识点。

当然方法多这是好事,同时我也一直相信熟能生巧。由于平时经常会遇到一些好用又方便的小技巧,但都没有做一个简单的记录。所以打算从现在开始不定时的对这些方法做一个归纳与记录,以简短的代码与描述对其说明。

groupby 分组聚合函数:

groupby 是数据分析中一个强大的帮手,通过该方法将 pandas 对象按照某个或多个键名进行分组聚合,筛选、呈现出想要的统计结果。该方法的使用很多样,我平常的使用过程中并没有接触完全,下面对几个好用又生僻的技能进行说明。这里生成一个数据,后面介绍都使用该数据:

1
2
3
4
5
6
7
8
9
df = pd.DataFrame({'机构编码':[102,102,103,102,103,103],'交易日期':['2019-4','2019-4','2019-5','2019-4','2019-5','2019-5'],'存1/取0':[1,0,1,0,0,0],'交易金额':[100,200,300,400,500,600]})

机构编码 交易日期 存1/取0 交易金额
0 102 2019-4 1 100
1 102 2019-4 0 200
2 103 2019-5 1 300
3 102 2019-4 0 400
4 103 2019-5 0 500
5 103 2019-5 0 600

groupby 对象迭代:

groupby 函数返回的是一个对象,你可以直接在后面使用统计函数如:sum()、mean()、count() 等来获取统计结果,当然也可以接 apply 传入自定义函数处理结果。这里要说的是这个对象的迭代问题,groupby 分组后的对象是可迭代的,可以使用 for 循环对其进行遍历,循环次数等于分组数量,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 以一个列名做为分组键
df_a = df.groupby(['机构编码'])
for i,j in df_a:
print(i)
print(j)

102
机构编码 交易日期 存1/取0 交易金额
0 102 2019-4 1 100
1 102 2019-4 0 200
3 102 2019-4 0 400
103
机构编码 交易日期 存1/取0 交易金额
2 103 2019-5 1 300
4 103 2019-5 0 500
5 103 2019-5 0 600

# 以二个列名做为分组建
df_a = df.groupby(['机构编码','交易日期','存1/取0'])
for i,j in df_a:
print(i)
print(j)

(102, '2019-4', 0)
机构编码 交易日期 存1/取0 交易金额
1 102 2019-4 0 200
3 102 2019-4 0 400
(102, '2019-4', 1)
机构编码 交易日期 存1/取0 交易金额
0 102 2019-4 1 100
(103, '2019-5', 0)
机构编码 交易日期 存1/取0 交易金额
4 103 2019-5 0 500
5 103 2019-5 0 600
(103, '2019-5', 1)
机构编码 交易日期 存1/取0 交易金额
2 103 2019-5 1 300

说明:其中 i 为迭代每次分组的键名,可以为单个值,也可以为多值的元组,这取决于你如何分组;j 为每个分组的数据集,这里是 DataFrame 类型。

如果有必要你可以在外部定义一个空数据集,按照某个条件筛选键或无条件在循环内对每个数据集做处理,然后将拼接每个数据集,循环结束得到一个结果。

groupby 字典映射:

前面说过 groupby 后的对象是可以迭代的,并且一个很有趣的是每次迭代的是一个元组。由于这个特性,我们可以对其做字典映射:

1
2
3
4
5
6
df_b = dict(list(df.groupby(['机构编码','交易日期','存1/取0'])))
print(df_b[(102,'2019-4',0)])

机构编码 交易日期 存1/取0 交易金额
1 102 2019-4 0 200
3 102 2019-4 0 400

说明:这里字典的键是一个长度为 3 的元组,这是取决于我们的分组键,如果分组建只有一个,那字典的键就是一个值。这里数据依旧是 DataFrame 类型。

groupby 的 as_index:

使用 groupby 进行分组后得到的是一个对象,同时如果你使用某个方法对该对象处理,返回的结果数据集默认是以分组键作为行索引,并且如果有多个分组键,行索引就是多层 level ,这便于我们观察数据划分,但有一个问题就是这些数据你可能并不好用来做后续处理。这时有几种方法处理这种情况,第一种方法:先保存数据,在读取数据(有点傻)、第二种方法:使用 groupby 的参数 as_index=False (默认为 Ture 造成这种结果)、第三种方法:使用 reset_index 函数,重定义数据集索引:

1
2
3
4
5
6
7
8
9
10
# 默认如下
df_c = df.groupby(['机构编码','交易日期','存1/取0']).sum()
print(df_c)

交易金额
机构编码 交易日期 存1/取0
102 2019-4 0 600
1 100
103 2019-5 0 1100
1 300
1
2
3
4
5
6
7
8
9
10
11
12
# 第二种方法:
df_c = df.groupby(['机构编码','交易日期','存1/取0'], as_index=False).sum()

# 第三种方法:
df_c = df.groupby(['机构编码','交易日期','存1/取0']).sum().reset_index()

# 结果如下:
机构编码 交易日期 存1/取0 交易金额
0 102 2019-4 0 600
1 102 2019-4 1 100
2 103 2019-5 0 1100
3 103 2019-5 1 300

说明:这种情况一般使用第二种方法是最好的解决方法,毕竟是自带参数。

reset_index 的 level:

前面介绍了,通过该方法或修改 as_index 可以重置 groupby 后的索引,修改 as_index 参数可能更好,但如果实际情况需要我们保留某个或多个键为索引,并不是全部取消,那修改 as_index 是做不到的。这个时候 reset_index 就会更有效,可以通过 level 参数来完成该功能:

1
2
3
4
5
6
7
8
9
10
df_d = df.groupby(['机构编码','交易日期','存1/取0']).sum()
df_d = df_d.reset_index(['机构编码','存1/取0'])
print(df_d)

机构编码 存1/取0 交易金额
交易日期
2019-4 102 0 600
2019-4 102 1 100
2019-5 103 0 1100
2019-5 103 1 300

说明:这里我们对三个键分组后的结果求和,然后对求和结果中的两个键进行还原,只保留了 交易日期 作为行索引。可以根据实际情况给参数还原与保留索引。

总结:

这里只做了 Python 小技巧的冰山一角,毕竟 Python 的世界很大,而我很小。通过工作中的不断尝试,你会为之眼前一亮原来有这么多你并不知道,但却很厉害的技巧。这应该是一个系列,日后我会慢慢在学习中补充。