Pipe

Pipe bawaan |> sebagai cara menyusun rantai langkah yang mudah dibaca.

Daftar isi

Topik ini membahas pipe, yaitu operator |> yang meneruskan hasil sebuah ekspresi menjadi argumen pertama pemanggilan fungsi berikutnya. Pembahasan dimulai dari masalah yang mendorong kemunculannya, yaitu ekspresi bersarang yang sulit dibaca, kemudian dilanjutkan dengan aturan penulisan rantai yang rapi, pemakaian pipe bersama verba dplyr (fungsi transformasi data seperti filter() dan arrange()), serta batasan pipe beserta cara mengatasinya. Sebagai bahan kerja, satu tibble kecil berisi nilai delapan siswa didefinisikan dan dipakai sepanjang modul ini. Setelah membacanya, transformasi data bertingkat dapat ditulis sebagai rangkaian langkah yang urutan bacanya sama dengan urutan eksekusinya.

r
library(tidyverse)
nilai <- tibble::tibble(
  nama = c("Ali", "Budi", "Citra", "Dewi", "Eka", "Fajar", "Gita", "Hana"),
  kelas = c("7A", "7B", "7A", "7B", "7A", "7B", "7A", "7B"),
  nilai = c(88, 74, 92, 65, 81, 79, 95, 70),
  kehadiran = c(0.95, 0.88, 1.00, 0.75, 0.92, 0.85, 0.98, 0.80)
)
nilai
text Keluaran
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.3     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# A tibble: 8 × 4
  nama  kelas nilai kehadiran
  <chr> <chr> <dbl>     <dbl>
1 Ali   7A       88      0.95
2 Budi  7B       74      0.88
3 Citra 7A       92      1   
4 Dewi  7B       65      0.75
5 Eka   7A       81      0.92
6 Fajar 7B       79      0.85
7 Gita  7A       95      0.98
8 Hana  7B       70      0.8 

Masalah ekspresi bersarang#

Satu perhitungan sering memerlukan beberapa fungsi yang diterapkan berurutan pada satu nilai. Contoh berikut menghitung akar kuadrat dari rata-rata kolom nilai, dengan pembulatan satu angka desimal pada tahap terakhirnya.

r
round(sqrt(mean(nilai$nilai)), 1)
text Keluaran
[1] 9

Cara membaca ekspresi di atas berlawanan dengan arah eksekusinya. Fungsi yang dijalankan lebih dahulu adalah mean() yang berada paling dalam, dilanjutkan sqrt(), dan terakhir round(). Semakin banyak langkahnya, semakin sulit memastikan fungsi mana yang menerima hasil fungsi mana, apalagi ketika setiap pemanggilan memuat argumen tambahan. Kesalahan penempatan kurung juga mudah terjadi karena seluruh argumen saling berimpit dalam satu ekspresi.

Alternatifnya adalah menyimpan hasil antara ke dalam variabel, lalu melanjutkan perhitungan pada variabel berikutnya.

r
rata <- mean(nilai$nilai)
akar <- sqrt(rata)
round(akar, 1)
text Keluaran
[1] 9

Urutan membaca kini searah dengan urutan eksekusi. Kerugiannya, dua variabel antara hanya dipakai satu kali, tetapi tetap menempati nama dan membebani lingkungan kerja. Pipe menawarkan jalan tengah, yaitu alur baca yang searah tanpa variabel antara.

Pipe bawaan |>#

Pipe bawaan R ditulis sebagai operator |> dan tersedia sejak R 4.1. Operator ini mengambil nilai di sisi kiri, lalu menyisipkannya sebagai argumen pertama pemanggilan fungsi di sisi kanan. Ekspresi x |> f(y) dihitung sebagai f(x, y), sehingga rantai panjang dapat dibaca dari kiri ke kanan sesuai urutan eksekusinya. Ekspresi bersarang pada awal topik dapat ditulis ulang menjadi berikut.

r
nilai$nilai |> mean() |> sqrt() |> round(digits = 1)
text Keluaran
[1] 9

Setiap tahap menerima hasil tahap sebelumnya. mean() menerima kolom nilai, sqrt() menerima rata-rata yang dihasilkan mean(), dan round() menerima akar kuadrat tersebut. Argumen tambahan seperti digits tetap dituliskan sebagaimana biasa di dalam pemanggilan fungsinya, sedangkan posisi argumen pertama diisi otomatis oleh pipe.

Menyusun rantai yang mudah dibaca#

Rantai pipe yang panjang ditulis dengan satu langkah per baris, dan tanda |> diletakkan di akhir baris. Baris-baris lanjutannya diberi indentasi agar tahapannya tampak sejajar. R melanjutkan ekspresi ke baris berikutnya selama ekspresi itu belum lengkap, sehingga pemenggalan baris tidak mengubah makna kode.

r
nilai$nilai |>
  mean() |>           # rata-rata kelas
  sqrt() |>           # akar kuadrat
  round(digits = 1)   # pembulatan satu desimal
text Keluaran
[1] 9

Komentar singkat pada setiap baris membantu menyatakan maksud langkahnya. Prinsip yang dijaga adalah satu baris memuat satu transformasi, sehingga penambahan, pembuangan, atau penyusunan ulang langkah cukup dilakukan pada satu baris. Rantai yang dipadatkan dalam satu baris tetap sah, tetapi lebih sulit diperiksa ketika salah satu tahapnya perlu disunting.

Pipe dengan verba dplyr#

Fungsi-fungsi dplyr disebut verba (verb) karena masing-masing menyatakan satu aksi pada data, seperti mengambil baris tertentu, memilih kolom, atau mengurutkannya. Semua verba menerima data frame sebagai argumen pertamanya dan mengembalikan data frame baru, sehingga hasil satu verba langsung dapat diteruskan ke verba berikutnya melalui pipe. Rantai berikut mengambil baris siswa kelas 7A, memilih tiga kolomnya, lalu mengurutkan baris menurut nilai dari yang tertinggi.

r
nilai |>
  filter(kelas == "7A") |>
  select(nama, kelas, nilai) |>
  arrange(desc(nilai))
text Keluaran
# A tibble: 4 × 3
  nama  kelas nilai
  <chr> <chr> <dbl>
1 Gita  7A       95
2 Citra 7A       92
3 Ali   7A       88
4 Eka   7A       81

Verba filter() mengambil baris yang kondisinya terpenuhi, select() memilih kolom yang disebutkan, dan arrange(desc(nilai)) mengurutkan baris menurut kolom nilai dari yang terbesar. Hasil akhir rantai adalah tabel baru, sedangkan nilai sebagai data asalnya tidak berubah sama sekali. Rantai semacam ini adalah bentuk pemakaian dplyr yang paling umum, dan setiap verba di dalamnya masing-masing memiliki aturan yang lebih rinci.

Batasan pipe dan placeholder _#

Pipe selalu menyisipkan nilai kiri sebagai argumen pertama pemanggilan di kanannya. Batasan ini tampak ketika posisi yang tepat bukan argumen pertama. Fungsi seq(from, to) menghasilkan deret dari from sampai to, sedangkan nilai yang diteruskan pipe seharusnya menempati argumen to, bukan from.

r
10 |> seq(1)
text Keluaran
 [1] 10  9  8  7  6  5  4  3  2  1

Pemanggilan itu tidak menghasilkan error, melainkan deret yang urutannya terbalik, karena seq(10, 1) sah sebagai deret menurun. Contoh semacam ini menunjukkan pentingnya memeriksa posisi argumen setiap kali pipe dipakai pada fungsi yang baru. Sejak R 4.2, tersedia placeholder _ yang menyatakan posisi nilai kiri secara eksplisit sebagai argumen bernama.

r
10 |> seq(1, to = _)
text Keluaran
 [1]  1  2  3  4  5  6  7  8  9 10

Placeholder _ pada argumen to menandai tempat nilai kiri disisipkan, sehingga ekspresi di atas dihitung sebagai seq(1, 10). Tanpa placeholder, urutan pemanggilan hanya dapat ditegakkan dengan menuliskan pemanggilan fungsi secara biasa, yaitu seq(1, 10) tanpa pipe sama sekali.

Selain pipe bawaan, paket magrittr yang menjadi dependensi tidyverse menyediakan pipe %>%, dan dplyr mengekspor ulang operator tersebut sehingga dapat dipakai begitu dplyr dimuat. Pipe %>% lebih tua daripada |> karena mendahuluinya bertahun-tahun, sehingga masih banyak dijumpai pada kode dan dokumentasi yang ditulis sebelum R 4.1. Keduanya serupa untuk pemakaian umum, dengan perbedaan yang mudah dijumpai pada placeholder dan bentuk sisi kanannya. Placeholder %>% adalah tanda titik ., sedangkan sisi kanan %>% juga menerima nama fungsi tanpa kurung, sebagaimana 5 %>% sin yang sah. Untuk kode baru, pipe bawaan |> menjadi pilihan yang disarankan karena tidak bergantung pada paket tambahan. Perbedaan keduanya terlihat pada pemanggilan berikut.

r
5 |> sin
text Keluaran
Error: The pipe operator requires a function call as RHS (<input>:1:6)

Pesan The pipe operator requires a function call as RHS menyatakan bahwa sisi kanan |> wajib berupa pemanggilan fungsi, sehingga sin harus ditulis dengan kurung menjadi sin(). Aturan yang lebih ketat ini membuat sisi kanan pipe bawaan selalu diperlakukan dengan cara yang sama.

Ringkasan#

  • Pipe bawaan |> menyisipkan nilai sisi kiri sebagai argumen pertama pemanggilan di sisi kanan, sehingga x |> f(y) dihitung sebagai f(x, y).
  • Rantai panjang ditulis satu langkah per baris dengan |> di akhir baris, sehingga urutan membaca sama dengan urutan eksekusi.
  • Verba dplyr menerima data frame sebagai argumen pertama dan mengembalikan data frame baru, sehingga cocok disambung menjadi rantai pipe.
  • Apabila posisi yang tepat bukan argumen pertama, gunakan placeholder _ pada argumen bernama sejak R 4.2, karena pemindahan yang keliru bisa tetap menghasilkan keluaran yang tampak sah.
  • Pipe %>% dari magrittr masih banyak dijumpai pada kode lama dan memakai . sebagai placeholder.

Referensi#