在日常数据分析中,经常需要将数据根据某个(多个)字段划分为不同群体(group)进行分析,如电商领域将全国的总销售额根据省份进行划分,分析各省销售额的变化情况,社交领域将用户根据画像(性别、年龄)进行细分,研究用户的使用情况和偏好,如电信诈骗领域,研究诈骗用户与非诈骗用户的语音通话行为等。在Pandas中,上述的数据处理操作主要运用groupby完成,这篇文章就介绍一下groupby的基本原理、对应的agg、transform和apply操作、groupby后的去重统计及重命名列名。
模拟的样本数据
import pandas as pd
import 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
group=data.groupby(["start_date"])
group
如何理解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后非常常见的操作,聚合操作可以用来求和、均值、最大值、最小值等,下面的表格列出了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()
单列分组
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聚合操作,得到的对象有什么不同??
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))
在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过程图解:
对于groupby后的apply,以分组后的子DataFrame作为参数传入指定函数的,基本操作单位是DataFrame。
如果现在需要获取当天主叫与同一个被叫号码的通话次数top1的数据,如何实现?
#统计主叫与同一个被叫号码的通话次数
data['calling_called_max']=data.groupby(['calling_nbr','called_nbr','start_date'])['calling_nbr'].transform('count')
#统计主叫与同一个被叫号码的通话次数top1
def 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进行操作。
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分组后对某列进行聚合计算,需要对聚合计算后的新列进行重命名。
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
查看列名的元素,我们可以看到每个列都是一个元组。通过下面两种方法都可以重命名:
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)
注意
在处理大量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