วิธีการใช้คุณสมบัติจุดไข่ปลาของ R เมื่อเขียนฟังก์ชั่นของคุณเอง?


186

ภาษา R มีคุณสมบัติที่ดีสำหรับการกำหนดฟังก์ชั่นที่สามารถรับจำนวนตัวแปรของการขัดแย้ง ตัวอย่างเช่นฟังก์ชันdata.frameรับจำนวนอาร์กิวเมนต์ใด ๆ และแต่ละอาร์กิวเมนต์กลายเป็นข้อมูลสำหรับคอลัมน์ในตารางข้อมูลผลลัพธ์ ตัวอย่างการใช้งาน:

> 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

ลายเซ็นของฟังก์ชันรวมถึงจุดไข่ปลาเช่นนี้

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

ฉันต้องการเขียนฟังก์ชั่นที่ทำสิ่งที่คล้ายกันรับหลายค่าและรวมเข้ากับค่าส่งคืนเดียว (รวมถึงการประมวลผลอื่น ๆ ) เพื่อที่จะทำสิ่งนี้ฉันต้องหาวิธีที่จะ "แกะ" ...จากข้อโต้แย้งของฟังก์ชั่นภายในฟังก์ชั่น ฉันไม่รู้จะทำยังไง บรรทัดที่เกี่ยวข้องในการกำหนดฟังก์ชั่นของdata.frameคือobject <- as.list(substitute(list(...)))[-1L]ซึ่งฉันไม่สามารถทำให้ความรู้สึกใด ๆ ของ

ดังนั้นฉันจะแปลงจุดไข่ปลาจากลายเซ็นของฟังก์ชันเป็นรายการได้อย่างไร

หากต้องการเจาะจงมากขึ้นฉันจะเขียนget_list_from_ellipsisรหัสด้านล่างได้อย่างไร

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)

แก้ไข

ดูเหมือนว่ามีวิธีที่เป็นไปได้สองวิธีในการทำเช่นนี้ พวกเขาเป็นและas.list(substitute(list(...)))[-1L] list(...)อย่างไรก็ตามทั้งสองนี้ไม่ได้ทำสิ่งเดียวกัน (สำหรับความแตกต่างให้ดูตัวอย่างในคำตอบ) ทุกคนสามารถบอกฉันได้ว่าความแตกต่างที่ใช้ได้จริงระหว่างพวกเขาคืออะไรและฉันควรใช้อันใด

คำตอบ:


113

ฉันอ่านคำตอบและความคิดเห็นและฉันเห็นว่ามีบางสิ่งที่ไม่ได้กล่าวถึง:

  1. data.frameใช้list(...)รุ่น ส่วนของรหัส:

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

    objectจะใช้ในการทำมายากลบางคนที่มีชื่อคอลัมน์ แต่ถูกนำมาใช้ในการสร้างครั้งสุดท้ายx สำหรับการใช้อาร์กิวเมนต์ที่ไม่ได้ประเมินค่าให้ดูที่โค้ดที่ใช้data.frame
    ...write.csvmatch.call

  2. ในขณะที่คุณเขียนในความคิดเห็นผลในคำตอบ Dirk ไม่ได้เป็นรายการของรายการ เป็นรายการความยาว 4 ซึ่งเป็นlanguageประเภทองค์ประกอบ วัตถุแรกคือsymbol- list, ที่สองคือการแสดงออก1:10และอื่น ๆ ที่อธิบายว่าทำไม[-1L]จำเป็น: มันเอาออกsymbolจากข้อโต้แย้งที่ให้ไว้ใน...(ทำให้มันเป็นรายการเสมอ)
    ในขณะที่เดิร์กsubstituteส่งคืน "การแยกวิเคราะห์ต้นไม้นิพจน์ที่ไม่ได้ประเมินค่า"
    เมื่อคุณเรียกmy_ellipsis_function(a=1:10,b=11:20,c=21:30)แล้ว..."สร้าง" รายการของอาร์กิวเมนต์: list(a=1:10,b=11:20,c=21:30)และsubstituteทำให้มันรายการสี่องค์ประกอบ:

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

    องค์ประกอบแรกไม่มีชื่อและนี่คือ[[1]]คำตอบของเดิร์ค ฉันบรรลุผลลัพธ์นี้โดยใช้:

    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. ดังกล่าวข้างต้นเราสามารถใช้strเพื่อตรวจสอบสิ่งที่วัตถุอยู่ในฟังก์ชั่น

    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 
    

    ไม่เป็นไร. ให้ดู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 
    

    ไม่ใช่สิ่งที่เราต้องการ คุณจะต้องมีลูกเล่นเพิ่มเติมเพื่อจัดการกับวัตถุประเภทนี้ (เหมือนในwrite.csv)

หากคุณต้องการใช้แล้วคุณควรจะใช้มันเป็นคำตอบในเชนโดย...list(...)


38

คุณสามารถแปลงจุดไข่ปลาเป็นรายการด้วยlist()แล้วดำเนินการกับมัน:

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

$b
[1] "numeric"

ดังนั้นget_list_from_ellipsisหน้าที่ของคุณคืออะไรมากกว่าlistนี้

กรณีการใช้งานที่ถูกต้องสำหรับกรณีนี้คือในกรณีที่คุณต้องการส่งวัตถุที่ไม่รู้จักจำนวนหนึ่งเพื่อการทำงาน (เช่นในตัวอย่างของคุณc()หรือdata.frame()) ไม่ใช่ความคิดที่ดีที่จะใช้...เมื่อคุณทราบพารามิเตอร์แต่ละตัวล่วงหน้าเนื่องจากเป็นการเพิ่มความคลุมเครือและความยุ่งยากเพิ่มเติมให้กับสตริงอาร์กิวเมนต์ (และทำให้ลายเซ็นของฟังก์ชันไม่ชัดเจนสำหรับผู้ใช้รายอื่น) รายการอาร์กิวเมนต์เป็นเอกสารสำคัญสำหรับผู้ใช้ฟังก์ชัน

มิฉะนั้นจะมีประโยชน์สำหรับกรณีที่เมื่อคุณต้องการส่งผ่านพารามิเตอร์ไปยังฟังก์ชั่นย่อยโดยไม่ต้องเปิดเผยทั้งหมดในอาร์กิวเมนต์ฟังก์ชันของคุณเอง สามารถบันทึกได้ในเอกสารประกอบการใช้งาน


ฉันรู้เกี่ยวกับการใช้จุดไข่ปลาเป็น pass-through สำหรับข้อโต้แย้งไปยัง subfunctions แต่มันก็เป็นเรื่องธรรมดาในหมู่ R primitives เพื่อใช้จุดไข่ปลาในวิธีที่ฉันได้อธิบายไว้ อันที่จริงทั้งฟังก์ชั่นlistและcฟังก์ชั่นทำงานในลักษณะนี้ แต่ทั้งคู่เป็นสิ่งพื้นฐานดังนั้นฉันไม่สามารถตรวจสอบซอร์สโค้ดของพวกเขาได้ง่ายเพื่อทำความเข้าใจวิธีการทำงานของมัน
Ryan C. Thompson

rbind.data.frameใช้วิธีนี้
Marek

5
หากlist(...)เพียงพอทำไม R builtins เช่นdata.frameใช้ฟอร์มที่ยาวกว่าas.list(substitute(list(...)))[-1L]แทน
Ryan C. Thompson

1
ขณะที่ผมไม่ได้สร้างdata.frameผมไม่ทราบคำตอบว่า (ที่กล่าวว่าฉันแน่ใจว่ามีเป็นเหตุผลที่ดีสำหรับมัน) ฉันใช้list()เพื่อจุดประสงค์นี้ในแพ็คเกจของตัวเองและยังไม่พบปัญหา
เชน

34

เพียงเพิ่มคำตอบของ Shane และ Dirk: มันน่าสนใจที่จะเปรียบเทียบ

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

กับ

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

ทั้งสองเวอร์ชันนั้นเหมาะสมสำหรับวัตถุประสงค์ของคุณในmy_ellipsis_functionขณะที่เวอร์ชันแรกนั้นเรียบง่ายกว่า


15

คุณตอบไปครึ่งหนึ่งแล้ว พิจารณา

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> 

ดังนั้นนี่จึงเอาสองข้อโต้แย้งaและbจากการโทรและแปลงเป็นรายการ นั่นไม่ใช่สิ่งที่คุณถามใช่ไหม


2
ไม่ใช่สิ่งที่ฉันต้องการ ที่จริงแล้วปรากฏขึ้นเพื่อส่งคืนรายการ [[1]]แจ้งให้ทราบล่วงหน้า นอกจากนี้ฉันต้องการทราบว่าคาถาเวทมนต์as.list(substitute(list(...)))ทำงานอย่างไร
Ryan C. Thompson

2
ด้านในlist(...)สร้างlistวัตถุตามข้อโต้แย้ง จากนั้นsubstitute()สร้างต้นไม้แยกวิเคราะห์สำหรับนิพจน์ที่ไม่ได้ประเมินค่า ดูวิธีใช้สำหรับฟังก์ชั่นนี้ เช่นเดียวกับข้อความขั้นสูงที่ดีใน R (หรือ S) นี่ไม่ใช่สิ่งเล็กน้อย
Dirk Eddelbuettel

ตกลงสิ่งที่เกี่ยวกับ[[-1L]]ส่วน (จากคำถามของฉัน) มันไม่ควรจะเป็น[[1]]?
Ryan C. Thompson

3
คุณต้องอ่านข้อมูลเกี่ยวกับการจัดทำดัชนี เครื่องหมายลบหมายถึง 'ไม่รวม' เช่นprint(c(1:3)[-1])จะพิมพ์ 2 และ 3 เท่านั้น นี่Lเป็นวิธีที่แปลกใหม่เพื่อให้แน่ใจว่ามันจะกลายเป็นจำนวนเต็มสิ่งนี้ทำได้มากมายในแหล่ง R
Dirk Eddelbuettel

7
ผมไม่จำเป็นต้องอ่านข้อมูลเกี่ยวกับการจัดทำดัชนี แต่ฉันจะต้องให้ความสนใจใกล้ชิดกับผลของคำสั่งที่คุณแสดง ความแตกต่างระหว่าง[[1]]และ$aดัชนีทำให้ฉันคิดว่ารายการที่ซ้อนกันมีส่วนเกี่ยวข้อง แต่ตอนนี้ฉันเห็นแล้วว่าสิ่งที่คุณได้รับจริง ๆ คือรายการที่ฉันต้องการ แต่มีองค์ประกอบเพิ่มเติมที่ด้านหน้า ดังนั้นสิ่งที่[-1L]สมเหตุสมผล องค์ประกอบแรกนั้นพิเศษมาจากไหน และมีเหตุผลใดที่ฉันควรใช้สิ่งนี้แทนเพียงแค่list(...)?
Ryan C. Thompson

6

ทำงานได้ตามที่คาดไว้ ต่อไปนี้เป็นเซสชันแบบโต้ตอบ:

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

เหมือนกันยกเว้นด้วยอาร์กิวเมนต์เริ่มต้น:

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

อย่างที่คุณเห็นคุณสามารถใช้สิ่งนี้เพื่อส่งผ่านอาร์กิวเมนต์ 'พิเศษ' ไปยังฟังก์ชันภายในฟังก์ชันของคุณหากค่าเริ่มต้นไม่ใช่สิ่งที่คุณต้องการในบางกรณี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.