renombrar - Pasar un nombre de columna de data.frame a una función
rename data frame r (3)
Intento escribir una función para aceptar un data.frame ( x
) y una 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.
Los dos ejemplos mínimos fun1
y fun2
continuación producen el resultado deseado, pudiendo realizar operaciones en x$column
, usando max()
como ejemplo. Sin embargo, ambos confían en el aparentemente (por lo menos para mí) poco elegante
- llamada a
substitute()
y posiblementeeval()
- 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 poder llamar a la función como fun(df, B)
, por ejemplo. Otras opciones que he considerado pero que no he intentado:
- Pase la
column
como un número entero de la columna. Creo que esto evitaría elsubstitute()
. Idealmente, la función podría aceptar cualquiera. -
with(x, get(column))
, pero, incluso si funciona, creo que esto todavía requeriría unsubstitute
- Haz uso de
formula()
ymatch.call()
, ninguno de los cuales tengo mucha experiencia.
Subjetivo : ¿ do.call()
prefiere do.call()
sobre eval()
?
Esta respuesta cubrirá muchos de los mismos elementos que las respuestas existentes, pero este problema (pasar los nombres de las columnas a las funciones) aparece con la frecuencia suficiente como para querer que haya una respuesta que cubra las cosas un poco más exhaustivamente.
Supongamos que tenemos un marco de datos muy simple:
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 x
e y
.
Un obstáculo muy común aquí es que un intento natural (pero incorrecto) 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)".
La solución más sencilla y recomendada es simplemente cambiar de $
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 la "mejor práctica", ya que es el método más difícil de arruinar. 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 usarlas bien requiere más cuidado y habilidad, ya que pueden introducir complejidades sutiles y puntos de falla imprevistos. This sección del libro Advanced Hadley de Hadley es una excelente referencia para algunos de estos temas.
Si realmente desea evitar que el usuario escriba todas esas comillas, una opción podría ser convertir los nombres de las columnas desnudas y sin comillas en 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.
Finalmente, si queremos ser realmente elegantes, podemos 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, es probable que recurramos a eval()
en una expresión que involucre 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
}
Solo por diversión, todavía estoy usando deparse(substitute())
para el nombre de la nueva columna. Aquí, todo lo siguiente funcionará:
> 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
Entonces la respuesta corta es básicamente: pasar los nombres de las columnas data.frame como cadenas y usar [[
para seleccionar columnas individuales. Solo empiece a profundizar en eval
, substitute
, etc., si realmente sabe lo que está haciendo.
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 rendirá:
> get.max(mpg,mtcars)
[1] 33.9
> get.max(c(1,2,3,4,5))
[1] 5
Observe cómo la especificación de un data.frame es opcional. incluso puedes trabajar con las funciones de tus columnas:
> get.max(1/mpg,mtcars)
[1] 0.09615385
Puedes usar 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 es necesario usar sustituto, eval, etc.
Incluso puede pasar la función deseada como 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")