paquete - group by r
dplyr-mutate: use nombres de variables dinĂ¡micas (7)
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
Quiero usar dplyr''s mutate mutate()
para crear múltiples columnas nuevas en un marco de datos. 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 dinámico 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 compilar 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)
}
El patrón UQ(rlang::sym("some string here")))
es realmente útil para trabajar con cadenas y verbos dplyr.
Aquí hay un ejemplo con mutate:
# add two values together
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'')
Funciona con otras funciones dplyr también.
## select a column
select_name <- function(name){
mtcars %>%
select(!!name)
}
select_name(''mpg'')
## filter a column by a value
filter_values <- function(name, value){
mtcars %>%
filter(UQ(rlang::sym(name)) != value)
}
filter_values(''gear'', 4)
## 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'')
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
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.
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)
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 marco de datos 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