dplyr – mutate: use nombres de variables dinámicas

Quiero usar dplyr‘s mutate mutate() para crear múltiples columnas nuevas en un dataframe. Los nombres de las columnas y sus contenidos deben generarse dinámicamente.

Datos de ejemplo del iris:

 require(dplyr) data(iris) iris <- tbl_df(iris) 

Petal.Width una función para mutar mis nuevas columnas de la variable Petal.Width :

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

Ahora creo un bucle para construir mis columnas:

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

Sin embargo, como mutate piensa que varname es un nombre de variable literal, el bucle solo crea una nueva variable (llamada varname) en lugar de cuatro (llamada petal.2 – petal.5).

¿Cómo puedo obtener mutate() para usar mi nombre dynamic como nombre de variable?

Debido a que está construyendo dramáticamente un nombre de variable como un valor de carácter, tiene más sentido hacer la asignación usando la indexación estándar de datos.frame que permite valores de caracteres para los nombres de columna. Por ejemplo:

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

La función mutate hace que sea muy fácil nombrar nuevas columnas a través de parámetros con nombre. Pero eso supone que sabes el nombre cuando escribes el comando. Si desea especificar dinámicamente el nombre de la columna, también debe comstackr el argumento nombrado.

La última versión de dplyr (0.7) lo usa usando := para asignar dinámicamente nombres de parámetros. Puedes escribir tu función como:

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

Para obtener más información, consulte la documentación disponible en la vignette("programming", "dplyr") .

Una versión ligeramente anterior de dplyr (> = 0.3 <0.7), alentó el uso de alternativas de "evaluación estándar" para muchas de las funciones. Consulte la viñeta de evaluación no estándar para obtener más información ( vignette("nse") ).

Entonces, aquí, la respuesta es usar mutate_() lugar de mutate() y hacer:

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

Versiones anteriores de dplyr

Tenga en cuenta que esto también es posible en versiones anteriores de dplyr que existían cuando la pregunta se planteó originalmente. Requiere un uso cuidadoso de quote y 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) } 

En la nueva versión de dplyr ( 0.6.0 en espera en abril de 2017), también podemos hacer una asignación ( := ) y pasar variables como nombres de columna al desmarcar ( !! ) para no evaluarlo

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

Comprobación de la salida basada en @ MrFlick's multipetal aplicado en 'iris1'

 identical(iris1, iris2) #[1] TRUE 

Aquí hay otra versión, y es posiblemente un poco más simple.

 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 

También estoy agregando una respuesta que aumenta un poco esto porque llegué a esta entrada al buscar una respuesta, y esto tenía casi lo que necesitaba, pero necesitaba un poco más, que obtuve a través de la respuesta de @MrFlik y la R lazyeval viñetas.

Quería hacer una función que pudiera tomar un dataframe y un vector de nombres de columnas (como cadenas) que quiero convertir de una cadena a un objeto Date. No pude entender cómo hacer que. as.Date() tome un argumento que sea una cadena y lo convierta en una columna, así que lo hice como se muestra a continuación.

A continuación se muestra cómo lo hice a través de SE mutate_() ) y el argumento .dots . Las críticas que mejoran esto son bienvenidas.

 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 

Después de mucho ensayo y error, encontré el patrón UQ(rlang::sym("some string here"))) realmente útil para trabajar con cadenas y verbos dplyr. Parece que funciona en muchas situaciones sorprendentes.

Aquí hay un ejemplo con mutate . Queremos crear una función que agregue juntas dos columnas, donde pasa la función de ambos nombres de columna como cadenas. Podemos usar este patrón, junto con el operador de asignación := , para hacer esto.

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

El patrón también funciona con otras funciones dplyr . Aquí está el filter :

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

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

Para select , no necesita usar el patrón. ¡En cambio, puedes usarlo !! :

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

Si bien disfruto de usar dplyr para uso interactivo, me resulta extraordinariamente complicado hacer esto usando dplyr porque debe pasar por aros para usar las soluciones lazyeval :: interp (), setNames, etc.

Aquí hay una versión más simple que utiliza la base R, en la que parece más intuitivo, al menos para mí, poner el ciclo dentro de la función y que extiende la solución de @ 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) 

Puede disfrutar del paquete friendlyeval que presenta una API simplificada de evaluación simplificada y documentación para los usuarios de dplyr más nuevos / casuales.

Está creando cadenas que desea mutate para tratar como nombres de columna. Entonces, usando friendlyeval podrías escribir:

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

Lo que bajo el capó llama a rlang funciones de rlang que comprueban que varname es legal como nombre de columna.

friendlyeval código de friendlyeval se puede convertir a un código de evaluación equivalente de ordenación simple en cualquier momento con un complemento de RStudio.