关于Pandas版本: 本文基于 pandas2.1.2 编写。
关于本文内容更新: 随着pandas的stable版本更迭,本文持续更新,不断完善补充。
Pandas稳定版更新及变动内容整合专题: Pandas稳定版更新及变动迭持续更新。
DataFrame.groupby()
方法用于使用映射器或指定的列,对 DataFrame
进行数据分组,可以实现类似Excel的数据透视、分类汇总的效果。
DataFrame.groupby()
的底层逻辑是:
by
参数指定)分割 DataFrame
为 groupby
对象;
DataFrame.groupby()
会自动的将聚合后的数据合并为新的 DataFrame
。?? 注意:
1、数据分割实际上是基于行索引进行的。
2、你指定的分割依据(分组依据)需要尽可能的,和行索引等长。
DataFrame.groupby (by=None, axis=_NoDefault.no_default, level=None, as_index=True, sort=True, group_keys=True, observed=_NoDefault.no_default, dropna=True)
groupby
对象。**by:**mapping, function, label, pd.Grouper or list of such
by
参数用于指定分组的依据(即分割DataFrame
的依据):
DataFrame
。例1
DataFrame
行索引等长的 Series
时 例4
Series
里的值,应该是可以有效分组的;Series
建议和 DataFrame
行索引等长;Series
必须和 DataFrame
行索引不等长,会自动进行对齐(.align()
),二者数据量如果差距太大,会产生很多缺失值,造成分组后计算不精准的结果。
axis: {0 or ‘index’, 1 or ‘columns’}, default 0
axis
参数用于指定分割方向(可以参照此图,了解什么是分割 数据分组流程示意图):
? 弃用于 Pandas 2.1.0 :
axis=1
在Pandas 2.1.0
版本标记为弃用。使用以下替代方法实现:
- 先 转置 再 分组
frame.T.groupby(...)
例8这样做的目的是:使分组后数据尽可能的保持更多的操作性和可读性。
level: int, level name, or sequence of such, default None 例9
如果 DataFrame
具有多层索引,可以用level
参数指定级别的编号或名称,不能和 by
参数同时使用。
as_index: bool, default True 例10
as_index
参数控制是否将组标签作为索引返回。
as_index=True
时,组标签将成为输出 DataFrame
的索引。as_index=False
时,组标签不会成为索引,而是返回一个类似 SQL
风格的输出。sort: bool, default True 例11
sort
参数用于控制是否对分组名进行排序,默认 sort=True
会对组名进行排序。此参数不会影响每个组内观察值的顺序:
📌 改动于 Pandas 2.0.0 :
自 Pandas2.0.0 开始,当使用
有序分类
数据进行分组,当sort=False
将不再对其进行排序。在之前的版本中(2.0.0 之前),即使设置了
sort=False
,对于有序分类,仍然会对分类进行排序。而在 2.0.0 版本中,这个行为发生了变化,即设置sort=False
不再影响有序分类的排序,保留原始顺序。这个改动的目的是为了提供更一致的行为,使得在使用
sort=False
时,无论分类是否有序,都不再对分类进行排序,从而减少用户的困惑。
group_keys: bool, default True
分组的键指的是 groupby
对象 各分组的行索引。
当使用 groupby
调用 apply
与 by
参数生成分组结果时, 并且 结果行索引数量 和 groupby
对象分组数量 不匹配(不匹配则意味着无法汇总),则默认会将 groupby
对象各分组的行索引 和 结果行索引 组合为多层行索引,以便观察。 例12
group_keys=True
时(默认值),分组的键会作为结果的索引。这意味着返回的对象会是一个带有分组键的多层次索引的 DataFrame
(或者 Series
,具体取决于你应用 groupby
的对象是 DataFrame
还是 Series)。group_keys=False
时,分组的键不会作为索引,而是返回一个不带有分组键的普通 DataFrame
(或者 Series
)。 例12-3📌 **改动于 Pandas 1.5.0 :
当使用
groupby
调用apply
与by
参数生成分组结果时,并且结果行索引数量 和groupby
对象分组数量 不匹配(不匹配则意味着无法汇总),则需要显式指定group_keys
是否包含组键。
📌 **改动于 Pandas 2.0.0 :
group_keys
默认为True
。
observed: bool, default False
观察值是指在实际数据中存在的唯一分类值。当应用 groupby
操作时,有时可能会遇到分类分组器中存在的分类值,但在实际数据中并未出现的情况。observed
参数允许你控制在分组操作中如何处理这些未观察到的分类值:
? 弃用于 Pandas 2.1.0 :
自2.1.0版本以来已弃用:在panda的未来版本中,默认值将更改为True。
dropna: bool, default True 例13
dropna
用于控制 groupby
对象的行数索引是否可以包含缺失值:
True
,并且组键包含缺失值,则将 缺失值与行/列一起删除。False
,则保留缺失值。?? 注意 :
舍弃缺失值的动作,是在分组前完成的,也就是说,在生成
groupby
分组对象的时候,就已经没有缺失值了。例13-2
?? 相关方法
Convenience method for frequency conversion and resampling of time series.
测试文件下载:
本文所涉及的测试文件,如有需要,可在文章顶部的绑定资源处下载。
若发现文件无法下载,应该是资源包有内容更新,正在审核,请稍后再试。或站内私信作者索要。
例1:如果没有指定聚合计算方法,分组结果将是一个 groupby
对象,只能通过 for
循环观察数据内容
import pandas as pd
# 读取一个演示文件
df = pd.read_excel("../../../../数据集/团队成员季度销售额.xlsx")
# 观察数据内容
df.sample(5)
姓名 | 片区 | 1季度 | 2季度 | 3季度 | 4季度 | year | |
---|---|---|---|---|---|---|---|
18 | 邹小琴 | 华南 | 4038 | 6053 | 4691 | 1178 | 2023 |
4 | 左美华 | 华南 | 579 | 2944 | 3408 | 7365 | 2023 |
84 | 祝成云 | 华东 | 1186 | 3155 | 1975 | 1922 | 2023 |
47 | 紫薇 | 华北 | 1728 | 4802 | 1857 | 6988 | 2023 |
40 | 邹博文 | 华北 | 3434 | 5814 | 1334 | 9061 | 2023 |
grouped = df.sample(5).groupby(by="片区")
grouped
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000001F9031CB800>
由上面结果可以发现,无法直接观察 GroupBy
对象
for
循环观察分组内容for group_name, group_data in grouped:
print(f"Group: {group_name}")
print(group_data)
print("\n")
Group: 华中
姓名 片区 1季度 2季度 3季度 4季度 year
73 庄海彬 华中 2534 968 4128 5454 2023
59 卓小珍 华中 3274 5837 3025 7993 2023
Group: 华北
姓名 片区 1季度 2季度 3季度 4季度 year
95 张华丽 华北 4584 1072 3029 8976 2023
48 紫湉 华北 3046 3918 6908 6444 2023
Group: 华南
姓名 片区 1季度 2季度 3季度 4季度 year
97 王娟 华南 661 6784 3660 8621 2023
例2:分组后,指定汇总计算方式,即可自动完成最终的合并过程,并生成新的 DataFrame
import pandas as pd
# 读取一个演示文件
df = pd.read_excel("../../../../数据集/团队成员季度销售额.xlsx")
# 观察数据内容
df.sample(5)
姓名 | 片区 | 1季度 | 2季度 | 3季度 | 4季度 | year | |
---|---|---|---|---|---|---|---|
71 | 庄骏 | 华中 | 4713 | 2588 | 6480 | 6224 | 2023 |
27 | 邹立文 | 华北 | 1547 | 4927 | 4693 | 8526 | 2023 |
38 | 邹凤艳 | 华北 | 2055 | 5330 | 6468 | 7229 | 2023 |
0 | 左院梅 | 华南 | 1491 | 1083 | 5000 | 9461 | 2023 |
77 | 祝艳斌 | 华东 | 5161 | 1639 | 1291 | 7528 | 2023 |
grouped = df.groupby(by="片区").sum()
grouped
姓名 | 1季度 | 2季度 | 3季度 | 4季度 | year | |
---|---|---|---|---|---|---|
片区 | ||||||
华东 | 转身,泪倾城筑梦祝艳斌祝艳祝小娟祝仙花祝卫平祝玛拉初祝海英祝成云竹林听雨竹合竹猪哥传说诸子燕... | 53720 | 62152 | 77185 | 85271 | 36414 |
华中 | 梓英籽艺子鱼子墨子岚子和子菡资格卓越卓小珍卓向吴追影追忆追梦状之元巍笑吧庄臻庄永奇庄晓运庄... | 86249 | 119934 | 119501 | 153889 | 54621 |
华北 | 邹美金邹灵美邹林华邹立文邹黎邹娟利邹杰邹建军邹建华邹吉宏邹积杰邹海利邹贵滨邹广坤邹凤艳邹昌乐... | 72776 | 125467 | 120002 | 186550 | 58667 |
华南 | 左院梅左艳艳左薇左娜左美华左梅香左火英左儿左成娟醉霖~棉花糖最终幻想走向幸福邹邹邹子龙邹忠珠... | 60822 | 109654 | 89342 | 143896 | 52598 |
以片区为分组依据,并传递了求和方法后,姓名列因为是字符串,所以相当于拼接。1季度、2季度、3季度、4季度、year等列,完成了求和计算。
grouped = df.groupby(by="片区").sum()
grouped
姓名 | 1季度 | 2季度 | 3季度 | 4季度 | year | |
---|---|---|---|---|---|---|
片区 | ||||||
华东 | 转身,泪倾城筑梦祝艳斌祝艳祝小娟祝仙花祝卫平祝玛拉初祝海英祝成云竹林听雨竹合竹猪哥传说诸子燕... | 53720 | 62152 | 77185 | 85271 | 36414 |
华中 | 梓英籽艺子鱼子墨子岚子和子菡资格卓越卓小珍卓向吴追影追忆追梦状之元巍笑吧庄臻庄永奇庄晓运庄... | 86249 | 119934 | 119501 | 153889 | 54621 |
华北 | 邹美金邹灵美邹林华邹立文邹黎邹娟利邹杰邹建军邹建华邹吉宏邹积杰邹海利邹贵滨邹广坤邹凤艳邹昌乐... | 72776 | 125467 | 120002 | 186550 | 58667 |
华南 | 左院梅左艳艳左薇左娜左美华左梅香左火英左儿左成娟醉霖~棉花糖最终幻想走向幸福邹邹邹子龙邹忠珠... | 60822 | 109654 | 89342 | 143896 | 52598 |
grouped = df.groupby(by="片区").agg(
{"1季度": "max", "2季度": "mean", "3季度": "sum", "4季度": "min"} # 最大值 # 平均值 # 总和
) # 最小值
grouped
1季度 | 2季度 | 3季度 | 4季度 | |
---|---|---|---|---|
片区 | ||||
华东 | 5161 | 3452.888889 | 77185 | 1066 |
华中 | 5308 | 4442.000000 | 119501 | 1684 |
华北 | 4584 | 4326.448276 | 120002 | 1136 |
华南 | 5070 | 4217.461538 | 89342 | 1055 |
grouped.axes
[Index(['华东', '华中', '华北', '华南'], dtype='object', name='片区'),
Index(['1季度', '2季度', '3季度', '4季度'], dtype='object')]
例3:使用字典数据分组(直接把行索引里的值用字典的方式,指定为分组依据)
import pandas as pd
# 读取一个演示文件
df = pd.read_excel("../../../../数据集/团队成员季度销售额.xlsx")
# 将 片区列,设置为索引
df.set_index("片区", inplace=True)
# 观察数据内容
df.sample(5)
姓名 | 1季度 | 2季度 | 3季度 | 4季度 | year | |
---|---|---|---|---|---|---|
片区 | ||||||
华东 | 李先锋 | 4057 | 4953 | 6776 | 1723 | 2023 |
华南 | 邹小琴 | 4038 | 6053 | 4691 | 1178 | 2023 |
华中 | 子墨 | 3393 | 1562 | 3607 | 7273 | 2023 |
华北 | 邹娟利 | 3840 | 2815 | 6551 | 3217 | 2023 |
华南 | 邹秀珍 | 3205 | 1772 | 1534 | 6995 | 2023 |
by
参数传入字典,字典的键是 DataFrame
行索引里的值,字典的值是分组名;grouped = df.groupby(by={"华东": "东部战区", "华南": "南部战区", "华北": "北部战区", "华中": "中部战区"}).agg(
{"1季度": "max", "2季度": "mean", "3季度": "sum", "4季度": "min"} # 最大值 # 平均值 # 总和
) # 最小值
grouped
1季度 | 2季度 | 3季度 | 4季度 | |
---|---|---|---|---|
片区 | ||||
东部战区 | 5161 | 3452.888889 | 77185 | 1066 |
中部战区 | 5308 | 4442.000000 | 119501 | 1684 |
北部战区 | 4584 | 4326.448276 | 120002 | 1136 |
南部战区 | 5070 | 4217.461538 | 89342 | 1055 |
例4:使用Series数据分组(用Series替换当前行索引,并使用里面的值作为分组依据)
import pandas as pd
# 读取一个演示文件
df = pd.read_excel("../../../../数据集/团队成员季度销售额.xlsx")
# 观察数据内容
df.sample(5)
姓名 | 片区 | 1季度 | 2季度 | 3季度 | 4季度 | year | |
---|---|---|---|---|---|---|---|
60 | 卓向吴 | 华中 | 4713 | 1691 | 2398 | 9270 | 2023 |
43 | 自在 | 华北 | 2354 | 4605 | 5928 | 8614 | 2023 |
63 | 追梦 | 华中 | 2689 | 6790 | 4247 | 7637 | 2023 |
17 | 邹秀珍 | 华南 | 3205 | 1772 | 1534 | 6995 | 2023 |
42 | 自在小英 | 华北 | 2100 | 2230 | 3409 | 3572 | 2023 |
# 提取片区列作为Series
s = df["片区"].copy(deep=True)
# 使用Series,构建数据分组
grouped = df.groupby(by=s).max()
grouped
姓名 | 片区 | 1季度 | 2季度 | 3季度 | 4季度 | year | |
---|---|---|---|---|---|---|---|
片区 | |||||||
华东 | 转身,泪倾城 | 华东 | 5161 | 6753 | 6776 | 9284 | 2023 |
华中 | 邹世军 | 华中 | 5308 | 7377 | 7204 | 9270 | 2023 |
华北 | 邹黎 | 华北 | 4584 | 7421 | 6945 | 9230 | 2023 |
华南 | 醉霖~棉花糖 | 华南 | 5070 | 7412 | 5971 | 9461 | 2023 |
import pandas as pd
# 读取一个演示文件
df = pd.read_excel("../../../../数据集/团队成员季度销售额.xlsx")
# 将 片区列,设置为索引
# df.set_index('片区',inplace=True)
# 观察数据内容
df.sample(5)
姓名 | 片区 | 1季度 | 2季度 | 3季度 | 4季度 | year | |
---|---|---|---|---|---|---|---|
38 | 邹凤艳 | 华北 | 2055 | 5330 | 6468 | 7229 | 2023 |
35 | 邹海利 | 华北 | 3875 | 5799 | 4057 | 8344 | 2023 |
83 | 祝海英 | 华东 | 1018 | 1430 | 4845 | 7155 | 2023 |
92 | 邹世军 | 华中 | 4343 | 4866 | 2743 | 7617 | 2023 |
11 | 走向幸福 | 华南 | 4312 | 2189 | 4431 | 7493 | 2023 |
# 定义区分单数双数的函数
def rename_index(index):
if index % 2 == 0:
return "双数"
else:
return "单数"
# 应用这个函数,处理行索引进行分组
grouped = df.sample(12).groupby(by=rename_index)
# 查看group对象里的内容
for group_name, group_data in grouped:
print(f"Group: {group_name}")
print(group_data)
print("\n")
Group: 单数
姓名 片区 1季度 2季度 3季度 4季度 year
37 邹广坤 华北 3015 5912 2120 3750 2023
77 祝艳斌 华东 5161 1639 1291 7528 2023
97 王娟 华南 661 6784 3660 8621 2023
89 诸子燕 华东 2454 1824 3306 4198 2023
79 祝小娟 华东 4419 6753 2838 1066 2023
11 走向幸福 华南 4312 2189 4431 7493 2023
63 追梦 华中 2689 6790 4247 7637 2023
Group: 双数
姓名 片区 1季度 2季度 3季度 4季度 year
34 邹积杰 华北 2175 4902 4874 3110 2023
92 邹世军 华中 4343 4866 2743 7617 2023
8 左成娟 华南 1747 5823 1480 7025 2023
76 筑梦 华东 1856 3905 5808 6265 2023
98 刘贤 华东 3960 6437 3148 1517 2023
从上面这个结果可以发现,函数处理并没有影响到 groupby
对象
DataFrame
里# 给分组对象一个计算方式,完成最终数据合并,并观察
grouped.max()
姓名 | 片区 | 1季度 | 2季度 | 3季度 | 4季度 | year | |
---|---|---|---|---|---|---|---|
单数 | 邹广坤 | 华南 | 5161 | 6790 | 4431 | 8621 | 2023 |
双数 | 邹积杰 | 华南 | 4343 | 6437 | 5808 | 7617 | 2023 |
从上面可以发现,如果 by
参数传递了函数,被修改的 行索引
只会作为分组依据、和分组名称,出现在汇总计算后,合并的新 DataFrame
里。
from datetime import datetime
import numpy as np
import pandas as pd
# 创建一个包含时间序列的DataFrame
date_rng = pd.date_range(start="2022-01-01", end="2022-01-19", freq="D")
df = pd.DataFrame(date_rng, columns=["date"])
# 添加一列随机数值
df["value"] = np.random.randn(len(date_rng))
# 观察数据内容
df
date | value | |
---|---|---|
0 | 2022-01-01 | 0.632771 |
1 | 2022-01-02 | 1.218292 |
2 | 2022-01-03 | -0.864251 |
3 | 2022-01-04 | 0.628204 |
4 | 2022-01-05 | -0.625454 |
5 | 2022-01-06 | 1.021081 |
6 | 2022-01-07 | 0.685509 |
7 | 2022-01-08 | 1.096754 |
8 | 2022-01-09 | -1.131979 |
9 | 2022-01-10 | 0.384067 |
10 | 2022-01-11 | 0.447377 |
11 | 2022-01-12 | 0.005861 |
12 | 2022-01-13 | 1.126507 |
13 | 2022-01-14 | -0.153360 |
14 | 2022-01-15 | 0.447708 |
15 | 2022-01-16 | 0.470841 |
16 | 2022-01-17 | -1.143815 |
17 | 2022-01-18 | -0.407859 |
18 | 2022-01-19 | 0.308274 |
# 创建grouper对象
grouper = pd.Grouper(key="date", freq="W")
grouper
TimeGrouper(key='date', freq=<Week: weekday=6>, axis=0, sort=True, dropna=True, closed='right', label='right', how='mean', convention='e', origin='start_day')
# 按轴分组,并计算每组的均值
result = df.groupby(grouper).mean()
result
value | |
---|---|
date | |
2022-01-02 | 0.925532 |
2022-01-09 | 0.115695 |
2022-01-16 | 0.389857 |
2022-01-23 | -0.414467 |
例7:by参数传递列名列表,构成多层索引,作为多维度的数据汇总
import pandas as pd
# 读取一个演示文件
df = pd.read_excel("../../../../数据集/团队成员日销售额.xlsx")
# 只保留需要的列
df = df[["职级", "片区", "业绩"]]
# 观察数据内容
df.sample(5)
职级 | 片区 | 业绩 | |
---|---|---|---|
82 | 经理 | 华中 | 14494.9 |
14 | 经理 | 华南 | 27318.5 |
67 | 组长 | 华东 | 843.3 |
22 | 经理 | 华南 | 853.8 |
43 | 组长 | 华北 | 545.8 |
df.groupby(by=["职级", "片区"]).sum()
业绩 | ||
---|---|---|
职级 | 片区 | |
组长 | 华东 | 17526.0 |
华中 | 4043.1 | |
华北 | 6524.8 | |
经理 | 华东 | 148805.5 |
华中 | 125646.0 | |
华北 | 58211.8 | |
华南 | 1067600.6 | |
销售员 | 华东 | 7896.9 |
华中 | 11233.6 | |
华北 | 1772.2 | |
华南 | 8281.1 |
import pandas as pd
# 读取一个演示文件
df = pd.read_excel("../../../../数据集/团队成员日销售额_用于转置.xlsx")
# 观察数据内容
df
Unnamed: 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ... | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 职级 | 销售员 | 经理 | 经理 | 销售员 | 销售员 | 销售员 | 销售员 | 销售员 | 销售员 | ... | 经理 | 经理 | 销售员 | 组长 | 销售员 | 销售员 | 销售员 | 经理 | 销售员 | 销售员 |
1 | 片区 | 华南 | 华南 | 华南 | 华南 | 华南 | 华南 | 华南 | 华南 | 华南 | ... | 华中 | 华中 | 华中 | 华中 | 华中 | 华中 | 华中 | 华中 | 华中 | 华中 |
2 | 业绩 | 523.9 | 16647.5 | 825896.9 | 1051.1 | 672.5 | 1542.2 | 540.9 | 752.5 | 585 | ... | 18261 | 8089 | 6825 | 1112.9 | 888.7 | 721.3 | 941.3 | 17740.2 | 692.1 | 1165.2 |
3 rows × 101 columns
可以发现,在这个演示数据中,如果需要分组,则需要 axis=1
, 但是这不符合Pandas新版本特性。
df.T.groupby(by=1).max()
0 | 2 | |
---|---|---|
1 | ||
华东 | 销售员 | 99327.4 |
华中 | 销售员 | 18261 |
华北 | 销售员 | 9388 |
华南 | 销售员 | 825896.9 |
片区 | 职级 | 业绩 |
分组完毕,by=1
是因为片区的哪一列,此时列名就是1
例9:多层索引需要使用 level
参数传递层级信息指定分组依据
import pandas as pd
# 读取一个演示文件
df = pd.read_excel("../../../../数据集/团队成员日销售额.xlsx")
# 只保留需要的列
df = df[["职级", "片区", "业绩"]]
# 构建多层索引
df.set_index(["片区", "职级"], inplace=True)
# 观察数据内容
df.sample(5)
业绩 | ||
---|---|---|
片区 | 职级 | |
华中 | 经理 | 1459.4 |
华南 | 销售员 | 540.9 |
销售员 | 773.4 | |
华中 | 经理 | 744.0 |
组长 | 1304.8 |
df.groupby(level="片区").sum()
业绩 | |
---|---|
片区 | |
华东 | 174228.4 |
华中 | 140922.7 |
华北 | 66508.8 |
华南 | 1075881.7 |
df.groupby(level=0).sum()
业绩 | |
---|---|
片区 | |
华东 | 174228.4 |
华中 | 140922.7 |
华北 | 66508.8 |
华南 | 1075881.7 |
df.groupby(level=[0, "职级"]).sum()
业绩 | ||
---|---|---|
片区 | 职级 | |
华东 | 组长 | 17526.0 |
经理 | 148805.5 | |
销售员 | 7896.9 | |
华中 | 组长 | 4043.1 |
经理 | 125646.0 | |
销售员 | 11233.6 | |
华北 | 组长 | 6524.8 |
经理 | 58211.8 | |
销售员 | 1772.2 | |
华南 | 经理 | 1067600.6 |
销售员 | 8281.1 |
例10:分组名称不再作为索引,使用SQL风格展示分组后的数据
import pandas as pd
# 读取一个演示文件
df = pd.read_excel("../../../../数据集/团队成员日销售额.xlsx")
# 只保留需要的列
df = df[["职级", "片区", "业绩"]]
df
# 用片区进行分组,并关闭索引返回
df.groupby(by="片区", as_index=False).max()
片区 | 职级 | 业绩 | |
---|---|---|---|
0 | 华东 | 销售员 | 99327.4 |
1 | 华中 | 销售员 | 18261.0 |
2 | 华北 | 销售员 | 9388.0 |
3 | 华南 | 销售员 | 825896.9 |
由上面结果可以发现,片区列,没有再作为行索引。
DataFrame
会开启组名排序import pandas as pd
# 构建演示数据
df = pd.DataFrame({"cat": ["b", "b", "a", "a"], "value": [1, 3, 2, 4]})
# 用cat列构建分组,保持分组名排序开启,
grouped = df.groupby(by="cat").mean()
grouped
value | |
---|---|
cat | |
a | 3.0 |
b | 2.0 |
sort=False
数据分组后输出的 DataFrame
不再对组名排序# 用cat列构建分组,关闭分组名排序
grouped2 = df.groupby(by="cat", sort=False).mean()
grouped2
value | |
---|---|
cat | |
b | 2.0 |
a | 3.0 |
例12:应用apply,如果结果行数 > 分组数量,则无法完成汇总,各分组的行索引(组键)会和分组名组成多层索引
import pandas as pd
# 构建演示数据
df = pd.DataFrame({"cat": ["b", "b", "a", "a"], "value": [1, 3, 2, 4]})
# 用cat列构建分组,保持分组名排序开启,
grouped = df.groupby(by="cat")
# 打印每个组的内容
for name, group in grouped:
print(f"Group {name}:")
print(group)
print("\n")
Group a:
cat value
2 a 2
3 a 4
Group b:
cat value
0 b 1
1 b 3
留意上面结果,a和b两个分组的行索引2、3、0、1。
apply
,但是结果行数 > 分组数量时,会产生由分组名、各分组行索引构成的多层索引,import pandas as pd
# 构建演示数据
df = pd.DataFrame({"cat": ["b", "b", "a", "a"], "value": [1, 3, 2, 4]})
# df['cat'] = df['cat'].astype('category')
# 用cat列构建分组,保持分组名排序开启,
grouped = df.groupby(by="cat").apply(lambda x: x)
grouped
cat | value | ||
---|---|---|---|
cat | |||
a | 2 | a | 2 |
3 | a | 4 | |
b | 0 | b | 1 |
1 | b | 3 |
group_keys=False
时,分组的键不会作为索引,而是返回一个不带有分组键的普通 DataFrame
(或者 Series
)。import pandas as pd
# 构建演示数据
df = pd.DataFrame({"cat": ["b", "b", "a", "a"], "value": [1, 3, 2, 4]})
# df['cat'] = df['cat'].astype('category')
# 用cat列构建分组,保持分组名排序开启,
grouped = df.groupby(by="cat", group_keys=False).apply(lambda x: x)
grouped
cat | value | |
---|---|---|
0 | b | 1 |
1 | b | 3 |
2 | a | 2 |
3 | a | 4 |
import pandas as pd
# 构建演示数据
df = pd.DataFrame({"cat": ["b", "b", "a", "a"], "value": [1, 3, 2, 4]})
# df['cat'] = df['cat'].astype('category')
# 用cat列构建分组,保持分组名排序开启,
grouped = df.groupby(by="cat").apply(lambda x: x.mean())
grouped
value | |
---|---|
cat | |
a | 3.0 |
b | 2.0 |
例13:组键(分组名、或可理解为结果的行索引、也可以理解为各分组的行索引)缺失值处理
import pandas as pd
# 构建演示数据
l = [["a", 12, 12], [None, 12.3, 33.0], ["b", 12.3, 123], ["a", 1, 1]]
df = pd.DataFrame(l, columns=["a", "b", "c"])
df
a | b | c | |
---|---|---|---|
0 | a | 12.0 | 12.0 |
1 | None | 12.3 | 33.0 |
2 | b | 12.3 | 123.0 |
3 | a | 1.0 | 1.0 |
grouped = df.groupby(by="a")
# 打印每个组的内容
for name, group in grouped:
print(f"Group {name}:")
print(group)
print("\n")
Group a:
a b c
0 a 12.0 12.0
3 a 1.0 1.0
Group b:
a b c
2 b 12.3 123.0
由上面结果可以发现,当完成分组的时候,就已经没有缺失值了,这一步发生在合并每个分组产生结果之前。
dropna=True
可以保留缺失值grouped = df.groupby(by="a", dropna=False).mean()
grouped
b | c | |
---|---|---|
a | ||
a | 6.5 | 6.5 |
b | 12.3 | 123.0 |
NaN | 12.3 | 33.0 |