首页 > > > 【R语言】dplyr1.0.0新功能解读
作为 tidyverse 数据操作的核心包 dplyr 在 2020-05-29 迎来了大版本的更新,从此进入 1.0.0 时代。既然是大版本的更新,当然有一些重磅的新功能推出,我也在第一时间做了体验,现在分享给大家。
建议更新方式:
remove.packages("dplyr")
install.packages("dplyr")顺便说一句,R 4.0 我还没更新,同样是大版本更新,怕有些包用不了,先缓一缓再说。
本篇来介绍如下几个新功能:
本篇用到的数据:
链接:https://pan.baidu.com/s/1-EgHgckAdkUrlQmCTqCZBg
提取码:ng8o
示例数据采用虚拟的学生成绩数据,包含随机生成的 20 个 NA:
library(tidyverse)
df = readxl::read_xlsx("ExamDatas_NAs.xlsx")
df
across() 人如其名,让零个/一个/多个函数穿过所选择的列,即同时对所选择的多列应用若干函数,基本语法为:
across(.cols = everything(), .fns = NULL, ..., .names = NULL)其中,
- NULL:不对列作变换;
- 一个函数,如 mean;
- 一个 purrr 风格的匿名函数,如 ~ .x * 10
- 多个函数或匿名函数构成的列表
这些函数内部可以使用 cur_column() 和 cur_group() 以访问当前列和分组键值。
2. across() 的用途
across() 支持各种选择列语法,与 mutate() 和 summarise() 连用,产生非常强大的同时修改/(多种)汇总多列效果;
也能与 filter() 连用,在限定列范围内筛选"所有值都满足某条件的行",但不擅长筛选"存在值满足某条件的行";
也能与 group_by(), count() 和 distinct() 连用,此时 .fns 为 NULL,只起选择列的作用。
across() 函数的引入,使得可以弃用那些限定列范围的后缀: _all, _if, _at:
3. 选择列语法
(1) 用列名或索引选择列
df %>%
select(name, sex, math) # 或者 select(2, 3, 5)(2) 借助算子符号选择列
(3) 借助选择助手函数
■ 选择指定列:
■ 选择列名匹配的列:
■ 结合函数选择列:
4. 若干实例
(1) across() 与 mutate() 连用:对所选择的列应用某个函数
df %>%
mutate(across(everything(), as.character)) 
rescale <- function(x) {
rng <- range(x, na.rm = TRUE)
(x - rng[1]) / (rng[2] - rng[1])
}
df %>%
mutate(across(where(is.numeric), rescale))
(2) across() 与 summarise() 连用:以对所选择的列做一个或多个汇总
df %>%
group_by(class, sex) %>%
summarise(across(contains("h"), mean))
df_grp <- df %>% drop_na() %>%
group_by(class) %>%
summarise(across(where(is.numeric),
list(sum=sum, mean=mean, min=min)))
df_grp
可读性不好,再来个宽变长:
df_grp %>% pivot_longer(-class,
names_to = c("Vars", ".value"),
names_sep = "_")
(3) across() 与 filter() 连用(二者在逻辑上不搭):根据多列值选择行
df[, 4:6] %>%
filter(across(everything(), ~ .x > 75)) # 选择所有列都>75的行
df %>%
filter(across(where(is.numeric), ~ !is.na(.x))) # 选择数值列都不包含NA的行
注:不能再套 all_val(), 因为 across() 等价于 all_var()。
目前的支持还不好,暂时需要借助如下函数实现,期待后续的改进:
rowAny <- function(x) rowSums(x, na.rm = TRUE) > df[, 4:6] %>%
filter(rowAny(across(everything(), ~. > 90))) # 选择在所有列存在值>90的行
注:从数学逻辑上来说,【存在值>90】等价于【非“所有值都≤90”】,这在 iris 上测试成功,但本例只返回 6 个观测,不知道是不是bug。
通常的数据操作逻辑都是按列方式(colwise),这使得按行汇总很困难。dplyr 包提供了 rowwise() 函数为数据框创建按行方式(rowwise),它不是真的改变数据框,只是创建了按行信息。
使用 rowwise() 后,就会改变数据框的内部操作逻辑:
df[,1:6] %>%
rowwise() %>%
mutate(total = sum(c(chinese, math, english)))
dplyr包还提供了 c_across() 是为 按行方式(rowwise)在选定的列范围汇总数据而设计的,它没有提供 .fns 参数,只能选择列:
df %>%
rowwise() %>%
mutate(total = sum(c_across(where(is.numeric))))
注:如果只是按行加和或求平均的话,用 R 自带的 rowSums() 和 rowMeans() 更好,这里的rowwise 行化后提供可以做更多的按行汇总的可能。
2. 批量建模技术
如果 rowwise 行化只是上面这点东西的话都不值一写,更让人惊喜的是:它的逐行处理的逻辑 + 嵌套数据框可以更好地实现批量建模。
批量建模可以用嵌套数据框 + purrr map_*() 实现,但这种 rowwise 技术,具有异曲同工之妙。
rowwise 按行方式,可以理解为一种特殊的分组:每一行作为一组。若为 rowwise() 提供行 ID,用 summarise() 做汇总更能体会这一点:
df %>%
rowwise(name) %>%
summarise(total = sum(c_across(where(is.numeric))))
下面进入正题,用 gapminder 数据集演示,包含 142 个国家的人口、预期寿命、人均 GDP 等:
library(broom) # tidy方式提取模型信息
load("gapminder.rda")
gapminder
我们想要对每个国家分别建模,像这样:

先对数据框用 nest_by() 做嵌套就得到这样 rowwise 化的嵌套数据框:
by_country <- gapminder %>%
nest_by(country)
by_country
一个国家的数据占一行,rowwise 化的逻辑,就是按行操作数据,正好适合逐行地对每个嵌套的数据框建模和提取模型信息:
by_country = by_country %>%
mutate(model = list(lm(lifeExp ~ year, data = data)),
pred = list(predict(model, data)))
by_country
by_country = by_country %>%
mutate(rmse = sqrt(mean((pred - data$lifeExp) ^ 2)),
rsq = summary(model)$r.squared,
slope = summary(model)$coefficients[2])
by_country
by_country %>%
summarise(glance(model))
by_country %>%
summarise(tidy(model))
by_country %>%
summarise(augment(model))
嵌套数据框可以同样地筛选行,再用 unnest() 解除嵌套就恢复到原数据级别。
by_country %>%
filter(rsq < 0.5) %>% unnest(data) %>%
ggplot(aes(year, lifeExp)) +
geom_line(aes(color = country))
by_country %>%
filter(slope > 0.6) %>% unnest(data) %>%
ggplot(aes(year, lifeExp)) +
geom_line(aes(color = country))
可见,在 rowwise 行化模式下,批量建模就像计算新列一样自然,这真是太神奇了!
以前行切片函数 slice() 存在感不强,现在变成一个系列之后,几乎和 filter() 平起平坐了。
slice(df, 3:7) # 选择3-7行
lice_head(df, n, prop) # 从前面开始选择若干行
slice_tail(df, n, prop) # 从后面开始选择若干行
slice_min(df, order_by, n, prop) # 根据order_by选择最小的若干行
slice_max(df, order_by, n, prop) # 根据order_by选择最大的若干行
slice_sample(df, n, prop) # 随机选择若干行它们的共同参数,n 表示选择 n 行,prop 表示选择某比例的行。
例如,选择数学成绩排名前5的行:
df %>%
slice_max(math, n = 5)
用 relocate() 函数,可将选择的列移到某列之前或之后,基本语法为:
relocate(.data, ..., .before, .after)例如,将数值列移到 name 列的后面:
df %>%
relocate(where(is.numeric), .after = name)
以前只支持一个返回值的汇总函数,如 sum, mean 等。现在也支持多返回值的汇总函数,如 range(), quantile() 等。
qs = c(0.25, 0.5, 0.75)
df_q = df %>% drop_na() %>%
group_by(sex) %>%
summarise(math_qs = quantile(math, qs), q = qs)
df_q
可读性不好,再来个长变宽:
df_q %>%
pivot_wider(everything(), names_from = q,
values_from = math_qs, names_prefix = "q_")
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31537575/viewspace-3149729/ ,如需转载,请注明出处,否则将追究法律责任。