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
substitute()
et éventuellement eval()
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:
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
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()
?
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")
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
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 tidyeval
ici .
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)
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.