Data analisis jarang berada dalam satu tabel utuh. Topik ini membahas penggabungan dua tabel dengan keluarga fungsi join dari dplyr, mulai dari kunci yang menghubungkan tabel, left_join() sebagai fungsi utama, perbandingan singkat dengan inner_join(), full_join(), dan right_join(), penanganan nama kolom yang bentrok, hingga filter antar tabel dengan semi_join() dan anti_join(). Contohnya memakai tabel siswa dan tabel nilai ujian.
Mengapa data dipisah ke banyak tabel#
Menyimpan seluruh informasi dalam satu tabel menimbulkan pengulangan. Apabila alamat dan tanggal lahir setiap siswa ditempelkan pada setiap baris nilai ujian, data siswa tercatat berulang untuk setiap mata pelajaran yang diikutinya. Karena itu, penyimpanan yang lazim memisahkan entitas ke tabelnya masing-masing, lalu menghubungkannya lewat kunci. Kolom nis pada tabel siswa berperan sebagai kunci primer (primary key), yaitu kolom yang menandai setiap baris secara unik, sedangkan nis pada tabel nilai berperan sebagai kunci asing (foreign key), yaitu kolom yang menunjuk baris pada tabel lain.
library(tidyverse)
siswa <- tribble(
~nis, ~nama, ~kelas,
1001, "Ali", "7A",
1002, "Budi", "7A",
1003, "Citra", "7B",
1004, "Dewi", "7B"
)
nilai <- tribble(
~nis, ~mata_pelajaran, ~nilai,
1001, "Matematika", 80,
1002, "Matematika", 75,
1002, "Bahasa Indonesia", 82,
1003, "Matematika", 88
)
siswa
nilai── 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: 4 × 3
nis nama kelas
<dbl> <chr> <chr>
1 1001 Ali 7A
2 1002 Budi 7A
3 1003 Citra 7B
4 1004 Dewi 7B
# A tibble: 4 × 3
nis mata_pelajaran nilai
<dbl> <chr> <dbl>
1 1001 Matematika 80
2 1002 Matematika 75
3 1002 Bahasa Indonesia 82
4 1003 Matematika 88Tabel nilai memuat empat baris untuk tiga siswa karena Budi mengikuti dua mata pelajaran, sedangkan Dewi belum tercatat sama sekali. Keadaan pertama menjadi sumber kecocokan ganda, sedangkan keadaan kedua menjadi sumber NA. Kedua keadaan ini memengaruhi hasil join, sehingga patut dicatat sejak awal.
left_join sebagai join utama#
Fungsi left_join() menggabungkan dua tabel dengan mempertahankan seluruh baris tabel kiri. Setiap baris kiri dicocokkan dengan baris kanan yang kuncinya sama, lalu kolom-kolom tabel kanan ditambahkan ke hasil. Kata kiri mengacu pada posisi tabel pertama dalam pemanggilan, bukan pada arah membaca.
left_join(siswa, nilai, by = "nis")# A tibble: 5 × 5
nis nama kelas mata_pelajaran nilai
<dbl> <chr> <chr> <chr> <dbl>
1 1001 Ali 7A Matematika 80
2 1002 Budi 7A Matematika 75
3 1002 Budi 7A Bahasa Indonesia 82
4 1003 Citra 7B Matematika 88
5 1004 Dewi 7B <NA> NAKelima baris tabel kiri tetap ada, termasuk Dewi yang tidak memiliki pasangan, dan kolom milik tabel kanan untuk Dewi terisi NA. Pencocokan dilakukan lewat argumen by, yaitu by = "nis" karena kedua tabel memakai nama kolom kunci yang sama. Fungsi ini menjadi pilihan utama karena pertanyaan analisis umumnya berangkat dari satu tabel utama, misalnya daftar siswa, lalu menambahkan atribut dari tabel lain tanpa membuang baris mana pun.
Kecocokan ganda melipatgandakan baris#
Satu baris kiri boleh cocok dengan lebih dari satu baris kanan, dan setiap kecocokan menghasilkan satu baris baru. Kondisi ini muncul apabila tabel kanan memuat beberapa baris untuk satu kunci yang sama. Tabel ganda berikut mencatat dua nilai untuk nis 1002.
ganda <- tribble(
~nis, ~mata_pelajaran, ~nilai,
1002, "Matematika", 75,
1002, "Matematika", 90
)
left_join(siswa, ganda, by = "nis")# A tibble: 5 × 5
nis nama kelas mata_pelajaran nilai
<dbl> <chr> <chr> <chr> <dbl>
1 1001 Ali 7A <NA> NA
2 1002 Budi 7A Matematika 75
3 1002 Budi 7A Matematika 90
4 1003 Citra 7B <NA> NA
5 1004 Dewi 7B <NA> NABaris Budi kini muncul dua kali dengan nilai 75 dan 90, sedangkan tiga siswa lain muncul sekali tanpa pasangan. Hasil join berjumlah lima baris dari empat baris awal karena setiap kecocokan ganda memperbanyak baris. Perilaku ini benar menurut definisi join, tetapi bermasalah apabila tidak diantisipasi, misalnya jumlah peserta yang dihitung dari jumlah baris menjadi melebihi jumlah siswa sebenarnya.
Keluarga join lainnya#
Empat fungsi lain melengkapi left_join() dengan aturan baris yang berbeda, sedangkan aturan kolomnya sama. Perbedaannya murni terletak pada baris mana yang dipertahankan apabila pasangan tidak lengkap. Tabel berikut merangkum keempatnya.
| Fungsi | Baris yang dipertahankan |
|---|---|
left_join() | seluruh baris tabel kiri |
right_join() | seluruh baris tabel kanan |
inner_join() | hanya baris yang berpasangan di kedua tabel |
full_join() | seluruh baris dari kedua tabel |
Fungsi inner_join() hanya mempertahankan baris yang memiliki pasangan, sehingga Dewi tidak muncul pada hasilnya. Baris Budi tetap muncul dua kali karena dua pasangannya. Fungsi ini cocok apabila hanya baris yang lengkap yang dibutuhkan pada analisis.
inner_join(siswa, nilai, by = "nis")# A tibble: 4 × 5
nis nama kelas mata_pelajaran nilai
<dbl> <chr> <chr> <chr> <dbl>
1 1001 Ali 7A Matematika 80
2 1002 Budi 7A Matematika 75
3 1002 Budi 7A Bahasa Indonesia 82
4 1003 Citra 7B Matematika 88Fungsi full_join() mempertahankan semuanya, termasuk baris kanan yang tidak memiliki pasangan. Untuk memperlihatkannya, satu baris nilai dengan nis 1099 yang tidak terdaftar pada tabel siswa ditambahkan terlebih dahulu.
nilai_lengkap <- add_row(nilai, nis = 1099, mata_pelajaran = "Matematika", nilai = 70)
full_join(siswa, nilai_lengkap, by = "nis")# A tibble: 6 × 5
nis nama kelas mata_pelajaran nilai
<dbl> <chr> <chr> <chr> <dbl>
1 1001 Ali 7A Matematika 80
2 1002 Budi 7A Matematika 75
3 1002 Budi 7A Bahasa Indonesia 82
4 1003 Citra 7B Matematika 88
5 1004 Dewi 7B <NA> NA
6 1099 <NA> <NA> Matematika 70Baris terakhir memuat nis 1099 dengan kolom nama dan kelas berisi NA karena tidak ada siswa yang cocok. Fungsi right_join() menghasilkan baris yang sama dengan left_join() apabila argumennya ditukar, sehingga penggunaannya bisa dihindari demi keterbacaan, karena pembaca kode umumnya mengharapkan tabel utama berada di posisi kiri. Perbedaannya hanya pada baris tabel mana yang menjadi acuan, sehingga hasilnya mudah diprediksi setelah kedua peran itu jelas.
Nama kolom bentrok dan kunci berbeda#
Dua tabel kadang memakai nama kolom yang sama untuk isi yang berbeda. Keadaan ini wajar pada data hasil ekspor dari sistem yang berbeda. Apabila kedua tabel sama-sama memuat kolom kelas, dplyr menambahkan akhiran .x dan .y secara bawaan agar nama hasilnya tetap unik.
nilai_kls <- tribble(
~nis, ~kelas, ~mata_pelajaran, ~nilai,
1001, "7A", "Matematika", 80,
1003, "7B", "Matematika", 88
)
left_join(siswa, nilai_kls, by = "nis")
left_join(siswa, nilai_kls, by = "nis", suffix = c("_siswa", "_nilai"))# A tibble: 4 × 6
nis nama kelas.x kelas.y mata_pelajaran nilai
<dbl> <chr> <chr> <chr> <chr> <dbl>
1 1001 Ali 7A 7A Matematika 80
2 1002 Budi 7A <NA> <NA> NA
3 1003 Citra 7B 7B Matematika 88
4 1004 Dewi 7B <NA> <NA> NA
# A tibble: 4 × 6
nis nama kelas_siswa kelas_nilai mata_pelajaran nilai
<dbl> <chr> <chr> <chr> <chr> <dbl>
1 1001 Ali 7A 7A Matematika 80
2 1002 Budi 7A <NA> <NA> NA
3 1003 Citra 7B 7B Matematika 88
4 1004 Dewi 7B <NA> <NA> NAAkhiran bawaan .x dan .y tidak menjelaskan asal kolomnya, sehingga argumen suffix diberikan untuk menggantinya. Hasil kedua menampilkan kelas_siswa dan kelas_nilai yang langsung terbaca asalnya. Apabila nama kuncinya berbeda, argumen by menerima vektor bernama yang menyandingkan nama pada tabel kiri dengan nama pada tabel kanan. Tabel nilai2 berikut memakai kolom id_siswa sebagai ganti nis.
nilai2 <- rename(nilai, id_siswa = nis)
left_join(siswa, nilai2, by = c("nis" = "id_siswa"))# A tibble: 5 × 5
nis nama kelas mata_pelajaran nilai
<dbl> <chr> <chr> <chr> <dbl>
1 1001 Ali 7A Matematika 80
2 1002 Budi 7A Matematika 75
3 1002 Budi 7A Bahasa Indonesia 82
4 1003 Citra 7B Matematika 88
5 1004 Dewi 7B <NA> NAPemetaan by = c("nis" = "id_siswa") menghasilkan gabungan yang persis sama dengan by = "nis", dan kolom id_siswa tidak ikut muncul di hasil karena telah habis dipakai sebagai kunci. Kolom kunci dari tabel kanan memang tidak pernah ikut dalam hasil, sehingga tidak ada duplikasi kolom kunci pada gabungannya.
Filter antar tabel dengan semi_join dan anti_join#
Kadang yang dibutuhkan bukan kolom tambahan, melainkan pemilihan baris. Fungsi semi_join() memilih baris kiri yang memiliki pasangan pada tabel kanan, sedangkan anti_join() memilih baris yang tidak memiliki pasangan. Keduanya tidak menambahkan kolom apa pun dari tabel kanan.
semi_join(siswa, nilai, by = "nis")
anti_join(siswa, nilai, by = "nis")# A tibble: 3 × 3
nis nama kelas
<dbl> <chr> <chr>
1 1001 Ali 7A
2 1002 Budi 7A
3 1003 Citra 7B
# A tibble: 1 × 3
nis nama kelas
<dbl> <chr> <chr>
1 1004 Dewi 7BHasil semi_join() berisi tiga siswa yang sudah memiliki nilai, sedangkan anti_join() menyisakan Dewi yang belum tercatat. Pasangan fungsi ini lazim dipakai untuk memeriksa kelengkapan data, misalnya mencari siswa tanpa nilai sebelum analisis dimulai. Karena tidak ada kolom kanan yang ditambahkan, hasilnya aman dipakai sebagai tabel utama tanpa penggandaan baris.
Kesalahan umum#
Kesalahan pertama adalah memanggil join tanpa argumen by ketika tidak ada nama kolom yang sama. Pada tabel siswa dan nilai2, seluruh nama kolomnya berbeda, sehingga dplyr tidak dapat menebak kuncinya. Kesalahan ini sering terjadi tepat setelah kolom kunci diganti namanya.
left_join(siswa, nilai2)Error in left_join(siswa, nilai2) : `by` must be supplied when `x` and `y` have no common variables.
ℹ Use `cross_join()` to perform a cross-join.Pesan `by` must be supplied muncul ketika kedua tabel tidak memiliki variabel bersama. Perbaikannya adalah menyebut kunci secara eksplisit, seperti by = c("nis" = "id_siswa"). Saran cross_join() pada pesan error tersebut berlaku untuk kebutuhan lain, yaitu hasil kali seluruh pasangan baris kedua tabel, dan bukan tujuan penggabungan siswa dengan nilai.
Kesalahan kedua adalah menganggap jumlah baris hasil pasti sama dengan tabel kiri. Seperti diperlihatkan pada bagian kecocokan ganda, baris tambahan muncul tanpa peringatan. Perbandingan nrow() sebelum dan sesudah join menjadi kebiasaan yang murah, tetapi berhasil menangkap penggandaan baris lebih awal.
nrow(siswa)
nrow(left_join(siswa, ganda, by = "nis"))[1] 4
[1] 5Selisih satu baris pada contoh di atas sesuai dengan satu kecocokan ganda yang ada. Apabila selisihnya tidak dapat dijelaskan, telusuri baris yang duplikat pada tabel kanan dengan count() sebelum melanjutkan analisis. Pemeriksaan yang sama juga bermanfaat setelah full_join() dan right_join() karena keduanya dapat menambah baris dari tabel kanan.
Ringkasan#
- Data dipisah per entitas dan dihubungkan lewat kunci primer pada tabel utama serta kunci asing pada tabel pendamping.
left_join()mempertahankan seluruh baris kiri, mengisi kolom tanpa pasangan dengan NA, dan melipatgandakan baris pada kecocokan ganda.inner_join()hanya menyimpan baris berpasangan, sedangkanfull_join()mempertahankan baris kedua tabel.- Nama kolom bentrok ditangani dengan
suffix, dan kunci yang berbeda nama ditulis sebagaiby = c("nis" = "id_siswa"). semi_join()dananti_join()memfilter baris kiri berdasarkan keberadaan pasangan tanpa menambah kolom.
Referensi#
- dplyr: A Grammar of Data Manipulation — tidyverse — dokumentasi resmi dplyr, termasuk halaman keluarga join beserta aturan pencocokan kuncinya.
- R for Data Science, edisi ke-2 — Wickham, Çetinkaya-Rundel, Grolemund — bagian Joins membahas kunci primer, kunci asing, dan pilihan jenis join.