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.