dplyr - mutate:使用动态变量名

时间:2014-09-23 19:51:16

标签: r dplyr r-faq

我想使用 dplyr 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()将动态名称用作变量名称?

9 个答案:

答案 0 :(得分:125)

由于您正在大幅度地将变量名称构建为字符值,因此使用标准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(&gt; = 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))
}

旧版本的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 :(得分:45)

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

!!

检查输出基于@ MrFlick的 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) } 应用于'iris1'

multipetal

答案 2 :(得分: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

答案 3 :(得分:10)

经过大量的反复试验,我发现模式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')

答案 4 :(得分:4)

我还添加了一个补充这个问题的答案,因为我在寻找答案时来到这个条目,这几乎是我需要的,但我需要更多,我通过@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 :(得分:1)

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

这是一个使用base 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) 

答案 6 :(得分:1)

您可能会喜欢friendlyeval软件包,它为新的/偶然的test用户提供了简化的评估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代码可通过RStudio插件随时转换为等效的整洁评估代码。

答案 7 :(得分:1)

有了rlang 0.4.0,我们有了卷曲卷曲的运算符(stargazer),这使这非常容易。

{{}}

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

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

答案 8 :(得分:1)

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

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