R for Pipes
1 Introduction
Pipes เป็นเครื่องมือที่มีประสิทธิภาพสำหรับการแสดงลำดับของการดำเนินการหลายอย่างชัดเจน จนถึงตอนนี้คุณได้ใช้พวกเขาโดยไม่ทราบว่าพวกเขาทำงานหรือสิ่งที่เป็นทางเลือก ตอนนี้ในบทนี้ถึงเวลาสำรวจท่อในรายละเอียดเพิ่มเติม คุณจะได้เรียนรู้ทางเลือกในการท่อเมื่อคุณไม่ควรใช้pipeและเครื่องมือที่มีประโยชน์บางอย่าง
ข้อกำหนดเบื้องต้น
pipe,%>%
, มาจากแพคเกจ magrittr โดย Stefan Milton Bache แพคเกจในโหลด tidyverse%>% สำหรับคุณโดยอัตโนมัติดังนั้นคุณจึงมักจะไม่โหลด magrittr อย่างชัดแจ้ง อย่างไรก็ตามในที่นี้เรากำลังมุ่งเน้นที่การวางท่อและเราไม่ได้โหลดแพคเกจอื่น ๆlibrary(magrittr)
2 ทางเลือก Piping
จุดของpipeคือการช่วยให้คุณเขียนโค้ดในแบบที่ง่ายต่อการอ่านและทำความเข้าใจ เพื่อดูว่าทำไมpipeมีประโยชน์ดังนั้นเราจะสำรวจหลายวิธีในการเขียนรหัสเดียวกัน ลองใช้โค้ดเพื่อบอกเล่าเรื่องราวเกี่ยวกับกระต่ายน้อยชื่อ Foo Foo:Little bunny Foo Foo
Went hopping through the forest
Scooping up the field mice
And bopping them on the head
นี่เป็นบทกวีของเด็กที่เป็นที่นิยมซึ่งมาพร้อมกับการกระทำด้วยมือ
เราจะเริ่มต้นด้วยการกำหนดวัตถุที่จะแสดงกระต่ายน้อย Foo Foo:
foo_foo <- little_bunny()
และเราจะใช้ฟังก์ชันสำหรับคำกริยาแต่ละคำ: hop (), scoop () และ bop () การใช้ออบเจ็กต์และคำกริยาเหล่านี้มีวิธีการอย่างน้อยสี่วิธีที่เราสามารถเล่าเรื่องราวในโค้ดได้:- บันทึกแต่ละขั้นตอนกลางเป็นวัตถุใหม่
- เขียนทับวัตถุเดิมหลาย ๆ ครั้ง
- เขียนฟังก์ชัน
- ใช้ pipe.
เราจะทำงานผ่านแต่ละวิธีแสดงรหัสและพูดถึงข้อดีและข้อเสีย
2.1 Intermediate steps
วิธีที่ง่ายที่สุดคือการบันทึกแต่ละขั้นตอนเป็นวัตถุใหม่:foo_foo_1 <- hop(foo_foo, through = forest)
foo_foo_2 <- scoop(foo_foo_1, up = field_mice)
foo_foo_3 <- bop(foo_foo_2, on = head)
ข้อเสียหลักของแบบฟอร์มนี้คือบังคับให้คุณตั้งชื่อองค์ประกอบกลางแต่ละส่วน ถ้ามีชื่อตามธรรมชาตินี่เป็นความคิดที่ดีและคุณควรจะทำ แต่หลายครั้งเช่นนี้ในตัวอย่างนี้ไม่มีชื่อตามธรรมชาติและคุณเพิ่มส่วนต่อท้ายตัวเลขเพื่อทำให้ชื่อไม่เหมือนใคร ที่นำไปสู่สองปัญหา:- โค้ดมีการรกด้วยชื่อที่ไม่สำคัญ
- คุณต้องเพิ่มส่วนต่อท้ายในแต่ละบรรทัดอย่างระมัดระวัง
เมื่อใดก็ตามที่ฉันเขียนโค้ดแบบนี้ฉันใช้หมายเลขที่ไม่ถูกต้องในบรรทัดหนึ่งแล้วใช้เวลา 10 นาทีเกาหัวของฉันและพยายามคิดว่าเกิดอะไรขึ้นกับโค้ดของฉัน
นอกจากนี้คุณอาจกังวลว่าฟอร์มนี้จะสร้างสำเนาข้อมูลจำนวนมากและใช้หน่วยความจำจำนวนมาก น่าแปลกใจที่ไม่ใช่กรณี ประการแรกโปรดทราบว่าการกังวลเกี่ยวกับหน่วยความจำในเชิงรุกไม่ใช่วิธีที่มีประโยชน์ในการใช้เวลาของคุณ: ต้องกังวลเกี่ยวกับเรื่องนี้เมื่อปัญหากลายเป็นปัญหา (เช่นคุณหมดความทรงจำ) ไม่ใช่ก่อน ประการที่สอง R ไม่ใช่โง่และจะแบ่งปันคอลัมน์ข้ามเฟรมข้อมูลที่เป็นไปได้ ลองมาดูข้อมูลการจัดการข้อมูลจริงที่เราเพิ่มคอลัมน์ใหม่ลงใน ggplot2 :: diamonds:
diamonds <- ggplot2::diamonds
diamonds2 <- diamonds %>%
dplyr::mutate(price_per_carat = price / carat)
pryr::object_size(diamonds)
#> 3.46 MB
pryr::object_size(diamonds2)
#> 3.89 MB
pryr::object_size(diamonds, diamonds2)
#> 3.89 MB
pryr :: object_size () ทำให้หน่วยความจำใช้งานได้โดยอาร์กิวเมนต์ทั้งหมด ผลดูเหมือน counterintuitive ในตอนแรก:diamonds
takes up 3.46 MB,diamonds2
takes up 3.89 MB,diamonds
anddiamonds2
together take up 3.89 MB!
diamonds$carat[1] <- NA
pryr::object_size(diamonds)
#> 3.46 MB
pryr::object_size(diamonds2)
#> 3.89 MB
pryr::object_size(diamonds, diamonds2)
#> 4.32 MB
(โปรดทราบว่าเราใช้ pryr :: object_size () ที่นี่ไม่ใช่ในตัว object.size () object.size () ใช้เวลาเพียงหนึ่งอ็อบเจ็กต์เท่านั้นจึงไม่สามารถคำนวณว่าข้อมูลถูกแชร์ผ่านหลายอ็อบเจ็กต์ได้อย่างไร)2.2 Overwrite the original
แทนที่จะสร้างวัตถุระดับกลางในแต่ละขั้นตอนเราสามารถเขียนทับวัตถุต้นฉบับ:
foo_foo <- hop(foo_foo, through = forest)
foo_foo <- scoop(foo_foo, up = field_mice)
foo_foo <- bop(foo_foo, on = head)
นี่เป็นการพิมพ์น้อย (และการคิดน้อย) ดังนั้นคุณจึงมีโอกาสน้อยที่จะทำผิดพลาด อย่างไรก็ตามมีปัญหาสองประการคือ- การแก้จุดบกพร่องเป็นเรื่องที่เจ็บปวด: ถ้าคุณทำผิดพลาดคุณจะต้องดำเนินการท่อทั้งหมดตั้งแต่เริ่มต้น
- การทำซ้ำของวัตถุที่ถูกเปลี่ยน (เราได้เขียน foo_foo หกครั้ง!) ปิดบังสิ่งที่เปลี่ยนแปลงในแต่ละบรรทัด
2.3 Function composition
อีกวิธีหนึ่งคือการละทิ้งการกำหนดและสายเพียงฟังก์ชันเรียกกัน:
bop(
scoop(
hop(foo_foo, through = forest),
up = field_mice
),
on = head
)
ที่นี่ข้อเสียคือคุณต้องอ่านจากด้านในออกจากขวาไปซ้ายและว่าข้อโต้แย้งสิ้นสุดกระจายไปไกล (เรียกว่า evocatively ปัญหา dagwood sandwhich) ในระยะสั้นรหัสนี้เป็นเรื่องยากสำหรับมนุษย์ที่จะบริโภค2.4 Use the pipe
สุดท้ายเราสามารถใช้pipe:foo_foo %>%
hop(through = forest) %>%
scoop(up = field_mouse) %>%
bop(on = head)
นี่เป็นรูปแบบที่ฉันชอบเพราะเน้นคำกริยาไม่ใช่คำนาม คุณสามารถอ่านชุดขององค์ประกอบฟังก์ชั่นนี้ได้เช่นชุดของการกระทำที่จำเป็น Foo Foo กระโดดจากนั้นตักแล้ว bops ข้อเสียคือคุณต้องคุ้นเคยกับท่อ หากคุณไม่เคยเห็น%>% มาก่อนคุณจะไม่รู้ว่ารหัสนี้เป็นอย่างไร โชคดีที่คนส่วนใหญ่รับไอเดียนี้ได้อย่างรวดเร็วดังนั้นเมื่อแชร์รหัสกับคนอื่นที่ไม่คุ้นเคยกับท่อคุณสามารถสอนได้ง่ายpipe ทำงานโดยการดำเนินการ "การแปลงศัพท์": เบื้องหลัง magrittr ประกอบรหัสในท่อกับแบบฟอร์มที่ทำงานโดยการเขียนทับวัตถุระดับกลาง เมื่อคุณเรียกใช้ท่อเช่นเดียวกับข้างต้น magrittr จะทำสิ่งนี้:
my_pipe <- function(.) {
. <- hop(., through = forest)
. <- scoop(., up = field_mice)
bop(., on = head)
}
my_pipe(foo_foo)
ซึ่งหมายความว่าpipeจะไม่ทำงานสำหรับสองชั้นของการทำงาน:- ฟังก์ชั่นที่ใช้สภาพแวดล้อมปัจจุบัน ตัวอย่างเช่นกำหนด () จะสร้างตัวแปรใหม่พร้อมด้วยชื่อที่กำหนดในสภาพแวดล้อมปัจจุบัน:
assign("x", 10)
x
#> [1] 10
"x" %>% assign(100)
x
#> [1] 10
การใช้งานที่กำหนดให้กับpipeไม่ทำงานเนื่องจากกำหนดให้ใช้กับสภาพแวดล้อมชั่วคราวที่ใช้โดย%>% ถ้าคุณต้องการใช้กำหนดกับท่อคุณต้องชัดเจนเกี่ยวกับสภาพแวดล้อม:env <- environment()
"x" %>% assign(100, envir = env)
x
#> [1] 100
ฟังก์ชันอื่น ๆ ที่มีปัญหานี้ ได้แก่ get () และ load ()หน้าที่ที่ใช้การประเมินผลขี้เกียจ ใน R อาร์กิวเมนต์ฟังก์ชันจะคำนวณเฉพาะเมื่อฟังก์ชันใช้ฟังก์ชันเหล่านี้ไม่ใช่ก่อนที่จะเรียกฟังก์ชัน ท่อคำนวณแต่ละองค์ประกอบเพื่อให้คุณไม่สามารถพึ่งพาพฤติกรรมนี้ได้
หนึ่งในสถานที่ที่มีปัญหาคือ tryCatch () ซึ่งช่วยให้คุณจับภาพและจัดการกับข้อผิดพลาด:
tryCatch(stop("!"), error = function(e) "An error")
#> [1] "An error"
stop("!") %>%
tryCatch(error = function(e) "An error")
#> Error in eval(lhs, parent, parent): !
มีฟังก์ชันระดับที่ค่อนข้างกว้างกับลักษณะการทำงานนี้ ได้แก่ try (), suppressMessages () และ suppressWarnings () ในฐาน R.referable http://r4ds.had.co.nz/pipes.html
ความคิดเห็น
แสดงความคิดเห็น