r - 如何避免for-loop *or*如何在迭代过程中动态更新purrr::map得到的结果?
问题描述
来自 Stata,我有时仍在为 R 的不同编程方法而苦苦挣扎。尤其是在避免for
循环方面。
在下面的示例中,我编写了两个函数来覆盖ex$status1' and
ex$status2' 的原始值。对于每个 id,如果在各自的 id 中x
出现 ,则应将两个变量的原始值替换为 。x
该函数myfunc2
完全能够为多个变量执行此任务(在下面的示例中:status1
和status2
)。
但是,我的问题发生在尝试强制替换初始值的顺序时。顺序为c(1,5,3,7)
。也就是说,如果对于给定的 id 观察到值 1,则该 id 的所有变量值都应替换为 1。然后应针对 的剩余值对更新的数据重复该过程c(1,5,3,7)
。我使用 for 循环完成了这项任务,但未能使用其中一个purrr
map 函数来完成,因为这些函数总是在原始 tibble 上执行并且没有按顺序更新 tibble(参见下面的代码)。谁能告诉我,如何使用 map 函数(或不使用for
循环)获得所需的结果?
ex <- tibble(id = c(1,1,1,1,2,2,2),
status1 = c(3,3,5,7,1,5,7),
status2 = c(3,3,3,7,7,5,7))
ex
myfunc <- function(df, id, var, val) {
df <- df %>%
group_by(id) %>%
mutate({{var}} := case_when(any({{var}} == val) ~ val,
TRUE ~ {{var}})) %>%
ungroup() %>%
select({{var}})
return(df)
}
myfunc(ex, id, status1, 1)
myfunc2 <- function(df, id, var, val) {
map_dfc(var,
~myfunc(df, id, !!sym(.x), val)) %>%
add_column(id = df$id, .before = 1)
}
myfunc2(ex, id, c("status1", "status2"), 1)
# this works
for (i in c(1,5,3,7)) {
ex <- myfunc2(ex, id, c("status1", "status2"), i)
}
# this does not work
c(1,5,3,7) %>%
map_dfc(function(x) {ex <- myfunc2(ex, id, c("status1", "status2"), x)})
# original data
# A tibble: 7 x 3
id status1 status2
<dbl> <dbl> <dbl>
1 1 3 3
2 1 3 3
3 1 5 3
4 1 7 7
5 2 1 7
6 2 5 5
7 2 7 7
# Data after executing the for-loop
# A tibble: 7 x 3
id status1 status2
<dbl> <dbl> <dbl>
1 1 5 3
2 1 5 3
3 1 5 3
4 1 5 3
5 2 1 5
6 2 1 5
7 2 1 5
解决方案
lapply
,map
在每个输入元素上循环并返回输出,但它不会像在for
循环中那样递归地更新原始对象。如果我们想这样做,则必须进行范围更新,<<-
这可能不是最佳选择。会推荐for
循环
library(dplyr)
library(purrr)
c(1,5,3,7) %>%
map_dfc(function(x) {
ex <<- myfunc2(ex, id, c("status1", "status2"), x)
})
现在,我们检查对象'ex'
ex
# A tibble: 7 x 3
# id status1 status2
# <dbl> <dbl> <dbl>
#1 1 5 3
#2 1 5 3
#3 1 5 3
#4 1 5 3
#5 2 1 5
#6 2 1 5
#7 2 1 5
使用tidyverse
,我们可以使用reduce
来代替map
and<<-
reduce(list(1, 5, 3, 7),
~myfunc2(.x, id, c("status1", "status2"), .y), .init = ex)
# A tibble: 7 x 3
# id status1 status2
# <dbl> <dbl> <dbl>
#1 1 5 3
#2 1 5 3
#3 1 5 3
#4 1 5 3
#5 2 1 5
#6 2 1 5
#7 2 1 5
这类似于base R
Reduce
Reduce(function(x, y) myfunc2(x, id, c("status1", "status2"), y),
list(1, 5, 3, 7), init = ex)
# A tibble: 7 x 3
# id status1 status2
# <dbl> <dbl> <dbl>
#1 1 5 3
#2 1 5 3
#3 1 5 3
#4 1 5 3
#5 2 1 5
#6 2 1 5
#7 2 1 5
这些方法的一个优点是避免了副作用,即我们不必更新原始对象
ex
# A tibble: 7 x 3
# id status1 status2
# <dbl> <dbl> <dbl>
#1 1 3 3
#2 1 3 3
#3 1 5 3
#4 1 7 7
#5 2 1 7
#6 2 5 5
但是,考虑到for
循环的简单性(在理解和执行方面),使用循环可能会更好for
(主观意见)
推荐阅读
- rust - 使用 Struct 的特征对象 - 错误:类型参数的数量错误
- python - 为什么我在计算波动率时得到 NaN?
- python - Python:没有 Pandas 的 csv 文件和字典
- javascript - 阻止自动回复 HTTPS 请求
- heroku - 错误“IOError:文件/.../不存在”在rails 6上的公共文件中使用sidekiq
- arrays - PowerShell - 使用带有字符串数组的管道 where-object 过滤文件名
- python - 如何将 XML 格式的表格导入 Excel 文件?
- algorithm - 使用 BST 查找基于某些属性的查询
- google-cloud-platform - GCP 虚拟机可以在我离线时运行吗?
- oracle - 具有多个表连接的 Oracle LISTAGG