【问题标题】:Use dynamic name for new column/variable in `dplyr`在`dplyr`中为新列/变量使用动态名称
【发布时间】:2014-11-18 03:50:35
【问题描述】:

我想使用dplyr::mutate() 在数据框中创建多个新列。列名及其内容应该是动态生成的。

鸢尾花数据示例:

library(dplyr)
iris <- as_tibble(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() 使用我的动态名称作为变量名?

【问题讨论】:

  • 这个小插曲甚至没有提到mutate_,从其他函数中也看不出如何使用它。
  • 我一直在努力理解 quosure 等人。多年的文档。虽然上面的小插图链接不再有效,但该评论将我引向此摘要以进行整理评估:shipt.tech/…。我终于明白了!谢谢。
  • dplyr 提供了涵盖此内容的 this excellent vignette on Programming with dplyr
  • 与基本 R 相比,在 dplyr 中似乎过于复杂,在循环时获取动态名称...
  • 如果您需要多次执行相同的操作,它通常会告诉您您的数据格式不是最优的。您想要更长的格式,n 是 data.frame 中的一列(请参阅下面的答案)

标签: r dplyr r-faq


【解决方案1】:

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

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

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


dplyr 版本 >= 1.0

使用最新的 dplyr 版本,在使用 := 命名参数时,您可以使用 glue 包中的语法。所以这里名称中的{} 通过评估里面的表达式来获取值。

multipetal <- function(df, n) {
  mutate(df, "petal.{n}" := Petal.Width * n)
}

如果要将列名传递给函数,则可以在字符串中使用{{}} 以及列名

meanofcol <- function(df, col) {
  mutate(df, "Mean of {{col}}" := mean({{col}}))
}
meanofcol(iris, Petal.Width)


dplyr 版本 >= 0.7

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 &

dplyr (>=0.3 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))
}

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)
}

【讨论】:

  • 谢谢,这很有帮助。顺便说一句,我总是创造非常戏剧性的变量。
  • 呵呵。这可能是我一段时间以来最喜欢的错别字之一。我想我会离开它。
  • 所以如果我理解你的观点@hadley,我已经更新了上面的do.call 以使用do.call("mutate") 并在列表中引用df。那是你的建议吗?而当dplyrlazyeval版本是发布版本,那么mutate_(df, .dots= setNames(list(~Petal.Width * n), varname))会是更好的解决方案吗?
  • 如果我需要变量列标题不仅在赋值的左侧而且在右侧怎么办?例如mutate(df, !!newVar := (!!var1 + !!var2) / 2) 不起作用 :(
  • @Mario Reutter:你的评论有没有得到答复?我问了同样的问题here 并且很想解决它!
【解决方案2】:

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

 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

【讨论】:

  • 为什么在取消引用变量时需要使用:= 进行赋值(即!!varname)?
【解决方案3】:

经过大量试验和错误后,我发现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')

【讨论】:

  • 您的提示效果很好,但我有一个小问题。我将初始列 myCol 更改为 url(例如),并使用新名称复制数据框 df 末尾的旧列 myColInitialValue。但是which(colnames(df)=='myCol') 发回myColInitialValue 的列号。我还没有写问题,因为我没有找到代表。我的目标是DT::datatable()escape 参数。我在等待时使用escape=FALSE。使用常量它也不起作用,但DT package 似乎也得到了错误的# 列。 :)
  • 似乎不是动态变量的原因。 (顺便说一句,添加了代表)
  • 感谢您的回答!这是我如何使用它的一个超级简单的示例:varname = sym("Petal.Width"); ggplot(iris, aes(x=!!varname)) + geom_histogram()
  • 这在 !!varname 不起作用的公式中对我有用。
【解决方案4】:

有了rlang 0.4.0,我们就有了卷曲运算符({{}}),这让这变得非常容易。当动态列名显示在分配的左侧时,请使用 :=

library(dplyr)
library(rlang)

iris1 <- tbl_df(iris)

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

multipetal(iris1, 4)

# A tibble: 150 x 6
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species petal.4
#          <dbl>       <dbl>        <dbl>       <dbl> <fct>     <dbl>
# 1          5.1         3.5          1.4         0.2 setosa      0.8
# 2          4.9         3            1.4         0.2 setosa      0.8
# 3          4.7         3.2          1.3         0.2 setosa      0.8
# 4          4.6         3.1          1.5         0.2 setosa      0.8
# 5          5           3.6          1.4         0.2 setosa      0.8
# 6          5.4         3.9          1.7         0.4 setosa      1.6
# 7          4.6         3.4          1.4         0.3 setosa      1.2
# 8          5           3.4          1.5         0.2 setosa      0.8
# 9          4.4         2.9          1.4         0.2 setosa      0.8
#10          4.9         3.1          1.5         0.1 setosa      0.4
# … with 140 more rows

我们还可以传递带引号/不带引号的变量名称以分配为列名。

multipetal <- function(df, name, n) {
   mutate(df, {{name}} := Petal.Width * n)
}

multipetal(iris1, temp, 3)

# A tibble: 150 x 6
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species  temp
#          <dbl>       <dbl>        <dbl>       <dbl> <fct>   <dbl>
# 1          5.1         3.5          1.4         0.2 setosa  0.6  
# 2          4.9         3            1.4         0.2 setosa  0.6  
# 3          4.7         3.2          1.3         0.2 setosa  0.6  
# 4          4.6         3.1          1.5         0.2 setosa  0.6  
# 5          5           3.6          1.4         0.2 setosa  0.6  
# 6          5.4         3.9          1.7         0.4 setosa  1.2  
# 7          4.6         3.4          1.4         0.3 setosa  0.900
# 8          5           3.4          1.5         0.2 setosa  0.6  
# 9          4.4         2.9          1.4         0.2 setosa  0.6  
#10          4.9         3.1          1.5         0.1 setosa  0.3  
# … with 140 more rows

它的工作原理与

相同
multipetal(iris1, "temp", 3)

【讨论】:

    【解决方案5】:

    这是另一个版本,可以说它更简单一些。

    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
    

    【讨论】:

      【解决方案6】:

      我还添加了一个答案来稍微增强这一点,因为我在搜索答案时来到了这个条目,这几乎满足了我的需求,但我还需要更多,这是我通过 @MrFlik 获得的答案和 Rlazyeval 小插曲。

      我想创建一个函数,它可以接受一个数据框和一个列名向量(作为字符串),我想将它们从字符串转换为 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
      

      【讨论】:

        【解决方案7】:

        虽然我喜欢使用 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) 
        

        【讨论】:

        • +1,虽然我在非交互式设置中仍然经常使用dplyr,但在函数内将它与变量输入一起使用会使用非常笨拙的语法。
        【解决方案8】:

        您可能会喜欢包friendlyeval,它为新/休闲dplyr 用户提供了一个简化的、整洁的评估API 和文档。

        您正在创建希望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 代码。

        【讨论】:

        • 我认为这个包不再可用了
        【解决方案9】:

        另一种选择:在引号内使用{} 轻松创建动态名称。这与其他解决方案类似,但不完全相同,我发现它更容易。

        library(dplyr)
        library(tibble)
        
        iris <- as_tibble(iris)
        
        multipetal <- function(df, n) {
          df <- mutate(df, "petal.{n}" := Petal.Width * n)  ## problem arises here
          df
        }
        
        for(i in 2:5) {
          iris <- multipetal(df=iris, n=i)
        }
        iris
        

        我认为这来自dplyr 1.0.0,但不确定(如果重要的话,我也有rlang 4.7.0)。

        【讨论】:

          【解决方案10】:

          如果您需要多次执行相同的操作,它通常会告诉您您的数据格式不是最佳的。您想要更长的格式,n 是 data.frame 中的一列,可以通过交叉连接实现:

          library(tidyverse)
          iris %>% mutate(identifier = 1:n()) %>% #necessary to disambiguate row 102 from row 143 (complete duplicates)
             full_join(tibble(n = 1:5), by=character()) %>% #cross join for long format
             mutate(petal = Petal.Width * n) %>% #calculation in long format
             pivot_wider(names_from=n, values_from=petal, names_prefix="petal.width.") #back to wider format (if desired)
          

          结果:

          # A tibble: 150 x 11
             Sepal.Length Sepal.Width Petal.Length Petal.Width Species identifier petal.width.1 petal.width.2 petal.width.3
                    <dbl>       <dbl>        <dbl>       <dbl> <fct>        <int>         <dbl>         <dbl>         <dbl>
           1          5.1         3.5          1.4         0.2 setosa           1           0.2           0.4           0.6
           2          4.9         3            1.4         0.2 setosa           2           0.2           0.4           0.6
           3          4.7         3.2          1.3         0.2 setosa           3           0.2           0.4           0.6
           4          4.6         3.1          1.5         0.2 setosa           4           0.2           0.4           0.6
           5          5           3.6          1.4         0.2 setosa           5           0.2           0.4           0.6
           6          5.4         3.9          1.7         0.4 setosa           6           0.4           0.8           1.2
           7          4.6         3.4          1.4         0.3 setosa           7           0.3           0.6           0.9
           8          5           3.4          1.5         0.2 setosa           8           0.2           0.4           0.6
           9          4.4         2.9          1.4         0.2 setosa           9           0.2           0.4           0.6
          10          4.9         3.1          1.5         0.1 setosa          10           0.1           0.2           0.3
          # ... with 140 more rows, and 2 more variables: petal.width.4 <dbl>, petal.width.5 <dbl>
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-09-13
            • 1970-01-01
            • 1970-01-01
            • 2018-10-03
            • 1970-01-01
            • 1970-01-01
            • 2018-05-06
            • 1970-01-01
            相关资源
            最近更新 更多