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?

89
demandé sur Jaap 2014-09-23 23:51:15

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)
}
100
répondu MrFlick 2017-12-19 08:23:42

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
33
répondu akrun 2017-04-14 21:01:37

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
10
répondu user2946432 2015-09-24 13:12:26

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
4
répondu mpettis 2015-07-29 01:54:23

, 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')
3
répondu Tom Roth 2018-07-07 05:00:02

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) 
1
répondu hackR 2017-01-22 15:01:18

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.

0
répondu MilesMcBain 2018-06-24 09:54:54