dplyr - mutate: использовать имена динамических переменных

Я хочу использовать 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() использовать мое динамическое имя в качестве имени переменной?

10 ответов

Решение

Поскольку вы существенно строите имя переменной как символьное значение, имеет смысл выполнять присваивание с использованием стандартной индексации 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 (>=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, которые существовали, когда вопрос был задан изначально. Это требует осторожного использования quote а также setName:

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

В новом выпуске dplyr (0.6.0 ожидая в апреле 2017 года), мы также можем сделать задание (:=) и передайте переменные как имена столбцов без кавычек (!!не оценивать

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

Проверка вывода на основе @MrFlick's multipetal применяется к "iris1"

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

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

Вот другая версия, и, возможно, она немного проще.

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

Вы можете наслаждаться пакетом friendlyeval который представляет упрощенный Tidy Eval 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 допустимо в качестве имени столбца.

friendlyeval код может быть преобразован в эквивалентный простой код в любое время с помощью надстройки RStudio.

Я также добавляю ответ, который немного увеличивает это, потому что я пришел к этой записи при поиске ответа, и это было почти то, что мне нужно, но мне нужно было немного больше, что я получил через ответ @MrFlik и Р лазевал виньетками.

Я хотел создать функцию, которая могла бы принимать фрейм данных и вектор имен столбцов (в виде строк), которые я хочу преобразовать из строки в объект 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

Несмотря на то, что мне нравится использовать 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) 

Другая альтернатива: использовать {}внутри кавычек, чтобы легко создавать динамические имена. Это похоже на другие решения, но не совсем то же самое, и мне это проще.

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>
Другие вопросы по тегам