dplyr - mutate:使用动态变量名

Tim*_* S. 131 r r-faq dplyr

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

来自虹膜的示例数据:

library(dplyr)
iris <- tbl_df(iris)
Run Code Online (Sandbox Code Playgroud)

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

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

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

for(i in 2:5) {
    iris <- multipetal(df=iris, n=i)
}
Run Code Online (Sandbox Code Playgroud)

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

如何将mutate()动态名称用作变量名?

MrF*_*ick 154

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

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

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

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

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

有关更多信息,请参阅可用文档表单vignette("programming", "dplyr").

稍早版本的dplyr(> = 0.3 <0.7),鼓励使用"标准评估"替代许多功能.有关详细信息,请参阅非标准评估小插图(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))
}
Run Code Online (Sandbox Code Playgroud)

旧版本的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)
}
Run Code Online (Sandbox Code Playgroud)

  • 呵呵.这可能是我在一段时间内制作的最喜欢的错别字之一.我想我会离开它. (26认同)
  • 谢谢,这很有帮助.顺便说一下,我总是创造出非常戏剧性的变数. (23认同)
  • 因此,如果我理解你的观点@hadley,我已经更新了上面的`do.call`以使用`do.call("mutate")`并在列表中引用`df`.这是你的建议吗?而当``dplyr`的lazyeval`版本发布的版本,然后`mutate_(DF,.dots = setNames(名单(〜Petal.Width*N),VARNAME))`会是一个更好的解决办法? (4认同)
  • 如果我不仅在赋值的左侧而且在右侧都需要变量列标题怎么办?例如`mutate(df, !!newVar := (!!var1 + !!var2) / 2)` 不起作用:( (3认同)
  • @Mario Reutter:您的评论得到答复了吗?我在这里问了同样的问题(/sf/ask/4830269111/)并且很想解决它! (2认同)
  • 对于任何从标记为重复的其他问题中阅读本文的人来说,这些带有“{{”和“!!”的技术是将变量名称注入*任何* Dplyr 函数的官方且正确的工具,而不仅仅是使用“:=”进行赋值。在答案中添加注释可能会有所帮助,以便该答案在将其他问题标记为重复项时变得“规范”。 (2认同)

akr*_*run 47

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

 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)
 }   
Run Code Online (Sandbox Code Playgroud)

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

identical(iris1, iris2)
#[1] TRUE
Run Code Online (Sandbox Code Playgroud)


Tom*_*oth 20

经过大量的反复试验,我发现这个模式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')
Run Code Online (Sandbox Code Playgroud)

该模式也适用于其他dplyr功能.这是filter:

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

或者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')
Run Code Online (Sandbox Code Playgroud)

因为select,您不需要使用该模式.相反,你可以使用!!:

## select a column 
select_name <- function(name){
  mtcars %>% 
    select(!!name)
}
select_name('mpg')
Run Code Online (Sandbox Code Playgroud)


Ron*_*hah 15

有了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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

它与

multipetal(iris1, "temp", 3)
Run Code Online (Sandbox Code Playgroud)


use*_*432 12

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

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
Run Code Online (Sandbox Code Playgroud)


Mil*_*ain 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)
}
Run Code Online (Sandbox Code Playgroud)

在幕后调用rlang函数来检查varname列名是否合法。

friendlyeval可以使用 RStudio 插件随时将代码转换为等效的简单整洁的评估代码。