【问题标题】:Tidyeval and apply family to add new variables to a dataframeTidyeval 并应用系列将新变量添加到数据框
【发布时间】:2020-08-19 09:07:50
【问题描述】:

我正在尝试编写一个函数来使用 tidyverse 工具自动创建一些新变量。我发现我的问题涉及到 tidyeval,但我还没有完全弄清楚我在下面的代码中哪里出错了,它只是复制了变量名。 作为第二步,除了 for 循环之外,我还想做一些事情来多次应用该函数。我已经阅读了足够多的 StackOverflow 回答 shaming for loops,但我找不到使用某种应用函数在现有数据帧上创建新变量的工作示例。谢谢!

library(tidyverse)
x = c(0,1,2,3,4)
y = c(0,2,4,5,8)
df <- data.frame(x,y)
df
simple_func <- function(x) {
  var_name <- paste0("pre_", x, "_months")
  var_name <-  enquo(var_name)
  df <- df %>%
    mutate(!! var_name := ifelse(x==y,1,0)) %>%
    mutate(!! var_name := replace_na(!! var_name))
  return(df)
}
simple_func(1)
#Desired result
temp <- data.frame("pre_1_months" = c(1,0,0,0,0))
temp
bind_cols(df,temp)

#Step 2, use some kind of apply function rather than a loop to apply this function sequentially
nums <- seq(1:10)
for (i in seq_along(nums)) {
  df <- simple_func(nums[i])
}
df

【问题讨论】:

    标签: r apply tidyeval


    【解决方案1】:

    因为它是一个字符串,我们可以使用sym 转换为符号然后求值(!!

    simple_func <- function(x) {
        var_name <- paste0("pre_", x, "_months")
        var_name <-  rlang::sym(var_name)
        df %>%
          mutate(!! var_name := ifelse(x==y,1,0)) %>%
          mutate(!! var_name := replace_na(!! var_name))
    
        }
    

    检查 OP 的代码

    nums <- seq(1:10)
    for (i in seq_along(nums)) {
       df <- simple_func(nums[i])
     }
    
    df
    #  x y pre_1_months pre_2_months pre_3_months pre_4_months pre_5_months pre_6_months pre_7_months pre_8_months
    #1 0 0            1            1            1            1            1            1            1            1
    #2 1 2            0            0            0            0            0            0            0            0
    #3 2 4            0            0            0            0            0            0            0            0
    #4 3 5            0            0            0            0            0            0            0            0
    #5 4 8            0            0            0            0            0            0            0            0
    #  pre_9_months pre_10_months
    #1            1             1
    #2            0             0
    #3            0             0
    #4            0             0
    #5            0             0
    

    我们可以使用map 并将mutate 更改为transmute

    simple_func <- function(x) {
        var_name <- paste0("pre_", x, "_months")
        var_name <-  rlang::sym(var_name)
        df %>%
          transmute(!! var_name := ifelse(x==y,1,0)) %>%
          transmute(!! var_name := replace_na(!! var_name))
    
        }
    
    library(purrr)
    library(dplyr)
    map_dfc(1:10, simple_func) %>% 
           bind_cols(df,.)
    

    【讨论】:

    • 非常感谢,这适用于第一部分!关于避免 for 循环的任何想法?
    • @JACKLANDRY 抱歉,我以为你的问题是关于函数本身的。
    • @JACKLANDRY 更新为 map,希望它对你有用
    【解决方案2】:

    要以@akrun 的答案为基础,更惯用的方法是将df 作为函数的第一个参数传递,并将x 作为第二个参数。您可以通过使用rlang::syms 而不是sym 将循环放在其中为x 中的每个元素运行一次来​​对函数进行矢量化。它还使代码更短,您可以将其添加到管道中,就像它是 dplyr 函数一样。

    simple_func <- function(df, x) 
    {
        for(var_name in rlang::syms(paste0("pre_", x, "_months")))
        {
          df <- mutate(df, !! var_name := replace_na(ifelse(x==y,1,0)))
        }
        df
    }
    

    所以现在你可以这样做了:

    df %>% simple_fun(1:5)
    #>   x y pre_1_months pre_2_months pre_3_months pre_4_months pre_5_months
    #> 1 0 0            1            1            1            1            1
    #> 2 1 2            0            0            0            0            0
    #> 3 2 4            0            0            0            0            0
    #> 4 3 5            0            0            0            0            0
    #> 5 4 8            0            0            0            0            0
    

    编辑

    根据 Lionel Henry 的评论,以及注意到 OP 希望避免循环,这是一个没有循环的单个函数,可以在具有任意长度的 x 的管道中使用,并且不依赖关于转换为符号:

    simple_func <- function(df, x) {
      f <- function(v) df <<- mutate(df, !!v := replace_na(ifelse(x == y, 1, 0)))
      lapply(paste0("pre_", x, "_months"), f)
      return(df)
    }
    

    同样的方法:

    df %>% simple_fun(1:10)
    #>   x y pre_1_months pre_2_months pre_3_months pre_4_months pre_5_months pre_6_months
    #> 1 0 0            1            1            1            1            1            1
    #> 2 1 2            0            0            0            0            0            0
    #> 3 2 4            0            0            0            0            0            0
    #> 4 3 5            0            0            0            0            0            0
    #> 5 4 8            0            0            0            0            0            0
    #>   pre_7_months pre_8_months pre_9_months pre_10_months
    #> 1            1            1            1             1
    #> 2            0            0            0             0
    #> 3            0            0            0             0
    #> 4            0            0            0             0
    #> 5            0            0            0             0
    
    

    【讨论】:

    • 如果您不使用名称进行计算,则无需将它们转换为符号。它们可以是简单的字符向量,因为您可以在 := 的 LHS 上取消引用字符串。此外,您现在可以使用字符串的胶水插值,即:"{var_name}" := replace_na(...)
    • @LionelHenry 谢谢 - 我错过了。我添加了一个带有确认的编辑版本。
    • 谢谢!我还要提到这仅适用于我们正在创建新名称(在 LHS 上)。要引用计算中的列(在 RHS 上),必须将字符串转换为符号,这样它们代表列而不是...字符串。
    猜你喜欢
    • 2012-04-11
    • 2019-10-29
    • 1970-01-01
    • 2017-03-18
    • 2017-11-29
    • 1970-01-01
    • 2018-08-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多