2011-01-13 14 views
9

rompecabezas para los conocedores R: Digamos que tenemos una trama de datos:Cómo escribir una función de R, que evalúa una expresión dentro de una trama de datos

df <- data.frame(a = 1:5, b = 1:5) 

Sé que podemos hacer cosas como

with(df, a) 

para obtener un vector de resultados.

Pero, ¿cómo puedo escribir una función que toma una expresión (como a o a > 3) y hace lo mismo dentro. Es decir. Quiero escribir una función fn que toma un marco de datos y una expresión como argumentos y devuelve el resultado de evaluar la expresión "dentro" del marco de datos como un entorno.

No importa que esto suene artificial (podría simplemente usar with como arriba), pero esta es solo una versión simplificada de una función más compleja que estoy escribiendo. Probé varias variantes (usando eval, with, envir, substitute, local, etc.) pero ninguna de ellas funciona. Por ejemplo, si defino fn así:

fn <- function(dat, expr) { 
    eval(expr, envir = dat) 
} 

me sale este error:

> fn(df, a) 
Error in eval(expr, envir = dat) : object 'a' not found 

Es evidente que me falta algo sutil sobre los entornos y evaluación. ¿Hay alguna manera de definir tal función?

+1

Hay una sección en [wiki de Hadley sobre este tema] (https: // github.com/hadley/devtools/wiki/Evaluation) – Marek

+0

@Marek es una gran referencia para leer, ¡gracias! –

+0

¿Se puede acceder a esta página? –

Respuesta

10

El paquete de celosía hace este tipo de cosas de una manera diferente. Ver, por ejemplo, lattice:::xyplot.formula.

fn <- function(dat, expr) { 
    eval(substitute(expr), dat) 
} 
fn(df, a)    # 1 2 3 4 5 
fn(df, 2 * a + b)  # 3 6 9 12 15 
+0

+1 esta es la manera más simple, gracias –

+0

+1, muy agradable (no pensé en el sustituto). La ventaja de match.call es que tienes todos tus argumentos en una lista conveniente, por eso los uso con mayor frecuencia. Pero si no necesitas el resto, el sustituto es de hecho una manera muy agradable y fácil. –

+0

¿Hay alguna forma de pasar múltiples expresiones en una lista() o c() y evaluar cada una en un ciclo for para diferentes marcos de datos que también están almacenados en una lista? Quiero la misma funcionalidad. Simplemente no puedo hacer que funcione para dataframes y expresiones almacenadas en la lista. – Blind0ne

9

Eso es porque no estás pasando una expresión.

Probar:

fn <- function(dat, expr) { 
    mf <- match.call() # makes expr an expression that can be evaluated 
eval(mf$expr, envir = dat) 
} 

> df <- data.frame(a = 1:5, b = 1:5) 
> fn(df, a) 
[1] 1 2 3 4 5 
> fn(df, a+b) 
[1] 2 4 6 8 10 

Un rápido vistazo al código fuente de las funciones que utiliza este (por ejemplo lm) puede revelar mucho más cosas interesantes al respecto.

+0

gracias, eso es lo que me falta! Y sí, traté de buscar funciones como 'subconjunto', y algunos otros, para ver cómo lo hacen, pero eran internos. No pensé en 'lm', buen punto para futuras referencias. –

+1

Creo que usar un sustituto en esta circunstancia es más canónico. Y no estoy seguro de que sea un buen modelo a seguir; al menos, asegúrese de leer las reglas estándar de evaluación no estándar. – hadley

+0

@hadley: cierto. Solo pensé en 'match.call()' y 'lm()' debido al argumento 'data'. –

-1

? Within también podría ser de su interés.

df <- data.frame(a = 1:5, b = 1:5) 
within(df, cx <- a > 3) 
    a b cx 
1 1 1 FALSE 
2 2 2 FALSE 
3 3 3 FALSE 
4 4 4 TRUE 
5 5 5 TRUE 
+0

@mdsummer: me temo que no entendiste completamente la pregunta ... –

2

una entrada tardía, pero el enfoque y la sintaxis data.table parecen ser lo que está después. Esto es exactamente cómo funciona [.data.table con los argumentos j, i y by.

Si lo necesita en forma fn(x,expr), a continuación, puede utilizar el siguiente

library(data.table) 

DT <- data.table(a = 1:5, b = 2:6) 

`[`(x=DT, j=a) 

## [1] 1 2 3 4 5 

`[`(x=DT, j=a * b) 
## [1] 2 6 12 20 30 

Creo que es más fácil de usar en forma nativa más

DT[,a] 
## [1] 1 2 3 4 5 

y así sucesivamente. En el fondo esto está usando substitute y eval

Cuestiones relacionadas