R for Pipes

1 Introduction

Pipes เป็นเครื่องมือที่มีประสิทธิภาพสำหรับการแสดงลำดับของการดำเนินการหลายอย่างชัดเจน จนถึงตอนนี้คุณได้ใช้พวกเขาโดยไม่ทราบว่าพวกเขาทำงานหรือสิ่งที่เป็นทางเลือก ตอนนี้ในบทนี้ถึงเวลาสำรวจท่อในรายละเอียดเพิ่มเติม คุณจะได้เรียนรู้ทางเลือกในการท่อเมื่อคุณไม่ควรใช้pipeและเครื่องมือที่มีประโยชน์บางอย่าง

ข้อกำหนดเบื้องต้น

pipe, %>% มาจากแพคเกจ magrittr โดย Stefan Milton Bache แพคเกจในโหลด tidyverse%>% สำหรับคุณโดยอัตโนมัติดังนั้นคุณจึงมักจะไม่โหลด magrittr อย่างชัดแจ้ง อย่างไรก็ตามในที่นี้เรากำลังมุ่งเน้นที่การวางท่อและเราไม่ได้โหลดแพคเกจอื่น ๆ

library(magrittr)

ทางเลือก 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 () การใช้ออบเจ็กต์และคำกริยาเหล่านี้มีวิธีการอย่างน้อยสี่วิธีที่เราสามารถเล่าเรื่องราวในโค้ดได้:


  1. บันทึกแต่ละขั้นตอนกลางเป็นวัตถุใหม่
  2. เขียนทับวัตถุเดิมหลาย ๆ ครั้ง
  3. เขียนฟังก์ชัน
  4. ใช้ 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)
ข้อเสียหลักของแบบฟอร์มนี้คือบังคับให้คุณตั้งชื่อองค์ประกอบกลางแต่ละส่วน ถ้ามีชื่อตามธรรมชาตินี่เป็นความคิดที่ดีและคุณควรจะทำ แต่หลายครั้งเช่นนี้ในตัวอย่างนี้ไม่มีชื่อตามธรรมชาติและคุณเพิ่มส่วนต่อท้ายตัวเลขเพื่อทำให้ชื่อไม่เหมือนใคร ที่นำไปสู่สองปัญหา:


  1. โค้ดมีการรกด้วยชื่อที่ไม่สำคัญ
  2. คุณต้องเพิ่มส่วนต่อท้ายในแต่ละบรรทัดอย่างระมัดระวัง


เมื่อใดก็ตามที่ฉันเขียนโค้ดแบบนี้ฉันใช้หมายเลขที่ไม่ถูกต้องในบรรทัดหนึ่งแล้วใช้เวลา 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 and diamonds2 together take up 3.89 MB!
วิธีการที่สามารถทำงานได้หรือไม่ diamonds2 มี 10 คอลัมน์ร่วมกับdiamondsไม่จำเป็นต้องทำซ้ำข้อมูลทั้งหมดดังนั้นเฟรมข้อมูลทั้งสองมีตัวแปรกัน ตัวแปรเหล่านี้จะได้รับการคัดลอกเท่านั้นหากคุณแก้ไขตัวแปรเหล่านี้ ในตัวอย่างต่อไปนี้เราจะปรับค่าเดี่ยวใน diamonds$carat นั่นหมายความว่าตัวแปรcaratไม่สามารถใช้ร่วมกันได้ระหว่างเฟรมข้อมูลทั้งสองและต้องทำสำเนา ขนาดของแต่ละเฟรมข้อมูลไม่มีการเปลี่ยนแปลง แต่ขนาดที่เพิ่มขึ้นโดยรวม:
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)
นี่เป็นการพิมพ์น้อย (และการคิดน้อย) ดังนั้นคุณจึงมีโอกาสน้อยที่จะทำผิดพลาด อย่างไรก็ตามมีปัญหาสองประการคือ


  1. การแก้จุดบกพร่องเป็นเรื่องที่เจ็บปวด: ถ้าคุณทำผิดพลาดคุณจะต้องดำเนินการท่อทั้งหมดตั้งแต่เริ่มต้น
  2. การทำซ้ำของวัตถุที่ถูกเปลี่ยน (เราได้เขียน 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จะไม่ทำงานสำหรับสองชั้นของการทำงาน:


  1. ฟังก์ชั่นที่ใช้สภาพแวดล้อมปัจจุบัน ตัวอย่างเช่นกำหนด () จะสร้างตัวแปรใหม่พร้อมด้วยชื่อที่กำหนดในสภาพแวดล้อมปัจจุบัน:

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

ความคิดเห็น

โพสต์ยอดนิยมจากบล็อกนี้

R STUDIO

R for Data import