在' dplyr '中为新列/变量使用动态名称

我想使用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),而不是四个(称为花瓣)。2 -花瓣。5)。

如何让mutate()使用我的动态名作为变量名?

187934 次浏览

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

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

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


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

从0.7版本开始,dplyr允许你使用:=动态分配参数名。你可以这样写你的函数:

# --- 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 && lt; 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 & lt;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)
}

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

我想做一个函数,可以接受一个dataframe和列名向量(作为字符串),我想从字符串转换为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

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

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

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

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

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

identical(iris1, iris2)
#[1] TRUE

经过大量的尝试和错误,我发现模式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')

你可能会喜欢friendlyeval包,它为新/普通的dplyr用户提供了一个简化的整洁的eval 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代码。

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)

另一种替代方法:在引号内使用{}来轻松创建动态名称。这与其他解决方案相似,但并不完全相同,而且我发现它更简单。

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

如果您需要多次执行相同的操作,它通常会告诉您,您的数据格式不是最佳的。你想要一个更长的格式,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>