2010-04-14 8 views
69

Estoy intentando escribir una función para aceptar un data.frame (x) y un column de él. La función realiza algunos cálculos en x y luego devuelve otro data.frame. Estoy atascado en el método de las mejores prácticas para pasar el nombre de la columna a la función.Pasar un nombre de columna de data.frame a una función

Los dos ejemplos mínimos fun1 y fun2 debajo de producir el resultado deseado, siendo capaz de realizar operaciones en x$column, utilizando max() como un ejemplo. Sin embargo, ambos se basan en la apariencia (al menos para mí) poco elegante

  1. llamada a substitute() y posiblemente eval()
  2. la necesidad de pasar el nombre de la columna como un vector de caracteres.

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") 

Me gustaría ser capaz de llamar a la función como fun(df, B), por ejemplo. Otras opciones que he considerado, pero no he probado:

  • Pass column como un entero del número de columna. Creo que esto evitaría substitute(). Idealmente, la función podría aceptar cualquiera.
  • with(x, get(column)), pero, incluso si funciona, creo que esto seguiría requiriendo substitute
  • Hacer uso de formula() y match.call(), ninguno de los cuales tengo mucha experiencia con.

de preguntas adicionales: ¿Es preferible do.call() sobre eval()?

Respuesta

66

justo Usted puede utilizar el nombre de la columna directamente:

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")) 

No hay necesidad de usar sustituto, eval, etc.

Incluso puede pasar a la función deseada como un parámetro:

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

Alternativamente, usando [[ también funciona para seleccionar una sola columna a la vez:

df <- data.frame(A=1:10, B=2:11, C=3:12) 
fun1 <- function(x, column){ 
    max(x[[column]]) 
} 
fun1(df, "B") 
+7

¿Hay alguna manera de pasar el nombre de la columna no como una cadena? – kmm

+2

Debe pasar el nombre de columna citado como carácter o el índice entero para la columna. Solo pasar 'B' supondrá que B es un objeto en sí mismo. – Shane

+0

Ya veo. No estoy seguro de cómo terminé con el complicado sustituto, eval, etc. – kmm

17

Personalmente, creo que pasar la columna como una cadena es bastante feo. Me gusta hacer algo como:

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

que dió:

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

Note como la especificación de un hoja.de.datos es opcional.incluso se puede trabajar con las funciones de sus columnas:

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

Tienes que salir del hábito de pensar usando comillas es feo. ¡No usarlos es feo! ¿Por qué? Debido a que ha creado una función que solo se puede usar de forma interactiva, es muy difícil programar con ella. – hadley

+23

Me alegra que se me muestre una mejor manera, pero no veo la diferencia entre esto y qplot (x = mpg, data = mtcars). ggplot2 nunca pasa una columna como una cadena, y creo que es mejor para ella. ¿Por qué dices que esto solo se puede usar de forma interactiva? ¿Bajo qué situación conduciría a resultados indeseables? ¿Cómo es más difícil programar? En el cuerpo de la publicación, muestro cómo es más flexible. –

+3

5 años después -) .. ¿Por qué necesitamos: parent.frame()? – mql4beginner

39

Esta respuesta va a cubrir muchos de los mismos elementos que las respuestas existentes, pero este tema (que pasa nombres de columna a funciones) surge a menudo suficiente que yo quería que hubiera una respuesta que cubría las cosas un poco más exhaustivamente.

Supongamos que tenemos una muy simple trama de datos:

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

y nos gustaría escribir una función que crea una nueva columna z que es la suma de las columnas y xy.

Un obstáculo muy común aquí es que un natural (pero incorrecta) tratan a menudo se ve así:

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

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

El problema aquí es que df$col1 no evalúa la expresión col1. Simplemente busca una columna en df literalmente llamada col1. Este comportamiento se describe en ?Extract en la sección "Objetos recursivos (similares a listas)".

El más simple, y la solución más recomendado es simplemente cambiar del $ a [[ y pasar los argumentos de la función como cadenas:

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 

Esto a menudo se considera "mejores prácticas" ya que es el método que sea más difícil meter la pata. Pasar los nombres de las columnas como cadenas es tan inequívoco como puede obtener.

Las siguientes dos opciones son más avanzadas. Muchos paquetes populares hacen uso de este tipo de técnicas, pero usarlos y requiere más cuidado y habilidad, ya que pueden introducir complejidades sutiles y puntos de falla imprevistos. This sección del libro Hadley's Advanced R es una excelente referencia para algunos de estos problemas.

Si realmente desea guardar el usuario escriba todas esas cotizaciones, una opción podría ser para convertir nombres de las columnas desnudas, sin cotización a cadenas usando 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 

Esto es, francamente, un poco tonto probablemente, ya que realmente estamos haciendo lo mismo que en new_column1, simplemente con un montón de trabajo extra para convertir nombres desnudos en cadenas.

Por último, si queremos obtener realmente de lujo, podríamos decidir que en lugar de pasar los nombres de dos columnas para agregar, nos gustaría ser más flexibles y permitir otras combinaciones de dos variables. En ese caso estaríamos propensos recurrir al uso de eval() en una expresión que incluya las dos columnas:

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

Sólo por diversión, todavía estoy usando deparse(substitute()) para el nombre de la nueva columna. Aquí, todo el siguiente trabajo:

> 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 

Así que la respuesta corta es básicamente: Pase data.frame nombres de columna como cadenas y utilizar [[ para seleccionar columnas individuales.Solo comience a profundizar en eval, substitute, etc. si realmente sabe lo que está haciendo.

Cuestiones relacionadas