![b883647680cc9707e1fd83550d9e9b7b.png](https://i-blog.csdnimg.cn/blog_migrate/b2ae78634a1c0b8e478bddfd56e8e8c0.jpeg)
![f8dc02f947258fbd62bff17da40eca5f.png](https://i-blog.csdnimg.cn/blog_migrate/590a23e375cda65a93b3d9fc9fdc03ee.jpeg)
2.6.1 按行汇总
通常的数据操作逻辑都是 按列方式(colwise
) ,这使得按行汇总很困难。
dplyr
包提供了 rowwise()
函数为数据框创建 按行方式(rowwise
) ,使用 rowwise()
后并不是真的改变数据框,只是创建了按行元信息,改变了数据框的操作逻辑:
rf = df %>%
rowwise()
rf %>%
mutate(total = sum(c(chinese, math, english)))
![25c7094f5a21756dd42c627536e83e46.png](https://i-blog.csdnimg.cn/blog_migrate/d4e0cdf7ac9d1c460ae22ad28972a5f0.jpeg)
函数 c_across()
是为 按行方式( rowwise
)在选定的列范围汇总数据而设计的,它没
有提供 .fns
参数,只能选择列。
rf %>%
mutate(total = sum(c_across(where(is.numeric))))
![a806cb239fa3554f7e5fd6cea37b1d9b.png](https://i-blog.csdnimg.cn/blog_migrate/5307486d856edcbad2212d0dc2696627.png)
![6a9d565bf576e93d8c52efb13fbbb725.png](https://i-blog.csdnimg.cn/blog_migrate/231c63f309cb90068b1debb753e3b3a7.jpeg)
若只是做按行求和或均值,直接用 rowSums() / rowMeans()
速度更快(不需要分割 - 汇总 - 合并),这里的 rowwise
行化后提供可以做更多的按行汇总的可能。
df %>%
mutate(total = rowSums(across(where(is.numeric))))
![c7ec7284b8e9520fc757091a98b3358d.png](https://i-blog.csdnimg.cn/blog_migrate/613b1db74a420834d9a48033a1f988b5.jpeg)
按行方式(rowwise
) 可以理解为一种特殊的分组:每一行作为一组。为 rowwise()
提
供行 ID ,用 summarise()
做汇总更能体会这一点:
df %>%
rowwise(name) %>%
summarise(total = sum(c_across(where(is.numeric))))
![4383fe791df6e099cbcd46378ed10128.png](https://i-blog.csdnimg.cn/blog_migrate/843de055d746f945ab68a43cc167e5f6.png)
![c4ebbe7166ea991bc4afc639b24c4dc1.png](https://i-blog.csdnimg.cn/blog_migrate/296a13583d786f9afd800fd8fb4bbf53.png)
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)
![c086ea8cbe435c0870638d5cb2e1ba4d.png](https://i-blog.csdnimg.cn/blog_migrate/7954e345e012176a9c22f8912470eef4.jpeg)
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))
![fa1902dfd5de00a72e5c899ff2f9f77e.png](https://i-blog.csdnimg.cn/blog_migrate/6166d091b0b4f0c56e73bec0c53654ed.png)
注 :默认是根据行序移位,可用参数 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)) # 选择第一次透支之后的所有行
![20adf53ede7f93a300ec0784328f5e3e.png](https://i-blog.csdnimg.cn/blog_migrate/9d2db7d43cf6dfcae6d4ef53883f6244.png)
dt %>%
filter(cumall(!(balance < 0))) # 选择所有行直到第一次透支
![4c0ae6a20d716359fa00834da35672cc.png](https://i-blog.csdnimg.cn/blog_migrate/128408768fca67f3725bf6e3ae099387.png)
![cec0dbf221cec520f922e9ca7d5f1d0c.png](https://i-blog.csdnimg.cn/blog_migrate/147fae3350f0db52b2b63715994f8443.png)
2.6.3 滑窗迭代
"窗口函数"术语来自 SQL
,意味着逐窗口浏览数据,将某函数重复应用于数据的每个 "窗口 " 。窗口函数的典型应用包括滑动平均、累计和以及更复杂如滑动回归。
![5db06c7320c9b744c7463b4490281d30.png](https://i-blog.csdnimg.cn/blog_migrate/812dd290db796b2cac4e80be7f294be1.jpeg)
slider::slide_*()
系列函数与 purrr::map_*()
是类似的,只是将 "逐元素迭代" 换成了"逐窗口迭代"。
金融时间序列数据经常需要计算滑动平均,比如计算 sales
的 3 日滑动平均:
library(slider)
dt %>%
mutate(avg_3 = slide_dbl(sales, mean, .before = 1, .after = 1))
![a006e68d846c5e163245eecccbcf3b49.png](https://i-blog.csdnimg.cn/blog_migrate/433c904ef47ba8828f68e0e896bf984b.png)
输出每个滑动窗口更便于该 3 日滑动平均理解是如何计算的:
slide(dt$sales, ~ .x, .before = 1, .after = 1)
![867bad8f7dc165ecb2dcb68aa9a3d238.png](https://i-blog.csdnimg.cn/blog_migrate/513789a2a1e9737e5e0dec1735b3a814.png)
![0a8574bcc0703ee834cd94013e8402de.png](https://i-blog.csdnimg.cn/blog_migrate/780a119f1cd8157b7d82a6dfaf972ff6.png)
细心的读者可能发现了:上面计算的并不是真正的 3 日滑动平均,而是连续 3 个值的滑动平均。这是因为 slide() 函数默认是以行索引来滑动,如果日期也是连续日期这是没有问题的。但是若日期有跳跃,则结果可能不是你想要的。
那么,怎么计算真正的 3 日滑动平均呢?需要改用 slide_index()
函数,并提供日期索引,其基本格式为:
slide_index(.x, .i, .f, ...)
其中参数 .i
用来传递索引向量,实现根据 “.i
的当前元 + 其前 / 后若干元 ”创建相应的 .x
的滑动窗口。
来看一下的连续 3 日滑动窗口与连续 3 值滑动窗口的区别:
slide(dt$day, ~ .x, .before = 1, .after = 1)
![3f40b751edc9a5290809af5fd289c0a2.png](https://i-blog.csdnimg.cn/blog_migrate/28489df8d3b662d613f8a3cb22cc6518.png)
slide_index(dt$day, dt$day, ~ .x, .before = 1, .after = 1)
![b28f304cc5b03336c7ce6b5563d67683.png](https://i-blog.csdnimg.cn/blog_migrate/8023ca5435574988100d7c941ac13f29.png)
![fdcb3efeff98509056a987a7638a1dfd.png](https://i-blog.csdnimg.cn/blog_migrate/206f850bb8d9013caa9deaa2b29ad28e.png)
最后,计算 sales
真正的 3 日滑动平均:
dt %>%
mutate(avg_3 = slide_index_dbl(sales, day, mean, .before = 1, .after = 1))
![cc75a467ea12d7b4f0b40d1486700cb0.png](https://i-blog.csdnimg.cn/blog_migrate/f385ee5baa9aefae099b55269378ce01.png)
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)
![8470ebadef630a1b98470041d7383403.png](https://i-blog.csdnimg.cn/blog_migrate/1b7785d1b9815892ffa11d9a501c0251.png)
若是字符向量形式,想作为数据变量,则需要在函数体中使用 .data[[var]]
,这里 .data
是代替数据集的代词:
var_summary = function(data, var) {
data %>%
summarise(n = n(), mean = mean(.data[[var]]))
}
mtcars %>%
group_by(cyl) %>%
var_summary("mpg")
![b1e7ccdf086fa7be11f6f3e7e16f23d5.png](https://i-blog.csdnimg.cn/blog_migrate/2e6dea6f57d2614b1487edfec3038dc8.png)
还可用于对列名向量的循环机制,比如对因子型各列计算各水平值频数:
mtcars[,9:10] %>%
names() %>%
map(~ count(mtcars, .data[[.x]]))
![e901d32ef3953893f33b947e8189e370.png](https://i-blog.csdnimg.cn/blog_migrate/a953b2df571602e0f94ba4999f938c5b.png)
![daf84cd95fe11c9f1db81a5961218e51.png](https://i-blog.csdnimg.cn/blog_migrate/192d73c5e7136d3786730e1afb4aea11.png)
同样地,整洁选择作为函数参数时的间接使用,也需要用两个大括号括起来 {{vars}}
:
summarise_mean = function(data, vars) {
data %>%
summarise(n = n(), across({{vars}}, mean))
}
mtcars %>%
group_by(cyl) %>%
summarise_mean(where(is.numeric))
![30a2c1b1447602a8541272e1e5937044.png](https://i-blog.csdnimg.cn/blog_migrate/d09628628c485f10195b818450d1fcd3.jpeg)
若是字符向量形式,则需要借助函数 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)
![eca1bbf6320325a3900ac4d5c3535623.png](https://i-blog.csdnimg.cn/blog_migrate/307a43a2458f827fd4c3a41d01c62301.png)
![d7763273a270fe36b478dc2be9dc7927.png](https://i-blog.csdnimg.cn/blog_migrate/94ecd6ad77ecb4adbbacf1245bc90eff.png)
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))
![acf441bd3dba66859cf3b4de9a45dfa9.png](https://i-blog.csdnimg.cn/blog_migrate/bcc3eaa4a02780b99cc521745ca4d03a.jpeg)
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)
![a74be3b322baabaf2f9c7400c7a04dbc.png](https://i-blog.csdnimg.cn/blog_migrate/2fc4e192bc59c1939f992e8a225f4982.png)
要想修改结果列名,可借助 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)
![0074fb383e2819a80c7199dfd78045b8.png](https://i-blog.csdnimg.cn/blog_migrate/d4888c68cd8e61ee614a6277337ad45c.png)
要传递多个参数可以用 ...
参数 。比如,我们还想让用于计算分组均值的 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)
![d74b75a5a673b2298888731ade4a3a12.png](https://i-blog.csdnimg.cn/blog_migrate/a8193d30389e95b2d5e786a1abae18cc.png)
...
参数不需要做引用和反引用就能正确工作,但若要修改结果列名就不行了,仍需要借助引用和反引用,但是要改用 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)
![3da1425b7d273a79e13f02e3ddb089fc.png](https://i-blog.csdnimg.cn/blog_migrate/e1eefc70f878df25b27b6929f0c18d29.png)
![001d112c0ddba20787450bc3175f28f7.png](https://i-blog.csdnimg.cn/blog_migrate/c7255ff130041b0968b03632b92683e3.png)
另外,参数 ...
也可以传递表达式:
filter_fun = function(df, ...) {
df %>%
filter(...)
}
mtcars %>% filter_fun(mpg > 25 & disp > 90)
![d1348ca3632d601af53dd0629542be55.png](https://i-blog.csdnimg.cn/blog_migrate/e3a7e21ab540f6cf2406ce195e956c42.png)
最后,再来看一个自定义绘制散点图的模板函数:
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)
![88f5a9d4034f76e9162e8b95a50a1eee.png](https://i-blog.csdnimg.cn/blog_migrate/245335bbe0c31f61d12fa906f2e5ef51.png)
![2afa4aeac9931fa37ae1f757fe870f8f.png](https://i-blog.csdnimg.cn/blog_migrate/0777c8bbb40124c7b58db66d6e270460.png)
参考文献
- Vignettes of dplyr
- Vignettes of slider
- Lionel Henry, Hadley Wickham. Tidy evaluation, https://tidyeval.tidyverse.org/
- Jesse Cambon, R-Bloggers:Practical Tidy Evaluation.
版权声明:原创作品,版权所有,禁止用于任何出版发行。