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() ?

139voto

joran Points 68079

Cette réponse couvrira la plupart des éléments des réponses existantes, mais cette question (passer les noms de colonnes aux fonctions) se pose assez souvent pour que je veuille qu'il y ait une réponse qui couvre les choses de manière un peu plus complète.

Supposons que nous ayons un cadre de données très simple :

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

et nous aimerions écrire une fonction qui crée une nouvelle colonne z qui est la somme des colonnes x y y .

La pierre d'achoppement la plus fréquente est qu'une tentative naturelle (mais incorrecte) ressemble souvent à ceci :

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

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

Le problème est que df$col1 n'évalue pas l'expression col1 . Il recherche simplement une colonne dans df littéralement appelé col1 . Ce comportement est décrit dans ?Extract dans la section "Objets récursifs (de type liste)".

La solution la plus simple, et la plus souvent recommandée, est de passer de $ a [[ et transmet les arguments de la fonction sous forme de chaînes de caractères :

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

Cette méthode est souvent considérée comme la "meilleure pratique", car c'est celle qui présente le plus de risques d'erreurs. Transmettre les noms de colonnes sous forme de chaînes de caractères est à peu près aussi peu ambigu que possible.

Les deux options suivantes sont plus avancées. De nombreux logiciels courants utilisent ce type de techniques, mais en les utilisant, il est possible d'obtenir des informations plus précises. bien nécessitent plus de soin et de compétence, car elles peuvent introduire des complexités subtiles et des points de défaillance imprévus. Le présent du livre Advanced R de Hadley est une excellente référence pour certaines de ces questions.

Si vous vraiment veulent éviter à l'utilisateur de taper tous ces guillemets, une option pourrait être de convertir les noms de colonnes nus, sans guillemets, en chaînes de caractères à l'aide de l'option 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

C'est franchement un peu stupide, puisque nous faisons en réalité la même chose qu'en new_column1 mais avec un travail supplémentaire pour convertir les noms nus en chaînes de caractères.

Enfin, si nous voulons obtenir vraiment Nous pourrions décider qu'au lieu de transmettre les noms des deux colonnes à ajouter, nous aimerions être plus flexibles et permettre d'autres combinaisons de deux variables. Dans ce cas, nous aurons probablement recours à l'utilisation de eval() sur une expression impliquant les deux colonnes :

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

Pour le plaisir, j'utilise toujours deparse(substitute()) pour le nom de la nouvelle colonne. Ici, tous les éléments suivants fonctionneront :

> 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

En résumé, la réponse est la suivante : passez les noms des colonnes de data.frame sous forme de chaînes de caractères et utilisez [[ pour sélectionner une seule colonne. Ce n'est que lorsque vous commencez à vous plonger dans les eval , substitute etc. si vous savez vraiment ce que vous faites.

136voto

Shane Points 40885

Vous pouvez utiliser directement le nom de la colonne :

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 substitute, eval, etc.

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

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

Il est également possible d'utiliser [[ fonctionne également pour la sélection d'une seule 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")

28voto

Ian Fellows Points 8013

Personnellement, je pense que le fait de passer la colonne sous forme de chaîne de caractères est assez laid. J'aime faire quelque chose comme :

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

ce qui donnera :

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

Remarquez 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

14voto

Tung Points 7486

Une autre solution consiste à utiliser tidy evaluation l'approche. Il est assez simple de transmettre les colonnes d'une base de données sous forme de chaînes de caractères ou de noms de colonnes nus. En savoir plus sur tidyeval ici .

library(rlang)
library(tidyverse)

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

Utiliser les noms de colonnes comme chaînes de caractères

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 nus

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)

13voto

mgrund Points 321

Avec dplyr il est désormais possible d'accéder à une colonne spécifique d'un tableau de données en utilisant simplement des accolades doubles {{...}} autour du nom de la colonne souhaitée dans le corps de la fonction, par exemple pour col_name :

library(tidyverse)

fun <- function(df, col_name){
   df %>% 
     filter({{col_name}} == "test_string")
}

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