truncar - ¿Cómo usar la característica de puntos suspensivos de R cuando escribe su propia función?
puntos suspensivos ejemplos (6)
El lenguaje R tiene una característica ingeniosa para definir funciones que pueden tomar una cantidad variable de argumentos. Por ejemplo, la función data.frame
toma cualquier número de argumentos, y cada argumento se convierte en los datos de una columna en la tabla de datos resultante. Ejemplo de uso:
> data.frame(letters=c("a", "b", "c"), numbers=c(1,2,3), notes=c("do", "re", "mi"))
letters numbers notes
1 a 1 do
2 b 2 re
3 c 3 mi
La firma de la función incluye una elipsis, como esta:
function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE,
stringsAsFactors = default.stringsAsFactors())
{
[FUNCTION DEFINITION HERE]
}
Me gustaría escribir una función que haga algo similar, tomar múltiples valores y consolidarlos en un único valor de retorno (así como también hacer algún otro procesamiento). Para hacer esto, necesito encontrar la manera de "descomprimir" ...
desde los argumentos de la función dentro de la función. No se como hacer esto. La línea relevante en la definición de la función de data.frame
es object <- as.list(substitute(list(...)))[-1L]
, que no puedo entender.
Entonces, ¿cómo puedo convertir las elipsis de la firma de la función en, por ejemplo, una lista?
Para ser más específico, ¿cómo puedo escribir get_list_from_ellipsis
en el siguiente código?
my_ellipsis_function(...) {
input_list <- get_list_from_ellipsis(...)
output_list <- lapply(X=input_list, FUN=do_something_interesting)
return(output_list)
}
my_ellipsis_function(a=1:10,b=11:20,c=21:30)
Editar
Parece que hay dos maneras posibles de hacer esto. Son as.list(substitute(list(...)))[-1L]
y list(...)
. Sin embargo, estos dos no hacen exactamente lo mismo. (Para las diferencias, vea ejemplos en las respuestas.) ¿Puede alguien decirme cuál es la diferencia práctica entre ellos y cuál debería usar?
Esto funciona como se esperaba La siguiente es una sesión interactiva:
> talk <- function(func, msg, ...){
+ func(msg, ...);
+ }
> talk(cat, c("this", "is", "a","message."), sep=":")
this:is:a:message.
>
Lo mismo, excepto con un argumento predeterminado:
> talk <- function(func, msg=c("Hello","World!"), ...){
+ func(msg, ...);
+ }
> talk(cat,sep=":")
Hello:World!
> talk(cat,sep=",", fill=1)
Hello,
World!
>
Como puede ver, puede usar esto para pasar argumentos ''adicionales'' a una función dentro de su función si los valores predeterminados no son los que desea en un caso particular.
Leí respuestas y comentarios y veo que pocas cosas fueron mencionadas:
data.frame
usa lalist(...)
versión. Fragmento del código:object <- as.list(substitute(list(...)))[-1L] mrn <- is.null(row.names) x <- list(...)
object
se utiliza para hacer algo de magia con los nombres de columna, perox
se utiliza para creardata.frame
final.
Para el uso de un...
argumento nowrite.csv
códigomatch.call
donde se usamatch.call
.Al escribir en el comentario, el resultado en la respuesta de Dirk no es una lista de listas. Es una lista de longitud 4, cuyos elementos son tipo de
language
. El primer objeto es unsymbol
-list
, el segundo es la expresión1:10
y así sucesivamente. Eso explica por qué[-1L]
es necesario: elimina elsymbol
esperado de los argumentos proporcionados en...
(porque siempre es una lista).
Como dice Dirk, elsubstitute
devuelve "analizar el árbol de la expresión no evaluada".
Cuando llamasmy_ellipsis_function(a=1:10,b=11:20,c=21:30)
entonces...
"crea" una lista de argumentos:list(a=1:10,b=11:20,c=21:30)
ysubstitute
por una lista de cuatro elementos:List of 4 $ : symbol list $ a: language 1:10 $ b: language 11:20 $ c: language 21:30
El primer elemento no tiene un nombre y esto es
[[1]]
en la respuesta de Dirk. Logro estos resultados usando:my_ellipsis_function <- function(...) { input_list <- as.list(substitute(list(...))) str(input_list) NULL } my_ellipsis_function(a=1:10,b=11:20,c=21:30)
Como arriba, podemos usar
str
para verificar qué objetos están en una función.my_ellipsis_function <- function(...) { input_list <- list(...) output_list <- lapply(X=input_list, function(x) {str(x);summary(x)}) return(output_list) } my_ellipsis_function(a=1:10,b=11:20,c=21:30) int [1:10] 1 2 3 4 5 6 7 8 9 10 int [1:10] 11 12 13 14 15 16 17 18 19 20 int [1:10] 21 22 23 24 25 26 27 28 29 30 $a Min. 1st Qu. Median Mean 3rd Qu. Max. 1.00 3.25 5.50 5.50 7.75 10.00 $b Min. 1st Qu. Median Mean 3rd Qu. Max. 11.0 13.2 15.5 15.5 17.8 20.0 $c Min. 1st Qu. Median Mean 3rd Qu. Max. 21.0 23.2 25.5 25.5 27.8 30.0
Está bien. Veamos la versión
substitute
:my_ellipsis_function <- function(...) { input_list <- as.list(substitute(list(...))) output_list <- lapply(X=input_list, function(x) {str(x);summary(x)}) return(output_list) } my_ellipsis_function(a=1:10,b=11:20,c=21:30) symbol list language 1:10 language 11:20 language 21:30 [[1]] Length Class Mode 1 name name $a Length Class Mode 3 call call $b Length Class Mode 3 call call $c Length Class Mode 3 call call
No es lo que necesitábamos Necesitará trucos adicionales para tratar con este tipo de objetos (como en
write.csv
).
Si quieres usar ...
entonces debes usarlo como en Shane answer, por list(...)
.
Puede convertir las elipsis en una lista con list()
y luego realizar sus operaciones en él:
> test.func <- function(...) { lapply(list(...), class) }
> test.func(a="b", b=1)
$a
[1] "character"
$b
[1] "numeric"
Entonces su función get_list_from_ellipsis
no es más que una list
.
Un caso de uso válido para esto es en los casos en que desea pasar un número desconocido de objetos para la operación (como en su ejemplo de c()
o data.frame()
). No es una buena idea usar el ...
cuando conoces cada parámetro por adelantado, sin embargo, ya que agrega ambigüedad y complicaciones adicionales a la cadena del argumento (y hace que la firma de la función no sea clara para ningún otro usuario). La lista de argumentos es una pieza importante de documentación para usuarios de funciones.
De lo contrario, también es útil para casos en los que desee pasar parámetros a una subfunción sin exponerlos todos en sus propios argumentos de función. Esto se puede observar en la documentación de la función.
Solo para agregar a las respuestas de Shane y Dirk: es interesante comparar
get_list_from_ellipsis1 <- function(...)
{
list(...)
}
get_list_from_ellipsis1(a = 1:10, b = 2:20) # returns a list of integer vectors
$a
[1] 1 2 3 4 5 6 7 8 9 10
$b
[1] 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
con
get_list_from_ellipsis2 <- function(...)
{
as.list(substitute(list(...)))[-1L]
}
get_list_from_ellipsis2(a = 1:10, b = 2:20) # returns a list of calls
$a
1:10
$b
2:20
Tal como está, cualquiera de las versiones parece adecuada para sus propósitos en my_ellipsis_function
, aunque la primera es claramente más simple.
Ya le diste la mitad de la respuesta. Considerar
R> my_ellipsis_function <- function(...) {
+ input_list <- as.list(substitute(list(...)))
+ }
R> print(my_ellipsis_function(a=1:10, b=2:20))
[[1]]
list
$a
1:10
$b
11:20
R>
Entonces esto tomó dos argumentos a
y b
de la llamada y los convirtió a una lista. ¿No fue eso lo que pediste?