【学习笔记】《深入浅出Pandas》第6章:Pandas分组聚合

分组聚合非常常见,我们的数据是扁平化的,没有任何分组信息。比如我们一周多次去同一家便利店,每次会产生一条购买记录,便利店要想统计每个人这周的购买情况,就需要以人来进行分组,然后将每个人的所有金额相加。

6.1 概述

在常规的数据探索方法中,我们将数据集按一定的粒度进行划分,然后以此粒度的聚合数据来了解数据的聚集趋势,以便解决问题。
本节将介绍数据分组的原理及简单操作。

6.1.1 原理

在这里插入图片描述

6.1.2 groupby语法

df.groupby()可以按指定字段对DataFrame进行分组,生成一个分组器对象。
df.groupby(by=None, axis=0, level=None, as_index: bool = True, sort: bool = True,
	group_keys: bool = True, observed: bool = False, dropna: bool = True,
	) -> 'DataFrameGroupBy'

分组操作会按制定规则对数据进行拆分,groupby完成的就是拆分的工作。groupby也能对Series完成分组操作。各个参数意义如下:

  • by:代表分组的依据和方法。如果by是一个函数,则会在数据的索引的每个值去调用它,从而产生值,按照这些值来确定分组。如果传递dict或Series,则将使用dict或Series的值来确定组;如果传递的是ndarray,则按原样使用这些值来确定组。传入字典,键为原索引名,值为分组名。
  • axis:沿行(0)或列(1)进行拆分。也可传入index或columns,默认是0。
  • level:如果轴是多层索引,则按一个或多个特定的层级进行拆分,支持数字、层名及序列。
  • as_index:数据分组聚合输出,默认返回带有组标签的对象作为索引,传False则不会。
  • sort:是否对分组进行排序。默认会排序,传False会让数据分组中第一个出现的值在前。
  • group_keys:调用函数时,将组键添加到索引中进行识别。
  • observed:仅当分组是分类数据时才适用。如果为True,仅显示分类分组数据的显示值;如果为False,显示分类分组数据的所有值。
  • dropna:如果为True,并且组键包含NA值,则NA值及行/列将被删除;如果为False,则NA值也将被视为组中的键。

以上大多参数也适用于Series,如果对DataFrame进行分组,会返回DataFrame-Groupby对象,对Series分组会返回SeriesGroupby对象。

6.1.3 DataFrame应用分组

# 按team分组对应列相加
df.groupby('team').sum()

# 对不同列采用不同的聚合计算方法
df.groupby('team').agg({'Q1': sum, 'Q2': 'count', 'Q3': 'mean', 'Q4': max})

# 对同一列使用不同的计算方法
df.groupby('team').agg({'Q1': [sum, 'std', max],
						'Q2': 'count', 'Q3': 'mean', 'Q4': max})

6.1.4 Series应用分组

# 对df.Q1(Series)按team分组,求和
df.Q1.groupby(df.team).sum()

6.2 分组

本节将针对分组对象介绍什么是分组对象, 分组对象的创建可以使用哪些方法。

6.2.1 分组对象

groupby方法最终输出的是一个分组对象,DataFrameGroupBy和SeriesGroupBy都是分组对象。

接下来介绍创建分组对象的一些方法:

6.2.2 按标签分组

# 指定DataFrame的一列,按这列的去重数据分组
grouped = df.groupby('col')
grouped = df.groupby('col', axis='columns') # 按行

grouped = df.groupby(['col1', 'col2']) # 多列

# get_group查看分组对象单个分组的内容
grouped = df.groupby('team')
grouped.get_group('D') # 查看D

6.2.3 表达式

通过行和列的表达式,生成一个布尔数据的序列,从而将数据分为True和False两组。

# 索引值是否为偶数,分成两组
df.groupby(lambda x:x%2==0).sum()
df.groupby(df.index%2==0).sum() # 同上
"""
		  Q1	  Q2	  Q3	 Q4
False	2322	2449	2823	2699
True	2598	2806	2444	2579
"""

6.2.4 函数分组

by参数可以调用函数来通过计算返回一个分组依据。

# 有一个时间列,按年进行分组,提取年份
df.groupby(df.time.apply(lambda x:x.year)).count()

如果DataFrame和Series函数接收到的参数是数值,想要传入其他列的值,可以使用apply来调用。

# 按照姓名首字母为元音、辅音分组:
def get_letter_type(letter):
	if letter[0].lower() in 'aeiou':
		return '元音'
	else:
		return '辅音'
df.set_index('name').groupby(get_letter_type).sum() # 需要设置name为索引index
"""
		  Q1	  Q2	  Q3	  Q4
元音	1462	1440	1410	1574
辅音	3458	3815	3857	3704
"""

6.2.5 多种方法混合

# eg:先按team分组,再按姓名首字母是否为元音分组
df.groupby(['team', df.name.apply(get_letter_type)]).sum()
# 这里没有先设置索引name,因此需要通过apply来调用
"""
			Q1	 Q2  Q3	 Q4
team name				
 A	 元音	274	197	141	199
	 辅音	792	442	734	584
 B	 元音	309	291	269	218
     辅音	666	927	933	918
 C	 元音	473	488	453	464
	 辅音	583	706	615	663
 D	 元音	273	333	409	486
	 辅音	587	858	832	713
 E	 元音	133	131	138	207
	 辅音	830	882	743	826
"""

6.2.6 用pipe调用分组方法

df.pipe()管道方法,可以调用函数对DataFrame进行处理。而Pandas的groupby是一个函数:

# 使用pipe调用分组函数
df.pipe(pd.DataFrame.groupby, 'team').sum()
"""
		Q1		Q2		Q3		Q4
team				
A		1066	639		875		783
B		975		1218	1202	1136
C		1056	1194	1068	1127
D		860		1191	1241	1199
E		963		1013	881		1033
"""

以此类推。可以传入更多参数。

6.2.7 分组器Grouper

# 分组器语法
pandas.Grouper(key=None, level=None, freq=None, axis=0, sort=False)

df.groupby(pd.Grouper('team'))
# <pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000021CD49D0E10>
# eg
df.groupby('team')
df.groupby(pd.Grouper('team')).sum()

# 如果是时间,可以60s一分组
df.groupby(Grouper(key='date', freq='60s'))

# 轴方向
df.groupby(Grouper(level='data', freq='60s', axis=1))
# 按索引
df.groupby(Grouper(level=1)).sum()
# 多列
df.groupby([pd.Grouper(freq='1M', key='Date'), 'Buyer']).sum()
df.groupby([pd.Grouper('dt', freq='D'), pd.Grouper('other_column')])

# 按轴层级
df.groupby([pd.Grouper(level='second'), 'A']).sum()
df.groupby([pd.Grouper(level=1), 'A']).sum()

6.2.8 索引

groupby操作后分组字段会成为索引,如果不想让它成为索引,可以使用as_index=False进行设置:

df.groupby('team', as_index=False).sum() #team仍然是表头
"""
	team	Q1		Q2		Q3		Q4
0	 A		1066	639		875		783
1	 B		975		1218	1202	1136
2	 C		1056	1194	1068	1127
3	 D		860		1191	1241	1199
4	 E		963		1013	881		1033
"""

6.2.9 排序

groupby操作后分组字段会成为索引,数据会对索引进行排序,如果不想排序,可以使用sort=False进行设置,不排序的情况下会按索引出现的顺序排列:

df.groupby('team', sort=False).sum()
"""
		Q1		Q2		Q3		Q4
team				
E		963		1013	881		1033
C		1056	1194	1068	1127
A		1066	639		875		783
D		860		1191	1241	1199
B		975		1218	1202	1136
"""

6.2.10 小结

groupby可以简单总结为拆开数据、应用数据和合并数据。Pandas提供了很多分组方法,能够灵活自由地进行分组。
另外,对于时序数据的分组, Pandas提供了df.resample()的方法,将会在14章介绍。

6.3 分组对象的操作

上一节完成了分组对象的创建,接下来对分组对象进行操作,获取其相关信息,为最后的数据聚合统计打好基础。

# 创建分组对象
grouped = df.drop('name', axis=1).groupby('team') # 为方便介绍,删去name列

# 应用聚合函数
grouped.sum() # 数据被分为A-E五组

6.3.1 选择分组

分组对象的groups方法会生成一个字典,包含分组名称和分组的内容索引列表,可以使用字典的.keys()方法取出分组名称:

# 查看分组内容
df.groupby('team').groups
# {'A': [2, 7,...], 'B': [6, ...], 'C': [1, ...], 'D': [4, ...], 'E': [0,...}

# 查看分组名
df.groupby('team').groups.keys()
# dict_keys(['A', 'B', 'C', 'D', 'E'])

多层索引:

# 用团队和姓名的首字母分组
grouped2 = df.groupby(['team', df.name.str[0]])
# 选择B组、姓名以A开头的数据
grouped2.get_group(('B', 'A')) 
"""
	name	team	Q1	Q2	Q3	Q4
6	Acob	B		61	95	94	8
83	Albert0	B		85	38	41	17
"""

grouped.indices返回一个字典,其键为组名,值为本组索引的array格式,可以实现对单分组数据的选取:

# 获取分组字典数据
grouped.indices

# 选择A组
grouped.indices['A']
# array([ 2,  7,  9, 16, 17, 20, 22, 34, 40, 42, 51, 67, 70, 71, 75, 79, 88],
      dtype=int64)

6.3.2 迭代分组

分组对象grouped的每个迭代元素是一个元组,每个元素又是由分组名和分组数据内容组成的元组:

# 迭代
for g in grouped:
	print(type(g))
# <class 'tuple'>

# 迭代元素的数据类型
for name, group in grouped:
	print(type(name))
	print(type(group))
# <class 'str'>
# <class 'pandas.core.frame.DataFrame'>

6.3.3 选择列

# 选择数据分组后的某一列,和DataFrame选择列操作一样
grouped.Q1
grouped['Q1'] # 同上

# 选择多列
grouped[['Q1', 'Q2']]

6.3.4 应用函数apply()

分组对象使用apply()调用一个函数,传入的是DataFrame,返回一个经过函数计算后的DataFrame、Series或标量,然后再把数据组合。
对每一列单独操作

# eg1:将数据中的所有元素*2 
df.groupby('team').apply(lambda x: x*2)
"""
	name		team	Q1	Q2	Q3	Q4
0	LiverLiver	EE		178	42	48	128
1	ArryArry	CC		72	74	74	114
2	AckAck		AA		114	120	36	168
3	EorgeEorge	CC		186	192	142	156
4	OahOah		DD		130	98	122	172
...	...			...		...	...	...	...
98	EliEli		EE		22	148	116	182
99	BenBen		EE		42	86	82	148
100 rows × 6 columns
"""
# eg2:实现每组Q1成绩最高的三个
def first_3(df_, c):
	return df_[c].sort_values(ascending=False).head(3)
# 调用函数
df.set_index('name').groupby('team').apply(first_3, 'Q1')	
"""
team  name     
A     Aaron        96
      Henry        91
      Nathan       87
B     Elijah       97
      Harrison     89
      Michael      89
C     Lincoln4     98
      Eorge        93
      Alexander    91
D     Mason        80
      Albie1       79
      Ethan        79
E     Max          97
      Ryan         92
      Liver        89
Name: Q1, dtype: int64
"""

6.3.5 管道方法pipe()

类似于DataFrame的管道方法,分组对象的管道方法接收之前的分组对象,将同组的所有数据应用在方法中,最后返回的是经过函数处理过的返回数据格式。

# eg1:每组最大值和最小值的和
df.groupby('team').pipe(lambda x: x.max()+ x.min())
"""
		name			Q1		Q2		Q3		Q4
team					
A		TylerAaron		105		91		113		105
B		ThomasAcob		99		103		111		101
C		WilliamAdam		99		109		88		118
D		Theodore3Aiden	85		104		105		110
E		ZacharyArlo8	101		99		100		101
"""
# eg2:定义了A组和B组平均值的差值
def mean_diff(x):
    return x.get_group('A').mean() - x.get_group('B').mean()

df.groupby('team').pipe(mean_diff)
"""
Q1    18.387701
Q2   -17.775401
Q3    -3.165775
Q4    -5.577540
dtype: float64
"""

pipe和apply的区别:
pipe接收的是分组对象;而apply接收的是DataFrame。

6.3.6 转换方法transform()

transform()类似于agg(),但不同的是,transform返回的是一个与原始数据形状相同的DataFrame,会将每个数据原来的值一一替换为统计后的值。
使用函数时,分别传入每个分组的子DataFrame的每一列,经过计算后每列返回一个结果,然后再将每组的这列所有值都替换为计算结果,最后以原DataFrame的形式显示所有数据。

# eg:将所有数据替换成分组中的平均成绩
df.groupby('team').transform(np.mean)
"""
		Q1			Q2			Q3			Q4
0	48.150000	50.650000	44.050000	51.650000
1	48.000000	54.272727	48.545455	51.227273
2	62.705882	37.588235	51.470588	46.058824
3	48.000000	54.272727	48.545455	51.227273
4	45.263158	62.684211	65.315789	63.105263
...	...	...	...	...
95	48.000000	54.272727	48.545455	51.227273
96	48.000000	54.272727	48.545455	51.227273
97	48.000000	54.272727	48.545455	51.227273
98	48.150000	50.650000	44.050000	51.650000
99	48.150000	50.650000	44.050000	51.650000
100 rows × 4 columns
"""

6.3.7 筛选方法filter()

使用filter对组作为整体进行筛选,如果满足条件,则整个组会被显示。传入它调用函数中的默认变量为每个分组的DataFrame,经过计算,最终返回一个布尔值(不是布尔序列),为真的DataFrame全部显示。

# 筛选出所在组平均分大于51的成员 (通过计算后只有BD两组,即显示出BD组的所有成员)
df.groupby('team').filter(lambda x: x.mean(1).mean() > 51)
"""
	name	team	Q1	Q2	Q3	Q4
4	Oah	D	65	49	61	86
6	Acob	B	61	95	94	8
8	Reddie	D	64	93	57	72
10	Leo	B	17	4	33	79
11	Logan	B	9	89	35	65
.....
"""

6.3.8 其他功能

df.groupby('team').first() # 组内第一个
df.groupby('team').last() # 组内最后一个
df.groupby('team').ngroups # 5 分组数
df.groupby('team').ngroup() # 分组序号

grouped.backfill()
grouped.bfill()
df.groupby('team').head() # 每组显示前5个
grouped.tail(1) # 每组最后一个
grouped.rank() # 排序值
grouped.fillna(0)
grouped.indices() # 组名:序列组成的字典

# 分组中的第几个值
gp.nth(1) # 第一个
gp.nth(-1) # 最后一个

# 第n个非空项
gp.nth(0, dropna='all') 
gp.nth(0, dropna='any') 

df.groupby('team').shift(-1) # 组内移动
grouped.tshift(1) # 按时间周期移动

# 返回布尔序列
df.groupby('team').any()
df.groupby('team').all()

df.groupby('team').rank() # 每个成员在组内的排名
# 仅SeriesGroupBy可用
df.groupby("team").Q1.nlargest(2) # 每组最大的两个
df.groupby("team").Q1.nsmallest(2) # 每组最小的两个
df.groupby("team").Q1.nunique() # 每组去重数量
df.groupby("team").Q1.unique() # 每组去重值
df.groupby("team").Q1.value_counts() # 每组去重值及数量
df.groupby("team").Q1.is_monotonic_increasing # 每组值是否单调递增
df.groupby("team").Q1.is_monotonic_decreasing # 每组值是否单调递减
# 仅DataFrameGroupBy可用
df.groupby("team").corrwith(df2) # 相关性

6.3.9 小结

本节介绍了对分组的基本操作和一些函数方法,特别注意分辨以下三个方法:

  • apply:最为灵活,可以对数据完成操作后返回各种形式的数据;
  • transform:对数据完成操作后返回原型形状的数据,可以类比为对一个汽车不改变结构,只重新进行涂装;
  • filter:每个分组传入后,通过计算返回这个分组的真假值,真的留下,作为最终结果。

其中transform和filter计算的都是每个分组的整体结果。

6.4 聚合统计

本节主要介绍对分组完的数据的统计工作,这是分组聚合的最后一步。通过最终数据的输出,可以观察到业务变化情况。

6.4.1 描述统计

分组对象如同df.describe(),也支持.describe(),用来对数据的总体进行描述:

# 描述统计,由于列过多,进行转置
df.groupby('team').describe().T
"""
team			A			B			C			D			E
Q1	count	17.000000	22.000000	22.000000	19.000000	20.000000
	mean	62.705882	44.318182	48.000000	45.263158	48.150000
	std		24.155136	32.607896	31.000768	25.886166	33.242767
	min		9.000000	2.000000	1.000000	5.000000	4.000000
	25%		52.000000	11.000000	21.750000	18.000000	11.750000
	50%		64.000000	48.000000	46.000000	50.000000	48.000000
	75%		78.000000	66.000000	77.250000	64.500000	73.250000
	max		96.000000	97.000000	98.000000	80.000000	97.000000
Q2	count	17.000000	22.000000	22.000000	19.000000	20.000000
...
"""

6.4.2 统计函数

对分组对象直接使用统计函数,对分组内的所有数据进行计算,最终以DataFrame形式显示数据。

grouped.mean()
"""
			Q1			Q2			Q3			Q4
team				
A		62.705882	37.588235	51.470588	46.058824
B		44.318182	55.363636	54.636364	51.636364
C		48.000000	54.272727	48.545455	51.227273
D		45.263158	62.684211	65.315789	63.105263
E		48.150000	50.650000	44.050000	51.650000
"""
# 常用的统计方法
df.groupby('team').describe() # 描述性统计
df.groupby('team').sum() 
df.groupby('team').count() # 每组数量,不包括缺失值
df.groupby('team').max()
df.groupby('team').min()
df.groupby('team').size() # 分组数量
df.groupby('team').mean() 
df.groupby('team').median()	# 中位数
df.groupby('team').std()
df.groupby('team').var()
grouped.corr() # 相关性系数
grouped.sem() # 标准误差
grouped.prod() # 乘积
grouped.cummax() # 每组的累计最大值  
grouped.cumsum() # 累加
grouped.mad() # 平均绝对偏差

6.4.3 聚合方法agg()

分组对象的方法.aggregate简写为.agg。它的作用是将分组后的对象给定统计方法,也支持按字段分别给定不同的统计方法。
单个统计方法实现与上个小节相同的功能:

df.groupby('team').aggregate(sum)
df.groupby('team').agg(sum)
grouped.agg(np.size)
grouped['Q1'].agg(np.mean)

使用它主要是为了实现一个字段使用多种统计方法,不同字段使用不同方法

# 每个字段使用多个计算方法
grouped[['Q1', 'Q3']].agg([np.sum, np.mean, np.std])
"""
		Q1								Q3
		sum		mean		std			sum		mean		std
team						
A		1066	62.705882	24.155136	875		51.470588	27.171027
B		975		44.318182	32.607896	1202	54.636364	29.981813
C		1056	48.000000	31.000768	1068	48.545455	27.921194
D		860		45.263158	25.886166	1241	65.315789	21.916642
E		963		48.150000	33.242767	881		44.050000	21.808919
"""
# 不同列使用不同计算方法,且一列用多个计算方法
df.groupby('team').agg({'Q1': ['min', 'max'], 'Q2': 'sum'})
	Q1			Q2
	min	max		sum
team			
A	9	96		639
B	2	97		1218
C	1	98		1194
D	5	80		1191
E	4	97		1013

类似于之前学过的增加新列的方法df.assign(),agg()可以指定新列名字:

# 指定列名,列表是原列和方法
df.groupby('team').Q1.agg(Mean='mean', Sum='sum')
df.groupby('team').agg(Mean=('Q1', 'mean'), Sum=('Q2', 'sum'))
df.groupby('team').agg(
    Q1_max=pd.NamedAgg(column='Q1', aggfunc='max'),
    Q2_sum=pd.NamedAgg(column='Q2', aggfunc='sum'))

如果列名不是有效的Python变量格式,则可以使用以下方法:

df.groupby('team').agg(**{
	'1_max':pd.NamedAgg(column='Q1', aggfunc='max')})

统计方法可以使用函数。在使用函数时,分别传入每个分组后的子DataFrame,会按子DataFrame把这组的所有列组成的序列传到函数里进行计算,最终返回一个固定值。

# 聚合结果使用函数
# lambda/函数,所有方法都可以使用
def max_min(x)return x.max() - x.min()
# 定义函数
df.groupby('team').Q1.agg(Mean='mean', Sum='sum', Diff=lambda x:x.max()-x.min(), Max_min=max_min) 

如果对同一列全使用同一函数,直接写函数名即可:

df.groupby('team').agg(max_min)

6.4.4 时序重采样方法resample()

针对时间序列数据,resample将分组后的时间索引按周期进行聚合计算。14章会详细介绍。

# eg:
idx = pd.date_range(start='1/1/2020', periods=100, freq='T')
df2 = pd.DataFrame(data={'a':[0, 1]*50, 'b':1}, index=idx)
df2
"""
						a	b
2020-01-01 00:00:00		0	1
2020-01-01 00:01:00		1	1
2020-01-01 00:02:00		0	1
2020-01-01 00:03:00		1	1
2020-01-01 00:04:00		0	1
...	...	...
2020-01-01 01:35:00		1	1
2020-01-01 01:36:00		0	1
2020-01-01 01:37:00		1	1
2020-01-01 01:38:00		0	1
2020-01-01 01:39:00		1	1
100 rows × 2 columns
"""

索引是一个时序数据,接下来按a列进行分组,然后按20分钟(由于1分钟是一个周期T,我们传入20T)对b进行求和计算:

# 每20分钟聚合一次
df2.groupby('a').resample('20T').sum()
"""
							a	b
a			
0	2020-01-01 00:00:00		0	10
	2020-01-01 00:20:00		0	10
	2020-01-01 00:40:00		0	10
	2020-01-01 01:00:00		0	10
	2020-01-01 01:20:00		0	10
1	2020-01-01 00:00:00		10	10
	2020-01-01 00:20:00		10	10
	2020-01-01 00:40:00		10	10
	2020-01-01 01:00:00		10	10
	2020-01-01 01:20:00		10	10
"""

6.4.5 组内头尾值

在一个组内,如果希望取第一个值和最后一个值,可以使用以下方法。当然,定义第一个和最后一个需要事先完成。

# 每组第一个
df.groupby('team').first()
"""
		name	Q1	Q2	Q3	Q4
team					
A		Ack		57	60	18	84
B		Acob	61	95	94	8
C		Arry	36	37	37	57
D		Oah		65	49	61	86
E		Liver	89	21	24	64
"""

6.4.6 组内分位数

中位数是二分位,如果在分组中需要看指定分位数据,使用.quantile()来实现。

# 以下均为二分位
df.groupby('team').median() 
df.groupby('team').quantile()
df.groupby('team').quantile(0.5)

6.4.7 组内差值

和DataFrame的diff()一样,分组对象的diff()方法会在组内进行前后数据的差值计算,并以原DataFrame形状返回数据:

grouped.diff()

6.4.8 小结

本节介绍的功能是将分组的结果最终统计并展示出来。我们需要掌握常见的数学统计函数,也可以使用Numpy的大量统计方法。

6.5 数据分箱

数据分箱(离散组合或数据分桶)是一种数据预处理技术,将原始数据分成几个区间,即bin(小箱子),是一种量子化形式。数据分箱可以最大限度减小观察误差的影响。落入给定区间的原始数据值被代表该区间的值(通常是中心值)替换。然后将其替换为针对该区间计算的常规值。具有平滑输入数据的作用,并且在小数据集的情况下还可以减少过拟合。
Pandas主要基于以下两个函数实现连续数据的离散化处理:

  • pandas.cut:根据指定分界点对连续数据进行分箱处理。
  • pandas.qcut:根据指定区间数量对连续数据进行等宽分箱处理。等宽指的是每个区间中的数据量相同。

6.5.1 定界分箱pd.cut()

pd.cut()可以指定区间将数字进行划分。

# eg:将Q1成绩换60分及以上、60分以下进行分类
pd.cut(df.Q1, bins=[0, 60, 100])
"""
0     (60, 100]
1       (0, 60]
2       (0, 60]
3     (60, 100]
4     (60, 100]
        ...    
95      (0, 60]
96      (0, 60]
97    (60, 100]
98      (0, 60]
99      (0, 60]
Name: Q1, Length: 100, dtype: category
Categories (2, interval[int64]): [(0, 60] < (60, 100]]
"""

将分箱结果应用到groupby分组中:

# Series使用
df.Q1.groupby(pd.cut(df.Q1, bins=[0, 60, 100])).count()
"""
Q1
(0, 60]      57
(60, 100]    43
Name: Q1, dtype: int64
"""

# DataFrame使用 
df.groupby(pd.cut(df.Q1, bins=[0, 60, 100])).count()
"""
			name	team	Q1	Q2	Q3	Q4
Q1						
(0, 60]		57		57		57	57	57	57
(60, 100]	43		43		43	43	43	43
"""

以下显示了每个分组的数据。其他参数示例如下:

# 不显示区间,使用数字作为每个箱子的标签,形式如012,n
pd.cut(df.Q1, bins=[0, 60, 100], labels=False)
# 指定标签名
pd.cut(df.Q1, bins=[0, 60, 100], labels=['不及格', '及格'])
# 包含最低部分
pd.cut(df.Q1, bins=[0, 60, 100], include_lowest=True)
# 是否为右闭区间
pd.cut(df.Q1, bins=[0, 60, 100], right=False)

6.5.2 等宽分箱pd.qcut()

pd.qcut()可以指定所分区间的数量,Pandas会自动进行分箱:

# 按Q1成绩分为两组
pd.qcut(df.Q1, q=2)
"""
0      (51.5, 98.0]
1     (0.999, 51.5]
2      (51.5, 98.0]
3      (51.5, 98.0]
4      (51.5, 98.0]
          ...      
95    (0.999, 51.5]
96    (0.999, 51.5]
97     (51.5, 98.0]
98    (0.999, 51.5]
99    (0.999, 51.5]
Name: Q1, Length: 100, dtype: category
Categories (2, interval[float64]): [(0.999, 51.5] < (51.5, 98.0]]
"""
# 查看分组区间
pd.qcut(df.Q1, q=2).unique()
"""
[(51.5, 98.0], (0.999, 51.5]]
Categories (2, interval[float64]): [(0.999, 51.5] < (51.5, 98.0]]
"""

应用到分组中:

# Series使用
df.Q1.groupby(pd.qcut(df.Q1, q=2)).count()
"""
Q1
(0.999, 51.5]    50
(51.5, 98.0]     50
Name: Q1, dtype: int64
"""

# DataFrame使用
df.groupby(pd.qcut(df.Q1, q=2)).count()
"""
	name	team	Q1	Q2	Q3	Q4
Q1						
(0.999, 51.5]	50	50	50	50	50	50
(51.5, 98.0]	50	50	50	50	50	50
"""

其他参数如下:

# 0-54个区间
pd.qcut(range(5), 4)
"""
[(-0.001, 1.0], (-0.001, 1.0], (1.0, 2.0], (2.0, 3.0], (3.0, 4.0]]
Categories (4, interval[float64]): [(-0.001, 1.0] < (1.0, 2.0] < (2.0, 3.0] < (3.0, 4.0]]
"""
pd.qcut(range(5), 4, labels=False)	# array([0, 0, 1, 2, 3], dtype=int64)

# 指定标签名
pd.qcut(range(5), 3, labels=["good", "medium", "bad"])
# 返回箱子标签: array([ 1. , 51.5, 98. ]))
pd.qcut(df.Q1, q=2, retbins=True)
# 分箱位小数位数
pd.qcut(df.Q1, q=2, precision=3)
# 排名分三个层次
pd.qcut(df.Q1.rank(method='first'), 3)

6.5.3 小结

分箱也是一种数据分组方式,经常用于数据建模、机器学习中,更适合离散数据。

6.6 分组可视化

6.6.1 绘图方法plot()

数据分组对象也支持plot(),不过它以分组对象中每个DataFrame或Series为对象,绘制出所有分组图形。默认情况下, 它绘制的是折线图。

# 分组,设置索引name
grouped = df.set_index('name').groupby('team')
# 绘制图形
grouped.plot()

"""
team
A    AxesSubplot(0.125,0.125;0.775x0.755)
B    AxesSubplot(0.125,0.125;0.775x0.755)
C    AxesSubplot(0.125,0.125;0.775x0.755)
D    AxesSubplot(0.125,0.125;0.775x0.755)
E    AxesSubplot(0.125,0.125;0.775x0.755)
dtype: object
"""

生成如下图形:(这里只展示A组)
在这里插入图片描述
还可以通过plot.x()或者plot(kind=‘x’)的形式调用其他形状的图形:

  • plot.line:折线图
  • plot.pie:饼图
  • plot.bar:柱状图
  • plot.hist:直方图
  • plot.box:箱型图
  • plot.area:面积图
  • plot.scatter:散点图
  • plot.hexbin:六边形分箱图

6.6.2 直方图hist()

# 绘制直方图
grouped.hist()
"""
team
A    [[AxesSubplot(0.125,0.551739;0.336957x0.328261...
B    [[AxesSubplot(0.125,0.551739;0.336957x0.328261...
C    [[AxesSubplot(0.125,0.551739;0.336957x0.328261...
D    [[AxesSubplot(0.125,0.551739;0.336957x0.328261...
E    [[AxesSubplot(0.125,0.551739;0.336957x0.328261...
dtype: object
"""

共生成五组直方图(这里只展示A组的直方图):
在这里插入图片描述

6.6.3 箱线图boxplot()

箱线图展示了各个字段的最大值、最小值、分位数等信息。

# 分组箱线图
grouped.boxplot(figsize=(15, 12))
"""
A         AxesSubplot(0.1,0.679412;0.363636x0.220588)
B    AxesSubplot(0.536364,0.679412;0.363636x0.220588)
C         AxesSubplot(0.1,0.414706;0.363636x0.220588)
D    AxesSubplot(0.536364,0.414706;0.363636x0.220588)
E             AxesSubplot(0.1,0.15;0.363636x0.220588)
dtype: object
"""

以上代码将按组显示一个箱线图矩阵(这里展示A、B两组):
在这里插入图片描述
另外,DataFrame的boxplot()方法可以传入分组字段,绘制出每个字段在不同分组中的数据图像:

# 分组箱线图
df.boxplot(by='team', figsize=(15, 10))
"""
array([[<AxesSubplot:title={'center':'Q1'}, xlabel='[team]'>,
        <AxesSubplot:title={'center':'Q2'}, xlabel='[team]'>],
       [<AxesSubplot:title={'center':'Q3'}, xlabel='[team]'>,
        <AxesSubplot:title={'center':'Q4'}, xlabel='[team]'>]],
      dtype=object)
"""

以上代码会按team分组并返回箱线图:
在这里插入图片描述

6.7 本章小结

本章全面介绍了分组聚合的数据操作原理,依次可分为以下部分:

  • 分拆(split):将DataFrame或Series按照一定规则进行分组,生成分组对象,其中包含多个子DataFrame或Series。
  • 应用(apply):对每个组进行操作或数据统计,如算平均数,还可以使用函数进行复杂操作或计算。
  • 合并(combine):将每组的计算结果再拼合起来,最终得到一个DataFrame或Series,或者直接进行可视化显示。
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值