pandas groupby 详解

pandas groupby 详解

在日常的数据分析中,我们常常需要根据**某个字段或多个字段把数据划分为不同的群体进行分析,**比如我们分析全国工资水平,可能就需要分析不同地区工资的情况,在pandas中实现划分功能的函数便是groupby。这篇文章介绍了groupby的基本原理、常用属性及搭配属性(agg、transform、apply),每个知识点均配有示例、部分知识点通过图示加以说明,相信看完之后大家会有所收获。

一、Groupby的基本原理

Groupby的功能用通俗的话来讲,可以分为三步:分组、数据处理和汇总。具体解释为使用指定的规则对DataFrame数据分组,这个规则可以是一个字段(如电商领域将全国的总销售额根据省份字段进行划分,分析各省销售额的变化情况),也可以是函数、字典等。分组后对每个分组的数据做处理(比如求和、求平均值、自定义函数等),最后将处理后的数据汇总合并。

为了加深理解,来看一个使用groupby实现求平均的全过程:

使用的数据如下:

company=["A","B","C"]

data=pd.DataFrame({
"company":[company[x] for x in np.random.randint(0,len(company),5)],
"salary":np.random.randint(5,50,5),
"age":np.random.randint(15,50,5)})

结果:
在这里插入图片描述
下面代码实现了求每个公司的平均工资:

data.groupby('company')['salary'].mean()

结果:

company
A    41.5
B    13.5
C    38.0
Name: salary, dtype: float64

整个过程的图解如下如,先将数据以公司分组,分为A、B、C三个公司,接着对每个分组的salary列求平均,最后将结果拼接起来。
在这里插入图片描述

接下来,再看一些例子,帮助理解。

用字段分组

用字段分组的规则为,以该字段为标准,将字段中相同的值作为一个分组来划分DataFrame数据集,该字段共有多少个不同的值,便会有多少分组。

company=["A","B","C"]

data=pd.DataFrame({
"company":[company[x] for x in np.random.randint(0,len(company),10)],
"salary":np.random.randint(5,50,10),
"age":np.random.randint(15,50,10),
'height':np.random.randint(5,200,10),
'weight':np.random.randint(5,200,10)
}
)

数据:

![

假如我们需要根据公司名称分组该如何实现?

示例 1

#使用company列对数据进行分组
group = data.groupby("company")
print(group)

在这里插入图片描述

<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000017FA1F02DC0>

group = data.groupby(“company”)实现用公司名对DataFrame数据分组,返回的group是一个DataFrameGroupBy 对象,不便于观察,list(group)可以将所有分组以列表展示。

list(group)
[('A',
    company  salary  age  height  weight
  0       A      37   29     138     155
  8       A      27   44      41     185),
 ('B',
    company  salary  age  height  weight
  2       B      47   27     111      96
  4       B      13   19     144      23
  6       B      28   42     189      44
  7       B      44   40     144      90
  9       B      36   35     158     104),
 ('C',
    company  salary  age  height  weight
  1       C      35   48     154     110
  3       C      30   42     189      24
  5       C      20   39      27      47)]

可以看出groupby,将数据按不同公司名(company字段共有三个不同的值A,B,C),分成了三组。

使用函数分组

用函数进行分组,那么分组规则需要在函数中写明。需要注意的是,分组函数(传入groupby的函数)用来决定分组的依据是DataFrame的index值或者columns值(axis=1则columns作为依据,axis=0则index作为依据)

看起来是不是有点迷糊,看完下面的示例,便会明白。

axis=1时的分组介绍

数据同上示例,假如我们需要以列名中是否含有a字符作为分组标准,将数据集分成good和bad组,该如何实现?

示例 2

#分组函数
def gb(x):
    if 'a' in x :
        return 'good'
    else:
        return 'bad'
    
group_function = data.groupby(gb,axis=1)
list(group_function)

结果:

[('bad',
     height  weight
  0     138     155
  1     154     110
  2     111      96
  3     189      24
  4     144      23
  5      27      47
  6     189      44
  7     144      90
  8      41     185
  9     158     104),
 ('good',
    company  salary  age
  0       A      37   29
  1       C      35   48
  2       B      47   27
  3       C      30   42
  4       B      13   19
  5       C      20   39
  6       B      28   42
  7       B      44   40
  8       A      27   44
  9       B      36   35)]

函数gb(x)根据传入参数是否含有‘a’,返回不同值,参数中有‘a’字符则返回good,反之则返回bad。

data.groupby(gb,axis=1)将data中的columns传入gb(x)中进行分组,从结果得知,company ,salary 和age中含有a,为good组,而height和weight,为bad组。

axis=0时的分组情况

假如我们需要以索引数值是否大于5作为分组标准,将数据集分成max和min组,该如何实现?

示例 3

def gb(x):
    if x > 5 :
        return 'max'
    else:
        return 'min'

    
group_function = data.groupby(gb,axis=0)

list(group_function)

结果:

[('max',
    company  salary  age  height  weight
  6       B      28   42     189      44
  7       B      44   40     144      90
  8       A      27   44      41     185
  9       B      36   35     158     104),
 ('min',
    company  salary  age  height  weight
  0       A      37   29     138     155
  1       C      35   48     154     110
  2       B      47   27     111      96
  3       C      30   42     189      24
  4       B      13   19     144      23

函数gb(x)根据传入参数是否大于5,返回不同值,大于则返回max,反之则返回min。

data.groupby(gb,axis=0)将data中的index传入gb(x)中进行分组,从结果得知,0-4,分为了min组,而6-9分为了bad组。

使用字典分组

使用字典进行分时,利用键值对的映射关系作为分组规则。例如我想实现将列1-3分为组1,列4-6分为组2,将如下字典{’列1‘:’组1’,’列2‘:’组1’,’列3‘:’组1’,’列4‘:’组2’,’列5‘:’组2’,’列6‘:’组2’}传入groupby,便可实现。

示例:

示例 4

gb_dict = {0:'min',1:'min',2:'min',3:'min',4:'midle',5:'midle',6:'midle',7:'max',8:'max',9:'max'}
group_dict = data.groupby(gb_dict,axis=0)
list(group_dict)

结果:

[('max',
    company  salary  age  height  weight
  7       B      44   40     144      90
  8       A      27   44      41     185
  9       B      36   35     158     104),
 ('midle',
    company  salary  age  height  weight
  4       B      13   19     144      23
  5       C      20   39      27      47
  6       B      28   42     189      44),
 ('min',
    company  salary  age  height  weight
  0       A      37   29     138     155
  1       C      35   48     154     110
  2       B      47   27     111      96
  3       C      30   42     189      24)]

通过gb_dict,将index0-3分为min组,4-6分为midle组,7-9分为max组。

使用list分组

利用列表(list)分组,与利用DataFrame中的字段进行分组的规则基本一样,分组时将list中相同的值作为一个组,list中有多少个不同的值,便会有多少个组。注意list的长度必须与被分组的DataFrame长度一致。

示例 5

gb_list = ['1','2','3','2','3','1','2','3','2','3']
group_list = data.groupby(gb_list,axis=0)
list(group_list)

结果:

[('1',
    company  salary  age  height  weight
  0       A      37   29     138     155
  5       C      20   39      27      47),
 ('2',
    company  salary  age  height  weight
  1       C      35   48     154     110
  3       C      30   42     189      24
  6       B      28   42     189      44
  8       A      27   44      41     185),
 ('3',
    company  salary  age  height  weight
  2       B      47   27     111      96
  4       B      13   19     144      23
  7       B      44   40     144      90
  9       B      36   35     158     104)]

gb_list中有1、2、3三个不同的值,DataFrame数据被分成了对应的三组。

二、Groupby常用方法和属性

方法功能
mean()求均值
sort_values()排序
value_counts()计算频率
rename_index()修改索引名
count()计数
groups获取所有分组
get_group()获取指定分组

本节所使用的数据集:

company=["西瓜科技","苹果集团","老虎信息","金刚传媒","狗子通信","快乐外卖"]
name = ["张三","李四","王五","张飞","陈六","徐三","欧燕","黄十","六六"]
district = ['长沙','武汉','北京']

data=pd.DataFrame({
"company":[company[x] for x in np.random.randint(0,len(company),20)],
   "name":[name[x] for x in np.random.randint(0,len(name),20)] ,
      "district":[district[x] for x in np.random.randint(0,len(district),20)] ,
"salary":np.random.randint(5000,10000,20),
"age":np.random.randint(15,50,20),
'height':np.random.randint(5,200,20),
'weight':np.random.randint(5,200,20)
})

data.head()

结果 :

在这里插入图片描述

1、计算每个地区的平均工资

data.groupby('district')['salary'].mean().apply(lambda x:'%0.2f'%x)

结果:

district
北京    8580.50
武汉    7875.38
长沙    6338.67
Name: salary, dtype: object

2、计算每个地区的平均工资,不使用group键作为index

data.groupby('district',as_index=Fasle)['salary'].mean()

在这里插入图片描述

3、计算每个地区的平均工资,并将index改为“城市”

data.groupby('district')['salary'].mean().rename_axis('城市')

结果:

城市
北京    8580.500000
武汉    7875.375000
长沙    6338.666667
Name: salary, dtype: float64

4、计算每个地区的平均工资并排序

data.groupby('district')['salary'].mean().sort_values()

结果:

district
长沙    6338.666667
武汉    7875.375000
北京    8580.500000
Name: salary, dtype: float64

5、计算每个地区公司的数量

data.groupby('district')['company'].count()

结果:

district
北京    6
武汉    8
长沙    6
Name: company, dtype: int64

6、计算每个地区公司出现的频次

data.groupby('district')['company'].value_counts()

结果:

district  company
北京        金刚传媒       2
          快乐外卖       1
          老虎信息       1
          苹果集团       1
          西瓜科技       1
武汉        狗子通信       2
          老虎信息       2
          快乐外卖       1
          苹果集团       1
          西瓜科技       1
          金刚传媒       1
长沙        金刚传媒       3
          快乐外卖       2
          老虎信息       1
Name: count, dtype: int64

7、根据district和salary分组,并查看所有分组的情况

data.groupby(['district','salary']).groups

结果:

{('北京', 6329): [4], ('北京', 8032): [17], ('北京', 8799): [8], ('北京', 8930): [3], ('北京', 9512): [0], ('北京', 9881): [13], ('武汉', 5382): [16], ('武汉', 6719): [18], ('武汉', 6850): [6], ('武汉', 8052): [9], ('武汉', 8395): [11], ('武汉', 8904): [2], ('武汉', 9324): [19], ('武汉', 9377): [5], ('长沙', 5259): [12], ('长沙', 5961): [14], ('长沙', 5996): [7], ('长沙', 6011): [10], ('长沙', 6333): [1], ('长沙', 8472): [15]}

8、根据district和salary分组,并查看所有分组的情况,并查看北京薪资为 6329的工作

data.groupby(['district','salary']).get_group(('北京', 6329))

结果:

在这里插入图片描述

三、Groupby的“最佳拍档”

现实情况中有很多问题单独使用Groupby以及它自带的方法无法解决或者解决起来十分繁琐,为此,pandas官方给Groupby配备了许多“最佳拍档“,如agg、tramsform、apply。

3.1 agg

agg是一个聚合函数,能够完成常见的数学统计,如min、max、mean、sum、median、std、var、count等。比如我们要计算不同公司的薪资平均值。

data.groupby('company')['salary'].agg('mean')

结果:

company
快乐外卖    7143.750000
狗子通信    7379.500000
老虎信息    8055.250000
苹果集团    7890.000000
西瓜科技    9138.000000
金刚传媒    7151.166667
Name: salary, dtype: float64

求平均值groupby自带的mean()方法也可以实现,为什么还需要多此一举搞个agg函数?我们接着看。

假如我们不仅要计算不同公司的薪资平均值,同时还要计算最小值、最大值,如果我们用groupby自带的函数实现,需要先分别将需求值求出后再拼接起来,而agg的实现过程就比较简捷。

data.groupby('company')['salary'].agg(['mean','min','max'])

结果:
在这里插入图片描述
利用agg还可以针对不同字段,做不同的计算。假如我们需要计算薪资的平均值、最小值和最大值的同时,还需要计算年龄的最小值和最大值。

data.groupby('company').agg({'salary':['mean','min','max'],'age':['min','max']})

结果:
在这里插入图片描述

3.2 transform

transform的功能和agg类似,区别在于返回值的形状不同。假如单个series使用agg,那么返回是每个分组聚合后的值,值的数量等于分组数,而transfrom返回值的形状和原series一样(DataFrame数据类型类似)。具体理解,请看示例:

假如我们现在需要计算每个公司的平均值,并需要将计算值新增到原始数据集中。使用agg计算平均值,因数据长度问题,返回的结果无法直接插入到原始数据集。所以整个实现过程相对繁琐,过程如下:

salary_avg_dict = data.groupby('company')['salary'].mean().to_dict()
data['salary_avg'] = data['company'].map(salary_avg_dict)
data

结果:
在这里插入图片描述

使用transform来实现,一行代码搞定

data['salary_avg'] = data.groupby('company')['salary'].transform('mean')

结果:

在这里插入图片描述

3.3 apply

apply应该是大家的老朋友了,它相比agg和transform而言更加灵活,能够传入任意自定义的函数,实现复杂的数据操作。介绍了apply的使用,那在groupby后使用apply和之前所介绍的有什么区别呢?

区别是有的,但是整个实现原理是基本一致的。两者的区别在于,对于groupby后的apply,以分组后的子DataFrame作为参数传入指定函数的,基本操作单位是DataFrame,而之前介绍的apply的基本操作单位是Series。还是以一个案例来介绍groupby后的apply用法。

假设我现在需要获取各个公司年龄最大的员工的数据,该怎么实现呢?可以用以下代码实现:

import pandas as pd
import numpy as np

company=["A","B","C"]

data=pd.DataFrame({
"company":[company[x] for x in np.random.randint(0,len(company),5)],
"salary":np.random.randint(5,50,5),
"age":np.random.randint(15,50,5)})

def get_oldest_staff(x):
     df = x.sort_values(by = 'age',ascending=True)
    return df.iloc[-1,:]

数据:
在这里插入图片描述

oldest_staff = data.groupby('company',as_index=False).apply(get_oldest_staff)

结果:
在这里插入图片描述
图解过程:

先将DataFrame数据集按公司名分组,得到三个DataFrame子集后,将各子集传入get_oldest_staff(),筛选出每个子集age最大的数据作拼接得到最后的结果。
在这里插入图片描述

关于apply的使用,这里有个小建议,虽然说apply拥有更大的灵活性,但apply的运行效率会比agg和transform更慢。所以,groupby之后能用agg和transform解决的问题还是优先使用这两个方法,实在解决不了了才考虑使用apply进行操作。

四、Groupby的参数

看完groupby的基本原理后,对groupby有了一定的了解,让我们来看看Groupby的参数,作进一步的学习。

groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, [squeeze](https://so.csdn.net/so/search?q=squeeze&spm=1001.2101.3001.7020)=False

参数说明:

by是指分组依据(列表、字典、函数,元组,Series)

axis:是作用维度(0为行,1为列)

level:根据索引级别分组

sort:对groupby分组后新的dataframe中索引进行排序,sort默认为True代表升序

as_index:在groupby中使用的键是否成为新的dataframe中的索引,默认as_index=True

group_keys:在调用apply,将group键添加到索引中以识别片段

squeeze :减少返回类型的维数,否则返回一个一致的类型

参考:

https://zhuanlan.zhihu.com/p/101284491?utm_source=wechat_session

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值