Verwenden Sie dynamische Variablennamen in `dplyr`

168

Ich möchte verwenden dplyr::mutate(), um mehrere neue Spalten in einem Datenrahmen zu erstellen. Die Spaltennamen und deren Inhalt sollten dynamisch generiert werden.

Beispieldaten von Iris:

library(dplyr)
iris <- tbl_df(iris)

Ich habe eine Funktion erstellt, um meine neuen Spalten aus dem zu mutieren Petal.Width Variablen :

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

Jetzt erstelle ich eine Schleife, um meine Spalten zu erstellen:

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

Da mutate jedoch glaubt, dass varname ein Literalvariablenname ist, erstellt die Schleife nur eine neue Variable (als varname bezeichnet) anstelle von vier (als petal.2 - petal.5 bezeichnet).

Wie kann ich mutate()meinen dynamischen Namen als Variablennamen verwenden?

Timm S.
quelle
1
Ich bestehe nicht darauf zu mutieren, ich frage, ob es möglich ist. Vielleicht ist es nur ein kleiner Trick, den ich nicht kenne. Wenn es einen anderen Weg gibt, lass es uns hören.
Timm S.
Ich glaube, es gibt einen Platz im Lazyeval-Paket zu sehen
Baptiste
1
Zu diesem Zeitpunkt dplyrhat eine ganze Vignette über nicht standardmäßige Bewertung
Gregor Thomas
16
In der Vignette wird nicht einmal erwähnt mutate_, und aus den anderen Funktionen ist nicht ersichtlich, wie sie verwendet werden soll.
Nacnudus

Antworten:

191

Da Sie einen Variablennamen dynamisch als Zeichenwert erstellen, ist es sinnvoller, die Zuweisung mithilfe der Standardindizierung von data.frame vorzunehmen, die Zeichenwerte für Spaltennamen ermöglicht. Beispielsweise:

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

Die mutateFunktion macht es sehr einfach, neue Spalten über benannte Parameter zu benennen. Dies setzt jedoch voraus, dass Sie den Namen kennen, wenn Sie den Befehl eingeben. Wenn Sie den Spaltennamen dynamisch angeben möchten, müssen Sie auch das benannte Argument erstellen.


dplyr version> = 0.7

In der neuesten Version von dplyr(0.7) werden :=dazu Parameternamen dynamisch zugewiesen. Sie können Ihre Funktion wie folgt schreiben:

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

Weitere Informationen finden Sie in der verfügbaren Dokumentation vignette("programming", "dplyr").


dplyr (> = 0,3 & <0,7)

Eine etwas frühere Version von dplyr(> = 0,3 <0,7) befürwortete die Verwendung von "Standardbewertungs" -Alternativen zu vielen Funktionen. Weitere Informationen finden Sie in der nicht standardmäßigen Bewertungsvignette ( vignette("nse")).

Hier lautet die Antwort also mutate_()eher: als mutate():

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

dplyr <0,3

Beachten Sie, dass dies auch in älteren Versionen möglich ist dplyr, als die Frage ursprünglich gestellt wurde. Es erfordert den sorgfältigen Umgang mit quoteund 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)
}
MrFlick
quelle
24
Danke, das ist hilfreich. Übrigens erstelle ich immer wirklich dramatische Variablen.
Timm S.
27
Hehe. Das ist wahrscheinlich einer meiner Lieblingsfehler, die ich seit einiger Zeit gemacht habe. Ich denke, ich werde es verlassen.
MrFlick
1
do.call()tut wahrscheinlich nicht das, was du denkst: rpubs.com/hadley/do-call2 . Siehe auch die nse-Vignette in der Dev-Version von dplyr.
Hadley
4
Wenn ich also Ihren Punkt @hadley verstehe, habe ich das do.callObige aktualisiert , um do.call("mutate")es dfin der Liste zu verwenden und zu zitieren . Haben Sie das vorgeschlagen? Und wenn die lazyevalVersion von dplyrdie veröffentlichte Version ist, mutate_(df, .dots= setNames(list(~Petal.Width * n), varname))wäre dann eine bessere Lösung?
MrFlick
1
Was ist, wenn ich den variablen Spaltenkopf nicht nur auf der linken Seite der Zuweisung, sondern auch auf der rechten Seite benötige? zB mutate(df, !!newVar := (!!var1 + !!var2) / 2)funktioniert nicht :(
Mario Reutter
54

In der neuen Version von dplyr(bis 0.6.0April 2017) können wir auch eine Zuweisung ( :=) vornehmen und Variablen als Spaltennamen übergeben, indem wir ( !!) nicht zitieren ( ), um sie nicht auszuwerten

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

Überprüfen der Ausgabe basierend auf @ MrFlicks multipetalAnwendung auf 'iris1'

identical(iris1, iris2)
#[1] TRUE
akrun
quelle
26

Nach vielen Versuchen und Irrtümern fand ich das Muster UQ(rlang::sym("some string here")))wirklich nützlich für die Arbeit mit Strings und Dplyr-Verben. Es scheint in vielen überraschenden Situationen zu funktionieren.

Hier ist ein Beispiel mit mutate. Wir möchten eine Funktion erstellen, die zwei Spalten addiert, wobei Sie der Funktion beide Spaltennamen als Zeichenfolgen übergeben. Wir können dieses Muster zusammen mit dem Zuweisungsoperator verwenden :=, um dies zu tun.

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

Das Muster funktioniert auch mit anderen dplyrFunktionen. Hier ist filter:

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

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

Für select, brauchen Sie nicht das Muster zu verwenden. Stattdessen können Sie verwenden !!:

## select a column 
select_name <- function(name){
  mtcars %>% 
    select(!!name)
}
select_name('mpg')
Tom Roth
quelle
Ihre Tipps funktionieren sehr gut, aber ich habe ein kleines Problem. Ich ändere eine anfängliche Spalte myColin eine URL (zum Beispiel) und kopiere die alte Spalte myColInitialValueam Ende des Datenrahmens dfmit einem neuen Namen. Aber which(colnames(df)=='myCol')senden Sie die Spalte zurück myColInitialValue. Ich habe noch keine Ausgabe geschrieben, weil ich keinen Reprex gefunden habe. Mein Ziel ist der escapeParameter von DT::datatable(). Ich escape=FALSEwarte darauf. Mit Konstanten funktioniert es auch nicht, aber das DT-Paket scheint auch die schlechte # -Spalte zu bekommen. :)
phili_b
Es scheint, dass dynamische Variablen nicht die Ursache sind. (Übrigens Reprex hinzugefügt)
Phili_b
Danke für diese Antwort! Hier ist ein supereinfaches Beispiel, wie ich es benutzt habe:varname = sym("Petal.Width"); ggplot(iris, aes(x=!!varname)) + geom_histogram()
bdemarest
Dies funktionierte für mich in einer Formel, in der !! varname nicht funktionierte.
Daknowles
12

Hier ist eine andere Version, und es ist wohl ein bisschen einfacher.

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
user2946432
quelle
8

Mit haben rlang 0.4.0wir Curly-Curly-Operatoren ( {{}}), was dies sehr einfach macht.

library(dplyr)
library(rlang)

iris1 <- tbl_df(iris)

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

multipetal(iris1, 4)

# A tibble: 150 x 6
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species petal.4
#          <dbl>       <dbl>        <dbl>       <dbl> <fct>     <dbl>
# 1          5.1         3.5          1.4         0.2 setosa      0.8
# 2          4.9         3            1.4         0.2 setosa      0.8
# 3          4.7         3.2          1.3         0.2 setosa      0.8
# 4          4.6         3.1          1.5         0.2 setosa      0.8
# 5          5           3.6          1.4         0.2 setosa      0.8
# 6          5.4         3.9          1.7         0.4 setosa      1.6
# 7          4.6         3.4          1.4         0.3 setosa      1.2
# 8          5           3.4          1.5         0.2 setosa      0.8
# 9          4.4         2.9          1.4         0.2 setosa      0.8
#10          4.9         3.1          1.5         0.1 setosa      0.4
# … with 140 more rows

Wir können auch zitierte / nicht zitierte Variablennamen übergeben, die als Spaltennamen zugewiesen werden sollen.

multipetal <- function(df, name, n) {
   mutate(df, {{name}} := Petal.Width * n)
}

multipetal(iris1, temp, 3)

# A tibble: 150 x 6
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species  temp
#          <dbl>       <dbl>        <dbl>       <dbl> <fct>   <dbl>
# 1          5.1         3.5          1.4         0.2 setosa  0.6  
# 2          4.9         3            1.4         0.2 setosa  0.6  
# 3          4.7         3.2          1.3         0.2 setosa  0.6  
# 4          4.6         3.1          1.5         0.2 setosa  0.6  
# 5          5           3.6          1.4         0.2 setosa  0.6  
# 6          5.4         3.9          1.7         0.4 setosa  1.2  
# 7          4.6         3.4          1.4         0.3 setosa  0.900
# 8          5           3.4          1.5         0.2 setosa  0.6  
# 9          4.4         2.9          1.4         0.2 setosa  0.6  
#10          4.9         3.1          1.5         0.1 setosa  0.3  
# … with 140 more rows

Es funktioniert genauso mit

multipetal(iris1, "temp", 3)
Ronak Shah
quelle
4

Ich füge auch eine Antwort hinzu, die dies ein wenig erweitert, da ich bei der Suche nach einer Antwort zu diesem Eintrag gekommen bin und diese fast das hatte, was ich brauchte, aber ich brauchte ein bisschen mehr, das ich über die Antwort von @MrFlik und die R faulen Vignetten.

Ich wollte eine Funktion erstellen, die einen Datenrahmen und einen Vektor von Spaltennamen (als Zeichenfolgen) aufnehmen kann, die von einer Zeichenfolge in ein Datumsobjekt konvertiert werden sollen. Ich konnte nicht herausfinden, wie ich as.Date()ein Argument, das eine Zeichenfolge ist, in eine Spalte konvertieren kann, also habe ich es wie unten gezeigt gemacht.

Unten ist, wie ich dies über SE mutate ( mutate_()) und das .dotsArgument gemacht habe. Kritikpunkte, die dies verbessern, sind willkommen.

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
mpettis
quelle
3

Obwohl ich dplyr gerne für die interaktive Verwendung verwende, finde ich es außerordentlich schwierig, dies mit dplyr zu tun, da Sie die Rahmen durchlaufen müssen, um die Problemumgehungen für lazyeval :: interp (), setNames usw. zu verwenden.

Hier ist eine einfachere Version mit Base R, in der es mir zumindest intuitiver erscheint, die Schleife in die Funktion einzufügen, und die die Lösung von @ MrFlicks erweitert.

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) 
hackR
quelle
2
+1, obwohl ich dplyrin nicht interaktiven Einstellungen immer noch viel verwende, verwendet die Verwendung mit variabler Eingabe innerhalb einer Funktion eine sehr klobige Syntax.
Paul Hiemstra
3

Möglicherweise genießen Sie ein Paket, friendlyevaldas eine vereinfachte API und Dokumentation für neuere / gelegentliche dplyrBenutzer enthält.

Sie erstellen Zeichenfolgen, die Sie mutateals Spaltennamen behandeln möchten . Mit friendlyevalkönnte man also schreiben:

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

Was unter der Haube rlangFunktionen aufruft , die prüfen, varnameist als Spaltenname zulässig.

friendlyeval Mit einem RStudio-Add-In kann der Code jederzeit in einen gleichwertigen Code umgewandelt werden.

MilesMcBain
quelle