文章目录

  • 一、groupby使用场景
  • 二、groupby基本原理
  • 三、groupby分组运算
    • 基础聚合操作:只能选择一种聚合操作
    • agg 聚合操作:可以针对同列选择不同聚合方法
    • transform
    • apply
  • 四、groupby分组后去重统计nunique()
  • 五、groupby分组后重命名列名
    • rename()
    • 直接重新命名列名
    • 重命名所有的列名:add_prefix() /add_suffix()

一、groupby使用场景

在日常数据分析中,经常需要将数据根据某个(多个)字段划分为不同群体(group)进行分析,如电商领域将全国的总销售额根据省份进行划分,分析各省销售额的变化情况,社交领域将用户根据画像(性别、年龄)进行细分,研究用户的使用情况和偏好,如电信诈骗领域,研究诈骗用户与非诈骗用户的语音通话行为等。在Pandas中,上述的数据处理操作主要运用groupby完成,这篇文章就介绍一下groupby的基本原理、对应的agg、transform和apply操作、groupby后的去重统计及重命名列名。

二、groupby基本原理

模拟的样本数据

import pandas as pdimport numpy as np#主叫号码 calling_nbr=["13389012374","13389012375","13389012376","13389012377","13389012379","13389012378","16758439532","16758439533","16758439534","16758439535","16758439536","16758439537"] #对端号码called_nbr=["14374397533","14374397533","14374397533","15926372438","15926372439"] #通话时间start_date=["20230404","20230406","20230408"]data=pd.DataFrame({"calling_nbr":[calling_nbr[x] for x in np.random.randint(0,len(calling_nbr),20)],"called_nbr":[called_nbr[x] for x in np.random.randint(0,len(called_nbr),20)],"calling_duration":np.random.randint(10,120,20),"start_date":[start_date[x] for x in np.random.randint(0,len(start_date),20)]})data


将上面的样本数据集按照start_date字段划分:

group=data.groupby(["start_date"])group

会得到一个DataFrameGroupBy对象

如何理解DataFrameGroupBy?对data进行groupby后发生了什么,ipython所返回的结果是其内存地址,并不利于直观地理解,为了看看group内部究竟是什么,把group转为list的形式看看:

list(group)


转成列表的形式后,可以看到,列表由三个元组组成,每个元组中,第一元素是组别,这里是按时start_date进行分组,所以最后分为了20230404、20230406、20230408,第二个元素的是对应组别下的DataFrame。

总结:groupby的过程就是将原有的DataFrame按照groupby的字段(这里是start_date),划分为若干个分组DataFrame,被分为多少个组就有多少个分组DataFrame。所以说,在groupby之后的一系列操作(如agg、apply等),均是基于子DataFrame的操作。理解了这点,也就基本摸清了Pandas中的groupby操作的主要原理。


三、groupby分组运算

聚合操作是groupby后非常常见的操作,聚合操作可以用来求和、均值、最大值、最小值等,下面的表格列出了Pandas中常见的聚合操作。

函数作用
min最小值
max最大值
sum求和
mean均值
median中位数
std标准差
var方差
count计数

基础聚合操作:只能选择一种聚合操作

单列分组

1、单组其他所有列
按calling_nbr分组(groupby),获取数据表其他所列的统计值。因为called_nbr和start_date不是数字类型,所以会自动忽略。

data.groupby('calling_nbr').mean()

2、单组单列
按calling_nbr分组(groupby),仅获取calling_duration的平均值。

data.groupby('calling_nbr')['calling_duration'].mean()

3、单组多列
按calling_nbr分组(groupby),获取calling_duration和calling_fee列的平均值。

data.groupby('calling_nbr')[['calling_duration','calling_fee']].mean()

多列分组
1、多组其他所有列
按calling_nbr和start_date分组(groupby),获取数据表其他所列的统计值。只针对数字类型的列。

data.groupby(['calling_nbr','start_date']).mean()

2、多组单列
按calling_nbr和start_date分组(groupby),仅获取calling_duration的平均值。

data.groupby(['calling_nbr','start_date'])['calling_duration'].mean()

3、多组多列
按calling_nbr和start_date分组(groupby),获取calling_duration和calling_fee列的平均值。

data.groupby(['calling_nbr','start_date'])[['calling_duration','calling_fee']].mean()

agg 聚合操作:可以针对同列选择不同聚合方法

单列分组

1、单组单列

一种聚合方法,多种写法

比如求主叫号码通话时长的平均值

data.groupby('calling_nbr').agg({'calling_duration':'mean'})data.groupby('calling_nbr')['calling_duration'].agg(['mean'])

多种聚合方法,多种写法

比如:求主叫号码通话时长的平均值,总和和标准差

data.groupby('calling_nbr').agg({'calling_duration':['mean','sum','std']})data.groupby('calling_nbr').agg({'calling_duration':['mean',np.sum,'std']})data.groupby('calling_nbr')['calling_duration'].agg([np.mean,np.sum,np.std])data.groupby('calling_nbr')['calling_duration'].agg([np.mean,'sum',np.std])

2、单组多列

一种聚合方法,多种写法

data.groupby('calling_nbr').agg({'calling_duration':['mean'],'calling_fee':['sum']})data.groupby('calling_nbr')[['calling_duration','calling_fee']].agg(['mean'])

多种聚合方法,多种写法

data.groupby('calling_nbr').agg({'calling_duration':['mean','sum','std'],'calling_fee':['mean','sum','std']})data.groupby('calling_nbr').agg({'calling_duration':['mean',np.sum,'std'],'calling_fee':['mean',np.sum,'std']})data.groupby('calling_nbr')[['calling_duration','calling_fee']].agg([np.mean,np.sum,np.std])data.groupby('calling_nbr')[['calling_duration','calling_fee']].agg([np.mean,'sum',np.std])

多列分组

1、多组单列

一种聚合方法,多种写法

比如求主叫号码当天通话时长总和

data.groupby(['calling_nbr','start_date']).agg({'calling_duration':'sum'})data.groupby(['calling_nbr','start_date'])['calling_duration'].agg(['sum'])

多种聚合方法,多种写法

data.groupby(['calling_nbr','start_date']).agg({'calling_duration':'sum','calling_duration':np.mean})data.groupby(['calling_nbr','start_date'])['calling_duration'].agg(['sum',np.mean])

2、多组多列

一种聚合方法,多种写法

data.groupby(['calling_nbr','start_date']).agg({'calling_fee':'mean','calling_duration':'mean'})data.groupby(['calling_nbr','start_date'])[['calling_duration','calling_fee']].agg(['mean'])

多种聚合方法,多种写法

比如求主叫号码不同天的主叫次数和平均通话时长,代码如下:

data.groupby(['calling_nbr','start_date']).agg({'calling_fee':'count','calling_duration':'mean'})

比如求主叫号码不同天的主叫次数与通话时长的总和、标准差和平均值

data.groupby(['calling_nbr','start_date']).agg({'calling_fee':['sum','count','mean'],'calling_duration':['sum','count','mean']})data.groupby(['calling_nbr','start_date'])[['calling_duration','calling_fee']].agg(['sum','count','mean'])

注意:对于单组单列的分组来说,基础聚合操作与agg聚合操作,得到的对象有什么不同??

  • 基础聚合操作,由于只选择一列数据,所以生成的对象是Series数据结构。
  • agg聚合操作后生成的对象是DataFrame数据结构。
a=data.groupby('calling_nbr')['calling_duration'].mean()print(type(a))

a=data.groupby('calling_nbr')['calling_duration'].agg(['mean'])a=data.groupby('calling_nbr').agg({'calling_duration':'mean'})print(type(a))

transform

在agg 聚合中,我们学会了如何求不同主叫号码同一天的平均通话时长,agg 聚合操作形成的新DataFrame,如果现在需要在原数据集中新增加一列avg_calling_duration,代表主叫号码同一天的平均通话时长(相同一天具有一样的平均通话时长)。

如何实现?

1、正常过程:先求得主叫号码不同天的平均通话时长,然后按照主叫号码和通话时间的对应关系填充到对应的位置

avg_calling_duration=data.groupby(['calling_nbr','start_date'])['calling_duration'].mean().rename("avg_calling_duration").reset_index()data_1 = data.merge(mean_calling_duration) data_1

2、使用transform函数,仅需要一行代码

data['avg_calling_duration']=data.groupby(['calling_nbr','start_date'])['calling_duration'].transform('mean')data

transform与agg对比

  • 对agg而言,会计算得到不同号码当天的通话时长的均值并直接返回。
  • 对transform而言,则会对第每一条数据求得相应的结果,同一组内的样本会有相同的值,组内求完均值后会按照原索引的顺序返回结果。

可以看下图agg与transform过程图解:

apply

对于groupby后的apply,以分组后的子DataFrame作为参数传入指定函数的,基本操作单位是DataFrame。

如果现在需要获取当天主叫与同一个被叫号码的通话次数top1的数据,如何实现?

#统计主叫与同一个被叫号码的通话次数data['calling_called_max']=data.groupby(['calling_nbr','called_nbr','start_date'])['calling_nbr'].transform('count')#统计主叫与同一个被叫号码的通话次数top1def get_max_calling_called_num(x):df=x.sort_values(by='calling_called_max',ascending=True)return df.iloc[-1:]get_max_calling_called_num_data=data.groupby(['calling_nbr','called_nbr','start_date'],as_index=False).apply(get_max_calling_called_num)

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


四、groupby分组后去重统计nunique()

nunique():统计groupby()分组后组内不同值的个数,比如适用于统计主叫号码拨打去重后的对端号码个数。

举例1
要求:统计主叫号码拨打去重后的对端号码个数。

#方法二选一。data.groupby('calling_nbr')['called_nbr'].nunique()data.groupby('calling_nbr').agg({ "called_nbr": pd.Series.nunique})

unique()的区别:unique()方法返回的是去重之后的不同值

data.groupby('calling_nbr')['called_nbr'].unique()


五、groupby分组后重命名列名

groupby分组后对某列进行聚合计算,需要对聚合计算后的新列进行重命名。

  • 第一:先观察数据类型,要转变为DataFrame数据类型,再重命名。
  • 第二:查看groupby后DataFrame.columns是什么格式。
  • 第三:再选择合适的方法进行重命名。

rename()

rename()重命名,reset_index()将DataFrame的索引转成DataFrame的列。

1、对单列进行重命名。

举例1

data_1=data.groupby('calling_nbr').agg({'calling_duration':'mean'})print(type(data_1)) #查看数据类型print(data_1.colunms) #查看列名格式

我们可以看到,是DataFrame数据类型且DataFrame的列名格式是含单元素的列表,可以直接使用rename重命名。

data_1.rename(columns={'calling_duration':'calling_duration_avg'}).reset_index()

举例2

data_1=data.groupby('calling_nbr')['calling_duration'].mean()print(type(data_1)) #查看数据类型

我们可以看到,对单列进行聚合得到是Series数据类型,因此先转换成DataFrame数据类型,再重命名。

data_1.to_frame() #将Series数据类型转成DataFrame数据类型。
data_1.rename(columns={'calling_duration':'calling_duration_avg'}).reset_index()

注意:如果是对单列进行聚合得到是Series数据类型,要先转换成DataFrame数据类型,再重命名。

2、对多列进行重命名

举例1

#两种常用的聚合方法,二选一。data_1=data.groupby('calling_nbr')[['calling_duration','calling_fee']].mean()data_1=data.groupby('calling_nbr').agg({'calling_duration':'mean','calling_fee':'mean'})

使用这两种方法都可以对多列进行聚合计算,groupby聚合计算后的数据情况如下。

print(type(data_1)) #查看数据类型


都是DataFrame数据类型。

print(data_1.columns) #查看列名格式


DataFrame的列名格式都是含单元素的列表。

我们可以看到,是DataFrame数据类型且DataFrame的列名格式是含单元素的列表,可以直接使用rename()重命名。

data_1.rename(columns={'calling_duration':'calling_duration_avg','calling_fee':'calling_fee_avg'})

举例2

data_1=data.groupby('calling_nbr')[['calling_duration','calling_fee']].agg(['mean'])data_1

print(type(data_1)) #查看数据类型


是DataFrame数据类型。

print(data_1.columns) #查看列名格式

查看dataframe的列名格式,发现聚合后的列名是MultiIndex类型。此时,必须通过元组的复合索引方式,才能有效提取列的信息。

data_1.columns.values


查看列名的元素,我们可以看到每个列都是一个元组。通过下面两种方法都可以重命名:

  • 方法一:通过遍历columns的方式,将MultiIndex的一级和二级索引拼接在一起,作为data的新列名。
data_1.columns=[i[0] + "_" + i[1] for i in data_1.columns]data_1

  • 方法二:只需要将元组拼接成字符串,然后替换原来的列名就可以了。
data_1.columns = ['_'.join(col).strip() for col in data_1.columns.values]data_1

直接重新命名列名

使用”dataframe.columns=[‘新列名’]或dataframe.columns=pd.Series([‘新列名’])” 重命名。

1、要求:对单列进行重命名。

举例1

#对单列进行聚合计算的两种方式,二选一data_1=data.groupby('calling_nbr').agg({'calling_duration':'mean'})data_1=data.groupby('calling_nbr')['calling_duration'].mean()data_1.to_frame()#生成是Series数据类型,因此使用to_frame()转换成DataFrame数据类型。
#直接重命名的两种方法,二选一。data_1.columns = pd.Series(['calling_duration_avg'])data_1.columns = ['calling_duration_avg']#reset_index()将DataFrame的索引转成DataFrame的列。data_1.reset_index(inplace=True) 

2、要求:对多列进行重命名。

举例1

#对多列进行聚合计算的两种方式,二选一data_1=data.groupby('calling_nbr')[['calling_duration','calling_fee']].mean()data_1=data.groupby('calling_nbr').agg({'calling_duration':'mean','calling_fee':'mean'})
#直接重命名的两种方法,二选一。data_1.columns = pd.Series(['calling_duration_avg','calling_fee_avg'])data_1.columns = ['calling_duration_avg','calling_fee_avg']#reset_index()将DataFrame的索引转成DataFrame的列。data_1.reset_index(inplace=True) 

举例2

data_1=data.groupby('calling_nbr')[['calling_duration','calling_fee']].agg(['mean'])
print(data_1.columns)#查看列名格式


聚合后的列名是MultiIndex类型,不能使用”dataframe.columns=[‘新列名’]或dataframe.columns=pd.Series([‘新列名’])” 直接重命名。要多做一个步骤的处理,可以通过遍历columns的方式,先将MultiIndex的一级和二级索引拼接在一起,作为data的新列名或者先将元组拼接成字符串,再替换原来的列名就可以了。(具体例子见rename()目录下“对多列进行重命名”的举例2

注意

  • 对单列进行重命名,注意基础聚合方法:data_1=data.groupby(‘calling_nbr’)[‘calling_duration’].mean()生成的是Series数据类型。
  • 对多列进行重命名,注意agg聚合方法写法:data_1=data.groupby(‘calling_nbr’)[[‘calling_duration’,‘calling_fee’]].agg([‘mean’]),生成的列名格式是MultiIndex类型,不能直接使用rename方法和”dataframe.columns=[‘新列名’]或dataframe.columns=pd.Series([‘新列名’])” 重命名。

重命名所有的列名:add_prefix() /add_suffix()

在处理大量dataframe数据时,我们可能对所有列名进行重命名操作。

pandas.DataFrame.add_prefix() 函数来修改列名的前缀
pandas.DataFrame.add_suffix() 函数来修改列名的后缀
聚合后的列名是MultiIndex类型,不能使用这种方法。

#三种方法达到效果是一致data_1=data.groupby('calling_nbr').agg({'calling_duration':'mean','calling_fee':'mean'}).add_prefix('new_')data_1=data.groupby('calling_nbr')[['calling_duration','calling_fee']].mean().add_prefix('new_')data_1=data.groupby('calling_nbr').mean().add_prefix('new_')

参考文章:

https://zhuanlan.zhihu.com/p/101284491
https://blog.csdn.net/qq_39657585/article/details/114789396
https://blog.csdn.net/yeshang_lady/article/details/105345653
https://blog.csdn.net/weixin_43609275/article/details/86220907
https://blog.csdn.net/weixin_39923556/article/details/123002620
https://blog.csdn.net/pz789as/article/details/106059610
https://zhuanlan.zhihu.com/p/358006128