159 votes

Passer un nom de colonne de data.frame à une fonction

J'essaie d'écrire une fonction qui accepte un data.frame ( x ) et un column de celui-ci. La fonction effectue quelques calculs sur x et renvoie ensuite un autre data.frame. Je ne sais pas quelle est la meilleure méthode pour transmettre le nom de la colonne à la fonction.

Les deux exemples minimaux fun1 y fun2 ci-dessous produisent le résultat souhaité, en étant capable d'effectuer des opérations sur les x$column , en utilisant max() à titre d'exemple. Cependant, tous deux s'appuient sur la formule apparemment (du moins pour moi) inélégante suivante

  1. appeler à substitute() et éventuellement eval()
  2. la nécessité de transmettre le nom de la colonne sous la forme d'un 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")

J'aimerais pouvoir appeler la fonction en tant que fun(df, B) par exemple. J'ai envisagé d'autres options, mais je ne les ai pas essayées :

  • Passez column sous la forme d'un entier du numéro de la colonne. Je pense que cela permettrait d'éviter 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 tout de même substitute
  • Utiliser formula() y match.call() Je n'ai pas beaucoup d'expérience dans ce domaine.

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

1voto

Valentin Points 1062

En outre, s'il est nécessaire de transmettre le nom de la colonne sans guillemets à la fonction personnalisée, on peut imaginer que match.call() pourrait également être utile dans ce cas, en tant qu'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 sûr 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 typage et de complexité que de simplement passer le nom de la colonne citée comme indiqué dans les réponses ci-dessus, mais c'est une approche.

1voto

Réponse de Tung y Réponse de mgrund présenté évaluation ordonnée . Dans cette réponse, je montrerai comment nous pouvons utiliser ces concepts pour faire quelque chose de similaire à réponse de joran (notamment sa fonction new_column3 ). L'objectif est de faciliter la compréhension des différences entre l'évaluation de base et l'évaluation ordonnée, ainsi que des différentes syntaxes qui peuvent être utilisées dans l'évaluation ordonnée. Vous aurez besoin de rlang y dplyr pour cela.

Utilisation d'outils d'évaluation de la base (réponse de Joran) :

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

Dans la première ligne, substitute nous fait évaluer col_name comme une expression, plus précisément un symbole (aussi parfois appelé un nom), et non un objet. Les substituts de rlang peuvent être :

  • ensym - le transforme en symbole ;
  • enexpr - le transforme en expression ;
  • enquo - la transforme en une quosure, une expression qui indique également l'environnement dans lequel R doit chercher les variables pour l'évaluer.

La plupart du temps, vous voulez avoir ce pointeur sur l'environnement. Lorsque vous n'en avez pas spécifiquement besoin, le fait de l'avoir pose rarement des problèmes. Ainsi, la plupart du temps, vous pouvez utiliser enquo . Dans ce cas, vous pouvez utiliser ensym pour rendre le code plus facile à lire, car il rend plus clair ce que l'on doit faire. col_name est.

Également dans la première ligne, deparse consiste à transformer l'expression/le symbole en une chaîne de caractères. Vous pouvez également utiliser as.character o rlang::as_string .

Dans la deuxième ligne, le substitute tourne expr en une expression "complète" (pas un symbole), de sorte que ensym n'est plus une option.

De même, à la deuxième ligne, nous pouvons maintenant changer eval a rlang::eval_tidy . Eval fonctionnerait toujours avec enexpr mais pas avec une quosté. Lorsque vous avez une quosure, vous n'avez pas besoin de passer l'environnement à la fonction d'évaluation (comme joran l'a fait avec parent.frame() ).

Une combinaison des substitutions suggérées ci-dessus pourrait être :

new_column3 <- function(df,col_name,expr){
  col_name <- as_string(ensym(col_name))
  df[[col_name]] <- eval_tidy(enquo(expr), df)
  df
}

Nous pouvons également utiliser la fonction dplyr qui permettent le masquage des données (évaluation d'une colonne d'un cadre de données en tant que variable, en l'appelant par son nom). Nous pouvons changer la méthode de transformation du symbole en caractère + sous-ensemble df en utilisant [[ con mutate :

new_column3 <- function(df,col_name,expr){
  col_name <- ensym(col_name)
  df %>% mutate(!!col_name := eval_tidy(enquo(expr), df))
}

Pour éviter que la nouvelle colonne ne soit nommée "col_name", nous l'évaluons avec anxiété (par opposition à lazy-evaluate, la valeur par défaut de R) à l'aide de la commande bang-bang !! de l'opérateur. Comme nous avons effectué une opération sur le côté gauche, nous ne pouvons pas utiliser "normal = et doit utiliser la nouvelle syntaxe := .

L'opération courante qui consiste à transformer le nom d'une colonne en un symbole, puis à l'évaluer avec anxiété à l'aide de bang-bang, a un raccourci : le curly-curly. {{ de l'opérateur :

new_column3 <- function(df,col_name,expr){
  df %>% mutate({{col_name}} := eval_tidy(enquo(expr), df))
}

Je ne suis pas un expert en évaluation dans R et j'ai peut-être simplifié à l'excès, ou utilisé un terme erroné, alors n'hésitez pas à me corriger dans les commentaires. J'espère avoir aidé à comparer les différents outils utilisés dans les réponses à cette question.

0voto

deesolie Points 186

Si vous essayez d'intégrer cette fonction dans un paquetage R ou si vous souhaitez simplement réduire la complexité, vous pouvez procéder comme suit :

test_func <- function(df, column) {
  if (column %in% colnames(df)) {
    return(max(df[, column, with=FALSE])) 
  } else {
    stop(cat(column, "not in data.frame columns."))
  }
}

L'argument with=FALSE "désactive la possibilité de se référer aux colonnes comme s'il s'agissait de variables, rétablissant ainsi le mode "data.frame" (par Documentation CRAN ). L'instruction if est un moyen rapide de vérifier si le nom de la colonne fournie se trouve dans le data.frame. On peut également utiliser la gestion des erreurs tryCatch ici.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X