通过groupby()方法将数据拆分成组
分组聚合的第一个步骤是将数据拆分成组。在Pandas中,可以通过groupby()方法将数据集按照某些标准划分成若干个组,该方法的语法格式如下:
groupby(by=None, axis=0, level=None, as_index=True, sort=True,group_keys=True,
squeeze=False, observed=False, **kwargs)
部分参数表示的含义如下:
(1) by:用于确定进行分组的依据。
(2) axis:表示分组轴的方向,可以为0(表示按行)或1(表示按列),默认为0。
(3) level:如果某个轴是一个MultiIndex 对象(索引层次结构),则会按特定级别或多个级别分组。
(4) as_index:表示聚合后的数据是否以组标签作为索引的DataFrame对象输出,接收布尔值,默认为True。
(5) sort:表示是否对分组标签进行排序,接收布尔值,默认为True。
通过groupby()方法执行分组操作,会返回一个GroupBy对象,该对象实际上并没有进行任何计算,只是包含一些关于分组键(比如df_obj['key1'])的中间数据而已。一般,使用Series调用groupby()方法返回的是SeriesGroupBy对象,而使用DataFrame调用groupby()方法返回的是DataFrameBy对象。
在进行分组时,可以通过groupby()方法的by参数来指定按什么标准分组,by参数可以接收的数据有多种形式,类型也不必相同,常用的分组方式主要有以下4种:
列表或数组,其长度必须与待分组的轴一样。
DataFrame对象中某列的名称。
字典或Series对象,给出待分组轴上的值与分组名称之间的对应关系。
函数,用于处理轴索引或索引中的各个标签。
为了能够让读者更好地理解,接下来,分别对上述几种分组方式进行详细地讲解,具体内容如下。
1. 通过列名进行分组
在Pandas对象中,如果它的某一列数据满足不同的划分标准,则可以将该列当做分组键来拆分数据集。例如,创建一个DataFrame对象,具体代码如下。
In [1]: import pandas as pd
df = pd.DataFrame({"Key":['C','B','C','A','B','B','A','C','A'],
"Data":[2,4,6,8,10,1,14,16,18]})
df
Out[1]:
Key Data
0 C 2
1 B 4
2 C 6
3 A 8
4 B 10
5 B 1
6 A 14
7 C 16
8 A 18
然后,调用groupby()方法时把列名Key传给by参数,代表将Key作为分组键,让df对象按照Key列进行分组,具体示例代码如下:
In [2]: # 按Key列进行分组
df.groupby(by='Key')
Out[2]: <pandas.core.groupby.groupby.DataFrameGroupBy object at 0x0000000006E274A8>
从输出的结果可以看出,DataFrame经过分组后得到了一个DataFrameGroupBy对象,该对象是一个可迭代的对象,即只有在真正需要的时候才会执行计算(采用惰性计算)。
如果要查看每个分组的具体内容,则可以使用for循环遍历DataFrameGroupBy对象。例如,在上述示例代码的基础上,使用一个变量接收返回的DataFrameGroupBy对象,之后用for循环进行遍历,示例代码如下。
In [3]: group_obj = df.groupby('Key')
# 遍历分组对象
for i in group_obj:
print(i)
# 输出结果
('A', Key Data
3 A 8
6 A 14
8 A 18)
('B', Key Data
1 B 4
4 B 10
5 B 1)
('C', Key Data
0 C 2
2 C 6
7 C 16)
上述示例中,调用groupby()方法让df对象按分组键Key对应的一列数据进行分组,这表明(其划分标准)一旦发现该列中存在相同的数据,就把其所在的一行从整个df对象中拆分出来,与其他具有相同键的数据组合在一起,例如,所有“A”为一组,所有“B”为一组,所有“C”为一组,共分成3组数据,这些分组信息都保存在group_obj对象中,使用for循环遍历打印每个分组的信息。
上述示例输出了三个表示分组的元组,每个元组的第一个元素为该组的名称(Key列的数据),第二个元素为该组的具体数据。
2、通过Series对象进行分组
除此之外,还可以自定义Series类对象,将其作为分组键进行分组。例如,创建一个5行4列的DataFrame对象,示例代码如下:
In [4]: import pandas as pd
import numpy as np
df = pd.DataFrame({'key1': ['A', 'A', 'B', 'B', 'A'],
'key2': ['one', 'two', 'one', 'two', 'one'],
'data1': [2, 3, 4, 6, 8],
'data2': [3, 5, 6, 3, 7]})
df
Out[4]:
key1 key2 data1 data2
0 A one 2 3
1 A two 3 5
2 B one 4 6
3 B two 6 3
4 A one 8 7
然后创建一个用做分组键的Series对象,示例代码如下。
In [5]: se = pd.Series(['a', 'b', 'c', 'a', 'b'])
se
Out[5]:
0 a
1 b
2 c
3 a
4 b
dtype: object
接下来,在调用groupby()方法时把se对象传入给by参数,将se对象作为分组键拆分df对象,以得到一个分组对象,遍历该分组对象并查看每个分组的具体内容,示例代码如下。
In [6]: group_obj = df.groupby(by = se) # 按自定义Series对象进行分组
for i in group_obj: # 遍历分组对象
print(i)
Out[6]:
('a', key1 key2 data1 data2
0 A one 2 3
3 B two 6 3)
('b', key1 key2 data1 data2
1 A two 3 5
4 A one 8 7)
('c', key1 key2 data1 data2
2 B one 4 6)
从输出结果中可以看出,se将df对象分为a、b、c三组,其中索引0和3对应的两行为a组数据,索引1和4对应的两行为b组数据,索引2对应的一行为c组数据。
如果Series对象的长度与原数据的行索引长度不相等时,那么在进行分组时会怎么样呢?接下来,我们使用代码进行测试,具体如下。
In [7]: # 当Series长度与原数据的索引值长度不同时
se = pd.Series(['a', 'a', 'b'])
group_obj = df.groupby(se)
for i in group_obj: # 遍历分组对象
print(i)
Out[7]:
('a', key1 key2 data1 data2
0 A one 2 3
1 A two 3 5)
('b', key1 key2 data1 data2
2 B one 4 6)
上述示例中,首先创建了一个Series对象,它里面共包含三行数据,其中前两行的数据是“a”, 最后一行数据为“b”,然后调用groupby()方法按se对象将df对象拆分成组,由于se只有三行数据,所以它只需要对df对象的前三行数据进行分组,即df对象的前两行分为一组,最后一行分为一组。
值得一提的是,如果Series对象的索引长度与Pandas对象的索引长度不相同时,则只会将部分(具有相同索引长度)数据进行分组,而不会将全部的数据进行分组。
3、通过字典进行分组
当用字典对DataFrame进行分组时,则需要确定轴的方向及字典中的映射关系,即字典中的键为列名,字典的值为自定义的分组名。例如,创建一个5行5列的DataFrame对象,具体代码如下:
In [8]: from pandas import DataFrame, Series
num_df = DataFrame({'a': [1, 2, 3, 4, 5],
'b': [6, 7, 8, 9, 10],
'c': [11, 12, 13, 14, 15],
'd': [5, 4, 3, 2, 1],
'e': [10, 9, 8, 7, 6]})
num_df
Out[8]:
a b c d e
0 1 6 11 5 10
1 2 7 12 4 9
2 3 8 13 3 8
3 4 9 14 2 7
4 5 10 15 1 6
然后,创建一个表示分组规则的字典,其中字典的键为num_df对象的列名称,值为自定义的分组名称,具体代码如下。
In [9]: # 定义分组规则
mapping = {'a':'第一组','b':'第二组','c':'第一组','d':'第三组','e':'第二组'}
mapping
Out[9]:
{'a':'第一组',
'b': '第二组',
'c': '第一组',
'd': '第三组',
'e': '第二组'}
接着调用groupby()方法,在该方法中传入刚创建的字典mapping,将mapping作为分组键拆分num_df对象,具体代码如下。
In [10]: # 按字典分组
by_column = num_df.groupby(mapping, axis=1)
for i in by_column:
print(i)
Out[10]:
('第一组', a c
0 1 11
1 2 12
2 3 13
3 4 14
4 5 15)
('第三组', d
0 5
1 4
2 3
3 2
4 1)
('第二组', b e
0 6 10
1 7 9
2 8 8
3 9 7
4 10 6)
上述示例在拆分num_df时,按照横轴的方向进行分组,将a列、c列数据映射到第一组;将b列、e列数据映射到第二组;将d列数据映射到第三组。从输出结果中可以看出,num_df共分为“第一组”、“第二组”、“第三组”三组。
4、通过函数进行分组
与字典或Series对象相比,使用函数作为分组键会更加灵活,任何一个被当做分组键的函数都会在各个索引值上被调用一次,返回的值会被用作分组名称。
创建一个DataFrame对象,将其行索引的名称设为字符串类型的,具体代码如下。
In [11]: import pandas as pd
df = pd.DataFrame({'a': [1, 2, 3, 4, 5],
'b': [6, 7, 8, 9, 10],
'c': [5, 4, 3, 2, 1]},
index=['Sun', 'Jack', 'Alice', 'Helen', 'Job'])
df
Out[11]:
a b c
Sun 1 6 5
Jack 2 7 4
Alice 3 8 3
Helen 4 9 2
Job 5 10 1
如果以行索引名称的长度进行分组,则长度相同的行索引名称会分为一组,即索引名称长度为3的分为一组,长度为4的分为一组,长度为5的分为一组,共分成三组。接下来,以行索引名称的长度作为分组键,将DataFrame对象的数据拆拆分成三组数据,具体代码如下。
In [12]: groupby_obj = df.groupby(len) # 使用内置函数len进行分组
for group in groupby_obj: # 遍历分组对象
print(group)
Out[12]:
(3, a b c
Sun 1 6 5
Job 5 10 1)
(4, a b c
Jack 2 7 4)
(5, a b c
Alice 3 8 3
Helen 4 9 2)
上述示例中,在调用groupby()方法时传入了内置函数len,表明len函数会对行索引一列执行求长度的操作,调用len函数返回的长度值作为分组名称,一旦发现索引名称的长度值一样,就归类为一组。
从输出结果中可以看出,索引名称长度为3的“Sun”和“Job”归为第一组,长度为4的是“Jack”单独为第二组,长度为5的是“Alice”和“Helen”归为第三组。