import pandas as pd
import numpy as np
19 数据分组和汇总
19.1 介绍
在本课程中,我们将探讨两个强大的 pandas 方法:agg()
和 groupby()
。这些工具将使您能够轻松提取汇总统计信息并对分组数据执行操作。
汇总统计量是描述一系列值(通常是数据集中的一列)的单个值(如平均值或中位数)。
让我们看看如何使用它们!
19.2 学习目标
您可以使用
pandas.DataFrame.agg()
从数据集中提取汇总统计信息。您可以使用
pandas.DataFrame.groupby()
按一个或多个变量对数据进行分组,然后对其执行操作。您可以将自定义函数传递给
agg()
以计算汇总统计信息。
19.3 库
运行以下代码导入必要的库:
19.4 雅温得 COVID-19 数据集
在本课程中,我们将再次使用喀麦隆雅温得进行的 COVID-19 血清调查数据的一个子集。
您可以从此链接下载数据集:yaounde_mini.csv
= pd.read_csv("data/yaounde_mini.csv")
yao yao
age | age_category_3 | sex | weight_kg | height_cm | neighborhood | is_smoker | is_pregnant | occupation | treatment_combinations | symptoms | n_days_miss_work | n_bedridden_days | highest_education | igg_result | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 45 | Adult | Female | 95 | 169 | Briqueterie | Non-smoker | No | Informal worker | Paracetamol | Muscle pain | 0.0 | 0.0 | Secondary | Negative |
1 | 55 | Adult | Male | 96 | 185 | Briqueterie | Ex-smoker | NaN | Salaried worker | NaN | No symptoms | NaN | NaN | University | Positive |
2 | 23 | Adult | Male | 74 | 180 | Briqueterie | Smoker | NaN | Student | NaN | No symptoms | NaN | NaN | University | Negative |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
968 | 35 | Adult | Male | 77 | 168 | Tsinga Oliga | Smoker | NaN | Informal worker | Paracetamol | Headache | 0.0 | 0.0 | University | Positive |
969 | 31 | Adult | Female | 66 | 169 | Tsinga Oliga | Non-smoker | No | Unemployed | NaN | No symptoms | NaN | NaN | Secondary | Negative |
970 | 17 | Child | Female | 67 | 162 | Tsinga Oliga | Non-smoker | No response | Unemployed | NaN | No symptoms | NaN | NaN | Secondary | Negative |
971 rows × 15 columns
您可以在这里了解更多关于该数据集的信息:https://www.nature.com/articles/s41467-021-25946-0
19.5 介绍 pandas.DataFrame.agg()
首先,让我们考虑如何在不使用 agg()
的情况下获取简单的汇总统计信息,然后我们将考虑为什么您应该实际使用 agg()
。
假设有人要求您找出 yao
数据框中受访者的平均年龄。您可以通过调用 yao
数据框的 age
列上的 mean()
方法来实现:
"age"]].mean() yao[[
age 29.017508
dtype: float64
现在,让我们看看如何使用 agg()
来做到这一点。
=('age', 'mean')) yao.agg(mean_age
age | |
---|---|
mean_age | 29.017508 |
这种语法的结构是:
=("COLUMN_TO_SUMMARIZE", "SUMMARY_FUNCTION")) dataframe.agg(summary_name
这一部分 ("COLUMN_TO_SUMMARIZE", "SUMMARY_FUNCTION")
被称为元组。元组的第一个元素是要汇总的列的名称,第二个元素是要应用于该列的汇总函数。
语法更为复杂,但正如您稍后将看到的,它更加强大,因为它允许您计算多个汇总统计信息,并按组计算统计数据。
让我们看看如何在单个 agg()
语句中计算多个汇总统计信息。如果您想要年龄的平均值和中位数,您可以运行:
=("age", "mean"), median_age=("age", "median")) yao.agg(mean_age
age | |
---|---|
mean_age | 29.017508 |
median_age | 26.000000 |
很好,现在试试下面的练习题。
19.6 练习题:平均值和中位数体重
使用 agg()
和相关的汇总函数,从 yao
数据框的 weight_kg
变量中获取受访者体重的平均值和中位数。
# Your code here
19.7 使用 pandas.DataFrame.groupby()
进行分组汇总
现在让我们看看如何使用 groupby()
来获得分组汇总,这是使用 agg()
的主要原因。
顾名思义,pandas.DataFrame.groupby()
让您可以按变量中的值对数据框进行分组(例如,按性别分组为男性和女性)。然后,您可以执行按这些组拆分的操作。
让我们尝试按性别对 yao
数据框进行分组,并观察效果:
"sex") yao.groupby(
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x10882c110>
嗯。显然没有发生任何事情。我们只是得到了一个 GroupBy
对象。
但是,当我们将 groupby()
与前一节中使用的 agg()
调用链式连接时,看看会发生什么:
"sex").agg(mean_age=("age", "mean"), median_age=("age", "median")) yao.groupby(
mean_age | median_age | |
---|---|---|
sex | ||
Female | 29.495446 | 26.0 |
Male | 28.395735 | 25.0 |
现在我们为每个组获得了不同的统计数据!女性受访者的平均年龄约为 29.5 岁,而男性受访者的平均年龄约为 28.4 岁。
如前所述,这种分组汇总是 agg()
函数如此有用的主要原因。
您可能注意到有两行标题。这是因为输出具有层次索引(在 pandas 中称为 MultiIndex)。虽然在某些情况下这可能有用,但它通常会使进一步的数据操作更加困难。我们可以使用 reset_index()
方法重置索引,将组标签转换回常规列。
"sex").agg(mean_age=("age", "mean"), median_age=("age", "median")).reset_index() yao.groupby(
sex | mean_age | median_age | |
---|---|---|---|
0 | Female | 29.495446 | 26.0 |
1 | Male | 28.395735 | 25.0 |
您可能会注意到代码行变得相当长。我们可以将每个新的方法调用移到新的一行以提高代码的可读性,但需要将整个链包裹在括号中。
("sex")
yao.groupby(=("age", "mean"), median_age=("age", "median"))
.agg(mean_age
.reset_index() )
sex | mean_age | median_age | |
---|---|---|---|
0 | Female | 29.495446 | 26.0 |
1 | Male | 28.395735 | 25.0 |
让我们看一个例子。
假设您被要求获取不同社区中个体的最大和最小体重,并呈现每个社区中的个体数量。我们可以编写:
("neighborhood")
yao.groupby(
.agg(=("weight_kg", "max"),
max_weight=("weight_kg", "min"),
min_weight=("weight_kg", "size"), # the size function counts rows per group
count
)
.reset_index() )
neighborhood | max_weight | min_weight | count | |
---|---|---|---|---|
0 | Briqueterie | 128 | 20 | 106 |
1 | Carriere | 129 | 14 | 236 |
2 | Cité Verte | 118 | 16 | 72 |
... | ... | ... | ... | ... |
6 | Nkomkana | 161 | 15 | 75 |
7 | Tsinga | 105 | 15 | 81 |
8 | Tsinga Oliga | 100 | 17 | 67 |
9 rows × 4 columns
19.8 练习题:按性别分组的最小和最大身高
使用 groupby()
、agg()
和相关的汇总函数,从 yao
数据框中获取每个性别的最小和最大身高,以及每个性别组中的个体数量。
您的输出应为如下所示的 DataFrame:
sex | min_height_cm | max_height_cm | count |
---|---|---|---|
Female | |||
Male |
# Your code here
19.9 按多个变量分组(嵌套分组)
可以按多个变量对数据框进行分组。这有时称为“嵌套”分组。
假设您想知道每个社区中男性和女性的平均年龄,您可以在 groupby()
语句中同时放入 sex
和 neighborhood
:
(
yao'sex', 'neighborhood'])
.groupby([=('age', 'mean'))
.agg(mean_age
.reset_index() )
sex | neighborhood | mean_age | |
---|---|---|---|
0 | Female | Briqueterie | 31.622951 |
1 | Female | Carriere | 28.164286 |
2 | Female | Cité Verte | 31.750000 |
... | ... | ... | ... |
15 | Male | Nkomkana | 29.812500 |
16 | Male | Tsinga | 28.820513 |
17 | Male | Tsinga Oliga | 24.297297 |
18 rows × 3 columns
从这个输出数据框您可以看出,例如,来自 Briqueterie 的女性的平均年龄为 31.6 岁。
19.10 练习题:按年龄和性别组的最小和最大身高
使用 groupby()
、agg()
以及 min()
和 max()
,获取 yao
数据框中每个年龄-性别组的最小和最大身高。需要的变量是 age_category_3
和 sex
。
您的输出应为如下所示的 DataFrame:
age_category_3 | sex | min_height | max_height |
---|---|---|---|
Adult | Female | 78 | 185 |
Adult | Male | 147 | 196 |
Child | Female | 54 | 183 |
Child | Male | 96 | 190 |
Senior | Female | 143 | 174 |
Senior | Male | 160 | 195 |
# Your code here
19.11 agg()
中的 NaN 值
在使用 agg()
计算分组汇总统计信息时,请注意您关注的组是否包含 NaN 值。
例如,要按吸烟状态获取平均体重,我们可以编写:
("is_smoker")
yao.groupby(=("weight_kg", "mean"))
.agg(weight_mean
.reset_index() )
is_smoker | weight_mean | |
---|---|---|
0 | Ex-smoker | 76.366197 |
1 | Non-smoker | 63.033760 |
2 | Smoker | 72.410256 |
但这实际上会将一些具有 NaN 吸烟状态的行从汇总表中排除。
我们可以通过在 groupby()
函数中设置 dropna=False
来将这些个体包含在汇总表中。
("is_smoker", dropna=False)
yao.groupby(=("weight_kg", "mean"))
.agg(weight_mean
.reset_index() )
is_smoker | weight_mean | |
---|---|---|
0 | Ex-smoker | 76.366197 |
1 | Non-smoker | 63.033760 |
2 | Smoker | 72.410256 |
3 | NaN | 73.000000 |
此外,记住您可以使用 size()
函数查看每个吸烟状态组中有多少个体。这通常在您的汇总表中包含此信息很有用,这样您就知道每个汇总统计背后有多少个体。
("is_smoker", dropna=False)
yao.groupby(=("weight_kg", "mean"),
.agg(weight_mean=("weight_kg", "size"))
count
.reset_index() )
is_smoker | weight_mean | count | |
---|---|---|---|
0 | Ex-smoker | 76.366197 | 71 |
1 | Non-smoker | 63.033760 | 859 |
2 | Smoker | 72.410256 | 39 |
3 | NaN | 73.000000 | 2 |
19.12 练习题:按怀孕状态的平均体重
使用 groupby()
、agg()
和 mean()
函数,从 yao
数据框中按怀孕状态获取平均体重(公斤)。在汇总表中包含怀孕状态为 NaN 的个体。
输出的数据框应类似于:
is_pregnant | weight_mean |
---|---|
No | |
No response | |
Yes | |
NaN |
# your code here
19.13 使用 lambda 函数进行自定义汇总统计
在深入研究自定义汇总统计之前,让我们简要介绍一下 lambda 函数。Python 中的 lambda 函数是使用 lambda
关键字定义的小型匿名函数。
例如,考虑一个计算列表的范围(最大值与最小值之差)的函数。您可以使用常规函数如下定义:
def range_func(x):
return max(x) - min(x)
print(range_func([1, 2, 3, 4])) # Output: 3
3
或者,您可以使用 lambda 函数实现相同的结果:
= lambda x: max(x) - min(x)
range_func print(range_func([1, 2, 3, 4])) # Output: 3
3
现在,让我们看看如何使用 lambda 函数在数据分析中应用自定义汇总统计。
例如,假设我们想计算每个社区中体重的范围。我们可以使用 range_func
函数来实现:
("neighborhood")
yao.groupby(=("weight_kg", range_func))
.agg(weight_range
.reset_index() )
neighborhood | weight_range | |
---|---|---|
0 | Briqueterie | 108 |
1 | Carriere | 115 |
2 | Cité Verte | 102 |
... | ... | ... |
6 | Nkomkana | 146 |
7 | Tsinga | 90 |
8 | Tsinga Oliga | 83 |
9 rows × 2 columns
注意我们没有将 range_func
放在引号中。只有内置函数才会被放在引号中。
现在,我们可以在 agg()
调用中直接使用 lambda 函数,而不调用 range_func
:
("neighborhood")
yao.groupby(=("weight_kg", lambda x: max(x) - min(x)))
.agg(weight_range
.reset_index() )
neighborhood | weight_range | |
---|---|---|
0 | Briqueterie | 108 |
1 | Carriere | 115 |
2 | Cité Verte | 102 |
... | ... | ... |
6 | Nkomkana | 146 |
7 | Tsinga | 90 |
8 | Tsinga Oliga | 83 |
9 rows × 2 columns
请注意,我们仍然向 agg()
函数提供了一个元组,('weight_kg', lambda x: max(x) - min(x))
,但元组的第二个元素是一个 lambda 函数。
这个 lambda 函数作用于元组中提供的列 weight_kg
。
再看一个例子:计算每个社区内体重的变异系数(CV)。CV 是标准差除以均值,是分布相对可变性的无单位度量。
("neighborhood")
yao.groupby(=("weight_kg", lambda x: (np.std(x) / np.mean(x)) * 100))
.agg(weight_cv
.reset_index() )
neighborhood | weight_cv | |
---|---|---|
0 | Briqueterie | 33.531748 |
1 | Carriere | 32.027533 |
2 | Cité Verte | 33.255829 |
... | ... | ... |
6 | Nkomkana | 33.187496 |
7 | Tsinga | 33.937145 |
8 | Tsinga Oliga | 35.894453 |
9 rows × 2 columns
19.14 练习题:按社区年龄的四分位距
找出每个社区的年龄变量的四分位距(IQR)。IQR 是第 75 和第 25 百分位数之间的差。您的 lambda 如下所示:lambda x: x.quantile(0.75) - x.quantile(0.25)
# Your code here
19.15 总结
在本课程中,您学会了如何使用 agg()
快速获取数据的汇总统计信息,使用 groupby()
对数据进行分组,并将 groupby()
与 agg()
结合使用以实现强大的数据汇总。
这些技能对于探索性数据分析和为展示或绘图准备数据至关重要。groupby()
和 agg()
结合使用是 pandas 中最常见和最有用的数据操作技术之一。
在接下来的课程中,我们将探讨将 groupby()
与其他 pandas 方法结合使用的方法。
下次见!