Menu

Toán tử pipe trong R: %>% và pipe gốc |>

%>% nghĩa là gì, pipe gốc |> của base R hoạt động ra sao, quy tắc và placeholder của từng loại, và nên dùng loại nào khi viết code mới.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Vấn đề mà pipe giải quyết

Pipe lấy giá trị đứng trước nó và đưa vào lời gọi hàm đứng sau: x |> f() nghĩa là f(x). Nghe như một cách viết lại tầm thường, cho đến khi bạn nối ba bốn bước liên tiếp. Không có pipe, các phép biến đổi nhiều bước sẽ lồng vào nhau, và lời gọi lồng nhau phải đọc ngược: việc xảy ra đầu tiên lại nằm ở vị trí trong cùng.

Để đọc round(sqrt(x), 1), bạn bắt đầu từ giữa (x), đi dần ra ngoài (sqrt, rồi round), và phải nhớ , 1) ở cuối thuộc về lời gọi nào. Lồng ba tầng thì khó chịu; lồng năm tầng, mỗi tầng là một động từ xử lý data frame với nhiều đối số, thì thật sự khó theo dõi. Cách mọi người hay dùng thay thế, lưu từng bước trung gian vào tmp1, tmp2, tmp3, lại làm rối không gian làm việc với những cái tên chẳng ai cần.

Pipe viết lại chuỗi theo đúng thứ tự các bước diễn ra: lấy x, rồi khai căn, rồi làm tròn.

Pipe gốc |>

Từ R 4.1, pipe được tích hợp sẵn trong ngôn ngữ, không cần package nào. |> chèn giá trị trước đó vào làm đối số đầu tiên của lời gọi tiếp theo:

c(1, 4, 9, 16) |> sqrt() |> round(1) chính xác là round(sqrt(c(1, 4, 9, 16)), 1): R viết lại dạng này thành dạng kia ngay khi phân tích cú pháp, nên không tốn chút chi phí nào lúc chạy. Nhưng giờ code đọc như một công thức, từ trên xuống dưới: dữ liệu, rồi từng phép biến đổi theo thứ tự. Cách này dùng được với mọi hàm, không chỉ các công cụ xử lý dữ liệu: mtcars |> head(3) hoàn toàn là base R.

Để ý pipe hợp đến mức nào với những hàm có tham số đầu tiên là "dữ liệu": head, sort, unique, summary và mọi động từ của dplyr đều được thiết kế như vậy, đó là lý do pipeline trong R trông tự nhiên đến thế.

Quy tắc của |>

Pipe gốc được thiết kế chặt chẽ có chủ ý. Ba quy tắc bao quát gần như mọi trường hợp:

1. Bước tiếp theo phải là một lời gọi hàm, có dấu ngoặc tròn. x |> sqrt là lỗi cú pháp; bạn phải viết x |> sqrt():

c(1, 4, 9) |> sqrt     # Error: The pipe operator requires a function call as RHS
c(1, 4, 9) |> sqrt()   # correct

2. Giá trị được đặt vào vị trí đối số đầu tiên. Các đối số khác bạn viết vẫn giữ nguyên sau nó: x |> round(1) là round(x, 1).

3. Để đặt vào chỗ khác, hãy dùng placeholder _ với một đối số có tên (R 4.2+). Trường hợp kinh điển: lm nhận công thức trước và dữ liệu sau, nên muốn pipe một data frame vào thì cần data = _:

_ chỉ được xuất hiện một lần, và chỉ ở dạng đối số có tên. Khi như vậy quá hạn chế, hãy pipe vào một hàm ẩn danh, hàm này có thể đặt giá trị ở bất cứ đâu:

Hàm lambda được bọc trong dấu ngoặc tròn rồi gọi bằng (), nhờ vậy vẫn thỏa quy tắc 1. Nếu bạn thấy mình làm việc này nhiều lần trong một pipeline, bước đó có lẽ nên trở thành một hàm có tên.

Pipe %>% của magrittr

Trước khi ngôn ngữ có pipe, package magrittr đã cung cấp một toán tử pipe, và %>% trở thành dấu hiệu nhận biết của code tidyverse; nạp dplyr là bạn có nó tự động. Bạn sẽ gặp nó trong gần như mọi bài hướng dẫn và script dplyr được viết trong mười năm qua:

library(dplyr)

mtcars %>%
  filter(mpg > 30) %>%
  select(mpg, wt)

Vì %>% đến từ một package, các ví dụ này không chạy được trong một phiên R trống: Error: could not find function "%>%" chính là lỗi bạn gặp nếu quên library(dplyr) (hoặc library(magrittr)).

Với trường hợp phổ biến, cách hoạt động giống hệt |>: đưa giá trị trước đó vào đối số đầu tiên của lời gọi tiếp theo. Nhưng magrittr dễ dãi hơn:

c(1, 4, 9) %>% sqrt                 # bare function name - no parentheses needed

mtcars %>% lm(mpg ~ wt, data = .)   # . placeholder, usable anywhere

c(-2, 3) %>% { max(abs(.)) }        # . can even appear several times, in nested calls

|> và %>%: những khác biệt đáng kể

Với pipeline đưa giá trị vào đối số đầu tiên, vốn chiếm đại đa số code thực tế, hai toán tử có thể thay thế cho nhau. Khác biệt nằm ở các trường hợp biên:

  • Khả dụng: |> có trong base R 4.1+; %>% cần magrittr (thường thông qua dplyr).
  • Dấu ngoặc tròn: |> bắt buộc sqrt(); %>% chấp nhận sqrt trần.
  • Placeholder: |> dùng _, một lần, chỉ ở dạng đối số có tên (4.2+); %>% dùng ., ở bất cứ đâu, bao nhiêu lần cũng được, kể cả bên trong các lời gọi lồng nhau.
  • Tốc độ: |> được viết lại thành lời gọi lồng nhau ngay lúc phân tích cú pháp, không tốn chi phí. %>% là một lời gọi hàm thông thường với chi phí nhỏ (trong thực tế không đáng kể).

Không điều gì ở đây khiến %>% là sai: nó đã được kiểm chứng qua thực tế và linh hoạt hơn một chút. Nhưng mọi thứ nó làm được vượt quá |> đều có thể làm bằng lambda, và pipe gốc không cần phụ thuộc nào.

Bạn nên dùng loại nào?

Với code mới: hãy dùng |>. Nó là một phần của ngôn ngữ, chạy ở mọi nơi có R 4.1+, không cần package, và sự chặt chẽ của nó là một ưu điểm: pipeline luôn đơn giản, hoặc được tái cấu trúc thành các hàm có tên.

Nhưng bạn phải đọc thành thạo %>%, vì các codebase tidyverse, câu trả lời trên Stack Overflow và phần lớn sách R viết từ năm 2014 đầy rẫy toán tử này. Các nhóm đã có phong cách dplyr sẵn thường giữ %>% cho nhất quán, và đó là một quyết định hợp lý; lựa chọn tệ nhất là trộn cả hai toán tử trong cùng một tệp. Dù bạn viết loại nào, có một quy ước dùng chung: trong pipeline dài, hãy đặt mỗi bước trên một dòng riêng với pipe ở cuối dòng, để công thức đọc như một danh sách các bước.

Nguyên tắc ở đây cũng giống như với nhóm hàm apply: pipe dành cho các phép biến đổi tuyến tính, bước này nối tiếp bước kia. Khi phép tính rẽ nhánh hoặc cần dùng kết quả trung gian hai lần, hãy gán vào một biến có tên rõ ràng thay vì ép mọi thứ đi qua một chuỗi duy nhất.

Những điều cần nhớ

  • Pipe đưa giá trị trước đó vào lời gọi tiếp theo: x |> f() |> g() là g(f(x)), viết theo đúng thứ tự diễn ra.
  • |> thuộc base R (4.1+): bắt buộc có dấu ngoặc tròn, nhắm vào đối số đầu tiên, placeholder _ với đối số có tên (4.2+).
  • %>% thuộc magrittr/tidyverse: cho phép tên hàm trần, placeholder . ở bất cứ đâu, nhưng cần một package.
  • Ưu tiên |> cho code mới; đọc %>% ở mọi nơi khác mà không chút ngập ngừng.
  • Pipe hợp với công thức tuyến tính; logic rẽ nhánh xứng đáng có các biến được đặt tên.

Tiếp theo: package, nơi %>%, dplyr và 20.000 phần mở rộng khác của R thực sự đến từ.

Câu hỏi thường gặp

%>% trong R nghĩa là gì?

%>% là pipe của magrittr: nó lấy giá trị đứng trước và đưa vào làm đối số đầu tiên của lời gọi hàm đứng sau, nên x %>% f() %>% g() nghĩa là g(f(x)). Toán tử này đến từ package magrittr và được nạp tự động cùng dplyr và tidyverse; nó không thuộc base R.

Có cần package để dùng |> trong R không?

Không. |> là pipe gốc, được tích hợp sẵn trong base R từ phiên bản 4.1, chạy được trong một phiên R trống không cài gì thêm. %>% mới là toán tử cần package (magrittr, hoặc bất kỳ package nào xuất lại nó, như dplyr).

|> và %>% trong R khác nhau thế nào?

Cả hai đều đưa giá trị trước đó vào lời gọi tiếp theo. |> thuộc base R, bắt buộc có dấu ngoặc tròn (x |> sqrt()), và chỉ dùng placeholder _ với một đối số có tên. %>% cần magrittr, chấp nhận tên hàm trần (x %>% sqrt), và placeholder . của nó có thể đặt ở bất cứ đâu, bao nhiêu lần cũng được. Với trường hợp phổ biến là đưa vào đối số đầu tiên, hai toán tử hoạt động giống hệt nhau.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu