Użyj dynamicznych nazw zmiennych w ' dplyr`

Chcę użyć dplyr::mutate() do utworzenia wielu nowych kolumn w ramce danych. Nazwy kolumn i ich zawartość powinny być generowane dynamicznie.

Przykładowe dane z iris:

library(dplyr)
iris <- as_tibble(iris)

Stworzyłem funkcję mutującą moje nowe kolumny ze zmiennej Petal.Width:

multipetal <- function(df, n) {
    varname <- paste("petal", n , sep=".")
    df <- mutate(df, varname = Petal.Width * n)  ## problem arises here
    df
}

Teraz tworzę pętlę do budowania moich kolumn:

for(i in 2:5) {
    iris <- multipetal(df=iris, n=i)
}

Ponieważ mutate uważa, że varname jest literalną nazwą zmiennej, pętla tworzy tylko jedną nową zmienną (zwaną varname) zamiast czterech (zwany płatkiem.2-płatek.5).

Jak mogę zmusić mutate() do używania nazwy dynamicznej jako nazwy zmiennej?

 188
Author: MrFlick, 2014-09-23

9 answers

Ponieważ dynamicznie budujesz nazwę zmiennej jako wartość znakową, bardziej sensowne jest przypisywanie przy użyciu standardowych danych.indeksowanie ramek, które pozwala na wartości znaków dla nazw kolumn. Na przykład:

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

Funkcja mutate bardzo ułatwia nazywanie nowych kolumn za pomocą nazwanych parametrów. Ale to zakłada, że znasz nazwę po wpisaniu polecenia. Jeśli chcesz dynamicznie określać nazwę kolumny, wtedy musisz również zbudować nazwaną kłótnia.


Dplyr version > = 1.0

W najnowszej wersji dplyr możesz używać składni z pakietu glue podczas nazywania parametrów przy użyciu :=. Więc tutaj {} w nazwie pobiera wartość, oceniając wyrażenie wewnątrz.

multipetal <- function(df, n) {
  mutate(df, "petal.{n}" := Petal.Width * n)
}

Wersja Dplyr > = 0.7

dplyr począwszy od wersji 0.7 pozwala używać := do dynamicznego przypisywania nazw parametrów. Możesz zapisać swoją funkcję jako:

# --- dplyr version 0.7+---
multipetal <- function(df, n) {
    varname <- paste("petal", n , sep=".")
    mutate(df, !!varname := Petal.Width * n)
}

Aby uzyskać więcej informacji, zobacz dokumentacja dostępna w formie vignette("programming", "dplyr").


Dplyr (>=0.3 &

Nieco wcześniejsza wersja dplyr (>=0.3 vignette("nse")).

Więc tutaj odpowiedzią jest użycie mutate_() zamiast mutate() i wykonanie:

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

Uwaga jest to możliwe również w starszych wersjach dplyr, które istniały, gdy pytanie zostało pierwotnie postawione. Wymaga starannego użycia quote i 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)
}
 226
Author: MrFlick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-15 03:01:31

W nowym wydaniu dplyr (0.6.0 oczekiwanie w kwietniu 2017), Możemy również wykonać przypisanie (:=) i przekazać zmienne jako nazwy kolumn przez unquoting (!!), aby nie oceniać go

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

Sprawdzanie wyjścia na podstawie @MrFlick 's multipetal zastosowanego na 'iris1'

identical(iris1, iris2)
#[1] TRUE
 61
Author: akrun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-14 21:01:37

Po wielu próbach i błędach, znalazłem wzór UQ(rlang::sym("some string here"))) naprawdę przydatny do pracy z ciągami i czasownikami dplyr. Wydaje się działać w wielu zaskakujących sytuacjach.

Oto przykład z mutate. Chcemy utworzyć funkcję, która dodaje do siebie dwie kolumny, gdzie przekazujesz funkcję obie nazwy kolumn jako ciągi znaków. Możemy użyć tego wzoru, razem z operatorem przypisania :=, aby to zrobić.

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

Wzór działa również z innymi funkcjami dplyr. Oto filter:

## filter a column by a value 
filter_values <- function(name, value){
  mtcars %>% 
    filter(UQ(rlang::sym(name)) != value)
}
filter_values('gear', 4)

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

Dla select, nie musisz używać wzoru. Zamiast tego możesz użyć !!:

## select a column 
select_name <- function(name){
  mtcars %>% 
    select(!!name)
}
select_name('mpg')
 31
Author: Tom Roth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-07 05:00:02

Oto inna wersja i jest prawdopodobnie nieco prostsza.

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
 13
Author: user2946432,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-24 13:12:26

Z rlang 0.4.0 mamy operatory curly-curly ({{}}), co sprawia, że jest to bardzo proste.

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

Możemy również przekazać cytowane / nie cytowane nazwy zmiennych do przypisania jako nazwy kolumn.

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

Działa tak samo z

multipetal(iris1, "temp", 3)
 11
Author: Ronak Shah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-07 08:22:36

Dodaję też odpowiedź, która trochę to powiększa, ponieważ trafiłem do tego wpisu, szukając odpowiedzi, i to miało prawie to, czego potrzebowałem, ale potrzebowałem trochę więcej, co dostałem za pośrednictwem odpowiedzi @ MrFlik i winietek r lazyeval.

Chciałem stworzyć funkcję, która może przyjmować ramkę danych i Wektor nazw kolumn (jako ciągi znaków), które chcę przekonwertować z łańcucha znaków na obiekt daty. Nie mogłem wymyślić, jak sprawić, by as.Date() wziął argument, który jest ciągiem znaków i Konwertuj go na kolumnę, więc zrobiłem to, jak pokazano poniżej.

Poniżej jak to zrobiłem przez SE mutate (mutate_()) i argument .dots. Krytyka, która to poprawia, jest mile widziana.

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
 4
Author: mpettis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-29 01:54:23

Podczas gdy lubię używać dplyr do interaktywnego użytku, uważam, że niezwykle trudno jest to zrobić używając dplyr, ponieważ musisz przejść przez obręcze, aby użyć lazyeval:: interp (), setNames, itp. obejścia.

Oto prostsza wersja z wykorzystaniem bazy R, w której umieszczenie pętli wewnątrz funkcji wydaje mi się bardziej intuicyjne, a która rozszerza rozwiązanie @ 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) 
 3
Author: hackR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-22 15:01:18

Możesz cieszyć się pakietem friendlyeval który prezentuje uproszczone tidy eval API i dokumentację dla nowszych / zwykłych Użytkowników dplyr.

Tworzysz ciągi znaków, które chcesz mutate traktować jako nazwy kolumn. Więc używając friendlyeval możesz napisać:

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

Który pod maską wywołuje rlang funkcje sprawdzające varname jest legalny jako nazwa kolumny.

friendlyeval kod może być konwertowany na równoważny zwykły kod eval tidy w dowolnym momencie za pomocą dodatku RStudio.

 3
Author: MilesMcBain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-24 09:54:54

Inna alternatywa: użyj {} wewnątrz cudzysłowów, aby łatwo tworzyć dynamiczne nazwy. Jest to podobne do innych rozwiązań, ale nie do końca takie same, i uważam, że jest to łatwiejsze.

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

Myślę, że to pochodzi z dplyr 1.0.0, ale nie jestem pewien (mam też rlang 4.7.0 jeśli to ma znaczenie).

 1
Author: bretauv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-20 07:58:51