dplyr-mutate: utiliser des noms de variables dynamiques
Je veux utiliser dplyr de l' mutate()
pour créer plusieurs colonnes dans une trame de données. Les noms des colonnes et leur contenu doivent être générés dynamiquement.
Exemple de données d'iris:
require(dplyr)
data(iris)
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, puisque mutate pense que varname est un nom de variable littéral, la boucle ne crée qu'une nouvelle variable (appelée varname) à la place de quatre (appelé pétale.2 - pétale.5).
Comment puis-je obtenir mutate()
pour utiliser mon nom dynamique comme nom de variable?
7 réponses
Puisque vous construisez de manière spectaculaire un nom de variable en tant que valeur de caractère, il est plus logique de faire une affectation en utilisant des données standard.indexation de trame qui permet des valeurs de caractères pour les noms de colonnes. 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 très facilement de nouvelles colonnes via des paramètres nommés. Mais cela suppose que vous connaissez 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é.
Le la dernière version de dplyr (0.7) le fait en utilisant :=
pour attribuer dynamiquement des noms de paramètres. Vous pouvez écrire votre fonction:
# --- dplyr version 0.7+---
multipetal <- function(df, n) {
varname <- paste("petal", n , sep=".")
mutate(df, !!varname := Petal.Width * n)
}
Pour plus d'informations, consultez le formulaire documentation disponible vignette("programming", "dplyr")
.
Une version légèrement antérieure de dplyr (>=0.3 vignette("nse")).
Donc, ici, la réponse est d'utiliser mutate_()
, plutôt que de mutate()
et 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))
}
Les Anciennes versions de dplyr
Notez que cela est également possible dans les anciennes versions de dplyr qui existaient lorsque la question a été posée à l'origine. Il 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
en attente en avril 2017), nous pouvons également faire une affectation (:=
) et passer des variables comme noms de colonnes en unquoting (!!
) pour 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 basée sur @Mrflick's multipetal
appliquée sur 'iris1'
identical(iris1, iris2)
#[1] TRUE
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 aussi une réponse qui augmente un peu parce que je suis venu à cette entrée lors de la recherche d'une réponse, et cela avait presque ce dont j'avais besoin, mais j'avais besoin d'un peu plus, ce que j'ai obtenu via la réponse de @MrFlik et les vignettes R lazyeval.
Je voulais créer une fonction qui pourrait prendre un dataframe et un vecteur de noms de colonnes (en tant que chaînes) que je veux convertir d'une chaîne en un objet Date. Je ne pouvais pas comprendre comment faire en sorte que as.Date()
prenne un argument qui est une chaîne et convertir une colonne, donc j'ai fait comme indiqué ci-dessous.
Voici comment je l'ai fait via SE mutate (mutate_()
) et l'argument .dots
. Les critiques qui rendent cela Meilleur 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
, Après beaucoup d'essais et d'erreurs, j'ai trouvé le modèle UQ(rlang::sym("some string here")))
vraiment utile pour travailler avec des chaînes et dplyr verbes. Cela semble fonctionner dans beaucoup de situations surprenantes.
Voici un exemple avec mutate
. Nous voulons créer une fonction qui additionne deux colonnes, où vous passez la fonction les deux noms de colonne en tant que chaînes. Nous pouvons utiliser ce modèle, avec l'opérateur d'affectation :=
, pour ce faire.
## 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 modèle fonctionne également avec d'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 modèle. Au lieu de cela, vous pouvez utiliser !!
:
## select a column
select_name <- function(name){
mtcars %>%
select(!!name)
}
select_name('mpg')
Alors que j'aime utiliser dplyr pour une utilisation interactive, je trouve extraordinairement difficile de le faire en utilisant dplyr parce que vous devez passer par des cerceaux pour utiliser lazyeval::interp(), setNames, etc. contournement.
Voici une version plus simple utilisant la base R, dans laquelle il me semble plus intuitif, au moins, de mettre la boucle dans 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)
Vous pouvez profiter paquet friendlyeval
qui présente une API et une documentation EVAL tidy simplifiées pour les utilisateurs dplyr
plus récents/occasionnels.
Vous créez des chaînes que vous souhaitez mutate
traiter comme des noms de colonnes. Donc, en utilisant friendlyeval
vous pouvez é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 rlang
fonctions qui vérifient varname
est légal comme nom de colonne.
friendlyeval
le code peut être converti en un code eval équivalent à tout moment avec un addin RStudio.