web-dev-qa-db-fra.com

Passer un nom de colonne data.frame à une fonction

J'essaie d'écrire une fonction pour accepter un data.frame (x) et un column. La fonction effectue des calculs sur x et renvoie ultérieurement un autre nom data.frame. Je suis bloqué sur la méthode des meilleures pratiques pour transmettre le nom de colonne à la fonction.

Les deux exemples minimaux fun1 et fun2 ci-dessous produisent le résultat souhaité, en permettant d'effectuer des opérations sur x$column, en utilisant max() comme exemple. Cependant, les deux s'appuient sur l'apparente (au moins pour moi) inélégante 

  1. appelez substitute() et éventuellement eval() 
  2. la nécessité de transmettre le nom de la colonne en tant que vecteur de caractères. 

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

Je voudrais pouvoir appeler la fonction comme fun(df, B), par exemple. Autres options que j'ai envisagées mais que je n'ai pas essayées:

  • Passez column en tant qu'entier du numéro de colonne. Je pense que cela éviterait substitute(). Idéalement, la fonction pourrait accepter l'un ou l'autre.
  • with(x, get(column)), mais, même si cela fonctionne, je pense que cela nécessiterait toujours substitute 
  • Utilisez formula() et match.call(), dont je n'ai pas beaucoup d'expérience.

Sous-question : Est-ce que do.call() est préféré à eval()?

85
kmm

Vous pouvez simplement utiliser le nom de la colonne directement:

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

Il n'est pas nécessaire d'utiliser substitut, eval, etc.

Vous pouvez même passer la fonction souhaitée en tant que paramètre:

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

Sinon, utiliser [[ fonctionne également pour sélectionner une colonne à la fois:

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

Personnellement, je pense que passer la colonne en tant que chaîne est plutôt moche. J'aime faire quelque chose comme:

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

qui donnera:

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

Notez que la spécification d'un data.frame est facultative. vous pouvez même travailler avec les fonctions de vos colonnes:

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

Une autre méthode consiste à utiliser tidy evaluation . Il est assez simple de transmettre des colonnes d'un cadre de données sous forme de chaînes ou de noms de colonnes nues. En savoir plus sur tidyevalici .

library(rlang)
library(tidyverse)

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

Utilisez les noms de colonne comme des chaînes

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

Utiliser des noms de colonnes nues

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

Créé le 2019-03-01 par le paquet reprex (v0.2.1.9000)

2
Tung

En outre, si cela est nécessaire pour passer le nom de colonne sans être cité à la fonction personnalisée, peut-être que match.call() pourrait également être utile dans ce cas, en guise d'alternative à deparse(substitute()):

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

S'il y a une faute de frappe dans le nom de la colonne, il serait plus prudent d'arrêter avec une erreur:

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

Créé le 2019-01-11 par le paquet reprex (v0.2.1)

Je ne pense pas que j'utiliserais cette approche car il y a plus de dactylographie et de complexité que de simplement passer le nom de colonne cité comme indiqué dans les réponses ci-dessus, mais bon, c'est une approche.

0
Valentin