Используйте имена динамических переменных в `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) вместо четырех (называемых petal.2 - petal.5).

Как я могу mutate()использовать свое динамическое имя в качестве имени переменной?


1
Я не настаиваю на мутации, я спрашиваю, возможно ли это. Может быть, это просто маленькая хитрость, которую я не знаю. Если есть другой способ, давайте послушаем это.
Тимм С.



16
Виньетка даже не упоминается mutate_, и из других функций не совсем понятно, как ее использовать.
nacnudus

Ответы:


191

Поскольку вы динамически строите имя переменной как символьное значение, имеет смысл выполнять присваивание с использованием стандартной индексации 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)
}

24
Спасибо, это полезно. Кстати, я всегда создаю действительно драматические переменные.
Тимм С.

27
Хехе. это, наверное, одна из моих любимых опечаток, которые я сделал за последнее время. Я думаю, что я оставлю это.
MrFlick

1
do.call()вероятно, не делает то, что вы думаете, что делает: rpubs.com/hadley/do-call2 . Смотрите также виньетка nse в версии dplyr для разработчиков.
Хэдли

4
Так что, если я понимаю вашу точку зрения @hadley, я обновил do.callвыше, чтобы использовать do.call("mutate")и цитировать dfв списке. Это то, что вы предлагали? И когда lazyevalверсия dplyrбудет выпущена, тогда mutate_(df, .dots= setNames(list(~Petal.Width * n), varname))будет лучшим решением?
MrFlick

1
Что если мне понадобится заголовок столбца переменной не только слева, но и справа? например mutate(df, !!newVar := (!!var1 + !!var2) / 2)не работает :(
Марио Ройтер

55

В новом выпуске dplyr( 0.6.0ожидается в апреле 2017 г.) мы также можем выполнить assignment ( :=) и передать переменные в качестве имен столбцов с помощью 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

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, похоже, тоже получает столбец bad #. :)
phili_b


Кажется, что динамические переменные не являются причиной. (кстати, добавлено)
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.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)

4

Я также добавляю ответ, который немного увеличивает это, потому что я пришел к этой записи при поиске ответа, и это было почти то, что мне было нужно, но мне нужно было немного больше, что я получил через ответ @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

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много пользуюсь в неинтерактивных настройках, использование его с вводом переменных в функцию использует очень неуклюжий синтаксис.
Пол Химстра,

3

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


0

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

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если это имеет значение).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.