python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Pandas分组函数groupby

Pandas分组函数groupby的用法详解

作者:金戈鐡馬

在数据分析时,经常需要将数据分成不同的群组,pandas中的groupby()函数可以完美地完成各种分组操作,本文就来介绍一下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):

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

其他三个参数不用关注,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  growValue
2  member  female   40      NaN        NaN
4  member    male   40      LV2      190.0
svip
  isVip  gender  age vipLevel  growValue
1  svip  female   30      LV5      425.0
6  svip    male   30      LV3      240.0
vip
  isVip  gender  age vipLevel  growValue
0   vip    male   25      LV2      180.0
3   vip  female   25      LV3      288.0
5   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  growValue
2  member  female   40      NaN        NaN
4  member    male   40      LV2      190.0)
<class 'tuple'> <class 'str'> <class 'pandas.core.frame.DataFrame'>
('svip',   isVip  gender  age vipLevel  growValue
1  svip  female   30      LV5      425.0
6  svip    male   30      LV3      240.0)
<class 'tuple'> <class 'str'> <class 'pandas.core.frame.DataFrame'>
('vip',   isVip  gender  age vipLevel  growValue
0   vip    male   25      LV2      180.0
3   vip  female   25      LV3      288.0
5   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  growValue
0   vip    male   25      LV2      180.0
3   vip  female   25      LV3      288.0
5   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   growValue
isVip                        
member  40.000000  190.000000
svip    30.000000  332.500000
vip     22.666667  192.666667
----------------------------------------
              age   growValue
isVip                        
member  40.000000  190.000000
svip    30.000000  332.500000
vip     22.666667  192.666667
----------------------------------------
                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

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   growValue
gender                       
female  28.250000  274.333333
male    31.666667  203.333333
----------------------------------------
            growValue
gender age           
female 18       110.0
       25       288.0
       30       425.0
       40         NaN
male   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)

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

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

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

您可能感兴趣的文章:
阅读全文