values studio own multiple functions funciones define create r function

studio - Forma "correcta" de especificar argumentos opcionales en funciones R



r function define arguments (7)

¿Qué tal esto?

fun <- function(x, ...){ y=NULL parms=list(...) for (name in names(parms) ) { assign(name, parms[[name]]) } print(is.null(y)) }

Entonces intenta:

> fun(1,y=4) [1] FALSE > fun(1) [1] TRUE

Estoy interesado en cuál es la forma "correcta" de escribir funciones con argumentos opcionales en R. Con el tiempo, me topé con algunos códigos que toman una ruta diferente aquí, y no pude encontrar una posición adecuada (oficial) sobre este tema.

Hasta ahora, he escrito argumentos opcionales como este:

fooBar <- function(x,y=NULL){ if(!is.null(y)) x <- x+y return(x) } fooBar(3) # 3 fooBar(3,1.5) # 4.5

La función simplemente devuelve su argumento si solo se proporciona x . Utiliza un valor NULL predeterminado para el segundo argumento y si ese argumento no es NULL , entonces la función agrega los dos números.

Alternativamente, se podría escribir la función de esta manera (donde el segundo argumento debe especificarse por nombre, pero también se podría unlist(z) o definir z <- sum(...) lugar):

fooBar <- function(x,...){ z <- list(...) if(!is.null(z$y)) x <- x+z$y return(x) } fooBar(3) # 3 fooBar(3,y=1.5) # 4.5

Personalmente prefiero la primera versión. Sin embargo, puedo ver lo bueno y lo malo con ambos. La primera versión es un poco menos propensa a errores, pero la segunda podría usarse para incorporar un número arbitrario de opciones.

¿Hay una forma "correcta" de especificar argumentos opcionales en R? Hasta ahora, me he decidido por el primer enfoque, pero ambos ocasionalmente pueden sentirse un poco "hacky".


Estas son mis reglas generales:

Si los valores predeterminados se pueden calcular a partir de otros parámetros, use expresiones predeterminadas como en:

fun <- function(x,levels=levels(x)){ blah blah blah }

si de lo contrario usa falta

fun <- function(x,levels){ if(missing(levels)){ [calculate levels here] } blah blah blah }

En el caso poco frecuente de que un usuario desee especificar un valor predeterminado que dure una sesión R completa, use getOption

fun <- function(x,y=getOption(''fun.y'',''initialDefault'')){# or getOption(''pkg.fun.y'',defaultValue) blah blah blah }

Si se aplican algunos parámetros dependiendo de la clase del primer argumento, use un genérico S3:

fun <- function(...) UseMethod(...) fun.character <- function(x,y,z){# y and z only apply when x is character blah blah blah } fun.numeric <- function(x,a,b){# a and b only apply when x is numeric blah blah blah } fun.default <- function(x,m,n){# otherwise arguments m and n apply blah blah blah }

Use ... solo cuando pase parámetros adicionales a otra función

cat0 <- function(...) cat(...,sep = '''')

Finalmente, si elige el uso ... sin pasar los puntos a otra función, advierta al usuario que su función ignora los parámetros no utilizados, ya que de lo contrario puede ser muy confuso:

fun <- (x,...){ params <- list(...) optionalParamNames <- letters unusedParams <- setdiff(names(params),optionalParamNames) if(length(unusedParams)) stop(''unused parameters'',paste(unusedParams,collapse = '', '')) blah blah blah }


Hay varias opciones y ninguna de ellas es la forma correcta oficial y ninguna de ellas es realmente incorrecta, aunque pueden transmitir información diferente a la computadora y a otras personas que leen su código.

Para el ejemplo dado, creo que la opción más clara sería proporcionar un valor predeterminado de identidad, en este caso hacer algo como:

fooBar <- function(x, y=0) { x + y }

Esta es la más corta de las opciones mostradas hasta ahora y la brevedad puede ayudar a la legibilidad (y a veces incluso a acelerar la ejecución). Está claro que lo que se devuelve es la suma de x e y y puede ver que a y no se le da un valor de que será 0, que cuando se agrega a x solo dará como resultado x. Obviamente, si se usa algo más complicado que la suma, se necesitará un valor de identidad diferente (si existe).

Una cosa que realmente me gusta de este enfoque es que está claro cuál es el valor predeterminado cuando se usa la función args , o incluso cuando se mira el archivo de ayuda (no es necesario desplazarse hacia abajo para ver los detalles, está justo allí en el uso).

El inconveniente de este método es cuando el valor predeterminado es complejo (requiere múltiples líneas de código), entonces probablemente reduciría la legibilidad para tratar de poner todo eso en el valor predeterminado y los enfoques missing o NULL se vuelven mucho más razonables.

Algunas de las otras diferencias entre los métodos aparecerán cuando el parámetro se pase a otra función, o cuando se usen las funciones match.call o sys.call .

Así que supongo que el método "correcto" depende de lo que planeas hacer con ese argumento en particular y qué información quieres transmitir a los lectores de tu código.


Para ser honesto, me gusta la primera forma del OP de comenzarlo realmente con un valor NULL y luego verificarlo con is.null (principalmente porque es muy simple y fácil de entender). is.null vez depende de la forma en que las personas están acostumbradas a la codificación, pero Hadley también parece admitir la forma is.null :

Del libro de Hadley "Advanced-R" Capítulo 6, Funciones, p.84 (para la versión en línea verifique here ):

Puede determinar si se proporcionó un argumento o no con la función missing ().

i <- function(a, b) { c(missing(a), missing(b)) } i() #> [1] TRUE TRUE i(a = 1) #> [1] FALSE TRUE i(b = 2) #> [1] TRUE FALSE i(1, 2) #> [1] FALSE FALSE

A veces, desea agregar un valor predeterminado no trivial, que puede tomar varias líneas de código para calcular. En lugar de insertar ese código en la definición de la función, puede usar missing () para calcularlo condicionalmente si es necesario. Sin embargo, esto dificulta saber qué argumentos son necesarios y cuáles son opcionales sin leer detenidamente la documentación. En cambio, generalmente establezco el valor predeterminado en NULL y uso is.null () para verificar si se proporcionó el argumento.


Solo quería señalar que la función de sink incorporada tiene buenos ejemplos de diferentes formas de establecer argumentos en una función:

> sink function (file = NULL, append = FALSE, type = c("output", "message"), split = FALSE) { type <- match.arg(type) if (type == "message") { if (is.null(file)) file <- stderr() else if (!inherits(file, "connection") || !isOpen(file)) stop("''file'' must be NULL or an already open connection") if (split) stop("cannot split the message connection") .Internal(sink(file, FALSE, TRUE, FALSE)) } else { closeOnExit <- FALSE if (is.null(file)) file <- -1L else if (is.character(file)) { file <- file(file, ifelse(append, "a", "w")) closeOnExit <- TRUE } else if (!inherits(file, "connection")) stop("''file'' must be NULL, a connection or a character string") .Internal(sink(file, closeOnExit, FALSE, split)) } }


También puede usar missing() para probar si se proporcionó o no el argumento y :

fooBar <- function(x,y){ if(missing(y)) { x } else { x + y } } fooBar(3,1.5) # [1] 4.5 fooBar(3) # [1] 3


Yo preferiría usar NULL por la claridad de lo que se requiere y lo que es opcional. Una palabra de advertencia sobre el uso de valores predeterminados que dependen de otros argumentos, como lo sugiere Jthorpe. ¡El valor no se establece cuando se llama a la función, sino cuando se hace referencia al argumento por primera vez! Por ejemplo:

foo <- function(x,y=length(x)){ x <- x[1:10] print(y) } foo(1:20) #[1] 10

Por otro lado, si hace referencia a y antes de cambiar x:

foo <- function(x,y=length(x)){ print(y) x <- x[1:10] } foo(1:20) #[1] 20

Esto es un poco peligroso, porque hace que sea difícil hacer un seguimiento de lo que "y" se está inicializando como si no se llamara al principio de la función.