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

Ответ 1

Поскольку вы существенно строите имя переменной как символьное значение, имеет смысл выполнять присваивание с использованием стандартной индексации data.frame, которая допускает символьные значения для имен столбцов. Например:

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

Функция mutate позволяет очень легко именовать новые столбцы с помощью именованных параметров. Но это предполагает, что вы знаете имя при вводе команды. Если вы хотите динамически указать имя столбца, вам также необходимо создать именованный аргумент.


версия 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, которые существовали, когда вопрос был задан изначально. Требует осторожного использования 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)
}

Ответ 2

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

 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 multipetal, примененного к 'iris1'

identical(iris1, iris2)
#[1] TRUE

Ответ 3

После большого количества проб и ошибок я нашел шаблон 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

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

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

Вы можете наслаждаться пакетом friendlyeval который представляет упрощенный упрощенный API eval и документацию для более новых/случайных пользователей 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 код может быть преобразован в эквивалентный простой порядок кода eval в любое время с добавлением RStudio.