使用 tidyverse 在 R 中迭代

Iteration in R using tidyverse

我试图避免使用 for 循环,而是使用 tidyverse 进行迭代。具体来说,我有一个值向量,我想循环遍历数据框中的单个变量,以创建带有前缀的新变量。我试过使用 dplyr::across 但当矢量长度 >1

时我不成功

示例代码:

library(tidyverse)
library(glue)

data <- data.frame(id = 1:10, 
                   y = letters[1:10], 
                   z = LETTERS[1:10])
letter_list <- letters[1:10]

var_naming <- function(dat, list){
  dat %>%
    mutate(!!glue("hx_{list}") := ifelse(y == {list}, 1, 0))
}

我试过的代码:

**the correct dimensions of the data frame should be 13 variables and 10 observations**

# data_b outputs the correct number of observations but has 40 variables
data_b <- map(letter_list, 
             ~var_naming(data, .x)) %>%
  as.data.frame()

# data_c gives me the correct number of variables but has 100 observations
data_c <- map_df(letter_list,
                 ~var_naming(data, .x))

# error message from data_d when using dplyr::across:
>> Error in `mutate()`:
>> ! Problem while computing `..1 =
  >> across(...)`.
>> Caused by error in `across()`:
>> ! All unnamed arguments must be length 1
>> Run `rlang::last_error()` to see where the error occurred.

data_d <- data %>%
  mutate(
    across(
      .cols  = y, 
      .fns   = ~ifelse(y == {letter_list}, 1, 0),
      .names = glue("hx_{letter_list}")
  ))
Desired output:
id y     z      hx_a  hx_b  hx_c  hx_d  hx_e  hx_f  hx_g  hx_h  hx_i  hx_j

1  a     A         1     0     0     0     0     0     0     0     0     0
2  b     B         0     1     0     0     0     0     0     0     0     0
3  c     C         0     0     1     0     0     0     0     0     0     0
4  d     D         0     0     0     1     0     0     0     0     0     0
5  e     E         0     0     0     0     1     0     0     0     0     0
6  f     F         0     0     0     0     0     1     0     0     0     0
7  g     G         0     0     0     0     0     0     1     0     0     0
8  h     H         0     0     0     0     0     0     0     1     0     0
9  i     I         0     0     0     0     0     0     0     0     1     0
10 j     J         0     0     0     0     0     0     0     0     0     1

代码可以修改

  1. 删除 :=
  2. 右侧 list 周围的 {}
  3. 最好使用 transmute 而不是 mutate 作为 mutate returns 默认情况下的整个数据。
  4. 一旦我们从 map 获得列绑定 (_dfc) 数据,将原始数据与 bind_cols
  5. 绑定
library(dplyr)
library(purrr)
var_naming <- function(dat, list){
  dat %>%
    transmute(!!glue::glue('hx_{list}') := ifelse(y == list, 1, 0))
}

注意:list 是一个 base R 函数来构造 list 数据结构。最好创建参数名称不同于保留字或已存在的函数名称的函数。 -测试

map_dfc(letter_list, var_naming, dat = data) %>% 
   bind_cols(data, .)

-输出

   id y z hx_a hx_b hx_c hx_d hx_e hx_f hx_g hx_h hx_i hx_j
1   1 a A    1    0    0    0    0    0    0    0    0    0
2   2 b B    0    1    0    0    0    0    0    0    0    0
3   3 c C    0    0    1    0    0    0    0    0    0    0
4   4 d D    0    0    0    1    0    0    0    0    0    0
5   5 e E    0    0    0    0    1    0    0    0    0    0
6   6 f F    0    0    0    0    0    1    0    0    0    0
7   7 g G    0    0    0    0    0    0    1    0    0    0
8   8 h H    0    0    0    0    0    0    0    1    0    0
9   9 i I    0    0    0    0    0    0    0    0    1    0
10 10 j J    0    0    0    0    0    0    0    0    0    1

获得相同结果的另一种方法:

data %>%
  cbind(model.matrix(~y + 0, .)) %>%
  rename_with(~str_replace(., 'y\B', 'hx_'))

   id y z hx_a hx_b hx_c hx_d hx_e hx_f hx_g hx_h hx_i hx_j
1   1 a A    1    0    0    0    0    0    0    0    0    0
2   2 b B    0    1    0    0    0    0    0    0    0    0
3   3 c C    0    0    1    0    0    0    0    0    0    0
4   4 d D    0    0    0    1    0    0    0    0    0    0
5   5 e E    0    0    0    0    1    0    0    0    0    0
6   6 f F    0    0    0    0    0    1    0    0    0    0
7   7 g G    0    0    0    0    0    0    1    0    0    0
8   8 h H    0    0    0    0    0    0    0    1    0    0
9   9 i I    0    0    0    0    0    0    0    0    1    0
10 10 j J    0    0    0    0    0    0    0    0    0    1

如果你只考虑letters_list中的那些:

data %>%
  mutate( y =factor(y, letter_list)) %>%
  cbind(model.matrix(~y + 0, .) %>%
  as_tibble() %>%
  select(paste0('y', letter_list)) %>%
  rename_with(~str_replace(., 'y', 'hx_')))

您已经接近 mutate 调用,但您最终想要的是要传递给 .fns 的函数列表(letter_list 中的每个字母对应一个函数)。由于它们是匿名函数,因此将它们命名为 letter_list 以帮助 across 命名列

myFxs <- map(letter_list, ~function(y) ifelse(y == .x, 1, 0)) %>% 
  setNames(letter_list)

无论出于何种原因,.names 似乎在粘合字符向量方面存在问题(无论如何对我而言)。由于函数是根据它们匹配的字母命名的,您可以使用 .fn 代词来代替将模板传递给 across

data %>%
  mutate(
    across(
      .cols  = y, 
      .fns   = myFxs,
      .names = "hx_{.fn}"
    )
  )