

2.6.1 按行汇总
通常的数据操作逻辑都是 按列方式(colwise) ,这使得按行汇总很困难。
dplyr 包提供了 rowwise() 函数为数据框创建 按行方式(rowwise) ,使用 rowwise() 后并不是真的改变数据框,只是创建了按行元信息,改变了数据框的操作逻辑:
rf = df %>%
rowwise()
rf %>%
mutate(total = sum(c(chinese, math, english)))
函数 c_across() 是为 按行方式( rowwise )在选定的列范围汇总数据而设计的,它没
有提供 .fns 参数,只能选择列。
rf %>%
mutate(total = sum(c_across(where(is.numeric))))

若只是做按行求和或均值,直接用 rowSums() / rowMeans() 速度更快(不需要分割 - 汇总 - 合并),这里的 rowwise 行化后提供可以做更多的按行汇总的可能。
df %>%
mutate(total = rowSums(across(where(is.numeric))))
按行方式(rowwise) 可以理解为一种特殊的分组:每一行作为一组。为 rowwise() 提
供行 ID ,用 summarise() 做汇总更能体会这一点:
df %>%
rowwise(name) %>%
summarise(total = sum(c_across(where(is.numeric))))

rowwise 行化更让人惊喜的是:它的逐行处理的逻辑 + 嵌套数据框可以更好地实现批量建模,在 rowwise 行化模式下,批量建模就像计算新列一样自然。批量建模 [见下章] 可以用 " 嵌套数据框 + purrr::map_*() ”实现,但这种 rowwise 技术,具有异曲同工之妙。
2.6.2 窗口函数
汇总函数如 sum() 和 mean() 接受 n个输入,返回 1 个值。而窗口函数是汇总函数的变体:接受 n 个输入,返回 n 个值。
例如, cumsum() 、 cummean() 、 rank() 、 lead() 、 lag() 等。
1. 排名和排序函数
共有 6 个排名函数,只介绍最常用的 min_rank() :从小到大排名( ties.method="min" ) ,若要从大到小排名需要套一个 desc()
df %>%
mutate(ranks = min_rank(desc(math))) %>%
arrange(ranks)
2. 移位函数
lag() : 取前一个值,数据整体右移一位,相当于将时间轴滞后一个单位
lead() : 取后一个值,数据整体左移一位,相当于将时间轴超前一个单位
library(lubridate)
dt = tibble(
day = as_date("2019-08-30") + c(0,4:6),
wday = weekdays(day),
sales = c(2,6,2,3),
balance = c(30, 25, -40, 30)
)
dt %>%
mutate(sales_lag = lag(sales), sales_delta = sales - lag(sales))
注 :默认是根据行序移位,可用参数 order_by 设置根据某变量值大小顺序做移位。
3. 累计汇总
R base 已经提供了 cumsum() 、 cummin() 、 cummax() 、 cumprod()
dplyr 包又提供了 cummean() 、 cumany() 、 cumall() ,后两者可与 filter() 连用
选择行:
cumany(x): 用来选择遇到第一个满足条件之后的所有行cumany(!x): 用来选择遇到第一个不满足条件之后的所有行cumall(x): 用来选择所有行直到遇到第一个不满足条件的行cumall(!x): 用来选择所有行直到遇到第一个满足条件的行
dt %>%
filter(cumany(balance < 0)) # 选择第一次透支之后的所有行
dt %>%
filter(cumall(!(balance < 0))) # 选择所有行直到第一次透支

2.6.3 滑窗迭代
"窗口函数"术语来自 SQL,意味着逐窗口浏览数据,将某函数重复应用于数据的每个 "窗口 " 。窗口函数的典型应用包括滑动平均、累计和以及更复杂如滑动回归。

slider::slide_*() 系列函数与 purrr::map_*() 是类似的,只是将 "逐元素迭代" 换成了"逐窗口迭代"。
金融时间序列数据经常需要计算滑动平均,比如计算 sales 的 3 日滑动平均:
library(slider)
dt %>%
mutate(avg_3 = slide_dbl(sales, mean, .before = 1, .after = 1))
输出每个滑动窗口更便于该 3 日滑动平均理解是如何计算的:
slide(dt$sales, ~ .x, .before = 1, .after = 1)

细心的读者可能发现了:上面计算的并不是真正的 3 日滑动平均,而是连续 3 个值的滑动平均。这是因为 slide() 函数默认是以行索引来滑动,如果日期也是连续日期这是没有问题的。但是若日期有跳跃,则结果可能不是你想要的。
那么,怎么计算真正的 3 日滑动平均呢?需要改用 slide_index() 函数,并提供日期索引,其基本格式为:
slide_index(.x, .i, .f, ...)其中参数 .i 用来传递索引向量,实现根据 “.i 的当前元 + 其前 / 后若干元 ”创建相应的 .x 的滑动窗口。
来看一下的连续 3 日滑动窗口与连续 3 值滑动窗口的区别:
slide(dt$day, ~ .x, .before = 1, .after = 1)
slide_index(dt$day, dt$day, ~ .x, .before = 1, .after = 1)

最后,计算 sales 真正的 3 日滑动平均:
dt %>%
mutate(avg_3 = slide_index_dbl(sales, day, mean, .before = 1, .after = 1))
2.6.4 整洁计算
tidyverse 代码之所以这么“整洁、优雅”,访问列只需要提供列名,不需要加引号,不需要加数据框环境 df$, 这是因为它内部采用了一套 整洁计算( tidy evaluation )框架。
如果我们也想自定义这样的 "整洁、优雅" 函数,即在自定义函数中也这样 "整洁、优雅地传递参数,就需要掌握一点 整洁计算 的技术。
1. 数据屏蔽与整洁选择
整洁计算 的两种基本形式是:
- 数据屏蔽:使得可以不用带数据框(环境变量)名字,就能使用数据框内的变量(数据变量),便于在数据集内计算值
- 整洁选择:即各种选择列语法,便于使用数据集中的列
数据屏蔽为直接使用带来了代码简洁,但作为函数参数时的间接使用,正常是环境变量,要想作为数据变量使用,则需要用两个大括号括起来 {{var}} :
var_summary = function(data, var) {
data %>%
summarise(n = n(), mean = mean({{var}}))
}
mtcars %>%
group_by(cyl) %>%
var_summary(mpg)
若是字符向量形式,想作为数据变量,则需要在函数体中使用 .data[[var]] ,这里 .data 是代替数据集的代词:
var_summary = function(data, var) {
data %>%
summarise(n = n(), mean = mean(.data[[var]]))
}
mtcars %>%
group_by(cyl) %>%
var_summary("mpg")
还可用于对列名向量的循环机制,比如对因子型各列计算各水平值频数:
mtcars[,9:10] %>%
names() %>%
map(~ count(mtcars, .data[[.x]]))

同样地,整洁选择作为函数参数时的间接使用,也需要用两个大括号括起来 {{vars}}:
summarise_mean = function(data, vars) {
data %>%
summarise(n = n(), across({{vars}}, mean))
}
mtcars %>%
group_by(cyl) %>%
summarise_mean(where(is.numeric))
若是字符向量形式,则需要借助函数 all_of() 或 any_of() ,取决于你的选择:
vars = c("mpg", "vs")
mtcars %>% select(all_of(vars))
mtcars %>% select(!all_of(vars))最后,再来看使用数据屏蔽或整洁选择同时修改列名的用法:
my_summarise = function(data, mean_var, sd_var) {
data %>%
summarise("mean_{{mean_var}}" := mean({{mean_var}}),
"sd_{{sd_var}}" := mean({{sd_var}}))
}
mtcars %>%
group_by(cyl) %>%
my_summarise(mpg, disp)

my_summarise = function(data, group_var, summarise_var) {
data %>%
group_by(across({{group_var}})) %>%
summarise(across({{summarise_var}}, mean, .names = "mean_{.col}"))
}
mtcars %>%
my_summarise(c(am, cyl), where(is.numeric))
2. 引用与反引用
创建 tidyverse 风格的整洁函数,另一种做法是使用引用与反引用机制。这需要额外的两个步骤:
- 用
enquo()让函数自动引用其参数 - 用
!!反引用该参数
以自定义 计算分组均值 函数为例:
grouped_mean = function(data, summary_var, group_var) {
summary_var = enquo(summary_var)
group_var = enquo(group_var)
data %>%
group_by(!!group_var) %>%
summarise(mean = mean(!!summary_var))
}
grouped_mean(mtcars, mpg, cyl)
要想修改结果列名,可借助 as_label() 函数从引用中提取名字:
grouped_mean = function(data, summary_var, group_var) {
summary_var = enquo(summary_var)
group_var = enquo(group_var)
summary_nm = str_c("mean_", as_label(summary_var))
group_nm = str_c("group_", as_label(group_var))
data %>%
group_by(!!group_nm := !!group_var) %>%
summarise(!!summary_nm := mean(!!summary_var))
}
grouped_mean(mtcars, mpg, cyl)
要传递多个参数可以用 ...参数 。比如,我们还想让用于计算分组均值的 group_var
可以是任意多个,这就需要改用 ... 参数,为了更好地应付这种传递特意将该参数放在最后一个位置。另外,将其它函数参数都增加 . 前缀是一个好的做法,因为可以降低与 ... 参数的冲突风险。
grouped_mean = function(.data, .summary_var, ...) {
summary_var = enquo(.summary_var)
.data %>%
group_by(...) %>%
summarise(mean = mean(!!summary_var))
}
grouped_mean(mtcars, disp, cyl, am)
... 参数不需要做引用和反引用就能正确工作,但若要修改结果列名就不行了,仍需要借助引用和反引用,但是要改用 enques() 和 !!!
grouped_mean = function(.data, .summary_var, ...) {
summary_var = enquo(.summary_var)
group_vars = enquos(..., .named = TRUE)
summary_nm = str_c("avg_", as_label(summary_var))
names(group_vars) = str_c("groups_", names(group_vars))
.data %>%
group_by(!!!group_vars) %>%
summarise(!!summary_nm := mean(!!summary_var))
}
grouped_mean(mtcars, disp, cyl, am)

另外,参数 ... 也可以传递表达式:
filter_fun = function(df, ...) {
df %>%
filter(...)
}
mtcars %>% filter_fun(mpg > 25 & disp > 90)
最后,再来看一个自定义绘制散点图的模板函数:
scatter_plot = function(df, x_var,y_var) {
x_var = enquo(x_var)
y_var = enquo(y_var)
ggplot(data = df, aes(x = !!x_var, y = !!y_var)) +
geom_point() +
theme_bw() +
theme(plot.title = element_text(lineheight = 1, face = "bold", hjust = 0.5)) +
geom_smooth() +
ggtitle(str_c(as_label(y_var), " vs. ", as_label(x_var)))
}
scatter_plot(mtcars, disp, hp)

参考文献
- Vignettes of dplyr
- Vignettes of slider
- Lionel Henry, Hadley Wickham. Tidy evaluation, https://tidyeval.tidyverse.org/
- Jesse Cambon, R-Bloggers:Practical Tidy Evaluation.
版权声明:原创作品,版权所有,禁止用于任何出版发行。
本文介绍了R语言中dplyr包的各种数据处理方法,包括按行汇总、窗口函数的应用及滑窗迭代技巧,并探讨了如何利用tidyverse实现整洁计算。

被折叠的 条评论
为什么被折叠?



