Jak wykorzystać funkcję elipsy R podczas pisania własnej funkcji?

Język R ma sprytną funkcję do definiowania funkcji, które mogą przyjmować zmienną liczbę argumentów. Na przykład funkcja data.frame pobiera dowolną liczbę argumentów, a każdy argument staje się danymi dla kolumny w wynikowej tabeli danych. Przykład użycia:

> 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

Podpis funkcji zawiera elipsę, jak to:

function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE, 
    stringsAsFactors = default.stringsAsFactors()) 
{
    [FUNCTION DEFINITION HERE]
}

Chciałbym napisać funkcję, która robi coś podobnego, biorąc wiele wartości i konsolidując je w pojedyncza wartość zwracana (jak również inne przetwarzanie). Aby to zrobić, muszę dowiedzieć się, jak" rozpakować " ... z argumentów funkcji wewnątrz funkcji. Nie wiem, jak to zrobić. Odpowiedni wiersz w definicji funkcji data.frame to object <- as.list(substitute(list(...)))[-1L], którego nie mogę zrozumieć.

Więc jak mogę przekonwertować elipsę z podpisu funkcji na, na przykład, listę?

Mówiąc dokładniej, jak mogę napisać get_list_from_ellipsis w kodzie na dole?

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)

Edit

Wydaje się, że są dwa możliwe sposoby, aby to zrobić. Są to as.list(substitute(list(...)))[-1L] i list(...). Jednak te dwa nie robią dokładnie tego samego. (Różnice można znaleźć w przykładach w odpowiedziach.) Czy ktoś może mi powiedzieć jaka jest praktyczna różnica między nimi, a którą powinienem zastosować?

Author: Community, 2010-06-16

5 answers

Czytam odpowiedzi i komentarze i widzę, że niewiele rzeczy nie zostało wymienionych:

  1. data.frame używa wersji list(...). Fragment kodu:

    object <- as.list(substitute(list(...)))[-1L]
    mrn <- is.null(row.names)
    x <- list(...)
    

    object jest używany do wykonywania magii z nazwami kolumn, ale x jest używany do tworzenia ostatecznego data.frame.
    Aby użyć nieocenionego argumentu ..., spójrz na kod write.csv, w którym użyto match.call.

  2. Jak piszesz w komentarzu to nie jest lista list. Jest listą długości 4, które elementy są language Typ. Pierwszym obiektem jest symbol - list, drugi to wyrażenie 1:10 i tak dalej. To wyjaśnia, dlaczego {[17] } jest potrzebne: usuwa oczekiwane symbol z podanych argumentów w ... (ponieważ zawsze jest to lista).
    Jak stwierdza Dirk substitute zwraca "parse tree the unevaluated expression".
    Po wywołaniu my_ellipsis_function(a=1:10,b=11:20,c=21:30) ... "tworzy" listę argumentów: list(a=1:10,b=11:20,c=21:30) i substitute tworzy listę czterech elementów:

    List of 4
    $  : symbol list
    $ a: language 1:10
    $ b: language 11:20
    $ c: language 21:30
    

    Pierwszy element nie ma nazwy, a to [[1]] W Dirk answer. I osiągnij te wyniki używając:

    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)
    
  3. Jak powyżej możemy użyć str, aby sprawdzić, jakie obiekty są w funkcji.

    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 
    
    W porządku. Zobaczmy substitute Wersja:
       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 
    
    Nie tego potrzebowaliśmy. Będziesz potrzebował dodatkowych sztuczek, aby poradzić sobie z tego rodzaju obiektami (jak w write.csv).

Jeśli chcesz użyć ..., powinieneś użyć go tak jak w odpowiedzi Shane ' a, by list(...).

 115
Author: Marek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-25 02:58:16

Możesz przekonwertować wielokropek na Listę za pomocą list(), a następnie wykonać na niej swoje operacje:

> test.func <- function(...) { lapply(list(...), class) }
> test.func(a="b", b=1)
$a
[1] "character"

$b
[1] "numeric"

Więc twoja get_list_from_ellipsis funkcja jest niczym więcej niż list.

Prawidłowy przypadek użycia to przypadki, w których chcesz przekazać nieznaną liczbę obiektów do operacji(jak w twoim przykładzie c() lub data.frame()). Nie jest dobrym pomysłem, aby używać ..., Gdy znasz każdy parametr z góry, jednak, ponieważ dodaje pewne niejasności i dalsze komplikacje do ciągu argumentu (i sprawia, że podpis funkcji jest niejasny dla każdego innego użytkownika). Lista argumentów jest ważnym elementem dokumentacji dla użytkowników funkcji.

W przeciwnym razie, jest to również przydatne w przypadkach, gdy chcesz przekazać parametry do podfunkcji bez ujawniania ich wszystkich we własnych argumentach funkcji. Można to odnotować w dokumentacji funkcji.

 38
Author: Shane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-17 16:03:31

Aby dodać do odpowiedzi Shane ' a i Dirka: interesujące jest porównanie

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

Z

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

W obecnej formie, każda wersja wydaje się odpowiednia dla Twoich celów w my_ellipsis_function, choć pierwsza jest wyraźnie prostsza.

 35
Author: Richie Cotton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-31 08:56:22

Dałaś już połowę odpowiedzi. Consider

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> 

Więc to wzięło dwa argumenty a i b z wywołania i przekonwertowało je na listę. Czy nie o to prosiłeś?

 16
Author: Dirk Eddelbuettel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-16 21:45:09

To działa zgodnie z oczekiwaniami. Poniżej znajduje się sesja interaktywna:

> talk <- function(func, msg, ...){
+     func(msg, ...);
+ }
> talk(cat, c("this", "is", "a","message."), sep=":")
this:is:a:message.
> 

To samo, z wyjątkiem domyślnego argumentu:

> talk <- function(func, msg=c("Hello","World!"), ...){
+     func(msg, ...);
+ }
> talk(cat,sep=":")
Hello:World!
> talk(cat,sep=",", fill=1)
Hello,
World!
>

Jak widzisz, możesz użyć tego, aby przekazać "dodatkowe" argumenty do funkcji wewnątrz funkcji, jeśli domyślne wartości nie są tym, czego chcesz w danym przypadku.

 6
Author: Overloaded_Operator,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-09 02:08:31