Usa nomi di variabili dinamiche in `dplyr`


168

Voglio usare dplyr::mutate() per creare più nuove colonne in un frame di dati. I nomi delle colonne e il loro contenuto dovrebbero essere generati dinamicamente.

Dati di esempio dall'iride:

library(dplyr)
iris <- tbl_df(iris)

Ho creato una funzione per mutare le mie nuove colonne dalla Petal.Widthvariabile:

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

Ora creo un ciclo per costruire le mie colonne:

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

Tuttavia, poiché mutate pensa che varname sia un nome di variabile letterale, il ciclo crea solo una nuova variabile (chiamata varname) invece di quattro (chiamata petalo.2 - petalo 5).

Come posso mutate()usare il mio nome dinamico come nome variabile?


1
Non sto insistendo sul mutare, sto chiedendo se è possibile. Forse è solo un piccolo trucco che non conosco. Se c'è un altro modo, ascoltiamolo.
Timm S.



16
La vignetta non menziona nemmeno mutate_, e in realtà non è ovvio dalle altre funzioni come usarla.
nacnudus,

Risposte:


191

Poiché si sta creando dinamicamente un nome di variabile come valore di carattere, ha più senso eseguire l'assegnazione utilizzando l'indicizzazione standard data.frame che consente valori di carattere per i nomi di colonna. Per esempio:

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

La mutatefunzione semplifica la denominazione di nuove colonne tramite parametri denominati. Ma ciò presuppone che tu conosca il nome quando digiti il ​​comando. Se si desidera specificare in modo dinamico il nome della colonna, è necessario creare anche l'argomento denominato.


versione dplyr> = 0.7

L'ultima versione di dplyr(0.7) lo fa usando usando:= per assegnare dinamicamente i nomi dei parametri. Puoi scrivere la tua funzione come:

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

Per ulteriori informazioni, consultare il modulo di documentazione disponibile vignette("programming", "dplyr") .


dplyr (> = 0,3 e <0,7)

La versione leggermente precedente di dplyr(> = 0,3 <0,7), ha incoraggiato l'uso di alternative di "valutazione standard" a molte delle funzioni. Vedere la vignetta di valutazione non standard per ulteriori informazioni ( vignette("nse")).

Quindi qui, la risposta è usare mutate_()piuttosto che mutate()fare:

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

Si noti che ciò è possibile anche nelle versioni precedenti di dplyrquelle esistenti al momento della domanda iniziale. Richiede un uso attento di quotee 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
Grazie, è utile. tra l'altro, creo sempre variabili davvero drammatiche.
Timm S.

27
Hehe. è probabilmente uno dei miei errori di battitura preferiti che ho fatto da un po 'di tempo. Penso che lo lascerò.
MrFlick,

1
do.call()probabilmente non fa quello che pensi che faccia: rpubs.com/hadley/do-call2 . Vedi anche la vignetta nse nella versione dev di dplyr.
Hadley,

4
Quindi, se capisco il tuo punto @hadley, ho aggiornato quanto do.callsopra da utilizzare do.call("mutate")e da citare dfnell'elenco. È quello che stavi suggerendo? E quando la lazyevalversione di dplyrè la versione rilasciata, allora mutate_(df, .dots= setNames(list(~Petal.Width * n), varname))sarebbe una soluzione migliore?
MrFlick,

1
Cosa succede se ho bisogno dell'intestazione della colonna variabile non solo sul lato sinistro dell'assegnazione ma anche sul lato destro? ad es. mutate(df, !!newVar := (!!var1 + !!var2) / 2)non funziona :(
Mario Reutter

55

Nella nuova versione di dplyr(in 0.6.0attesa di aprile 2017), possiamo anche eseguire un'assegnazione ( :=) e passare variabili come nomi di colonna annullando la virgola ( !!) per non valutarla

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

Verifica dell'output in base a @ MrFlick multipetalapplicato su "iris1"

identical(iris1, iris2)
#[1] TRUE

26

Dopo molte prove ed errori, ho trovato il modello UQ(rlang::sym("some string here"))) davvero utile per lavorare con stringhe e verbi dplyr. Sembra funzionare in molte situazioni sorprendenti.

Ecco un esempio con mutate. Vogliamo creare una funzione che somma due colonne, in cui si passa la funzione a entrambi i nomi di colonna come stringhe. Possiamo usare questo schema, insieme all'operatore di assegnazione :=, per fare questo.

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

Il modello funziona anche con altre dplyrfunzioni. Ecco filter:

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

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

Per select, non è necessario utilizzare il modello. Invece puoi usare !!:

## select a column 
select_name <- function(name){
  mtcars %>% 
    select(!!name)
}
select_name('mpg')

I tuoi consigli funzionano molto bene, ma ho un piccolo problema. Cambio una colonna iniziale myColin un URL (ad esempio) e copio la vecchia colonna myColInitialValuealla fine del frame di dati dfcon un nuovo nome. Ma which(colnames(df)=='myCol')rimandare indietro il numero di myColInitialValue. Non ho ancora scritto un problema perché non ho trovato un reprex. Il mio obiettivo è per il escapeparametro di DT::datatable(). Lo uso escape=FALSEnell'attesa. Con le costanti non funziona anche, ma il pacchetto DT sembra anche avere la colonna cattiva #. :)
phili_b


Sembra che le variabili dinamiche non siano la causa. (tra cui reprex aggiunto)
phili_b

Grazie per questa risposta! Ecco un esempio semplicissimo di come l'ho usato:varname = sym("Petal.Width"); ggplot(iris, aes(x=!!varname)) + geom_histogram()
bdemarest,

Questo ha funzionato per me all'interno di una formula in cui !! varname non funzionava.
daknowles,

12

Ecco un'altra versione, ed è probabilmente un po 'più semplice.

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

Con rlang 0.4.0abbiamo operatori ricci-ricci ( {{}}) che lo rendono molto semplice.

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

Possiamo anche passare nomi di variabili quotati / non quotati da assegnare come nomi di colonna.

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

Funziona allo stesso modo con

multipetal(iris1, "temp", 3)

4

Sto anche aggiungendo una risposta che aumenta un po 'questo perché sono arrivato a questa voce durante la ricerca di una risposta, e questo aveva quasi quello di cui avevo bisogno, ma avevo bisogno di un po' di più, che ho ottenuto tramite la risposta di @MrFlik e il Vignette pigro.

Volevo creare una funzione che potesse prendere un frame di dati e un vettore di nomi di colonna (come stringhe) che volevo convertire da una stringa a un oggetto Date. Non sono riuscito a capire come fareas.Date() prendere un argomento che è una stringa e convertirlo in una colonna, quindi l'ho fatto come mostrato di seguito.

Di seguito è riportato come l'ho fatto tramite SE mutate ( mutate_()) e l' .dotsargomento. Le critiche che lo rendono migliore sono benvenute.

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

Mentre mi diverto ad usare dplyr per un uso interattivo, trovo straordinariamente difficile farlo usando dplyr perché devi usare i cerchi per usare soluzioni alternative lazyeval :: interp (), setNames, ecc.

Ecco una versione più semplice che utilizza la base R, in cui mi sembra più intuitivo inserire il loop all'interno della funzione e che estende la soluzione di @ 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, anche se uso ancora dplyrmolto in impostazioni non interattive, usarlo con input variabel all'interno di una funzione usa una sintassi molto voluminosa.
Paul Hiemstra,

3

È possibile usufruire del pacchetto friendlyevalche presenta un'API di valutazione ordinata semplificata e la documentazione per gli dplyrutenti più recenti / occasionali .

Stai creando stringhe che desideri mutatetrattare come nomi di colonna. Quindi usando friendlyevalpotresti scrivere:

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

Che sotto il cofano chiama rlangfunzioni che controllanovarname è legale come nome della colonna.

friendlyeval il codice può essere convertito in equivalente codice ordinario semplice in qualsiasi momento con un componente aggiuntivo RStudio.


0

Un'altra alternativa: utilizzare le {}virgolette all'interno per creare facilmente nomi dinamici. Questo è simile ad altre soluzioni ma non è esattamente lo stesso, e lo trovo più facile.

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

Penso che questo venga da dplyr 1.0.0ma non sono sicuro (ho anche rlang 4.7.0se è importante).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.