Join

Menggabungkan dua tabel dengan keluarga left_join dan pencocokan kunci.

Daftar isi

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.

r Contoh: tabel siswa dan tabel nilai
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
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: 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          88

Tabel 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.

r Contoh: menggabungkan siswa dengan nilai
left_join(siswa, nilai, by = "nis")
text Keluaran
# 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>                NA

Kelima 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.

r Contoh: kecocokan ganda pada kunci
ganda <- tribble(
  ~nis,  ~mata_pelajaran, ~nilai,
  1002,  "Matematika",    75,
  1002,  "Matematika",    90
)
left_join(siswa, ganda, by = "nis")
text Keluaran
# 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>              NA

Baris 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.

FungsiBaris 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.

r Contoh: inner_join hanya menyimpan baris berpasangan
inner_join(siswa, nilai, by = "nis")
text Keluaran
# 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          88

Fungsi 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.

r Contoh: full_join mempertahankan baris tanpa pasangan
nilai_lengkap <- add_row(nilai, nis = 1099, mata_pelajaran = "Matematika", nilai = 70)
full_join(siswa, nilai_lengkap, by = "nis")
text Keluaran
# 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          70

Baris 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.

r Contoh: kolom kelas yang bentrok
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"))
text Keluaran
# 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>              NA

Akhiran 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.

r Contoh: kunci dengan nama yang berbeda
nilai2 <- rename(nilai, id_siswa = nis)
left_join(siswa, nilai2, by = c("nis" = "id_siswa"))
text Keluaran
# 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>                NA

Pemetaan 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.

r Contoh: pemilihan baris dengan semi_join dan anti_join
semi_join(siswa, nilai, by = "nis")
anti_join(siswa, nilai, by = "nis")
text Keluaran
# 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  7B

Hasil 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.

r Contoh: join tanpa kunci yang dapat ditebak
left_join(siswa, nilai2)
text Keluaran
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.

r Contoh: membandingkan jumlah baris sebelum dan sesudah join
nrow(siswa)
nrow(left_join(siswa, ganda, by = "nis"))
text Keluaran
[1] 4
[1] 5

Selisih 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, sedangkan full_join() mempertahankan baris kedua tabel.
  • Nama kolom bentrok ditangani dengan suffix, dan kunci yang berbeda nama ditulis sebagai by = c("nis" = "id_siswa").
  • semi_join() dan anti_join() memfilter baris kiri berdasarkan keberadaan pasangan tanpa menambah kolom.

Referensi#