Використовуйте імена динамічних змінних у `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)
}

Однак, оскільки мутація вважає, що 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
Дякую, це корисно. btw, я завжди створюю дійсно драматичні змінні.
Тімм С.

27
Хе-хе. це, мабуть, одна з моїх улюблених помилок, яку я зробив за деякий час. Я думаю, що я його залишу.
MrFlick

1
do.call()напевно, не робить те, що, на вашу думку, робить: rpubs.com/hadley/do-call2 . Дивіться також nse віньєтку у версії dplyr для dev.
hadley

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), не працює :(
Mario Reutter

55

У новому випуску 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, 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 також отримує поганий стовпець. :)
phili_b


Здається, динамічні змінні не є причиною. (додано btw reprex)
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 та R lazyeval vignettes.

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


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.