在`dplyr`中使用动态变量名


168

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

来自虹膜的示例数据:

library(dplyr)
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),而不是四个(称为花瓣2-花瓣5)。

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


1
我不是在坚持变异,我在问是否有可能。也许这只是我不知道的小把戏。如果还有其他方法,请听一下。
Timm S.


1
在这一点上,dplyr有一个关于非标准评估
Gregor Thomas

16
该小插图甚至都没有提及mutate_,而且从其他功能上如何使用它实际上也并不明显。
nacnudus

Answers:


191

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

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

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


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&<0.7)

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

dplyr <0.3

请注意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)
}

24
谢谢,这很有帮助。顺便说一句,我总是创建非常戏剧性的变量。
Timm S.

27
呵呵。那可能是我一段时间以来最喜欢的错别字之一。我想我会离开的。
MrFlick 2014年

1
do.call()可能没有按照您的想象做:rpubs.com/hadley/do-call2。另请参见dplyr开发版中的nse小插图。
hadley 2014年

4
因此,如果我明白您的意见@hadley,我已经更新了do.call上面的内容do.call("mutate"),以df在列表中使用和引用。那是你的建议吗?而当的lazyeval版本dplyr是发行版本时,那mutate_(df, .dots= setNames(list(~Petal.Width * n), varname))将是一个更好的解决方案?
MrFlick 2014年

1
如果我不仅在任务的左侧而且在右侧需要变量列标题,该怎么办?例如,mutate(df, !!newVar := (!!var1 + !!var2) / 2)它不起作用:(
马里奥·

55

在新版本的dplyr0.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)
 }   

根据multipetal在'iris1' 上应用的@MrFlick 检查输出

identical(iris1, iris2)
#[1] TRUE

26

经过大量的试验和错误,我发现该模式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(例如),并使用新名称将旧列复制myColInitialValue到数据框的末尾df。但要which(colnames(df)=='myCol')寄回的列号myColInitialValue。我还没有写问题,因为我没有找到代表。我的目标是使用的escape参数DT::datatable()。我用来escape=FALSE等待。使用常量,它也不起作用,但是DT包似乎也出现了错误的#列。:)
phili_b


似乎不是动态变量引起的。(添加了btw reprex)
phili_b

感谢您的回答!这是我如何使用它的一个超简单示例:varname = sym("Petal.Width"); ggplot(iris, aes(x=!!varname)) + geom_histogram()
bdemarest

这对于!! varname不起作用的公式内的我有用。
daknowles

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

8

有了rlang 0.4.0curl-curly运算符({{}}),这使得这非常容易。

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)

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

3

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

2
+1,尽管我仍然dplyr在非交互设置中使用很多,但是将其与函数内部的variabel输入一起使用时,语法很笨拙。
Paul Hiemstra '17

3

您可能会喜欢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插件随时将代码转换为等效的整洁评估代码。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.