2010-11-08 17 views
26

Quiero escribir una función que llame tanto a plot() como a legend() y sería ideal si el usuario pudiera especificar una cantidad de argumentos adicionales que luego pasan a plot() o legend(). Sé que puedo lograr esto para una de las dos funciones utilizando ...:¿Hay alguna manera de usar dos sentencias '...' en una función en R?

foo.plot <- function(x,y,...) { 
    plot(x,y,...) 
    legend("bottomleft", "bar", pch=1) 
} 

foo.plot(1,1, xaxt = "n") 

Esto pasa xaxt = "n" para trazar. Pero, ¿hay alguna manera, por ejemplo, de aprobar, p. title = "legend" a la llamada legend() sin especificar previamente los argumentos en el encabezado de la función?


actualización de la respuesta aceptada: pensé que el camino de Vitoshka era el más elegante para lograr lo que quería. Sin embargo, hubo algunos problemas menores con los que tuve que lidiar hasta que funcionó como yo quería.

Al principio, verifiqué cuál de los parámetros quiero pasar a legend y cuál a plot. El primer paso en este sentido fue a ver qué argumentos de legend son únicos para legend y no forma parte de la trama y/o par:

legend.args <- names(formals(legend)) 
plot.args <- c(names(formals(plot.default)), names(par())) 
dput(legend.args[!(legend.args %in% plot.args)]) 

utilizo dput() aquí, porque la línea plot.args <- c(names(formals(plot.default)), names(par())) siempre llama a una nueva parcela de vacío, que me No quería. Entonces, utilicé la salida dput en la siguiente función.

A continuación, tuve que lidiar con los argumentos superpuestos (obténgalos a través de dput(largs.all[(largs.all %in% pargs.all)])). Para algunos, esto era trivial (por ejemplo, x, y), otros pasan a ambas funciones (por ejemplo, pch). Pero, en mi aplicación real, incluso uso otras estrategias (por ejemplo, nombres de variables diferentes para adj, pero no implementadas en este ejemplo).

Finalmente, la función do.call tuvo que cambiarse de dos maneras. En primer lugar, la parte what (es decir, llamada funciones) debe ser un carácter (es decir, 'plot' en lugar de plot). Y la lista de argumentos debe construirse ligeramente diferente.

foo.plot <- function(x,y,...) { 
    leg.args.unique <- c("legend", "fill", "border", "angle", "density", "box.lwd", "box.lty", "box.col", "pt.bg", "pt.cex", "pt.lwd", "xjust", "yjust", "x.intersp", "y.intersp", "text.width", "text.col", "merge", "trace", "plot", "ncol", "horiz", "title", "inset", "title.col", "title.adj") 
    leg.args.all <- c(leg.args.unique, "col", "lty", "lwd", "pch", "bty", "bg", "cex", "adj", "xpd") 
    dots <- list(...) 
    do.call('plot', c(list(x = x, y = x), dots[!(names(dots) %in% leg.args.unique)])) 
    do.call('legend', c(list("bottomleft", "bar"), dots[names(dots) %in% leg.args.all])) 
} 


foo.plot(1,1,pch = 4, title = "legendary", ylim = c(0, 5)) 

En este ejemplo, pch se pasa a tanto plot y legend, title solamente se pasa a legend, y ylim sólo para plot.


Actualización 2 basado en un comentario por Gavin Simpson (véase también los comentarios en la respuesta de Vitoshka):
(i) Eso es correcto.
(ii) Siempre puede ser un personaje. Pero si tiene una variable con el mismo nombre que la función, entonces es necesario citar el nombre de función en do.call:

min.plot <- function(x,y,plot=TRUE) if(plot == TRUE) do.call(plot, list(x = x, y = y)) 
min.plot(1,1) 
Error in do.call(plot, list(x = x, y = y)) : 
    'what' must be a character string or a function 

(iii) Puede utilizar c(x = 1, y = 1, list()) y funciona bien.Sin embargo, lo que realmente hice (no en el ejemplo que he dado, pero en mi función real) es la siguiente: c(x = 1, y = 1, xlim = c(0, 2), list(bla='foo'))
favor compare esto con: c(list(x = 1, y = 1, xlim = c(0, 2)), list(bla='foo'))
En el primer caso, la lista contiene dos elementos xlim, xlim1 y xlim2 (cada uno es un escalar), en el último caso la lista tiene solo xlim (que es vector de longitud 2, que es lo que yo quería).

Por lo tanto, tiene razón en todos sus puntos para mi ejemplo. Pero, para mi función real (con muchas más variables), encontré estos problemas y quería documentarlos aquí. Perdón por ser impreciso.

+1

Un par de puntos en su Q editada; i) 'par()' * solo * producirá un gráfico en blanco si no hay un dispositivo actualmente abierto. Esto es inofensivo ya que estás a punto de tramarlo, ii) Estás equivocado sobre el argumento '' what'' - puede ser un personaje o una función, iii) No necesitas '' list() 'wrapping in' list ("bottomleft", "bar") ', porque todo está forzado a un tipo común --- testigo' c ("foo", 1:10, list (a = 1:10, b = letters [1: 3 ])) '. Buen control de Q y seguimiento, pero es bueno limpiar tus ediciones para mayor precisión. –

+1

@Gavin Consulte mi actualización para obtener una respuesta a sus puntos. – Henrik

+2

buena actualización y es bueno señalar los casos de esquina como este. ¡Bravo! En el nombre de función/nombre de la variable clash, siempre puedes estar más a la defensiva y usar 'graphics :: plot' y' graphics :: legend' para estar doblemente seguro de que se encuentra la función correcta. –

Respuesta

17

Una manera automática:

foo.plot <- function(x,y,...) { 
    lnames <- names(formals(legend)) 
    pnames <- c(names(formals(plot.default)), names(par())) 
    dots <- list(...) 
    do.call('plot', c(list(x = x, y = x), dots[names(dots) %in% pnames])) 
    do.call('legend', c("bottomleft", "bar", pch = 1, dots[names(dots) %in% lnames])) 
} 

PCH debe filtrarse desde los lnames para evitar la duplicación de la llamada legend en caso de que el usuario proporciona PCH '', pero que tuvo la idea. Editado en enero de 2012 por Carl W: "do.call" solo funciona con las funciones entre comillas, como en las actualizaciones de Henrik. Lo edité aquí para evitar confusiones futuras.

+1

Buena solución, pero necesita bastante más filtrado que solo pch. Hay una serie de parámetros que pueden ser válidos tanto para el gráfico como para la leyenda. ¿Cómo vas sobre ellos? –

+1

@Joris, si un parámetro es válido para ambas funciones, se pasará a ambos. Realmente no entiendo lo que quieres decir. Uno necesita filtrar pch para evitar la duplicación en la llamada a la leyenda. – VitoshKa

+1

@VitoshKa: supongamos que desea establecer ese parámetro solo en el gráfico o solo en la leyenda. Digamos pch por ejemplo. Eso no será posible con la función tal como está, no hay ningún lugar donde pueda especificar para qué función exactamente quiere establecer el parámetro. –

6

Una forma de evitarlo es utilizar listas de argumentos en combinación con do.call. No es la solución más hermosa, pero funciona.

foo.plot <- function(x,y,legend.args,...) { 
    la <- list(
     x="bottomleft", 
     legend="bar", 
     pch=1 
    ) 
    if (!missing(legend.args)) la <- c(la,legend.args) 
    plot(x,y,...) 
    do.call(legend,la) 
} 
foo.plot(1,1, xaxt = "n")  
foo.plot(1,1, xaxt = "n",legend.args=list(bg="yellow",title="legend")) 

Un inconveniente es que no se puede especificar por ejemplo PCH = 2, por ejemplo, en la lista legend.args. Puedes soslayar eso con algunas cláusulas if, te dejaré para que sigas jugando con ello.


Editar: vea la respuesta de Gavin Simpson para una mejor versión de esta idea.

+0

esto me recuerda los argumentos 'control' en funciones como' lme() ', que usa argumentos como' control = lme.control() '. 'lme.control()' contiene los valores predeterminados y existe para completar una lista de argumentos adecuados. Agregaré un ejemplo a mi respuesta, ya que es demasiado complicado para hacer un comentario. –

+0

Gracias, esta parece ser una buena manera de hacerlo. Esperaré un poco más de tiempo antes de aceptar, quizás aparezca alguna otra buena respuesta. Pero, ya estoy dispuesto a aceptar esto. – Henrik

+0

Puede usar este enfoque con 'modifyList', para poder combinar legend.args con su conjunto predeterminado de argumentos de leyenda. – Charles

16

Estas cosas se vuelven complicadas, y no hay soluciones fáciles sin especificar argumentos adicionales en su función. Si tuviera ... en las llamadas plot y legend, terminaría recibiendo advertencias al pasar los argumentos específicos de legend. Por ejemplo, con:

foo.plot <- function(x,y,...) { 
    plot(x,y,...) 
    legend("bottomleft", "bar", pch = 1, ...) 
} 

se obtienen los siguientes advertencias:

> foo.plot(1, 1, xjust = 0.5) 
Warning messages: 
1: In plot.window(...) : "xjust" is not a graphical parameter 
2: In plot.xy(xy, type, ...) : "xjust" is not a graphical parameter 
3: In axis(side = side, at = at, labels = labels, ...) : 
    "xjust" is not a graphical parameter 
4: In axis(side = side, at = at, labels = labels, ...) : 
    "xjust" is not a graphical parameter 
5: In box(...) : "xjust" is not a graphical parameter 
6: In title(...) : "xjust" is not a graphical parameter 

hay formas redondas este problema, consulte plot.default y sus funciones locales definidas como envolturas alrededor funciones como axis, etc., donde box Tendría algo así como un contenedor localPlot(), función en línea y llama eso en vez de plot() directamente.

bar.plot <- function(x, y, pch = 1, ...) { 
    localPlot <- function(..., legend, fill, border, angle, density, 
          xjust, yjust, x.intersp, y.intersp, 
          text.width, text.col, merge, trace, plot = TRUE, ncol, 
          horiz, title, inset, title.col, box.lwd, 
          box.lty, box.col, pt.bg, pt.cex, pt.lwd) plot(...) 
    localPlot(x, y, pch = pch, ...) 
    legend(x = "bottomleft", legend = "bar", pch = pch, ...) 
} 

(Muy bien por qué el argumento 'plot' necesita un defecto está más allá de mí, pero no funcionará sin darle el valor por defecto TRUE.)

Ahora bien, esto funciona sin advertencias:

bar.plot(1, 1, xjust = 0.5, title = "foobar", pch = 3) 

La forma en que maneje los parámetros gráficos como bty, por ejemplo, dependerá de usted; bty afectará el tipo de cuadro de trazado y el tipo de cuadro de leyenda. Tenga en cuenta también que he manejado 'pch' de manera diferente porque si alguien usa ese argumento en la llamada bar.plot(), estaría yo) usando diferentes caracteres en la leyenda/trazado y recibiría una advertencia o error acerca de 'pch' coincidiendo dos veces.

Como se puede ver, este empieza a ser bastante complicado ...


respuesta Joris' ofrece una solución interesante, que me Comentada acuerdo de listas de control de argumentos en funciones como lme().Aquí está mi versión de respuesta Joris aplicación de la idea de esta idea de control de lista:

la.args <- function(x = "bottomleft", legend = "bar", pch = 1, ...) 
    c(list(x = x, legend = legend, pch = pch), list(...)) 

foo.plot <- function(x,y, legend.args = la.args(), ...) { 
    plot(x, y, ...) 
    do.call(legend, legend.args) 
} 

que funciona de esta manera, utilizando segunda llamada ejemplo de Jori, convenientemente modificada:

foo.plot(1,1, xaxt = "n", legend.args=la.args(bg = "yellow", title = "legend")) 

Usted puede ser tan completa como desee al configurar la función la.args() - aquí solo establezco los valores predeterminados para los argumentos configurados por Joris y concateno los demás. Sería más fácil si la.args() contuviera todos los argumentos de la leyenda con los valores predeterminados.

+0

Eso es más que un puñado de digerir a la vez ;-) Sin embargo, muchas gracias por su respuesta elaborada, realmente me enseñó una o dos cosas sobre la aprobación de argumentos. De hecho, obtuve la idea de lme -y de otras- funciones, en parte tomando listas como argumentos para algo específico. Thx para mostrar cómo resolver el problema "emparejado por múltiples argumentos reales". –

+1

@Joris: Gracias. El Prof. Ripley me educó sobre el método 'localFoo()' y lo usamos en uno de los paquetes a los que contribuyo. –

Cuestiones relacionadas