2010-04-14 5 views
69

Je suis en train d'écrire une fonction d'accepter un data.frame (x) et un column de celui-ci. La fonction effectue des calculs sur x et retourne plus tard un autre data.frame. Je suis bloqué sur la méthode des meilleures pratiques pour passer le nom de colonne à la fonction.Passer un nom de colonne data.frame à une fonction

Les deux exemples minimes fun1 et fun2 ci-dessous produisent le résultat souhaité, être en mesure d'effectuer des opérations sur x$column, en utilisant max() à titre d'exemple. Toutefois, les deux se fondent sur l'apparence (au moins pour moi) inélégante

  1. appel à substitute() et peut-être eval()
  2. la nécessité de transmettre le nom de colonne en tant que vecteur de caractère.

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 considérées mais que je n'ai pas essayées:

  • Transmettez column comme un nombre entier de colonnes. Je pense que cela éviterait substitute(). Idéalement, la fonction pourrait accepter non plus.
  • with(x, get(column)), mais, même si cela fonctionne, je pense que ce serait encore besoin substitute
  • Faire usage de formula() et match.call(), ni dont j'ai beaucoup d'expérience avec.

Sous-question: est-do.call() préféré sur eval()?

Répondre

66

Vous pouvez simplement utiliser le nom de 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 de remplacement, 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) 

L'utilisation de [[ permet également de 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") 
+7

Y at-il un moyen de transmettre le nom de la colonne pas comme une chaîne? – kmm

+2

Vous devez soit transmettre le nom de colonne indiqué en tant que caractère, soit l'index d'entier de la colonne. Le simple fait de passer 'B' supposera que B est un objet lui-même. – Shane

+0

Je vois. Je ne suis pas sûr de la façon dont j'ai fini avec le substitut alambiqué, eval, etc. – kmm

17

Personnellement, je pense que le passage de la colonne comme une chaîne est assez laid. J'aime faire quelque chose comme:

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

qui rapportera:

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

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

> get.max(1/mpg,mtcars) 
[1] 0.09615385 
+7

Vous devez sortir de l'habitude de penser en utilisant des citations est moche. Ne pas les utiliser est moche! Pourquoi? Parce que vous avez créé une fonction qui ne peut être utilisée que de manière interactive, il est très difficile de la programmer. – hadley

+23

Je suis heureux d'être montré un meilleur moyen, mais je ne vois pas la différence entre ceci et qplot (x = mpg, data = mtcars). ggplot2 ne passe jamais une colonne en tant que chaîne, et je pense que c'est mieux pour ça. Pourquoi dites-vous que cela ne peut être utilisé que de manière interactive? Dans quelle situation cela conduirait-il à des résultats indésirables? Comment est-il plus difficile de programmer? Dans le corps du message, je montre comment il est plus flexible. –

+3

5 ans plus tard -) .. Pourquoi avons-nous besoin: parent.frame()? – mql4beginner

39

Cette réponse couvrira un grand nombre des mêmes éléments que les réponses existantes, mais cette question (en passant les noms de colonnes à des fonctions) vient assez souvent que je voulais qu'il y ait une réponse qui a couvert les choses un peu plus complètement.

Supposons que nous ayons une trame 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 et y.

Un bloc d'achoppement très commun ici est qu'un naturel (mais incorrect) La tentative 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 cherche simplement une colonne au df appelée littéralement col1. Ce comportement est décrit dans ?Extract sous la section "Objets récursifs (semblables à une liste)".

La plus simple et la solution la plus souvent recommandée est tout simplement passer de $ à [[ et 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") 
    x y z 
1 1 5 6 
2 2 6 8 
3 3 7 10 
4 4 8 12 

Ceci est souvent considéré comme « meilleure pratique » puisqu'elle est la méthode qui est le plus difficile bousiller. Passer les noms de colonnes sous forme de chaînes est à peu près aussi clair que possible.

Les deux options suivantes sont plus avancées. Beaucoup de paquets populaires font usage de ce genre de techniques, mais en les utilisant, il faut plus de soin et de compétence, car ils peuvent introduire des complexités subtiles et des points d'échec imprévus. This section de Hadley's Advanced R livre est une excellente référence pour certains de ces problèmes.

Si vous vraiment voulez enregistrer l'utilisateur de taper toutes ces citations, une option pourrait consister à convertir les noms de colonnes nues, sans guillemets chaînes en utilisant 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 idiot probablement, puisque nous faisons vraiment la même chose que dans new_column1, juste avec un tas de travail supplémentaire pour convertir les noms nus en chaînes. Pour finir, si nous voulons obtenir vraiment fantaisie, nous pourrions décider que plutôt que de passer dans les noms de deux colonnes à ajouter, nous aimerions être plus flexibles et permettre d'autres combinaisons de deux variables. Dans ce cas, nous aurions probablement recourons à utiliser eval() 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 plaisir, je me sers encore deparse(substitute()) le nom de la nouvelle colonne. Ici, toutes les suivantes sont utilisables:

> 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 

Donc, la réponse courte est essentiellement: passer data.frame noms de colonnes sous forme de chaînes et d'utiliser [[ pour sélectionner une seule colonne.Seulement commencer à fouiller dans eval, substitute, etc. si vous savez vraiment ce que vous faites.

Questions connexes