Je souhaite utiliser dplyr::mutate()
pour créer plusieurs nouvelles colonnes dans un bloc de données. Les noms de colonne et leur contenu doivent être générés dynamiquement.
Exemple de données d'iris:
library(dplyr)
iris <- tbl_df(iris)
J'ai créé une fonction pour muter mes nouvelles colonnes à partir 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
}
Maintenant, je crée une boucle pour construire mes colonnes:
for(i in 2:5) {
iris <- multipetal(df=iris, n=i)
}
Cependant, étant donné que mutate pense que nomvar est un nom de variable littéral, la boucle ne crée qu'une nouvelle variable (appelée nomvar) au lieu de quatre (appelée petal.2 - petal.5).
Comment puis-je obtenir que mutate()
utilise mon nom dynamique comme nom de variable?
Etant donné que vous créez de manière spectaculaire un nom de variable en tant que valeur de caractère, il est plus logique de procéder à une affectation à l'aide de l'indexation standard data.frame, qui permet d'utiliser des valeurs de caractère pour les noms de colonne. Par exemple:
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
df[[varname]] <- with(df, Petal.Width * n)
df
}
La fonction mutate
permet de nommer facilement de nouvelles colonnes via des paramètres nommés. Mais cela suppose que vous connaissiez le nom lorsque vous tapez la commande. Si vous souhaitez spécifier dynamiquement le nom de la colonne, vous devez également générer l'argument nommé.
La dernière version de dplyr (0.7) utilise pour cela l’utilisation de :=
pour attribuer dynamiquement des noms de paramètres. Vous pouvez écrire votre fonction en tant que:
# --- dplyr version 0.7+---
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
mutate(df, !!varname := Petal.Width * n)
}
Pour plus d'informations, voir la documentation disponible sous la forme vignette("programming", "dplyr")
.
La version légèrement antérieure de dplyr (> = 0,3 <0,7) encourageait l'utilisation d'alternatives "d'évaluation standard" pour de nombreuses fonctions. Voir la vignette d'évaluation non standard pour plus d'informations (vignette("nse")
).
Donc, ici, la réponse est d'utiliser mutate_()
plutôt que mutate()
et de faire:
# --- 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))
}
Notez que cela est également possible dans les anciennes versions de dplyr qui existaient lorsque la question a été posée à l'origine. Cela nécessite une utilisation prudente de quote
et 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)
}
Dans la nouvelle version de dplyr
(0.6.0
attendue en avril 2017), nous pouvons également effectuer une affectation (:=
) et transmettre des variables comme noms de colonne en décochant (!!
) ne pas l'évaluer
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)
}
Vérification de la sortie en fonction de multipetal
de @ MrFlick appliqué à 'iris1'
identical(iris1, iris2)
#[1] TRUE
Après beaucoup d'essais et d'erreurs, j'ai trouvé le motif UQ(rlang::sym("some string here")))
vraiment utile pour travailler avec des chaînes et des verbes dplyr. Cela semble fonctionner dans beaucoup de situations surprenantes.
Voici un exemple avec mutate
. Nous voulons créer une fonction qui ajoute deux colonnes, où vous lui transmettez les noms de colonnes sous forme de chaînes. Pour ce faire, nous pouvons utiliser ce modèle avec l'opérateur d'affectation :=
.
## 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')
Le motif fonctionne avec les autres fonctions dplyr
. Voici filter
:
## filter a column by a value
filter_values <- function(name, value){
mtcars %>%
filter(UQ(rlang::sym(name)) != value)
}
filter_values('gear', 4)
Ou 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')
Pour select
, vous n'avez pas besoin d'utiliser le motif. Au lieu de cela, vous pouvez utiliser !!
:
## select a column
select_name <- function(name){
mtcars %>%
select(!!name)
}
select_name('mpg')
Voici une autre version, et c'est sans doute un peu plus 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
J'ajoute également une réponse qui augmente un peu ce point car je suis arrivé à cette entrée lorsque je cherchais une réponse, ce qui me donnait presque ce dont j'avais besoin, mais il me fallait un peu plus, ce que j'ai obtenu via la réponse de @MrFlik et le R vignettes lazyeval.
Je voulais créer une fonction pouvant prendre une structure de données et un vecteur de noms de colonnes (en tant que chaînes) que je souhaite convertir en une chaîne en objet Date. Je n'arrivais pas à comprendre comment faire que as.Date()
prenne un argument qui soit une chaîne et le convertisse en colonne, je l'ai donc fait comme indiqué ci-dessous.
Voici comment je l’ai fait via SE mutate (mutate_()
) et l’argument .dots
. Les critiques qui améliorent cette situation sont les bienvenues.
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
Vous pouvez profiter du paquet friendlyeval
qui présente une API simplifiée eval tidy eval et une documentation pour les utilisateurs plus récents/occasionnels dplyr
.
Vous créez des chaînes que vous souhaitez que mutate
traite comme des noms de colonne. Donc, en utilisant friendlyeval
, vous pourriez écrire:
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)
}
Qui sous le capot appelle des fonctions rlang
qui vérifient varname
est légal en tant que nom de colonne.
Le code friendlyeval
peut être converti en code eval équivalent à tout moment avec un complément RStudio.
Bien que j'apprécie d'utiliser dplyr pour une utilisation interactive, je trouve qu'il est extrêmement difficile de le faire à l'aide de dplyr, car il faut parcourir des étapes pour utiliser les solutions de contournement lazyeval :: interp (), setNames, etc.
Voici une version plus simple utilisant la base R, dans laquelle il me semble plus intuitif, du moins, de mettre la boucle à l'intérieur de la fonction, et qui étend la solution 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)