Làm cách nào để sử dụng tính năng dấu chấm lửng của R khi viết chức năng của riêng bạn?


185

Ngôn ngữ R có một tính năng tiện lợi để xác định các hàm có thể có số lượng đối số thay đổi. Ví dụ, hàm data.framelấy bất kỳ số lượng đối số nào và mỗi đối số trở thành dữ liệu cho một cột trong bảng dữ liệu kết quả. Ví dụ sử dụng:

> 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

Chữ ký của hàm bao gồm dấu chấm lửng, như thế này:

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

Tôi muốn viết một hàm thực hiện một cái gì đó tương tự, lấy nhiều giá trị và hợp nhất chúng thành một giá trị trả về duy nhất (cũng như thực hiện một số xử lý khác). Để thực hiện điều này, tôi cần tìm ra cách "giải nén" ...từ các đối số của hàm trong hàm. Tôi không biết làm thế nào để làm điều này. Dòng liên quan trong định nghĩa hàm data.frameobject <- as.list(substitute(list(...)))[-1L], mà tôi không thể hiểu được.

Vì vậy, làm thế nào tôi có thể chuyển đổi dấu chấm lửng từ chữ ký của hàm thành một danh sách?

Để cụ thể hơn, làm thế nào tôi có thể viết get_list_from_ellipsismã dưới đây?

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)

Biên tập

Có vẻ như có hai cách có thể để làm điều này. Họ là as.list(substitute(list(...)))[-1L]list(...). Tuy nhiên, hai điều này không làm chính xác cùng một điều. (Để biết sự khác biệt, hãy xem các ví dụ trong câu trả lời.) Có ai có thể cho tôi biết sự khác biệt thực tế giữa chúng là gì không và tôi nên sử dụng cái nào?

Câu trả lời:


113

Tôi đọc câu trả lời và bình luận và tôi thấy rằng một vài điều không được đề cập:

  1. data.framesử dụng list(...)phiên bản. Đoạn mã:

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

    objectđược sử dụng để làm một số phép thuật với tên cột, nhưng xđược sử dụng để tạo cuối cùng data.frame.
    Để sử dụng ...đối số không được đánh giá, hãy xem write.csvmatch.callđược sử dụng ở đâu .

  2. Khi bạn viết bình luận kết quả trong câu trả lời Dirk không phải là một danh sách các danh sách. Là một danh sách có độ dài 4, mà các yếu tố là languageloại. Đối tượng đầu tiên là một symbol- list, thứ hai là biểu thức 1:10và như vậy. Điều đó giải thích tại sao [-1L]cần thiết: nó loại bỏ dự kiến symbolkhỏi các đối số được cung cấp ...(vì nó luôn luôn là một danh sách).
    Khi Dirk substitutetrả về "cây phân tích, biểu thức không được đánh giá".
    Khi bạn gọi my_ellipsis_function(a=1:10,b=11:20,c=21:30)thì ..."tạo" một danh sách các đối số: list(a=1:10,b=11:20,c=21:30)substitutebiến nó thành một danh sách gồm bốn phần tử:

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

    Phần tử đầu tiên không có tên và đây là [[1]]câu trả lời của Dirk. Tôi đạt được kết quả này bằng cách sử dụng:

    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. Như trên, chúng ta có thể sử dụng strđể kiểm tra các đối tượng trong một hàm.

    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 

    Được rồi. Hãy xem substitutephiên bản:

       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 

    Không phải là những gì chúng ta cần. Bạn sẽ cần các thủ thuật bổ sung để đối phó với các loại đối tượng này (như trong write.csv).

Nếu bạn muốn sử dụng ...thì bạn nên sử dụng nó như trong câu trả lời của Shane list(...).


38

Bạn có thể chuyển đổi dấu chấm lửng thành một danh sách list(), sau đó thực hiện các thao tác của mình trên đó:

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

$b
[1] "numeric"

Vì vậy, get_list_from_ellipsischức năng của bạn là không có gì hơn list.

Một trường hợp sử dụng hợp lệ cho trường hợp này là trong trường hợp bạn muốn chuyển vào một số lượng đối tượng không xác định cho hoạt động (như trong ví dụ của bạn về c()hoặc data.frame()). Tuy nhiên, không nên sử dụng ...khi bạn biết trước từng tham số vì nó thêm một chút mơ hồ và phức tạp hơn cho chuỗi đối số (và làm cho chữ ký hàm không rõ ràng với bất kỳ người dùng nào khác). Danh sách đối số là một phần quan trọng của tài liệu cho người dùng chức năng.

Mặt khác, nó cũng hữu ích cho các trường hợp khi bạn muốn chuyển qua các tham số đến một hàm con mà không để lộ tất cả chúng trong các đối số hàm của riêng bạn. Điều này có thể được lưu ý trong tài liệu chức năng.


Tôi biết về việc sử dụng dấu chấm lửng làm đối số cho các đối số cho các hàm con, nhưng nó cũng là một cách phổ biến trong số các nguyên thủy R để sử dụng dấu chấm lửng theo cách tôi đã mô tả. Trên thực tế, cả chức năng listcchức năng đều hoạt động theo cách này, nhưng cả hai đều là nguyên thủy, vì vậy tôi không thể dễ dàng kiểm tra mã nguồn của chúng để hiểu cách chúng hoạt động.
Ryan C. Thompson

rbind.data.framesử dụng cách này
Marek

4
Nếu list(...)là đủ, tại sao các nội trang R như data.framesử dụng dạng dài hơn as.list(substitute(list(...)))[-1L]thay thế?
Ryan C. Thompson

1
Như tôi đã không tạo ra data.frame, tôi không biết câu trả lời đó (mà nói, tôi chắc chắn rằng có một lý do chính đáng cho nó). Tôi sử dụng list()cho mục đích này trong các gói của riêng tôi và chưa gặp phải vấn đề với nó.
Shane

33

Chỉ cần thêm vào câu trả lời của Shane và Dirk: thật thú vị khi so sánh

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

với

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

Vì hiện tại, một trong hai phiên bản có vẻ phù hợp với mục đích của bạn my_ellipsis_function, mặc dù phiên bản đầu tiên rõ ràng đơn giản hơn.


15

Bạn đã đưa ra một nửa câu trả lời rồi. Xem xét

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> 

Vì vậy, điều này đã lấy hai đối số abtừ cuộc gọi và chuyển đổi nó thành một danh sách. Đó không phải là những gì bạn yêu cầu?


2
Không hoàn toàn những gì tôi muốn. Điều đó thực sự xuất hiện để trả về một danh sách các danh sách. Hãy chú ý [[1]]. Ngoài ra, tôi muốn biết làm thế nào câu thần chú as.list(substitute(list(...)))hoạt động.
Ryan C. Thompson

2
Bên trong list(...)tạo ra một listđối tượng dựa trên các đối số. Sau đó substitute()tạo cây phân tích cho biểu thức không được đánh giá; xem trợ giúp cho chức năng này. Cũng như một văn bản nâng cao tốt trên R (hoặc S). Đây không phải là thứ tầm thường.
Dirk Eddelbuettel

Ok, những gì về [[-1L]]phần (từ câu hỏi của tôi)? Có nên không [[1]]?
Ryan C. Thompson

3
Bạn cần phải đọc lên về lập chỉ mục. Dấu trừ có nghĩa là 'loại trừ', tức là print(c(1:3)[-1])sẽ chỉ in 2 và 3. Đây Llà một cách mới để đảm bảo nó kết thúc dưới dạng một số nguyên, điều này được thực hiện rất nhiều trong các nguồn R.
Dirk Eddelbuettel

7
Tôi không cần phải đọc lên trên lập chỉ mục, nhưng tôi làm cần phải chú ý gần gũi hơn với đầu ra của các lệnh mà bạn hiển thị. Sự khác biệt giữa [[1]]và các $achỉ số khiến tôi nghĩ rằng các danh sách lồng nhau có liên quan. Nhưng bây giờ tôi thấy rằng những gì bạn thực sự nhận được là danh sách tôi muốn, nhưng có thêm một yếu tố ở phía trước. Vì vậy, sau đó [-1L]có ý nghĩa. Trường hợp nào yếu tố đầu tiên thêm đến từ đâu? Và có lý do nào tôi nên sử dụng nó thay vì đơn giản list(...)?
Ryan C. Thompson

6

Điều này hoạt động như mong đợi. Sau đây là phiên tương tác:

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

Tương tự, ngoại trừ với một đối số mặc định:

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

Như bạn có thể thấy, bạn có thể sử dụng điều này để truyền các đối số 'phụ' cho một hàm trong hàm của bạn nếu mặc định không phải là điều bạn muốn trong một trường hợp cụ thể.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.