Passer un nom de colonne data.frame à une fonction

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

Les deux exemples minimaux fun1 et fun2 ci-dessous produisent le résultat souhaité, pouvant effectuer des opérations sur x$column , en utilisant max() comme exemple. Cependant, tous les deux comptent sur l’élégante

  1. appel à 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 aussi fun(df, B) , par exemple. Autres options que j’ai envisagées mais que je n’ai pas essayées:

  • Passer la column sous forme d’entier du numéro de la colonne. Je pense que cela éviterait le substitute() . Idéalement, la fonction pourrait accepter non plus.
  • with(x, get(column)) , mais, même si cela fonctionne, je pense que cela nécessiterait encore un substitute
  • Faites appel à formula() et match.call() , avec lesquelles je n’ai pas beaucoup d’expérience.

Sous-question : Est-ce que do.call() préférable à 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'y a pas besoin d'utiliser substitut, 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) 

Vous pouvez également utiliser [[ fonctionne également pour sélectionner 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") 

Cette réponse couvrira plusieurs des mêmes éléments que les réponses existantes, mais cette question (passer des noms de colonne à des fonctions) apparaît assez souvent pour que je souhaite une réponse qui couvre un peu plus les choses.

Supposons que nous ayons un bloc 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 sum des colonnes x et y .

Un point d'achoppement très courant 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 ici est que df$col1 n'évalue pas l'expression col1 . Il recherche simplement une colonne dans df appelée littéralement col1 . Ce comportement est décrit dans ?Extract sous la section "Objets récursifs (de type liste)".

La solution la plus simple et la plus recommandée est simplement de passer de $ à [[ et de passer les arguments de la fonction sous forme de chaînes:

 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") xyz 1 1 5 6 2 2 6 8 3 3 7 10 4 4 8 12 

Ceci est souvent considéré comme "meilleure pratique" car c'est la méthode la plus difficile à bousiller. Passer les noms de colonne en tant que chaînes est à peu près aussi clair que possible.

Les deux options suivantes sont plus avancées. Beaucoup de paquets populaires utilisent ce genre de techniques, mais leur utilisation nécessite plus de soin et de compétence, car ils peuvent introduire des complexités subtiles et des points de défaillance imprévus. Cette section du livre Advanced R de Hadley est une excellente référence pour certains de ces problèmes.

Si vous voulez vraiment empêcher l'utilisateur de taper tous ces guillemets, une option peut être de convertir des noms de colonnes dénudés et non cotés en chaînes à l'aide de 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) xyz 1 1 5 6 2 2 6 8 3 3 7 10 4 4 8 12 

C'est franchement un peu bête probablement, puisque nous faisons vraiment la même chose que dans new_column1 , simplement avec un tas de travail supplémentaire pour convertir les noms dénudés en chaînes.

Enfin, si nous voulons être vraiment fantaisistes, nous pourrions décider que plutôt que de transmettre les noms de deux colonnes à append, nous aimerions être plus flexibles et permettre d’autres combinaisons de deux variables. Dans ce cas, nous aurions probablement recours à 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 } 

Juste pour le fun, 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) xyz 1 1 5 6 2 2 6 8 3 3 7 10 4 4 8 12 > new_column3(dat,z,xy) xyz 1 1 5 -4 2 2 6 -4 3 3 7 -4 4 4 8 -4 > new_column3(dat,z,x*y) xyz 1 1 5 5 2 2 6 12 3 3 7 21 4 4 8 32 

Donc, la réponse courte est essentiellement: passez les noms de colonne data.frame en tant que chaînes et utilisez [[ pour sélectionner des colonnes individuelles. Commencez par explorer eval , substitute , etc. si vous savez vraiment ce que vous faites.

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