【问题标题】:How to pass a vector of variable names to a function in a within a function in r如何在 r 中的函数内将变量名向量传递给 a 中的函数
【发布时间】:2018-02-26 00:41:55
【问题描述】:

我来自 Stata,需要在 R 中为特定项目做一些工作。不幸的是,当我尝试在函数内的数据框中引用名称向量时,我在函数式编程方面没有取得多大成功。

我编写了一个函数,该函数将 (1) 在我的数据集中创建一个虚拟对象以判断该值是否缺失,然后 (2) 使用 dplyr 和lazyeval(如下)将 NA 转换为零。我无法在第二个函数中将变量名向量传递给该函数。

执行数据修改的工作函数:

dumfrmna <- function(dset, varn) {
  expr <- enquo(varn)
  x <- dset %>% summarise(sum(is.na(!!expr)))

  if(x > 0) {
    varname <- paste0(quo_name(expr), "_mis", sep = "")
    dset <- mutate(dset, !!varname := is.na(!!expr),
                   !!expr = ifelse(is.na(!!expr), 0, !!expr))
  }

  return(dset)
}

第二个未能将字符名称传递给工作函数的函数:

misdum <- function(dset, vlist = character(), type = "exclude") {

  #Err messages
  if(!(is.vector(vlist) & is.character(vlist))) stop("vlist must be a character vector of variable names")
  if(!((type == "include") | (type == "exclude"))) stop("type must take either \"include\" or \"exclude\"")
  if(type == "exclude" & length(vlist) > 0 & all(vlist %in% names(dset))) stop("vlist contains variables not in dset")
  if(length(vlist) == 0 & type == "include") {
    stop("If type == include, vlist must be specified")
  }

  # Create list of included vars
  else if(length(vlist) == 0 & type == "exclude") {
    vname <- colnames(dset)
  }

  else if(length(vlist) > 0 & type == "include") {
    vname <- vlist
  } 

  else if(length(vlist) > 0 & type == "exclude") {
    vname <- names(dest) %>%
      setdiff(vlist)
  }

  #Create missing dummy 
  dset <- sapply(vname, function(x) dumfrmna(dset, x))
  return(dset)
}

此外,当我尝试将变量名称向量应用于 R 中的函数时,这会失败。我尝试使用各种策略,如 noquote()、as.name()、parse(eval()) 来修改第二个函数中 sapply 中的列名向量,但这只是在第一个函数中的条件下中断。反正有没有去掉那个向量名称的引号?

这段代码在 R 中应该如何构造?

【问题讨论】:

    标签: r dplyr purrr


    【解决方案1】:

    最好提供一个可重现的小例子来测试功能。在这里,我们使用的是内置数据集mtcars

    library(dplyr)
    library(purrr)
    
    data(mtcars)
    

    在某些列中创建一些 NA。

    mtcars$wt[5:6] <- NA
    mtcars$qsec[7:9] <- NA
    

    dumfrna 中的更改 - 在 !!expr = 语句中,它应该是 :=,LHS 也会采用符号或字符。

    dumfrmna <- function(dset, varn) {
      expr <- enquo(varn)
       exprC <- quo_name(expr)
      x <- dset %>% summarise(sum(is.na(!!expr)))
    
       x
      if(x > 0) {
        varname <- paste0(quo_name(expr), "_mis", sep = "")
        dset <- mutate(dset, !! (varname) := is.na(!!expr),
                       !! (exprC) := ifelse(is.na(!!expr), 0, !!expr))
      }
    
      return(dset)
    }
    

    在第二个函数中,我们评估dumfrna 的参数,即!! x。在这里,我们假设vlist 将字符vector 作为参数

    misdum <- function(dset, vlist = character(), type = "exclude") {
    
    
      if(!(is.vector(vlist) & is.character(vlist))) stop("vlist must be a character vector of variable names")
      if(!((type == "include") | (type == "exclude"))) stop("type must take either \"include\" or \"exclude\"")
      if(type == "exclude" & length(vlist) > 0 & all(vlist %in% names(dset))) stop("vlist contains variables not in dset")
      if(length(vlist) == 0 & type == "include") {
        stop("If type == include, vlist must be specified")
      }
    
      # Create list of included vars
      else if(length(vlist) == 0 & type == "exclude") {
        vname <- colnames(dset)
      }
    
      else if(length(vlist) > 0 & type == "include") {
        vname <- vlist
      } 
    
      else if(length(vlist) > 0 & type == "exclude") {
        vname <- names(dest) %>%
          setdiff(vlist)
      }
    
      #Create missing dummy 
      dset <- map(rlang::syms(vname),  ~ dumfrmna(dset, !! .x))
      return(dset)
    }
    

    用数据试试函数

    res <- misdum(mtcars, vlist =c("wt", "qsec"), type = "include")
    map(res, head, n = 9)
    #[[1]]
    #   mpg cyl  disp  hp drat    wt  qsec vs am gear carb wt_mis
    #1 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4  FALSE
    #2 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4  FALSE
    #3 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1  FALSE
    #4 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1  FALSE
    #5 18.7   8 360.0 175 3.15 0.000 17.02  0  0    3    2   TRUE
    #6 18.1   6 225.0 105 2.76 0.000 20.22  1  0    3    1   TRUE
    #7 14.3   8 360.0 245 3.21 3.570    NA  0  0    3    4  FALSE
    #8 24.4   4 146.7  62 3.69 3.190    NA  1  0    4    2  FALSE
    #9 22.8   4 140.8  95 3.92 3.150    NA  1  0    4    2  FALSE
    
    #[[2]]
    #   mpg cyl  disp  hp drat    wt  qsec vs am gear carb qsec_mis
    #1 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4    FALSE
    #2 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4    FALSE
    #3 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1    FALSE
    #4 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1    FALSE
    #5 18.7   8 360.0 175 3.15    NA 17.02  0  0    3    2    FALSE
    #6 18.1   6 225.0 105 2.76    NA 20.22  1  0    3    1    FALSE
    #7 14.3   8 360.0 245 3.21 3.570  0.00  0  0    3    4     TRUE
    #8 24.4   4 146.7  62 3.69 3.190  0.00  1  0    4    2     TRUE
    #9 22.8   4 140.8  95 3.92 3.150  0.00  1  0    4    2     TRUE
    

    【讨论】:

    • 这真的很有帮助(也感谢您在第一个程序中发现错误)。我仍然有点困惑为什么基本函数 as.name() 不起作用,但 syms() 函数起作用。当类名对象在控制台中工作时,类名对象在函数中不起作用,但类符号对象在函数中而不是控制台中工作,这似乎是不直观的。是否有关于对象如何在函数式编程中工作的好资源? Hadley Wickham 似乎并没有详细说明这一点。
    • @Michael 有为 rlang 和 dplyr/quosures 编写的小插曲。你能检查一下吗
    【解决方案2】:

    这种方法简化了一些 dplyr 表达式以使用基本 R 索引。 这里dumfrmna 函数只接受单列的数据框,

    library(dplyr)
    
    # function takes a single data frame column, and
    # returns the unchanged column if there are no NAs otherwise
    # returns NA s changed to zero and an additional column named with 
    # _mis as a suffix. 
    dumfrmna <- function(df_column) {
      x <- sum(is.na(df_column))
      if(x > 0) {
        # new column, suffixed _mis,  with TRUE for NAs
        varname <- paste0(names(df_column)[1], "_mis")
        df_column[varname] <- is.na(df_column[[1]])
    
        # zero out the NAs in his single column
        df_column[is.na(df_column[[1]]),1] <- 0
      }    
      return(df_column)
    }
    
    misdum <- function(dset, vlist = character(), type = "exclude") {
    
      #Err messages
      if(!(is.vector(vlist) & is.character(vlist))) stop("vlist must be a character vector of variable names")
      if(!((type == "include") | (type == "exclude"))) stop("type must take either \"include\" or \"exclude\"")
      if(type == "exclude" & length(vlist) > 0 & 
         !all(vlist %in% names(dset))) stop("vlist contains variables not in dset") # mod !all
      if(length(vlist) == 0 & type == "include") {
        stop("If type == include, vlist must be specified")
      }
    
      # Create list of included vars
      else if(length(vlist) == 0 & type == "exclude") {
        vname <- colnames(dset)
      }
    
      else if(length(vlist) > 0 & type == "include") {
        vname <- vlist
      } 
    
      else if(length(vlist) > 0 & type == "exclude") {
        vname <- names(dset) %>%  ## mod dset
          setdiff(vlist)
      }
    
      # Create missing dummy by passing the single column -
      # the return from dumfrma is a dataframe with 1 or two columns 
      new_columns <- sapply(vname, function(x) dumfrmna(dset[x]), simplify = FALSE)
      dset <- bind_cols(new_columns)
      return(dset)
    }
    
    df = read.csv(text="
    c1,c2,c3
    a,1,11
    b,2,12
    c,NA,12
    d,2,NA
    ", stringsAsFactors=FALSE)
    
    # test the dummy insertion for a single column
    dumfrmna(df["c1"]) ## should be unchangd
    dumfrmna(df["c2"]) ## should add a column "c2_mis" and zero out the NA
    dumfrmna(df["c3"]) ## should add a column "c3_mis" and zero out the NA
    
    # test on the full frame
    misdum(df, vlist = c("c2"), type = "exclude")
    
    # > dumfrmna(df["c2"]) ## should add a column "c2_mis" and zero out the NA
    # c2 c2_mis
    # 1  1  FALSE
    # 2  2  FALSE
    # 3  0   TRUE
    # 4  2  FALSE
    # 
    # > dumfrmna(df["c3"]) ## should add a column "c3_mis" and zero out the NA
    # c3 c3_mis
    # 1 11  FALSE
    # 2 12  FALSE
    # 3 12  FALSE
    # 4  0   TRUE
    # 
    # > # test on the full frame
    #   > misdum(df, vlist = c("c2"), type = "exclude")
    # c1 c3 c3_mis
    # 1  a 11  FALSE
    # 2  b 12  FALSE
    # 3  c 12  FALSE
    # 4  d  0   TRUE
    # > 
    

    【讨论】:

    • 太棒了!这也有效。在基础 R 中这样做有什么好处吗?来自 Stata,tidyverse 语法在修改数据方面似乎更加直观。
    • @Michael,我大量使用 tidyverse 并且普遍喜欢它。在这种情况下,当基本 R [varname] 的基本动态索引功能看起来更简单时,eval 和 quote 的东西似乎变得如此复杂。另请注意,尽管使 `dumfrmna` 在单个列上运行会带来一些简单性——也许 dplyr 解决方案也会简化一点。
    猜你喜欢
    • 2011-02-26
    • 2013-10-08
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多