详解Pandas分组函数groupby

在数据分析时,经常需要将数据分成不同的群组,pandas中的groupby()函数可以完美地完成各种分组操作。

分组是根据DataFrame/Series的某个字段值,将该字段的值相等的行/列分到同一组中,每一个小组是一个新的DataFrame或Series。

groupby()也可以按DataFrame中的多个字段分组,当多个字段的值都相等时分到同一组。

groupby()经常与批处理函数apply()、聚合函数agg()等配合使用,实现对数据的多元处理。

groupby用法和参数介绍

groupby(self, by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=no_default, observed=False, dropna=True):

  • by: 指定根据哪个/哪些字段分组,默认值是None,按多个字段分组时传入列表。by参数可以按位置参数的方式传入。

  • axis: 设置按列分组还是按行分组,0或index表示按列分组,1或columns表示按行分组,默认值为0。

  • level: 当DataFrame的索引为多重索引时,level参数指定用于分组的索引,可以传入多重索引中索引的下标(0,1…)或索引名,多个用列表传入。

level参数不能与by参数同时使用,如果两者同时存在,当by参数传入的是多重索引中的索引,则level不生效,当by参数传入的是DataFrame的列名,则报错。

  • as_index: 分组结果默认将分组列的值作为索引,如果按单列分组,结果默认是单索引,如果按多列分组,结果默认是多重索引。将as_index设置为False可以重置索引(0,1…)。

  • sort: 结果按分组列的值升序排列,将sort设置为False则不排序,可以提升性能。

  • dropna: 默认情况下,分组列的NaN在分组结果中不保留,将dropna设置为False,可以保留NaN分组。

其他三个参数不用关注,group_keys参数在源码中未使用,squeeze参数因为类型不兼容,官方已弃用,observed参数表示重设索引时,保留创建的全NaN行。

分组对象的内部结构

# coding=utf-8
import pandas as pd
import numpy as np

vip_df = pd.DataFrame(
    {'isVip': ['vip', 'svip', 'member', 'vip', 'member', 'vip', 'svip'],
     'gender': ['male', 'female', 'female', 'female', 'male', 'female', 'male'],
     'age': [25, 30, 40, 25, 40, 18, 30],
     'vipLevel': ['LV2', 'LV5', np.nan, 'LV3', 'LV2', 'LV2', 'LV3'],
     'growValue': [180, 425, np.nan, 288, 190, 110, 240]}
)
print(vip_df)
grouped = vip_df.groupby('isVip')
print(grouped)
    isVip  gender  age vipLevel  growValue
0     vip    male   25      LV2      180.0
1    svip  female   30      LV5      425.0
2  member  female   40      NaN        NaN
3     vip  female   25      LV3      288.0
4  member    male   40      LV2      190.0
5     vip  female   18      LV2      110.0
6    svip    male   30      LV3      240.0
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000001C3E81D1370>

groupby()分组得到的是一个DataFrameGroupBy对象,直接打印DataFrameGroupBy对象只能看到它的内存地址,看不到内部的结构。

for name, group in grouped:    print(name)    print(group)
member    isVip  gender  age vipLevel  growValue2  member  female   40      NaN        NaN4  member    male   40      LV2      190.0svip  isVip  gender  age vipLevel  growValue1  svip  female   30      LV5      425.06  svip    male   30      LV3      240.0vip  isVip  gender  age vipLevel  growValue0   vip    male   25      LV2      180.03   vip  female   25      LV3      288.05   vip  female   18      LV2      110.0

for group in grouped:    print(group)    print(type(group), type(group[0]), type(group[1]))
('member',     isVip  gender  age vipLevel  growValue2  member  female   40      NaN        NaN4  member    male   40      LV2      190.0)<class 'tuple'> <class 'str'> <class 'pandas.core.frame.DataFrame'>('svip',   isVip  gender  age vipLevel  growValue1  svip  female   30      LV5      425.06  svip    male   30      LV3      240.0)<class 'tuple'> <class 'str'> <class 'pandas.core.frame.DataFrame'>('vip',   isVip  gender  age vipLevel  growValue0   vip    male   25      LV2      180.03   vip  female   25      LV3      288.05   vip  female   18      LV2      110.0)<class 'tuple'> <class 'str'> <class 'pandas.core.frame.DataFrame'>

DataFrameGroupBy是一个可迭代对象,可以转换成list打印,也可以直接遍历打印出来。遍历出来的是一个个元组,每个元组对应一个分组,元组的第一个元素与分组列里的值对应,元组的第二个元素是分到当前小组的数据,是一个DataFrame。

DataFrameGroupBy对象的内部结构为:[(分组名1, 子DataFrame1), (分组名2, 子DataFrame2), …],相当于groupby()将DataFrame按字段值分成了多个小的DataFrame,然后将字段值和小的DataFrame用元组的方式保存在DataFrameGroupBy对象中。

print(grouped.groups)group_name = [gn for gn in grouped.groups.keys()]print(group_name)group = grouped.get_group(group_name[2])print('-'*40, '\n', group, sep='')
{'member': [2, 4], 'svip': [1, 6], 'vip': [0, 3, 5]}['member', 'svip', 'vip']----------------------------------------  isVip  gender  age vipLevel  growValue0   vip    male   25      LV2      180.03   vip  female   25      LV3      288.05   vip  female   18      LV2      110.0

分组对象的groups属性可以返回分组信息,结果是一个形似字典的对象,由分组名和此分组数据在原DataFrame中的行索引组成。

借用groups可以提取出所有分组的分组名,分组对象的get_group()方法可以返回指定分组名的子DataFrame。

按多重索引分组

vip_multi_df = vip_df.set_index(['isVip', 'gender'])
print('-'*40, '\n', vip_multi_df, sep='')
----------------------------------------
               age vipLevel  growValue
isVip  gender                         
vip    male     25      LV2      180.0
svip   female   30      LV5      425.0
member female   40      NaN        NaN
vip    female   25      LV3      288.0
member male     40      LV2      190.0
vip    female   18      LV2      110.0
svip   male     30      LV3      240.0

# 按多重索引中的指定索引进行分组grouped = vip_multi_df.groupby(level='isVip')print('-'*40, '\n', grouped.mean(), sep='')# 按多重索引中除指定索引之外的索引分组grouped = vip_multi_df.groupby(level=vip_multi_df.index.names.difference(['gender']))print('-'*40, '\n', grouped.mean(), sep='')# 按多重索引中的多个索引分组grouped = vip_multi_df.groupby(level=[0, 1])print('-'*40, '\n', grouped.mean(), sep='')
----------------------------------------              age   growValueisVip                        member  40.000000  190.000000svip    30.000000  332.500000vip     22.666667  192.666667----------------------------------------              age   growValueisVip                        member  40.000000  190.000000svip    30.000000  332.500000vip     22.666667  192.666667----------------------------------------                age  growValueisVip  gender                 member female  40.0        NaN       male    40.0      190.0svip   female  30.0      425.0       male    30.0      240.0vip    female  21.5      199.0       male    25.0      180.0

level参数用于设置按多重索引中的指定索引分组,level传入的方式可以是索引name,也可以是索引在多重索引中的下标,还可以是排除某个索引外的其他索引。指定多个时用列表的方式传入。

grouped = vip_multi_df.groupby('gender')print('-'*40, '\n', grouped.mean(), sep='')grouped = vip_multi_df.groupby(['gender', 'age'])print('-'*40, '\n', grouped.mean(), sep='')
----------------------------------------              age   growValuegender                       female  28.250000  274.333333male    31.666667  203.333333----------------------------------------            growValuegender age           female 18       110.0       25       288.0       30       425.0       40         NaNmale   25       180.0       30       240.0       40       190.0

多重索引中的索引也可以传给groupby()的by参数,分组结果与将多重索引作为DataFrame的列是一样的。

如果用DataFrame的列作为分组列,多重索引会被转换成列保留在结果中。也可以用多重索引中的索引与列一起组合分组,相当于先对DataFrame重设索引再分组。

重置结果的索引

grouped = vip_multi_df.groupby(['isVip', 'gender'])
print('-'*40, '\n', grouped.mean(), sep='')
# 重设索引
grouped = vip_multi_df.groupby(['isVip', 'gender'], as_index=False)
print('-'*40, '\n', grouped.mean(), sep='')
----------------------------------------
                age  growValue
isVip  gender                 
member female  40.0        NaN
       male    40.0      190.0
svip   female  30.0      425.0
       male    30.0      240.0
vip    female  21.5      199.0
       male    25.0      180.0
----------------------------------------
    age  growValue
0  40.0        NaN
1  40.0      190.0
2  30.0      425.0
3  30.0      240.0
4  21.5      199.0
5  25.0      180.0

分组结果的索引默认是分组列的值,将as_index设置为False可以重置索引,相当于先分组再调用reset_index()函数。

结果是否排序

grouped = vip_df.groupby('isVip')
print('-'*40, '\n', grouped.mean(), sep='')
grouped = vip_df.groupby('isVip', sort=False)
print('-'*40, '\n', grouped.mean(), sep='')
----------------------------------------
              age   growValue
isVip                        
member  40.000000  190.000000
svip    30.000000  332.500000
vip     22.666667  192.666667
----------------------------------------
              age   growValue
isVip                        
vip     22.666667  192.666667
svip    30.000000  332.500000
member  40.000000  190.000000

groupby()默认对结果按分组列的值升序排列,如果将sort参数修改为False,则不排序,保留原DataFrame中的顺序,不排序可以提升性能。

是否保留空值

grouped = vip_df.groupby('vipLevel')
print('-'*40, '\n', grouped.mean(), sep='')
grouped = vip_df.groupby('vipLevel', dropna=False)
print('-'*40, '\n', grouped.mean(), sep='')
----------------------------------------
                age  growValue
vipLevel                      
LV2       27.666667      160.0
LV3       27.500000      264.0
LV5       30.000000      425.0
----------------------------------------
                age  growValue
vipLevel                      
LV2       27.666667      160.0
LV3       27.500000      264.0
LV5       30.000000      425.0
NaN       40.000000        NaN

当分组列有空值(NaN)时,默认的分组结果中不保留NaN分组,将dropna参数修改为False,正常保留NaN分组。

提取分组结果的指定列

grouped = vip_df.groupby('isVip', dropna=False)
print('-'*40, '\n', grouped['gender'], sep='')
for name, group in grouped['gender']:
    print(name)
    print(group)
----------------------------------------
<pandas.core.groupby.generic.SeriesGroupBy object at 0x00000285CA1B0790>
member
2    female
4      male
Name: gender, dtype: object
svip
1    female
6      male
Name: gender, dtype: object
vip
0      male
3    female
5    female
Name: gender, dtype: object

从groupby()分组结果中取一列,得到的是一个SeriesGroupBy对象,直接打印SeriesGroupBy对象只能看到它的内存地址,看不到内部的结构。

SeriesGroupBy的内部结构与DataFrameGroupBy相似,SeriesGroupBy对象的内部结构为:[(分组名1, 子Series1), (分组名2, 子Series2), …],因此SeriesGroupBy也可以转换成list打印,也可以遍历取出每一个元素。

grouped = vip_df['gender'].groupby(vip_df['isVip'])for name, group in grouped:    print(name)    print(group)
member2    female4      maleName: gender, dtype: objectsvip1    female6      maleName: gender, dtype: objectvip0      male3    female5    femaleName: gender, dtype: object

也可以先指定需要获取的列,再按DataFrame的另一个列进行分组,结果与先分组再获取指定列相同。

以上就是pandas中groupby()函数的用法介绍和分析,本文都是用DataFrame举例,Series的用法相似,不重复了。

版权声明:本文为博主作者:金戈鐡馬原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/qq_39312146/article/details/129629972

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2024年1月6日
下一篇 2024年1月6日

相关推荐