dplyr-mutate: używa dynamicznych nazw zmiennych

I want to use dplyr ' s mutate() aby utworzyć wiele nowych kolumn w ramce danych. Nazwy kolumn i ich zawartość powinny być generowane dynamicznie.

Przykładowe dane z iris:

require(dplyr)
data(iris)
iris <- tbl_df(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ą (nazwaną varname) z czterech (tzw. Płatek.2-płatek.5).

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

Author: Jaap, 2014-09-23

7 answers

Ponieważ dramatycznie 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żeli chcesz dynamicznie określać nazwę kolumny, wtedy musisz również zbudować argument named.

The najnowsza wersja dplyr (0.7) robi to używając := 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 dostępny formularz dokumentacji vignette("programming", "dplyr").

Nieco wcześniejsza wersja dplyr (>=0,3 vignette("nse")).

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

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

Starsze wersje dplyr

Zauważ, że 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)
}
 100
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
2017-12-19 08:23:42

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

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

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

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

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) 
 1
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.

 0
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