为“dplyr”中的新列/变量使用动态名称

Use dynamic name for new column/variable in `dplyr`

提问人:Timm S. 提问时间:9/24/2014 最后编辑:Gregor ThomasTimm S. 更新时间:3/17/2023 访问量:213426

问:

我想用于在数据框中创建多个新列。列名及其内容应动态生成。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()

DPLYR R-常见问题

评论

18赞 nacnudus 7/25/2016
小插图甚至没有提到,从其他功能来看,如何使用它真的不明显。mutate_
2赞 Josh 11/24/2020
多年来,我一直在努力理解等人的文档。虽然上面的小插图链接不再有效,但该评论将我引向了这个摘要以整理评估:shipt.tech/......我终于明白了!谢谢。quosure
2赞 Markm0705 7/24/2021
与基础 R 相比,它在循环时在 dplyr get 中似乎过于复杂,具有动态名称......
1赞 shadowtalker 2/28/2023
@MarioReutter最适合什么?我不同意,例如,用 1000 万行的键值列替换 10 列 100 万行无疑是一种改进。循环列名一直是并且应该仍然是完全可以接受的事情,“整洁”是该死的。
2赞 shadowtalker 2/28/2023
@Markm0705 Dplyr/Rlang 认为是在 、 等基本 R 功能之上实现简洁的宏/元编程 DSL,这可能既笨拙又冗长。我喜欢他们所做的事情,但我真的很不喜欢所有的新术语,设计中不断的混乱,以及文档中过于复杂的描述,好像这是人们不应该做的晦涩难懂的事情。as.symbolsubstitute

答:

368赞 MrFlick 9/24/2014 #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 & <0.7)

稍早的版本(>=0.3<0.7)鼓励使用“标准评估”替代许多功能。有关详细信息,请参阅非标准评估小插图 ()。dplyrvignette("nse")

所以在这里,答案是 use 而不是 and do: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

请注意,这在最初提出问题时存在的旧版本中也是可能的。它需要谨慎使用和:dplyrquotesetName

# --- 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赞 hadley 9/26/2014
do.call()可能没有做你认为的那样:rpubs.com/hadley/do-call2。另请参阅 dplyr 开发版中的 nse 小插图。
4赞 MrFlick 9/26/2014
因此,如果我理解您的观点@hadley,我已经更新了上面的内容以使用并在列表中引用。这是你的建议吗?而当版本是发布版本时,会不会是更好的解决方案?do.calldo.call("mutate")dflazyevaldplyrmutate_(df, .dots= setNames(list(~Petal.Width * n), varname))
5赞 Mario Reutter 7/4/2017
如果我不仅需要在作业的左侧,而且在右侧也需要变量列标题,该怎么办?例如 :(不起作用mutate(df, !!newVar := (!!var1 + !!var2) / 2)
2赞 MsGISRocker 9/1/2021
@Mario Reutter:你有没有得到你的评论的答案?我在这里问了同样的问题,很想解决它!
2赞 shadowtalker 2/28/2023
对于从其他标记为重复的问题中阅读本文的任何人来说,这些带有 和 的技术是将变量名称注入任何 Dplyr 函数的官方和正确工具,而不仅仅是带有 .在答案中添加注释可能会有所帮助,以便该答案可以成为“规范”,以将其他问题标记为重复。{{!!:=
4赞 mpettis 7/29/2015 #2

我还添加了一个答案,因为它在搜索答案时来到了这个条目,这几乎有我需要的东西,但我需要更多,这是我通过 @MrFlik 的答案和 R 懒惰的小插曲得到的。

我想创建一个函数,该函数可以接受数据帧和列名向量(作为字符串),我想将其从字符串转换为 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
14赞 user2946432 9/24/2015 #3

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

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
4赞 hackR 1/22/2017 #4

虽然我喜欢使用 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赞 Paul Hiemstra 2/14/2017
+1,尽管我仍然在非交互式设置中使用很多,但在函数中将其与可变输入一起使用使用非常笨拙的语法。dplyr
80赞 akrun 4/15/2017 #5

在 ( 等待 2017 年 4 月) 的新版本中,我们还可以执行赋值 () 并通过取消引号 () 将变量作为列名传递,以不计算它dplyr0.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)
 }   

根据应用于“iris1”的@MrFlick检查输出multipetal

identical(iris1, iris2)
#[1] TRUE

评论

1赞 cmo 9/27/2021
为什么在取消引用变量(即 )时需要用于赋值?:=!!varname
1赞 tjebo 3/8/2023
这是一个很好的解决方案,但它仅在我们传递字符向量时有效,而不是直接传递数字(由于您之前的粘贴步骤,它在这里有效)。
50赞 Tom Roth 3/16/2018 #6

经过大量的试验和错误,我发现该模式(在早期的 R 版本中:)对于处理字符串和 dplyr 动词非常有用。它似乎在很多令人惊讶的情况下都有效。!!rlang::sym("my variable"))UQ(rlang::sym("my variable")))

下面是 的示例。我们想创建一个将两列相加的函数,您可以在其中将两个列名作为字符串传递该函数。我们可以将此模式与 赋值运算符 一起使用来执行此操作。mutate:=

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

该模式也适用于其他函数。这里是:dplyrfilter

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

或:arrange

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

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

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

评论

0赞 phili_b 7/20/2019
你的提示效果很好,但我有一个小问题。例如,我将初始列更改为 url,并使用新名称复制数据帧末尾的旧列。但是发回 的 col #。我还没有写问题,因为我没有找到 reprex。我的目标是 的参数。我用在等待那个。使用常量,它也不起作用,但 DT 包似乎也得到了坏的 # 列。:)myColmyColInitialValuedfwhich(colnames(df)=='myCol')myColInitialValueescapeDT::datatable()escape=FALSE
0赞 phili_b 7/22/2019
似乎动态变量不是原因。(顺便说一句,已添加 Reprex)
0赞 bdemarest 11/4/2019
谢谢你的回答!这是我如何使用它的一个超级简单的例子:varname = sym("Petal.Width"); ggplot(iris, aes(x=!!varname)) + geom_histogram()
1赞 Tyler R. 4/30/2022
您现在应该使用而不是!!rlang::sym(name)UQ(rlang::sym(name))
5赞 MilesMcBain 6/24/2018 #7

您可以享受软件包友好的 Friendlyeval,它为新用户/临时用户提供了简化整洁的 eval API 和文档。dplyr

您正在创建要视为列名的字符串。因此,使用你可以写:mutatefriendlyeval

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

在引擎盖下调用检查作为列名是合法的函数。rlangvarname

friendlyeval可以使用 RStudio 加载项随时将代码转换为等效的纯整洁的评估代码。

评论

0赞 Michael Bellhouse 8/29/2020
我认为这个包不再可用
0赞 David Muñoz Tord 10/12/2022
它是,它比上面的任何东西都好!当 case_when() 一切都失败时,这完美无缺
26赞 Ronak Shah 12/7/2019 #8

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)

评论

0赞 moodymudskipper 11/23/2022
mutate(df, 'petal.{n}' := Petal.Width * n)
0赞 tjebo 3/8/2023
注意,当将字符向量传递给循环中的函数时,此方法会失败 - 然后将创建一个带有函数参数名称的列
1赞 bretauv 7/20/2020 #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.0rlang 4.7.0

0赞 Mario Reutter 3/12/2022 #10

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

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>