¿Hay alguna manera de usar dos declaraciones ‘…’ en una función en R?

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 pasen a plot() o legend() . Sé que puedo lograr esto para una de las dos funciones usando ... :

 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 pasar, por ejemplo, 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 la legend y cuál plot . El primer paso para este fin fue ver qué argumentos de la legend son exclusivos de la legend y no parte de la plot y / o el 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 un nuevo diagtwig vacío que no quería. Entonces, utilicé la salida de dput en la siguiente función.

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

Finalmente, la función do.call tuvo que cambiar de dos maneras. En primer lugar, la parte qué (es decir, llamadas funciones) debe ser un carácter (es decir, 'plot' 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 tanto a la plot como a la legend , el title solo se pasa a la legend y ylim solo a la plot .


Actualización 2 basada en un comentario de Gavin Simpson (ver 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 debe citar el nombre de la 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 usar c(x = 1, y = 1, list()) y funciona bien. Sin embargo, lo que realmente hice (no en el ejemplo que di sino en mi función real) es el siguiente: c(x = 1, y = 1, xlim = c(0, 2), list(bla='foo'))
Por 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 escalar), en el último caso la lista solo tiene xlim (que es vector de longitud 2, que es lo que yo quería).

Entonces, tienes razón en todos tus 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.

Una forma 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 nombres para evitar la duplicación en la llamada de la legend en caso de que el usuario suministre 'pch', pero ya entendió 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.

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

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

Recibes las 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 de solucionar este problema, ver plot.default y sus funciones locales definidas como wrappers en torno a funciones como axis , box , etc. donde tendría algo así como un localPlot() , función inline y llamar eso en lugar 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, ...) } 

(El motivo por el cual el 'plot' argumento 'plot' necesita un valor predeterminado está más allá de mí, pero no funcionará sin darle el valor predeterminado TRUE ).

Ahora 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' diferente porque si alguien utiliza ese argumento en la llamada bar.plot() , estaría yo) usando diferentes caracteres en la leyenda / ttwig y obtendría una advertencia o error sobre 'pch' coincidencia dos veces.

Como puede ver, esto comienza a ser bastante complicado ...


La respuesta de Joris proporciona una solución interesante, que comenté me recordó los argumentos de listas de control en funciones como lme() . Aquí está mi versión de Joris 'Answer implementando la idea de esta idea de lista de control:

 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 así, usando la segunda llamada de ejemplo de Jori, adecuadamente modificada:

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

Puede estar tan completo como desee al configurar la función la.args() ; aquí solo configuro los parámetros de configuración de 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.

Una forma de do.call es usar 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")) 

Una desventaja es que no 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.

Intereting Posts