首页 文章

dplyr - mutate:使用动态变量名

提问于
浏览
99

我想使用 dplyr's mutate() 在数据框中创建多个新列 . 应动态生成列名称及其内容 .

来自虹膜的示例数据:

require(dplyr)
data(iris)
iris <- tbl_df(iris)

我已经创建了一个函数来改变 Petal.Width 变量中的新列:

multipetal <- function(df, n) {
    varname <- paste("petal", n , sep=".")
    df <- mutate(df, varname = Petal.Width * n)  ## problem arises here
    df
}

现在我创建一个循环来构建我的列:

for(i in 2:5) {
    iris <- multipetal(df=iris, n=i)
}

但是,由于mutate认为varname是一个文字变量名,因此循环只创建一个新变量(称为varname)而不是四个(称为petal.2 - petal.5) .

如何让 mutate() 使用我的动态名称作为变量名?

7 回答

  • 35

    虽然我喜欢使用dplyr进行交互式使用,但我发现使用dplyr这样做非常棘手,因为你必须通过箍来使用lazyeval :: interp(),setNames等工作区 .

    这是一个使用基本R的更简单的版本,在这个版本中,至少对我来说,将循环放在函数中是更直观的,并且扩展了@ MrFlicks的解决方案 .

    multipetal <- function(df, n) {
       for (i in 1:n){
          varname <- paste("petal", i , sep=".")
          df[[varname]] <- with(df, Petal.Width * i)
       }
       df
    }
    multipetal(iris, 3)
    
  • 4

    由于您正在将变量名称显着地构建为字符值,因此使用标准data.frame索引进行赋值更有意义,该索引允许列名称的字符值 . 例如:

    multipetal <- function(df, n) {
        varname <- paste("petal", n , sep=".")
        df[[varname]] <- with(df, Petal.Width * n)
        df
    }
    

    mutate 函数使通过命名参数命名新列变得非常容易 . 但是,假设您在键入命令时知道名称 . 如果要动态指定列名,则还需要构建命名参数 .

    最新版本的dplyr(0.7)使用 := 来动态分配参数名称 . 您可以将您的功能编写为:

    # --- dplyr version 0.7+---
    multipetal <- function(df, n) {
        varname <- paste("petal", n , sep=".")
        mutate(df, !!varname := Petal.Width * n)
    }
    

    有关更多信息,请参阅 vignette("programming", "dplyr") 中提供的文档 .

    稍微早一点的dplyr(> = 0.3 <0.7),鼓励使用"standard evaluation"替代许多函数 . 有关详细信息,请参阅非标准评估小插图( vignette("nse") ) .

    所以在这里,答案是使用 mutate_() 而不是 mutate() 并执行:

    # --- dplyr version 0.3-0.5---
    multipetal <- function(df, n) {
        varname <- paste("petal", n , sep=".")
        varval <- lazyeval::interp(~Petal.Width * n, n=n)
        mutate_(df, .dots= setNames(list(varval), varname))
    }
    

    Older versions of dplyr

    请注意,在最初提出问题时存在的旧版dplyr中也可以这样做 . 它需要小心使用 quotesetName

    # --- dplyr versions < 0.3 ---
    multipetal <- function(df, n) {
        varname <- paste("petal", n , sep=".")
        pp <- c(quote(df), setNames(list(quote(Petal.Width * n)), varname))
        do.call("mutate", pp)
    }
    
  • 1

    dplyr 的新版本中(2017年4月等待 0.6.0 ),我们也可以执行赋值( := )并通过取消引用( !! )将变量作为列名传递给不评估它

    library(dplyr)
     multipetalN <- function(df, n){
          varname <- paste0("petal.", n)
          df %>%
             mutate(!!varname := Petal.Width * n)
     }
    
     data(iris)
     iris1 <- tbl_df(iris)
     iris2 <- tbl_df(iris)
     for(i in 2:5) {
         iris2 <- multipetalN(df=iris2, n=i)
     }
    

    检查输出基于@ MrFlick的 multipetal 应用于'iris1'

    identical(iris1, iris2)
    #[1] TRUE
    
  • 110

    这是另一个版本,它可以说有点简单 .

    multipetal <- function(df, n) {
        varname <- paste("petal", n, sep=".")
        df<-mutate_(df, .dots=setNames(paste0("Petal.Width*",n), varname))
        df
    }
    
    for(i in 2:5) {
        iris <- multipetal(df=iris, n=i)
    }
    
    > head(iris)
    Sepal.Length Sepal.Width Petal.Length Petal.Width Species petal.2 petal.3 petal.4 petal.5
    1          5.1         3.5          1.4         0.2  setosa     0.4     0.6     0.8       1
    2          4.9         3.0          1.4         0.2  setosa     0.4     0.6     0.8       1
    3          4.7         3.2          1.3         0.2  setosa     0.4     0.6     0.8       1
    4          4.6         3.1          1.5         0.2  setosa     0.4     0.6     0.8       1
    5          5.0         3.6          1.4         0.2  setosa     0.4     0.6     0.8       1
    6          5.4         3.9          1.7         0.4  setosa     0.8     1.2     1.6       2
    
  • 1

    经过大量的反复试验,我发现模式 UQ(rlang::sym("some string here"))) 对于使用字符串和dplyr动词非常有用 . 它似乎在许多令人惊讶的情况下工作 .

    这是 mutate 的示例 . 我们想要创建一个将两列相加的函数,您可以将函数作为字符串传递给列 . 我们可以使用此模式以及赋值运算符 := 来执行此操作 .

    ## Take column `name1`, add it to column `name2`, and call the result `new_name`
    mutate_values <- function(new_name, name1, name2){
      mtcars %>% 
        mutate(UQ(rlang::sym(new_name)) :=  UQ(rlang::sym(name1)) +  UQ(rlang::sym(name2)))
    }
    mutate_values('test', 'mpg', 'cyl')
    

    该模式也适用于其他 dplyr 函数 . 这是 filter

    ## filter a column by a value 
    filter_values <- function(name, value){
      mtcars %>% 
        filter(UQ(rlang::sym(name)) != value)
    }
    filter_values('gear', 4)
    

    arrange

    ## transform a variable and then sort by it 
    arrange_values <- function(name, transform){
      mtcars %>% 
        arrange(UQ(rlang::sym(name)) %>%  UQ(rlang::sym(transform)))
    }
    arrange_values('mpg', 'sin')
    

    对于 select ,您不需要使用该模式 . 相反,你可以使用 !!

    ## select a column 
    select_name <- function(name){
      mtcars %>% 
        select(!!name)
    }
    select_name('mpg')
    
  • 10

    我也添加了一个答案,增加了一点,因为我在寻找答案时来到这个条目,这几乎是我需要的,但我需要更多,我通过@MrFlik的回答和R lazyeval小插曲 .

    我想创建一个函数,可以将数据帧和列名称的矢量(作为字符串)转换为我想要从字符串转换为Date对象 . 我无法弄清楚如何使 as.Date() 获取一个字符串的参数并将其转换为列,所以我按照下面的说明进行了操作 .

    以下是我通过SE mutate( mutate_() )和 .dots 参数进行此操作的方法 . 欢迎使这更好的批评 .

    library(dplyr)
    
    dat <- data.frame(a="leave alone",
                      dt="2015-08-03 00:00:00",
                      dt2="2015-01-20 00:00:00")
    
    # This function takes a dataframe and list of column names
    # that have strings that need to be
    # converted to dates in the data frame
    convertSelectDates <- function(df, dtnames=character(0)) {
        for (col in dtnames) {
            varval <- sprintf("as.Date(%s)", col)
            df <- df %>% mutate_(.dots= setNames(list(varval), col))
        }
        return(df)
    }
    
    dat <- convertSelectDates(dat, c("dt", "dt2"))
    dat %>% str
    
  • 5

    您可以享受包friendlyeval,它提供了一个简化的整洁评估API和较新/休闲 dplyr 用户的文档 .

    您正在创建希望 mutate 作为列名称处理的字符串 . 所以使用 friendlyeval 你可以写:

    multipetal <- function(df, n) {
      varname <- paste("petal", n , sep=".")
      df <- mutate(df, !!treat_string_as_col(varname) := Petal.Width * n)
      df
    }
    
    for(i in 2:5) {
      iris <- multipetal(df=iris, n=i)
    }
    

    在引擎盖下调用 rlang 函数,检查 varname 是合法的列名 .

    friendlyeval 代码可以随时使用RStudio插件转换为等效的简单整理eval代码 .

相关问题