R 聚合函数:summarise() 和 group_by() 示例
⚡ 智能摘要
R 语言中的 dplyr 包的聚合函数可以将多行数据合并成每个组的单个统计量。本教程将结合使用 group_by() 和 summarise() 函数,对 Lahman 的击球数据进行分析,计算 104,324 个观测值的均值、总和、差值、计数和位置值。
本教程中使用的数据集
对变量进行汇总可以让你初步了解数据,而按组进行汇总则可以让你更深入地了解值的分布情况。本教程将演示如何使用 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()" ) )
输出:
您可以在 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)) |



