查看全课大纲(第 55 / 101 节)
  1. 1.数据分析基本概念
  2. 2.学习数据分析的一般路线
  3. 3.数据分析的流程
  4. 4.数据类型
  5. 5.环境部署(1)
  6. 6.环境部署(2)
  7. 7.课程介绍
  8. 8.TXT文件操作
  9. 9.JSON文件操作
  10. 10.CSV文件操作
  11. 11.Excel文件操作
  12. 12.数据库及SQL常用语法
  13. 13.数据库基本操作
  14. 14.数据库多表连接
  15. 15.实战:欧洲职业足球数据库分析
  16. 16.爬虫简介
  17. 17.URL管理模块
  18. 18.网页下载模块
  19. 19.网页解析模块(1)
  20. 20.网页解析模块(2)
  21. 21.Scrapy简介
  22. 22.Scrapy使用步骤(1)
  23. 23.Scrapy使用步骤(2)
  24. 24.Scrapy使用步骤(3)
  25. 25.Scrapy使用步骤(4)
  26. 26.实战:获取国内城市空气质量指数数据
  27. 27.NumPy和SciPy介绍
  28. 28.多维数组
  29. 29.多维数组操作
  30. 30.NumPy的常用方法
  31. 31.向量化介绍
  32. 32.向量化及通用函数
  33. 33.实战:2016美国大选分析
  34. 34.数据结构-Series
  35. 35.数据结构-DataFrame
  36. 36.数据结构-Index
  37. 37.Series的索引操作
  38. 38.DataFrame的索引操作
  39. 39.索引操作总结
  40. 40.运算与对齐
  41. 41.函数应用操作(1) -- map
  42. 42.函数应用操作 (2) -- apply applymap
  43. 43.文件读写操作
  44. 44.排序操作
  45. 45.数据清洗--处理缺失数据
  46. 46.数据清洗--处理重复数据
  47. 47.数据清洗--替换数据
  48. 48.常用统计方法(1) -- describe quantile
  49. 49.常用统计方法(2) -- sum mean median count
  50. 50.常用统计方法(3) -- max min idxmax idxmin
  51. 51.常用统计方法(4) -- mad var std cumsum
  52. 52.实战:全球食品数据分析
  53. 53.层级索引
  54. 54.分组与聚合介绍
  55. 55.分组操作(1) -- GroupBy对象及常用聚合操作
  56. 56.分组操作(2) -- 自定义分组及聚合操作
  57. 57.透视表介绍
  58. 58.透视表操作
  59. 59.数据规整(1) -- 数据合并concat
  60. 60.数据规整(2) -- 数据连接merge
  61. 61.数据重构(3) -- 数据重构stack unstack
  62. 62.实战:互联网电影资料库分析
  63. 63.探索性数据分析EDA介绍
  64. 64.EDA的目的
  65. 65.EDA常用工具
  66. 66.Matplotlib绘图基本介绍
  67. 67.Matplotlib画布
  68. 68.散点图和柱状图的绘制
  69. 69.直方图的绘制
  70. 70.矩阵绘图
  71. 71.子图的使用
  72. 72.Matplotlib颜色、标记、线型
  73. 73.Matplotlib坐标刻度、标签、图例、标题
  74. 74.Seaborn介绍
  75. 75.数据集分布可视化(1) -- 单变量分布、双变量分布
  76. 76.数据集分布可视化(2) -- 变量关系可视化
  77. 77.类别数据可视化 -- 类别散布图、类别内数据分布、类别内统计图
  78. 78.交互式数据可视化工具Bokeh介绍
  79. 79.Bokeh绘制散点图、柱状图、盒子图、弦图
  80. 80.Bokeh绘制常用图形元素
  81. 81.3D绘图 -- mplot3d
  82. 82.3D曲线可视化
  83. 83.3D散点图可视化
  84. 84.3D柱状图可视化
  85. 85.Pandas绘图
  86. 86.实战:Lending Club借贷数据探索性分析及可视化
  87. 87.机器学习介绍及应用场景
  88. 88.机器学习建模介绍 (1) -- 分类
  89. 89.机器学习建模介绍 (2) -- 回归
  90. 90.机器学习建模介绍 (3) -- 聚类
  91. 91.机器学习分类
  92. 92.机器学习工具scikit-learn
  93. 93.使用scikit-learn的流程
  94. 94.数据集准备及划分
  95. 95.模型选择
  96. 96.数据预处理及特征工程
  97. 97.过拟合与欠拟合
  98. 98.模型调参介绍
  99. 99.模型调参方法
  100. 100.模型测试及评价
  101. 101.实战:通过移动设备行为数据预测性别和年龄

分组操作(1) -- GroupBy对象及常用聚合操作

约 8 分钟

📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)

GroupBy 对象与常用聚合操作

小象实战讲义 · Python数据分析实战

在数据分析中,我们经常需要按照某个或某几个维度对数据进行分组,然后对每个组进行统计计算。Pandas的groupby()功能正是为此而生,它能够高效地实现“拆分-应用-合并”的数据处理范式。本节我们将深入理解GroupBy对象,并掌握常用的聚合操作方法。

💡 核心导读

  • 分组操作的本质:groupby()并不直接计算结果,而是创建一个GroupBy对象,它记录了数据的分组信息。
  • 单列与多列分组:可以按单个列名分组,也可以传入列表按多个列名进行分组,后者会生成多层索引。
  • 延迟计算机制:GroupBy对象只有在调用聚合方法(如mean(), max(), size())时才会执行实际计算。
  • 聚合操作的选择性:聚合计算默认只作用于数值列,非数值列(如字符串)在某些聚合操作(如max())中也可能参与。
  • 常用聚合方法:mean()(均值)、max()(最大值)、size()(分组大小)、count()(非空值计数)等。

理解GroupBy对象

groupby()操作的核心产出是一个GroupBy对象。这个对象本身并不包含计算结果,它更像是一个“分组方案”或“计算计划”,记录了原始数据中每一行属于哪个分组。只有当我们对这个对象调用具体的聚合函数时,Pandas才会执行计算并返回结果。

这种设计称为“延迟计算”或“惰性求值”,它使得Pandas可以更高效地组织计算,尤其是在处理大数据时,避免了不必要的中间数据复制。

创建GroupBy对象

我们可以按单列分组,也可以按多列分组。按多列分组时,结果会具有多层索引(MultiIndex),这在处理复杂的分组维度时非常有用。

import pandas as pd

# 在代码块内构造示例数据,模拟原课程中的幸福指数数据集
data = pd.DataFrame({
    'Country': ['Finland', 'Norway', 'Denmark', 'Iceland', 'Switzerland', 'New Zealand', 'Australia', 'Sweden'],
    'Region': ['Western Europe', 'Western Europe', 'Western Europe', 'Western Europe',
               'Western Europe', 'Australia and New Zealand', 'Australia and New Zealand', 'Western Europe'],
    'Happiness Rank': [1, 2, 3, 4, 5, 6, 7, 8],
    'Happiness Score': [7.526, 7.522, 7.520, 7.504, 7.494, 7.286, 7.284, 7.283]
})

# 按单列'Region'进行分组
grouped_single = data.groupby('Region')
print("按单列分组产生的对象类型:", type(grouped_single).__name__)

# 按多列['Region', 'Country']进行分组
grouped_multi = data.groupby(['Region', 'Country'])
print("按多列分组产生的对象类型:", type(grouped_multi).__name__)
按单列分组产生的对象类型: DataFrameGroupBy
按多列分组产生的对象类型: DataFrameGroupBy

从输出可以看到,两种方式都产生了DataFrameGroupBy对象。此时并没有进行任何数值计算,对象内部只是存储了分组映射关系。

常用聚合操作

对GroupBy对象调用聚合方法,才会触发实际计算,并返回一个通常为DataFrame或Series的结果。

求均值 .mean()

.mean()方法会计算每个分组内数值列的平均值。注意:在 pandas 3.0 中,groupby().mean() 不再自动排除非数值列(旧版本会自动丢弃字符串列并给出警告,新版本直接报错),因此这里显式选择数值列进行计算。

import pandas as pd

# 重新构造数据(每个代码块自包含)
data_mean = pd.DataFrame({
    'Country': ['Finland', 'Norway', 'Denmark', 'Iceland', 'Switzerland', 'New Zealand', 'Australia', 'Sweden'],
    'Region': ['Western Europe', 'Western Europe', 'Western Europe', 'Western Europe',
               'Western Europe', 'Australia and New Zealand', 'Australia and New Zealand', 'Western Europe'],
    'Happiness Rank': [1, 2, 3, 4, 5, 6, 7, 8],
    'Happiness Score': [7.526, 7.522, 7.520, 7.504, 7.494, 7.286, 7.284, 7.283]
})

# 按区域分组并对数值列求均值(显式选择数值列,字符串列 Country 不参与)
mean_by_region = data_mean.groupby('Region')[['Happiness Rank', 'Happiness Score']].mean()
print("按区域分组的平均幸福指数与排名:")
print(mean_by_region)
按区域分组的平均幸福指数与排名:
                           Happiness Rank  Happiness Score
Region
Australia and New Zealand        6.500000         7.285000
Western Europe                   3.833333         7.474833

结果是一个DataFrame,索引是分组键(Region),列是原始数据中的数值列。可以看到,“澳新区”的平均排名是6.5,平均幸福得分是7.285;“西欧”地区的平均排名是3.83,平均得分是7.475。字符串列Country没有出现在结果中。

求最大值 .max()

.max()方法返回每个分组内各列的最大值。对于数值列,就是数学最大值;对于字符串列,则是按字母顺序排序后的最大值。

import pandas as pd

# 重新构造数据
data_max = pd.DataFrame({
    'Country': ['Finland', 'Norway', 'Denmark', 'Iceland', 'Switzerland', 'New Zealand', 'Australia', 'Sweden'],
    'Region': ['Western Europe', 'Western Europe', 'Western Europe', 'Western Europe',
               'Western Europe', 'Australia and New Zealand', 'Australia and New Zealand', 'Western Europe'],
    'Happiness Rank': [1, 2, 3, 4, 5, 6, 7, 8],
    'Happiness Score': [7.526, 7.522, 7.520, 7.504, 7.494, 7.286, 7.284, 7.283]
})

# 按区域分组并求最大值
max_by_region = data_max.groupby('Region').max()
print("按区域分组的最大值:")
print(max_by_region)
按区域分组的最大值:
                               Country  Happiness Rank  Happiness Score
Region
Australia and New Zealand  New Zealand               7            7.286
Western Europe             Switzerland               8            7.526

这次,Country列出现了!在“澳新区”分组中,按字母顺序“New Zealand”排在“Australia”之后,所以最大值是“New Zealand”。同时,该组的Happiness Rank最大值是7(排名数字越大,实际排名越靠后),Happiness Score最大值是7.286。这演示了.max()等操作对非数值列的处理逻辑。

统计分组大小 .size()

.size()方法返回每个分组包含的数据行数,结果是一个Series。这是一个非常快速且常用的操作,用于查看数据分布。

import pandas as pd

# 重新构造数据
data_size = pd.DataFrame({
    'Country': ['Finland', 'Norway', 'Denmark', 'Iceland', 'Switzerland', 'New Zealand', 'Australia', 'Sweden'],
    'Region': ['Western Europe', 'Western Europe', 'Western Europe', 'Western Europe',
               'Western Europe', 'Australia and New Zealand', 'Australia and New Zealand', 'Western Europe'],
    'Happiness Rank': [1, 2, 3, 4, 5, 6, 7, 8],
    'Happiness Score': [7.526, 7.522, 7.520, 7.504, 7.494, 7.286, 7.284, 7.283]
})

# 统计每个区域有多少个国家/数据行
size_by_region = data_size.groupby('Region').size()
print("每个区域的数据行数:")
print(size_by_region)
每个区域的数据行数:
Region
Australia and New Zealand    2
Western Europe               6
dtype: int64

非空值计数 .count()

.count()与.size()不同,它返回的是每个分组内每一列的非空值(Non-Null)数量。如果数据中有缺失值,这两个方法的结果就会不同。

import pandas as pd

# 重新构造数据,并故意加入一个缺失值(None)
data_count = pd.DataFrame({
    'Country': ['Finland', 'Norway', 'Denmark', 'Iceland', 'Switzerland', 'New Zealand', 'Australia', 'Sweden'],
    'Region': ['Western Europe', 'Western Europe', 'Western Europe', 'Western Europe',
               'Western Europe', 'Australia and New Zealand', 'Australia and New Zealand', 'Western Europe'],
    'Happiness Rank': [1, 2, 3, 4, 5, 6, 7, None],  # 最后一行Rank为缺失值
    'Happiness Score': [7.526, 7.522, 7.520, 7.504, 7.494, 7.286, 7.284, 7.283]
})

# 使用count统计非空值
count_by_region = data_count.groupby('Region').count()
print("每个区域各列的非空值数量:")
print(count_by_region)
每个区域各列的非空值数量:
                           Country  Happiness Rank  Happiness Score
Region                                                             
Australia and New Zealand        2               2                2
Western Europe                   6               5                6

可以看到,在“西欧”组,Happiness Rank列只有5个非空值,而Country和Happiness Score列有6个。.count()是按列统计的,而.size()是统计整个分组的行数(无论是否有缺失值),在本例中“西欧”组的.size()结果仍是6。

多列分组与多层索引

当按多列分组时,分组键会形成多层索引。我们通常用.size()来直观查看这种分组组合。

import pandas as pd

# 重新构造数据
data_multi = pd.DataFrame({
    'Country': ['Finland', 'Norway', 'Denmark', 'Iceland', 'Switzerland', 'New Zealand', 'Australia', 'Sweden'],
    'Region': ['Western Europe', 'Western Europe', 'Western Europe', 'Western Europe',
               'Western Europe', 'Australia and New Zealand', 'Australia and New Zealand', 'Western Europe'],
    'Happiness Rank': [1, 2, 3, 4, 5, 6, 7, 8],
    'Happiness Score': [7.526, 7.522, 7.520, 7.504, 7.494, 7.286, 7.284, 7.283]
})

# 按Region和Country两列分组,并查看每组大小
size_by_region_country = data_multi.groupby(['Region', 'Country']).size()
print("按区域和国家分组后每组的大小:")
print(size_by_region_country)
按区域和国家分组后每组的大小:
Region                     Country     
Australia and New Zealand  Australia      1
                           New Zealand    1
Western Europe             Denmark        1
                           Finland        1
                           Iceland        1
                           Norway         1
                           Sweden         1
                           Switzerland    1
dtype: int64

结果是一个具有两层索引的Series。第一层是Region,第二层是Country。由于每个“地区-国家”组合在数据中只出现一次,所以每个分组的大小都是1。这种分组方式在需要更细粒度分析时非常有用。

📝 动手练一练

  1. 概念辨析:GroupBy对象调用.size()和.count()返回的结果有什么本质区别?在什么情况下它们的结果会不同?
👉 点击查看参考答案
  • 本质区别:.size()返回的是一个按分组索引的 Series,表示每个分组的总行数,无论这些行中是否有缺失值。.count()返回的是一个DataFrame(或Series),表示每个分组内,每一列的非空值数量。
  • 结果不同的情况:当数据中存在缺失值(NaN或None)时,两者结果就会不同。例如,某个分组有5行数据,但其中一列在2行上是空值,那么该分组.size()返回5,而该列的.count()返回3。如果数据中完全没有缺失值,那么每个分组.size()的值会等于该分组任何一列.count()的值。
  1. 动手实践:使用上面示例中的data_multi DataFrame,尝试按Region分组后,计算每个区域Happiness Score的总和(sum())与最小值(min())。观察字符串列Country是否出现在结果中?为什么?
👉 点击查看参考答案
import pandas as pd

data_multi = pd.DataFrame({
    'Country': ['Finland', 'Norway', 'Denmark', 'Iceland', 'Switzerland', 'New Zealand', 'Australia', 'Sweden'],
    'Region': ['Western Europe', 'Western Europe', 'Western Europe', 'Western Europe',
               'Western Europe', 'Australia and New Zealand', 'Australia and New Zealand', 'Western Europe'],
    'Happiness Rank': [1, 2, 3, 4, 5, 6, 7, 8],
    'Happiness Score': [7.526, 7.522, 7.520, 7.504, 7.494, 7.286, 7.284, 7.283]
})

sum_score = data_multi.groupby('Region')['Happiness Score'].sum()
min_score = data_multi.groupby('Region')['Happiness Score'].min()

print("每个区域幸福指数总和:")
print(sum_score)
print("\n每个区域幸福指数最小值:")
print(min_score)
每个区域幸福指数总和:
Region
Australia and New Zealand    14.570
Western Europe               44.849
Name: Happiness Score, dtype: float64

每个区域幸福指数最小值:
Region
Australia and New Zealand    7.284
Western Europe               7.283
Name: Happiness Score, dtype: float64

观察与解释:字符串列Country没有出现在结果中。因为我们在分组后通过['Happiness Score']明确指定了只对Happiness Score这一列进行聚合计算(这种语法称为“列选择”),Pandas就只会处理这一列。另外要注意:min()/max() 对字符串列并非自动跳过——它们会按字典序返回“最小/最大”的字符串(本节 .max() 示例中 Country 列就出现在结果里);只有 mean() 这类纯数值运算才会因字符串列无法计算而直接报错。因此,做数值统计时用列选择语法把范围限定在数值列,是最稳妥的写法。

本章小结

本节我们揭开了Pandas分组操作的神秘面纱。groupby()的核心在于创建了一个惰性求值的GroupBy对象,它将数据拆分到不同的组中,等待一个聚合指令来触发计算。我们学习了按单列和多列分组的方法,并实践了mean()、max()、size()、count()等最常用的聚合操作,理解了它们之间的区别以及对数值/非数值列的不同处理逻辑。

📋 行动清单

学完本节,你可以立即:

  • 对你手头的数据集,尝试按一个关键列进行分组,并使用.mean()或.size()查看分组统计信息。
  • 理解并能在.size()(计数行)和.count()(计数非空值)之间做出正确选择。
  • 在按多列分组时,能够解读具有多层索引的聚合结果。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 本节课件:分组操作(1) -- GroupBy对象及常用聚合操作
    PDF · 276KB
    下载
  • 全套课件打包(第1-5章)
    ZIP · 12.8MB
    下载
  • 全套课件打包(第6-8章)
    ZIP · 15MB
    下载
  • 实战数据集:AppleStore 应用商城分析
    ZIP · 329KB
    下载
  • 实战数据集:女性服装电商分析
    ZIP · 2.8MB
    下载
  • Python 数据分析环境搭建指南
    PDF · 2MB
    下载
  • Scrapy 安装教程
    PDF · 12.7MB
    下载
  • 附加实战项目:AppleStore 应用商城数据分析
    ZIP · 0.3MB · ipynb + CSV 数据
    下载
  • 附加实战项目:银行电话营销数据分析
    ZIP · 0.4MB · ipynb + CSV 数据
    下载
  • 附加实战项目:女性服装电商评论数据分析
    ZIP · 2.7MB · ipynb + CSV 数据
    下载
  • 附加实战项目:美国化学学会杂志数据分析
    ZIP · 34.2MB · ipynb + SQLite 数据库
    下载