R 聚合函数:summarise() 和 group_by() 示例

⚡ 智能摘要

R 语言中的 dplyr 包的聚合函数可以将多行数据合并成每个组的单个统计量。本教程将结合使用 group_by() 和 summarise() 函数,对 Lahman 的击球数据进行分析,计算 104,324 个观测值的均值、总和、差值、计数和位置值。

  • 🧮 核心动词: summarise(df, name = function(column)) 返回一行,而 group_by() 首先将其转换为每组一行。
  • 📊 统计类别: 均值和中位数描述中心位置,标准差和四分位距描述离散程度,最小值和最大值描述极差。
  • 🔢 数数: n() 计算当前组中的行数,n_distinct() 计算该组中的唯一值数。
  • 📍 位置辅助角色: first()、last() 和 nth() 例如trac各组的具体观察结果。
  • 🔄 多栏工作: across() 函数将一个函数应用于多列,而无需对每一列重复编写代码。
  • 🔓 的Grouping 卫生: 在对整个表再次执行计算之前,务必先调用 ungroup()。

R 聚合函数

本教程中使用的数据集

对变量进行汇总可以让你初步了解数据,而按组进行汇总则可以让你更深入地了解值的分布情况。本教程将演示如何使用 dplyr 库按组聚合数据集。

本教程使用 Lahman 击球数据集。导入数据集后,请继续阅读。ping 仅包含以下列出的九列,工作数据框包含 104,324 个观测值和 9 个变量:

  • playerID:玩家ID代码。因素
  • yearID: 年份。因素
  • teamID:团队。因素
  • lgID:联盟。因素:AA AL FL NL PL UA
  • AB:击球数。数字
  • G:游戏:玩家的游戏次数。数字
  • R:运行。数值
  • HR:本垒打。数字
  • SH:牺牲命中。数值

在进行汇总之前,您将执行以下步骤来准备数据:

  • 步骤 1:导入数据
  • 第 2 步:选择相关变量
  • 步骤 3:对数据进行排序
library(dplyr)

# Step 1
data <- read.csv("https://raw.githubusercontent.com/guru99-edu/R-Programming/master/lahman-batting.csv") %>%

# Step 2
select(c(playerID, yearID, AB, teamID, lgID, G, R, HR, SH))  %>% 

# Step 3
arrange(playerID, teamID, yearID)

导入数据集时,一个好的做法是使用 glimpse() 函数来了解数据集的结构。

# Structure of the data
glimpse(data)

输出:

Observations: 104,324
Variables: 9
$ playerID <fctr> aardsda01, aardsda01, aardsda01, aardsda01, aardsda01, a...
$ yearID   <int> 2015, 2008, 2007, 2006, 2012, 2013, 2009, 2010, 2004, 196...
$ AB       <int> 1, 1, 0, 2, 0, 0, 0, 0, 0, 603, 600, 606, 547, 516, 495, ...
$ teamID   <fctr> ATL, BOS, CHA, CHN, NYA, NYN, SEA, SEA, SFN, ATL, ATL, A...
$ lgID     <fctr> NL, AL, AL, NL, AL, NL, AL, AL, NL, NL, NL, NL, NL, NL, ...
$ G        <int> 33, 47, 25, 45, 1, 43, 73, 53, 11, 158, 155, 160, 147, 15...
$ R        <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 113, 84, 100, 103, 95, 75...
$ HR       <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 39, 29, 44, 38, 47, 34, 40...
$ SH       <int> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, ...

总结()

summarise() 的语法很基本,并且与 dplyr 库中包含的其他动词一致。

summarise(df, variable_name=condition) 
arguments: 
- `df`: Dataset used to construct the summary statistics 
- `variable_name=condition`: Formula to create the new variable

看看下面的代码:

summarise(data, mean_run =mean(R))

Code 说明

  • summarise(data, mean_run = mean(R)):创建一个名为 mean_run 的变量,它是数据集数据中列运行的平均值。

输出:

##   mean_run
## 1 19.20114

你可以添加任意数量的变量。返回平均比赛场次和平均牺牲命中数。

summarise(data, mean_games = mean(G),
    mean_SH = mean(SH, na.rm = TRUE))

Code 说明

  • mean_SH = mean(SH, na.rm = TRUE):汇总第二个变量。您设置 na.rm = TRUE,因为 SH 列包含缺失的观测值。

输出:

##   mean_games  mean_SH
## 1   51.98361 2.340085

group_by() 与不使用 group_by()

单独使用 summarise() 函数会将整个数据框合并成一行,如上面的示例所示。它的真正威力在与 group_by() 函数结合使用时才会显现出来: dplyr 然后,该库分别对每个组应用相同的计算,并为每个组返回一行。

请注意,group_by 与所有其他动词(即 mutate()、filter()、arrange() 等)完美配合。

当您有多个步骤时,使用管道运算符会很方便。您可以计算棒球联盟的平均本垒打数。

data %>%
	group_by(lgID) %>%
	summarise(mean_run = mean(HR))

Code 说明

  • 数据:用于构建汇总统计数据的数据集
  • group_by(lgID): 在汇总之前,按 lgID 值拆分数据
  • summarise(mean_run = mean(HR)):计算平均本垒打数

输出:

## 
# A tibble: 7 x 2
##     lgID  mean_run
##   <fctr>     <dbl>
## 1     AA 0.9166667
## 2     AL 3.1270988
## 3     FL 1.3131313
## 4     NL 2.8595953
## 5     PL 2.5789474
## 6     UA 0.6216216
## 7   <NA> 0.2867133	

该管道也适用于 ggplot() 函数,因此从原始数据到最终图表的整个流程都只需一条语句即可完成。以下代码结合了 group_by()、summarise() 和 ggplot() 函数,以柱状图的形式展示每个联盟的平均本垒打数。

您将执行以下步骤:

  • 步骤 1:选择数据框
  • 第 2 步:分组数据
  • 步骤 3:汇总数据
  • 步骤 4:绘制汇总统计数据
library(ggplot2)
# Step 1
data %>% 
#Step 2
group_by(lgID) %>% 
#Step 3
summarise(mean_home_run = mean(HR)) %>% 
#Step 4
ggplot(aes(x = lgID, y = mean_home_run, fill = lgID)) +
    geom_bar(stat = "identity") +
    theme_classic() +
    labs(
        x = "baseball league",
        y = "Average home run",
        title = paste(
            "Example group_by() with summarise()"
        )
    )

输出:

Group_by 示例与 Summarise

您可以在 summarise() 函数内部使用的函数

动词 summarise() 与 R 中的几乎所有函数兼容。下面是可以与 summarise() 一起使用的一些有用函数的简短列表:

目的 功能 描述
基础版 意思() 向量 x 的平均值
中位数() 向量 x 的中值
总和() 向量 x 的和
变异 sd() 向量 x 的标准差
四分位间距() 向量 x 的四分位数
范围 分钟() 向量 x 的最小值
最大限度() 向量 x 的最大值
分位数() 向量 x 的分位数
职务 第一() 与 group_by() 一起使用对组进行首次观察
最后的() 与 group_by() 一起使用。该组的最后一次观察
第 n 个() 与 group_by() 一起使用。该组的第 n 次观察
计数 n() 与 group_by() 一起使用。统计行数
n_distinct() 与 group_by() 一起使用。计算不同观察值的数量

以下各节将逐一讲解上表中的每个函数的示例。如果您是第一次使用管道符和其他动词,请从第一个示例开始。 选择、筛选和排列教程.

基本功能

在前面的例子中,您没有将汇总统计数据存储在数据框中。

您可以通过两个步骤从摘要生成数据框:

  • 步骤 1:存储数据框以供进一步使用
  • 步骤 2:使用数据集创建线图

步骤1) 计算每年玩过的游戏的平均场次。

## Mean
ex1 <- data %>%
	group_by(yearID) %>%
	summarise(mean_game_year = mean(G))
head(ex1)

Code 说明

  • 击球数据集的摘要统计数据存储在数据框 ex1 中。

输出:

## # A tibble: 6 x 2
##   yearID mean_game_year
##    <int>          <dbl>
## 1   1871       23.42308
## 2   1872       18.37931
## 3   1873       25.61538
## 4   1874       39.05263
## 5   1875       28.39535
## 6   1876       35.90625	

步骤2) 您可以使用线图显示汇总统计数据并查看趋势。

# Plot the graph
ggplot(ex1, aes(x = yearID, y = mean_game_year)) +
    geom_line() +
    theme_classic() +
    labs(
        x = "Year",
        y = "Average games played",
        title = paste(
            "Average games played from 1871 to 2016"
        )
    )

输出:

基本函数示例

子集

函数 summarise() 与子集兼容。

## Subsetting + Median
data %>%
group_by(lgID) %>%
summarise(median_at_bat_league = median(AB), 
	#Compute the median without the zero 
	median_at_bat_league_no_zero = median(AB[AB > 0]))

Code 说明

  • median_at_bat_league_no_zero = median(AB[AB > 0]): 变量 AB 包含大量 0。你可以比较 在蝙蝠 有或没有 0 的变量。

输出:

## # A tibble: 7 x 3
##     lgID median_at_bat_league median_at_bat_league_no_zero
##   <fctr>                <dbl>                        <dbl>
## 1     AA                  130                          131
## 2     AL                   38                           85
## 3     FL                   88                           97
## 4     NL                   56                           67
## 5     PL                  238                          238
## 6     UA                   35                           35
## 7   <NA>                  101                          101	

总和

另一个用于聚合变量的有用函数是 sum()。

您可以查看哪些联赛的本垒打更多。

## Sum
data %>%
	group_by(lgID) %>%
	summarise(sum_homerun_league = sum(HR))

输出:

## # A tibble: 7 x 2
##     lgID sum_homerun_league
##   <fctr>              <int>
## 1     AA                341
## 2     AL              29426
## 3     FL                130
## 4     NL              29817
## 5     PL                 98
## 6     UA                 46
## 7   <NA>                 41	

标准偏差

数据的分散性通过 R 中的标准差或 sd() 来计算。

# Spread
data %>%
	group_by(teamID) %>%
	summarise(sd_at_bat_league = sd(HR))

输出:

## # A tibble: 148 x 2
##    teamID sd_at_bat_league
##    <fctr>            <dbl>
##  1    ALT               NA
##  2    ANA        8.7816395
##  3    ARI        6.0765503
##  4    ATL        8.5363863
##  5    BAL        7.7350173
##  6    BFN        1.3645163
##  7    BFP        0.4472136
##  8    BL1        0.6992059
##  9    BL2        1.7106757
## 10    BL3        1.0000000
## # ... with 138 more rows		

各队本垒打数的分布差异很大,ALT 返回 NA,因为该队只有一行数据,所以无法计算标准差。

最小值和最大值

您可以使用函数 min() 和 max() 访问向量的最小值和最大值。

下面的代码返回一名球员在一个赛季中参加的最低和最高比赛场次。

# Min and max
data %>%
	group_by(playerID) %>%
	summarise(min_G = min(G),
    max_G = max(G))

输出:

## # A tibble: 10,395 x 3
##     playerID min_G max_G
##       <fctr>       <int>
##  1 aardsda01    53    73
##  2 aaronha01   120   156
##  3  aasedo01    24    66
##  4  abadfe01    18    18
##  5 abadijo01    11    11
##  6 abbated01     3   153
##  7 abbeybe01    11    11
##  8 abbeych01    80   132
##  9 abbotgl01     5    23
## 10 abbotji01    13    29
## # ... with 10,385 more rows

计数

统计每个组的观测值总是很有意义的。在 R 语言中,n() 函数返回当前组中的行数。

例如,下面的代码计算每个球员打球的年数。

# count observations
data %>%
	group_by(playerID) %>%
	summarise(number_year = n()) %>%
	arrange(desc(number_year))

输出:

## # A tibble: 10,395 x 2
##     playerID number_year
##       <fctr>       <int>
##  1 pennohe01          11
##  2 joosted01          10
##  3 mcguide01          10
##  4  rosepe01          10
##  5 davisha01           9
##  6 johnssi01           9
##  7  kaatji01           9
##  8 keelewi01           9
##  9 marshmi01           9
## 10 quirkja01           9
## # ... with 10,385 more rows

第一和最后

您可以选择一个组的第一个、最后一个或第 n 个位置。

例如,您可以找到每个球员的第一年和最后一年。

# first and last
data %>%
	group_by(playerID) %>%
	summarise(first_appearance = first(yearID),
		last_appearance = last(yearID))

输出:

## # A tibble: 10,395 x 3
##     playerID first_appearance last_appearance
##       <fctr>            <int>           <int>
##  1 aardsda01             2009            2010
##  2 aaronha01             1973            1975
##  3  aasedo01             1986            1990
##  4  abadfe01             2016            2016
##  5 abadijo01             1875            1875
##  6 abbated01             1905            1897
##  7 abbeybe01             1894            1894
##  8 abbeych01             1895            1897
##  9 abbotgl01             1973            1979
## 10 abbotji01             1992            1996
## # ... with 10,385 more rows

第 n 次观察

nth() 函数是对 first() 和 last() 函数的补充。它返回每个组中指定索引处的观测值。

例如,您可以仅过滤某支球队比赛的第二年。

# nth
data %>%
	group_by(teamID) %>%
	summarise(second_game = nth(yearID, 2)) %>%
	arrange(second_game)

输出:

## # A tibble: 148 x 2
##    teamID second_game
##    <fctr>       <int>
##  1    BS1        1871
##  2    CH1        1871
##  3    FW1        1871
##  4    NY2        1871
##  5    RC1        1871
##  6    BR1        1872
##  7    BR2        1872
##  8    CL1        1872
##  9    MID        1872
## 10    TRO        1872
## # ... with 138 more rows

不同观测值的数量

n() 返回当前组中的行数。相关的 n_distinct() 则统计唯一值的数量。

在下一个例子中,您将一支球队在所有时期内招募的球员总数加起来。

# distinct values
data %>%
	group_by(teamID) %>%
	summarise(number_player = n_distinct(playerID)) %>%
	arrange(desc(number_player))

Code 说明

  • group_by(teamID): 按团队分组
  • 总结(number_player = n_distinct(playerID):统计每支球队的不同球员人数
  • 排列(desc(number_player)):按玩家数量对数据进行排序

输出:

## # A tibble: 148 x 2
##    teamID number_player
##    <fctr>         <int>
##  1    CHN           751
##  2    SLN           729
##  3    PHI           699
##  4    PIT           683
##  5    CIN           679
##  6    BOS           647
##  7    CLE           646
##  8    CHA           636
##  9    DET           623
## 10    NYA           612
## # ... with 138 more rows

多组

可实现多个组之间的汇总统计。

# Multiple groups
data %>%
	group_by(yearID, teamID) %>%
	summarise(mean_games = mean(G)) %>%
	arrange(desc(teamID, yearID))

Code 说明

  • group_by(yearID, teamID): 按年份分组 和 团队
  • summarise(mean_games = mean(G)): 计算平均游戏次数
  • 排列(desc(teamID,yearID)):按团队和年份对数据进行排序

输出:

## # A tibble: 2,829 x 3
## # Groups:   yearID [146]
##    yearID teamID mean_games
##     <int> <fctr>      <dbl>
##  1   1884    WSU   20.41667
##  2   1891    WS9   46.33333
##  3   1886    WS8   22.00000
##  4   1887    WS8   51.00000
##  5   1888    WS8   27.00000
##  6   1889    WS8   52.42857
##  7   1884    WS7    8.00000
##  8   1875    WS6   14.80000
##  9   1873    WS5   16.62500
## 10   1872    WS4    4.20000
## # ... with 2,819 more rows

筛选

在进行操作之前,可以先过滤一下数据集,数据集从1871年开始,分析不需要1980年之前的年份。

# Filter
data %>%
	filter(yearID > 1980) %>%
	group_by(yearID) %>%
	summarise(mean_game_year = mean(G))

Code 说明

  • filter(yearID > 1980):过滤数据以仅显示相关年份(即 1980 年以后)
  • group_by(yearID):按年份分组
  • summarise(mean_game_year = mean(G)):汇总数据

输出:

## # A tibble: 36 x 2
##    yearID mean_game_year
##     <int>          <dbl>
##  1   1981       40.64583
##  2   1982       56.97790
##  3   1983       60.25128
##  4   1984       62.97436
##  5   1985       57.82828
##  6   1986       58.55340
##  7   1987       48.74752
##  8   1988       52.57282
##  9   1989       58.16425
## 10   1990       52.91556
## # ... with 26 more rows

取消组合

最后,你必须移除颗粒ping 在更改计算运行级别之前。如果没有使用 ungroup() 函数,第二个 summarise() 函数仍然会按玩家进行计算。

# Ungroup the data
data %>%
	filter(HR > 0) %>%
	group_by(playerID) %>%
	summarise(average_HR_game = sum(HR) / sum(G)) %>%
	ungroup() %>%
	summarise(total_average_homerun = mean(average_HR_game))

Code 说明

  • filter(HR >0) :排除零本垒打
  • group_by(playerID): 按玩家分组
  • summarise(average_HR_game = sum(HR)/sum(G)): 计算球员平均本垒打数
  • ungroup(): 移除组ping
  • summarise(total_average_homerun = mean(average_HR_game)):汇总数据

输出:

## # A tibble: 1 x 1
##   total_average_homerun
##                   <dbl>
## 1            0.06882226	

如何使用 across() 函数汇总多列数据

以上每个示例都是一次指定一列。当需要对多列应用相同的统计信息时,可以使用 across() 函数一次性将其应用于所有列。

# One function, several columns
data %>%
    group_by(lgID) %>%
    summarise(across(c(R, HR, G), mean, .names = "mean_{.col}"))

# Every numeric column at once
data %>%
    group_by(lgID) %>%
    summarise(across(where(is.numeric), mean, na.rm = TRUE))

# Several functions applied to several columns
data %>%
    group_by(teamID) %>%
    summarise(across(c(R, HR), list(avg = mean, total = sum)))

三个因素控制着这种行为:

  • .cols要操作的列,使用与 select() 相同的辅助函数编写,包括 where(is.numeric)。
  • .fns:一个函数,或者一个用于对每一列应用的函数命名列表。
  • .names:输出的命名模式,其中 {.col} 是列名,{.fn} 是函数名。

在 dplyr 1.0.0 中,across() 取代了旧的 summarise_all()、summarise_at() 和 summarise_if() 辅助函数。这些函数仍然可以运行,但已被取代,因此新代码应该使用 across()。

summarise() 函数的常见错误及解决方法

导致聚合失败的主要原因有以下四个方面。

  • 结果为NA。 如果缺少任何值,mean() 和 sum() 函数将返回 NA。请添加 na.rm = TRUE,就像上面的 mean_SH 示例一样。
  • “summarise() 已分组输出”消息。 的Grouping 使用两个变量分组,结果将按第一个变量分组。传递 .groups = “drop” 可移除分组。ping或者之后调用 ungroup()。
  • 后续计算得出的水平值有误。 数据框会一直保持分组状态,直到你取消分组,因此每个分组仍然会执行第二次 summarise() 函数。这正是 ungroup() 示例所演示的内容。
  • 输出结果的行数比预期要多。 summarise() 为每个组返回一行,因此可能会出现意外的组ping 变量会乘以输出值。请查看任意 tibble 上方打印的“Groups:”行。
# Drop the grouping as part of the call
data %>%
    group_by(yearID, teamID) %>%
    summarise(mean_games = mean(G), .groups = "drop")

R 中的 summarise() 函数:函数参考

要按组返回汇总信息,请先拆分数据:

# group by X1, X2, X3
group_by(df, X1, X2, X3)

并移除颗粒ping 当计算级别发生变化时:

ungroup(df)

下表列出了本教程中与 summarise() 一起使用的所有函数:

付款方式 功能 Code
意味着 意味着
summarise(df, mean_x1 = mean(x1))
中位数 中位数
summarise(df, median_x1 = median(x1))
总和 总和
summarise(df, sum_x1 = sum(x1))
标准偏差 sd
summarise(df, sd_x1 = sd(x1))
四分位数 智商
summarise(df, interquartile_x1 = IQR(x1))
最低限度 分钟
summarise(df, minimum_x1 = min(x1))
最多 最大
summarise(df, maximum_x1 = max(x1))
分位数 分位数
summarise(df, quantile_x1 = quantile(x1, probs = 0.75))
第一次观察 第一
summarise(df, first_x1 = first(x1))
最后的观察 最后
summarise(df, last_x1 = last(x1))
第 n 次观察 第XNUMX
summarise(df, nth_x1 = nth(x1, 2))
发生次数 n
summarise(df, n_x1 = n())
不同发生次数 n_distinct
summarise(df, n_distinct_x1 = n_distinct(x1))

常见问题

`summarise()` 函数会将每个分组合并成一行统计信息。`mutate()` 函数会保留每一行,并添加一个新列。当分组统计信息必须与原始观测值并列显示时,请使用 `mutate()` 函数。

的Grouping 使用两个或多个变量进行分组会导致结果按除最后一个变量之外的所有变量分组。dplyr 会发出警告,因为后续的动词会按分组静默运行。传递 `.groups = "drop"` 参数可以避免这种情况。

n() 函数统计当前分组中的每一行,包括重复行。n_distinct() 函数统计列中的唯一值数量。统计每个球队的球员数量需要使用 n_distinct() 函数,因为一名球员可能出现在多个赛季中。

诸如客户平均值、计数和最近购买时间等分组级聚合数据是表格型人工智能模型中最强的预测因子之一。使用 `group_by()` 和 `summarise()` 是构建此类聚合数据的标准方法,可以实现数据的可复现性。

是的。AI 助手可以将 aggregate() 和 tapply() 重写为 group_by() 和 summarise() 管道。请比较两种版本中的行数,因为 R 基础包和 dplyr 包处理缺失分组的方式不同。

总结一下这篇文章: