Estoy tratando de 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 mejores prácticas para pasar el nombre de la columna a la función.
Los dos ejemplos mínimos fun1
y fun2
siguientes producen el resultado deseado, pudiendo realizar operaciones sobre x$column
, utilizando max()
como ejemplo. Sin embargo, ambos confían en lo aparentemente (al menos para mí) poco elegante
- llamar
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 no he probado:
- Pasa
column
como un número entero del número de columna. Creo que esto evitaríasubstitute()
. Idealmente, la función podría aceptar cualquiera. with(x, get(column))
, pero, incluso si funciona, creo que esto aún requeriríasubstitute
- Hacer uso de
formula()
ymatch.call()
, ninguno de los cuales tengo mucha experiencia.
Subpregunta : ¿Se do.call()
prefiere sobre eval()
?
B
supondrá que B es un objeto en sí mismo.[[
solución era la única que funcionaba para mí.Esta respuesta cubrirá muchos de los mismos elementos que las respuestas existentes, pero este problema (pasar los nombres de las columnas a las funciones) surge con tanta frecuencia que quería que hubiera una respuesta que cubriera las cosas de manera un poco más completa.
Supongamos que tenemos un marco de datos muy simple:
y nos gustaría escribir una función que cree una nueva columna
z
que es la suma de las columnasx
yy
.Un obstáculo muy común aquí es que un intento natural (pero incorrecto) a menudo se ve así:
El problema aquí es que
df$col1
no evalúa la expresióncol1
. Simplemente busca una columna endf
literalmente llamadacol1
. Este comportamiento se describe en?Extract
la sección "Objetos recursivos (en forma de lista)".La solución más simple, y más a menudo simplemente se recomienda cambiar de
$
a[[
y pasar los argumentos de la función como cadenas:Esto a menudo se considera "mejor práctica", ya que es el método más difícil de estropear. Pasar los nombres de las columnas como cadenas es lo más inequívoco posible.
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. Esta sección del libro Advanced R 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 columnas desnudos y sin comillas en cadenas usando
deparse(substitute())
:Esto es, francamente, probablemente un poco tonto, ya que en realidad estamos haciendo lo mismo que en
new_column1
, solo que con un montón de trabajo extra para convertir nombres desnudos en cadenas.Finalmente, si queremos ser realmente sofisticados, 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, probablemente recurriremos al uso
eval()
de una expresión que involucre las dos columnas:Solo por diversión, todavía lo estoy usando
deparse(substitute())
para el nombre de la nueva columna. Aquí, todo lo siguiente funcionará:Entonces, la respuesta corta es básicamente: pasar los nombres de las columnas data.frame como cadenas y usarlos
[[
para seleccionar columnas individuales. Sólo empezar a ahondar eneval
,substitute
, etc, si usted realmente sabe lo que está haciendo.fuente
Personalmente, creo que pasar la columna como una cadena es bastante feo. Me gusta hacer algo como:
que producirá:
Observe que la especificación de un data.frame es opcional. incluso puedes trabajar con funciones de tus columnas:
fuente
Otra forma es utilizar el
tidy evaluation
enfoque. Es bastante sencillo pasar columnas de un marco de datos como cadenas o nombres de columnas desnudos. Vea más sobretidyeval
aquí .Usar nombres de columna como cadenas
Usar nombres de columnas desnudos
Creado el 2019-03-01 por el paquete reprex (v0.2.1.9000)
fuente
Como idea adicional, si es necesario pasar el nombre de la columna sin comillas a la función personalizada, quizás también
match.call()
podría ser útil en este caso, como alternativa adeparse(substitute())
:Si hay un error tipográfico en el nombre de la columna, sería más seguro detenerse con un error:
Creado el 2019-01-11 por el paquete reprex (v0.2.1)
No creo que usaría este enfoque, ya que hay una escritura y una complejidad adicionales que simplemente pasar el nombre de la columna entre comillas como se indica en las respuestas anteriores, pero bueno, es un enfoque.
fuente