wake-up-neo.com

Übergeben Sie einen data.frame-Spaltennamen an eine Funktion

Ich versuche, eine Funktion zu schreiben, um ein data.frame (x) und eine column daraus zu akzeptieren. Die Funktion führt einige Berechnungen für x aus und gibt später ein anderes data.frame zurück. Ich bleibe bei der Best-Practices-Methode, um den Spaltennamen an die Funktion zu übergeben.

Die beiden folgenden minimalen Beispiele fun1 und fun2 erzeugen das gewünschte Ergebnis. Sie können Operationen an x$column ausführen, wobei max() als Beispiel verwendet wird. Beide verlassen sich jedoch auf die scheinbar (zumindest für mich) unelegante 

  1. aufruf an substitute() und möglicherweise eval() 
  2. die Notwendigkeit, den Spaltennamen als Zeichenvektor zu übergeben. 

fun1 <- function(x, column){
  do.call("max", list(substitute(x[a], list(a = column))))
}

fun2 <- function(x, column){
  max(eval((substitute(x[a], list(a = column)))))
}

df <- data.frame(B = rnorm(10))
fun1(df, "B")
fun2(df, "B")

Ich möchte die Funktion zum Beispiel als fun(df, B) aufrufen können. Andere Optionen, die ich berücksichtigt habe, aber noch nicht ausprobiert habe:

  • Übergeben Sie column als ganze Zahl der Spaltennummer. Ich denke, das würde substitute() vermeiden. Idealerweise könnte die Funktion beide akzeptieren.
  • with(x, get(column)), aber selbst wenn es funktioniert, denke ich, würde dies substitute erfordern 
  • Nutzen Sie formula() und match.call(), mit denen ich nicht viel Erfahrung habe.

Unterfrage : Wird do.call() der eval() vorgezogen?

85
kmm

Sie können den Spaltennamen einfach direkt verwenden:

df <- data.frame(A=1:10, B=2:11, C=3:12)
fun1 <- function(x, column){
  max(x[,column])
}
fun1(df, "B")
fun1(df, c("B","A"))

Es ist nicht notwendig, Ersatz, Bewertung usw. zu verwenden.

Sie können sogar die gewünschte Funktion als Parameter übergeben:

fun1 <- function(x, column, fn) {
  fn(x[,column])
}
fun1(df, "B", max)

Alternativ können Sie mit [[ auch jeweils eine einzelne Spalte auswählen:

df <- data.frame(A=1:10, B=2:11, C=3:12)
fun1 <- function(x, column){
  max(x[[column]])
}
fun1(df, "B")
75
Shane

Diese Antwort behandelt viele der gleichen Elemente wie vorhandene Antworten, aber dieses Problem (Übergabe von Spaltennamen an Funktionen) wird häufig genug angesprochen, um eine Antwort zu geben, die die Dinge etwas umfassender behandelt.

Angenommen, wir haben einen sehr einfachen Datenrahmen:

dat <- data.frame(x = 1:4,
                  y = 5:8)

und wir möchten eine Funktion schreiben, die eine neue Spalte z erstellt, die die Summe der Spalten x und y ist.

Ein sehr häufiger Stolperstein dabei ist, dass ein natürlicher (aber falscher) Versuch oft so aussieht:

foo <- function(df,col_name,col1,col2){
      df$col_name <- df$col1 + df$col2
      df
}

#Call foo() like this:    
foo(dat,z,x,y)

Das Problem hierbei ist, dass df$col1 Den Ausdruck col1 Nicht auswertet. Es wird einfach nach einer Spalte in df gesucht, die wörtlich col1 Heißt. Dieses Verhalten ist unter ?Extract Im Abschnitt "Rekursive (listenartige) Objekte" beschrieben.

Die einfachste und am häufigsten empfohlene Lösung ist, einfach von $ Zu [[ Zu wechseln und die Funktionsargumente als Zeichenfolgen zu übergeben:

new_column1 <- function(df,col_name,col1,col2){
    #Create new column col_name as sum of col1 and col2
    df[[col_name]] <- df[[col1]] + df[[col2]]
    df
}

> new_column1(dat,"z","x","y")
  x y  z
1 1 5  6
2 2 6  8
3 3 7 10
4 4 8 12

Dies wird oft als "Best Practice" angesehen, da es die Methode ist, die am schwierigsten ist, Fehler zu machen. Das Übergeben der Spaltennamen als Zeichenfolgen ist so eindeutig wie möglich.

Die folgenden beiden Optionen sind weiter fortgeschritten. Viele populäre Pakete verwenden diese Art von Techniken, aber die Verwendung von gut erfordert mehr Sorgfalt und Geschick, da sie subtile Komplexitäten und unerwartete Fehlerquellen einführen können . This Abschnitt von Hadleys Advanced R-Buch ist eine ausgezeichnete Referenz für einige dieser Probleme.

Wenn Sie wirklich möchten, dass der Benutzer nicht all diese Anführungszeichen eingibt, besteht eine Möglichkeit darin, bloße, nicht in Anführungszeichen gesetzte Spaltennamen mit deparse(substitute()):

new_column2 <- function(df,col_name,col1,col2){
    col_name <- deparse(substitute(col_name))
    col1 <- deparse(substitute(col1))
    col2 <- deparse(substitute(col2))

    df[[col_name]] <- df[[col1]] + df[[col2]]
    df
}

> new_column2(dat,z,x,y)
  x y  z
1 1 5  6
2 2 6  8
3 3 7 10
4 4 8 12

Dies ist, ehrlich gesagt, wahrscheinlich ein bisschen albern, da wir genau das Gleiche tun wie in new_column1, Nur mit einigem zusätzlichen Aufwand, um bloße Namen in Zeichenfolgen umzuwandeln.

Wenn wir wirklich Lust haben möchten, können wir uns entscheiden, dass wir lieber die Namen von zwei hinzuzufügenden Spalten übergeben, als sie zu übernehmen flexibler und erlauben andere Kombinationen von zwei Variablen. In diesem Fall würden wir wahrscheinlich eval() für einen Ausdruck verwenden, der die beiden Spalten umfasst:

new_column3 <- function(df,col_name,expr){
    col_name <- deparse(substitute(col_name))
    df[[col_name]] <- eval(substitute(expr),df,parent.frame())
    df
}

Nur zum Spaß benutze ich noch deparse(substitute()) für den Namen der neuen Spalte. Hier werden alle folgenden Funktionen ausgeführt:

> new_column3(dat,z,x+y)
  x y  z
1 1 5  6
2 2 6  8
3 3 7 10
4 4 8 12
> new_column3(dat,z,x-y)
  x y  z
1 1 5 -4
2 2 6 -4
3 3 7 -4
4 4 8 -4
> new_column3(dat,z,x*y)
  x y  z
1 1 5  5
2 2 6 12
3 3 7 21
4 4 8 32

Die kurze Antwort lautet also: Übergeben Sie die Spaltennamen von data.frame als Zeichenfolgen und verwenden Sie [[, Um einzelne Spalten auszuwählen. Stöbern Sie nur dann in eval, substitute usw., wenn Sie wirklich wissen, was Sie tun.

63
joran

Ich persönlich finde, dass es ziemlich hässlich ist, die Spalte als String zu übergeben. Ich mache gerne etwas wie:

get.max <- function(column,data=NULL){
    column<-eval(substitute(column),data, parent.frame())
    max(column)
}

was ergibt:

> get.max(mpg,mtcars)
[1] 33.9
> get.max(c(1,2,3,4,5))
[1] 5

Beachten Sie, dass die Angabe eines data.frame optional ist. Sie können sogar mit Funktionen Ihrer Kolonnen arbeiten:

> get.max(1/mpg,mtcars)
[1] 0.09615385
20
Ian Fellows

Eine andere Möglichkeit ist die Verwendung von tidy evaluation . Es ist ziemlich einfach, Spalten eines Datenrahmens entweder als Zeichenfolgen oder als bloße Spaltennamen zu übergeben. Mehr über tidyevalhier .

library(rlang)
library(tidyverse)

set.seed(123)
df <- data.frame(B = rnorm(10), D = rnorm(10))

Verwenden Sie Spaltennamen als Zeichenfolgen

fun3 <- function(x, ...) {
  # capture strings and create variables
  dots <- ensyms(...)
  # unquote to evaluate inside dplyr verbs
  summarise_at(x, vars(!!!dots), list(~ max(., na.rm = TRUE)))
}

fun3(df, "B")
#>          B
#> 1 1.715065

fun3(df, "B", "D")
#>          B        D
#> 1 1.715065 1.786913

Verwenden Sie bloße Spaltennamen

fun4 <- function(x, ...) {
  # capture expressions and create quosures
  dots <- enquos(...)
  # unquote to evaluate inside dplyr verbs
  summarise_at(x, vars(!!!dots), list(~ max(., na.rm = TRUE)))
}

fun4(df, B)
#>          B
#> 1 1.715065

fun4(df, B, D)
#>          B        D
#> 1 1.715065 1.786913
#>

Erstellt am 01.01.2017 vom reprex-Paket (v0.2.1.9000)

2
Tung

Als zusätzlicher Gedanke, falls erforderlich, um den Spaltennamen ohne Anführungszeichen an die benutzerdefinierte Funktion zu übergeben, könnte match.call() in diesem Fall möglicherweise auch als Alternative zu deparse(substitute()) nützlich sein:

df <- data.frame(A = 1:10, B = 2:11)

fun <- function(x, column){
  arg <- match.call()
  max(x[[arg$column]])
}

fun(df, A)
#> [1] 10

fun(df, B)
#> [1] 11

Wenn der Spaltenname einen Tippfehler enthält, wäre es sicherer, mit einem Fehler zu stoppen:

fun <- function(x, column) max(x[[match.call()$column]])
fun(df, typo)
#> Warning in max(x[[match.call()$column]]): no non-missing arguments to max;
#> returning -Inf
#> [1] -Inf

# Stop with error in case of typo
fun <- function(x, column){
  arg <- match.call()
  if (is.null(x[[arg$column]])) stop("Wrong column name")
  max(x[[arg$column]])
}

fun(df, typo)
#> Error in fun(df, typo): Wrong column name
fun(df, A)
#> [1] 10

Erstellt am 11.01.2011 durch das Paket reprex (v0.2.1)

Ich denke nicht, dass ich diesen Ansatz verwenden würde, da es mehr Schreibarbeit und Komplexität gibt, als nur den zitierten Spaltennamen wie in den obigen Antworten angegeben zu übergeben, aber gut, ein Ansatz.

0
Valentin