2012-07-04 16 views
9

¿Es posible encadenar funciones en R?Método de encadenamiento con R

datos de la muestra:

m <- matrix(c(1:10, 11:20), nrow = 10, ncol = 2) 

Por ejemplo, me gustaría sustituir las siguientes afirmaciones siguientes:

step1 <- mean(m) 
step2 <- sum(step1) 
res <- step2 

O

res <- sum(mean(m)) 

Con algo como esto:

res <- [email protected]()@sum() 

En algunos casos, eso aclararía mi código considerablemente.

EDIT1 Este es un ejemplo ficticio. Escogí al azar 'suma' y 'significa'.

Ben ha dado una primera pieza de respuesta utilizando% @%, sin embargo, que impide utilizar argumentos adicionales dentro de las funciones:

m %@% function1(arg1, arg2) %@% function2(arg1, arg2) 

¿Cómo puedo evitar eso?

Edit2 Adición de un ejemplo

require(xts) 
require(PerformanceAnalytics) 
xts.ts <- xts(rnorm(231),as.Date(13514:13744,origin="1970-01-01")) 
plot(na.omit(lag(rollapply(xts.ts, width=rolling.per-1, FUN= function(x){sqrt(var(x))*sqrt(252)}), k=1)), main = "Dummy Example") 

Este ejemplo parece funcionar bien con una solución de Charles:

`%@%` <- function(x, f) eval.parent(as.call(append(as.list(substitute(f)), list(x), 1))) 
xts.ts %@% rollapply(width = rolling.per-1, FUN= function(x) x%@%var%@%sqrt * sqrt(252)) %@% lag(k=1) %@% na.omit %@% plot(main = "Dummy Example") 

menos importante para mi caso, pero valió la pena mencionar, la siguiente DECLARACIÓN falla con solución de Charles :

xts.ts %@% names <- 'ts name' 
+2

¿Qué pasa con 'res <- sum (mean (m))'? –

+0

Nada, pero no tiene mucho sentido tomar la 'suma 'de un vector de longitud 1 (que es lo que se devuelve con' mean' en una matriz). – Henrik

+0

Aunque se irá "pronto", todavía hay una función 'mean.data.frame' que devuelve un vector. –

Respuesta

4

En una línea similar a la respuesta de Ben, pero permitiendo argumentos:

`%@%` <- function(x, f) eval.parent(as.call(append(as.list(substitute(f)), list(x), 1))) 

x %@% mean %@% sqr # => 6.25 
c(1, 2, NA, 3, 4) %@% mean(na.rm=T) %@% sqr # => 6.25 
m %@% colMeans() %@% sum() # => 21 
+0

Para que la última funcione, necesitaría usar 'names <-' o' setNames', por ejemplo 'xts.ts% @% setNames ('ts name')', ya que '<-' tiene un manejo especial para la llamada a la función lhs que no funcionará aquí. – Charles

+0

No había visto esto antes. Es súper listo. Tendría mucho miedo de usarlo en el código de producción debido a las posibilidades de que sea frágil ... –

+0

¡No me gustaría usar esto en el código - producción o de otra manera! Independientemente de si esta es una buena idea o no, lo que me gusta de R es que este tipo de cosas es posible. Puedo ver cómo sería más familiar para alguien que está acostumbrado al OO 'object.verb()' común en lugar del verbo '(object) de R'. – Charles

9

Más o menos la tinta no es idiomática y tal vez frágil/no es una buena idea. (Esto se da a entender, creo que, por el comentario de @ RichieCotton arriba.)

De http://cran.r-project.org/doc/manuals/R-lang.html:

10.3.4 operadores especiales

R permite a los operadores infijos definidos por el usuario. Estos tienen la forma de una cadena de caracteres delimitados por el carácter '%'. La cadena puede contener cualquier carácter imprimible excepto '%'. Las secuencias de escape para las cadenas no se aplican aquí.

Tenga en cuenta que los siguientes operadores están predefinidos

%% %*% %/% %in% %o% %x% 
"%@%" <- function(x,f) { 
    f(x) 
} 

sqr <- function(x) x^2 
x <- 1:4 

x %@% mean ## 2.5 
x %@% mean %@% sqr ## 6.25 
x %@% (mean %@% sqr) ## fails 

Dado m como se definió anteriormente - tal vez lo que tenías en mente?

m %@% colMeans %@% sum ## 21 

Notas:

  • su ejemplo es un poco divertido, porque mean(x) siempre devuelve un escalar (es decir, una longitud-1 vector), por lo sum(mean(x)) siempre va a ser el mismo que mean(x)
  • los operadores de infijo deben estar rodeados por %, por lo que no puede tener nada tan compacto como un solo símbolo (y %% ya está tomado).
  • este tipo de encadenamiento no asociativo, lo que me preocupa, parece que los ejemplos anteriores funcionan, por lo que R está (aparentemente) evaluando de izquierda a derecha, pero no sé si eso está garantizado ...

editar: la pregunta ahora pregunta cómo se pueden incorporar argumentos adicionales. No creo que la sintaxis sugerida (x %@% fun1(arg1) %@% fun2(arg2)) funcione sin alguna magia seria. Esto es lo más cercano que puedo obtener en este momento: crear una función de envoltura que crea una versión modificada de la función original.

F <- function(f,...) { 
    function(x) { 
     f(x,...) 
    } 
} 

Pruebas:

pow <- function(x,b=2) { x^b } 
sqr <- function(x) x^2 
x <- 1:4 

x %@% F(mean,na.rm=TRUE) ## 2.5 
x %@% F(mean,na.rm=TRUE) %@% F(pow,3) ## 16.25 

(Nótese que he utilizado F como una función aquí, que puede ser arriesgado en algunas situaciones, ya que sobrescribe el acceso directo F==FALSE)

+0

Parece ser una solución, pero puede que sea el truco perfecto. ¿Cómo sugeriría modificar el operador% @% para que pueda agregar argumentos? Por ejemplo: m% @% function1 (arg1, arg2)% function2 (arg1, arg2) – Sam

+1

¿Qué es con '"% @% "<- function (f, ...) f (...)'? Con esto, todos los argumentos deben pasarse a 'f', incluso a los argumentos nombrados. (nota: no probado) – Henrik

+0

@Sam, ¿me puede dar un ejemplo específico de lo que quiere hacer y qué resultados debería producir? Su sintaxis no funcionará exactamente como está escrita porque 'function1 (arg1, arg2)' no es en general una función. Creo que la idea de @ Henrik puede no funcionar, porque tenemos que tener cuidado de mantener los argumentos en el orden correcto ... 'f' debería ser el * segundo * argumento para que la operación sea correcta de izquierda a derecha orden ... –

11

Prueba el paquete de funciones :

library(functional) 
squared <- function(x)x*x 
Compose(sum, squared)(m) 
## [1] 44100 
squared(sum(m)) 
## [1] 44100 

EDIT:

Con respecto a la pregunta en los comentarios de otra respuesta sobre argumentos aquí hay un ejemplo de composición con argumentos. Curry es también del paquete de funciones:

addn <- function(n, x) x + n 
Compose(Curry(addn, 1), squared)(10) 
## [1] 121 
squared(addn(1, 10)) 
## [1] 121 

EDIT 2:

cuanto a la pregunta acerca de la depuración, debug funciona si la función es al curry. Si no es que ya al curry y luego envolverlo en Curry:

# this works since addn is curried 
debug(addn) 
Compose(Curry(addn, 1), squared)(10) 

# to debug squared put it in a Curry -- so this works: 
debug(squared) 
Compose(Curry(addn, 1), Curry(squared))(10) 
+0

Pensé que el paquete gsubfn podría hacer algo así, al menos la sintaxis postfix de env @ fn? –

+0

@DWin, Buen punto. En gsubfn podríamos hacer esto: 'Compose (fn $ identity (~ addn (1, x)), squared) (10)'. Por supuesto, incluso sin gsubfn podríamos hacer esto: 'Compose (function (x) addn (1, x), squared) (10)' –

+0

@ G.Grothendieck, funciona, pero no creo que te dé las ventajas habituales de una interfaz fluida (http://en.wikipedia.org/wiki/Fluent_interface) como lo harían algunos otros lenguajes orientados a objetos. Además, creo que no es fácil depurar parte de una declaración. – Sam

2

me gustaría utilizar el paquete magrittr. Cuenta con un operador de "tubo" que toma el resultado de una función y lo pasa como primer argumento a la siguiente:

m <- matrix(c(1:10, 11:20), nrow = 10, ncol = 2) 

m %>% mean %>% sum 

Ceci n'est pas tubo de la ONU!

Cuestiones relacionadas