Manipulasi data jarang selesai dalam satu operasi. Topik ini membahas tiga kelompok operasi DataFrames.jl yang paling sering dirangkai, yaitu join untuk menggabungkan dua tabel berdasarkan kolom kunci, subset dan transform untuk menyaring baris serta menambah kolom, dan penyusunan semuanya menjadi pipeline transformasi yang terbaca dari atas ke bawah. Contohnya tetap pada ranah nilai ujian dan daftar siswa agar perubahan datanya mudah diikuti.
Menggabungkan tabel dengan join#
Join menggabungkan baris dua tabel yang cocok pada kolom kuncinya. Fungsi innerjoin hanya mengambil pasangan baris yang kuncinya ada pada kedua tabel, sedangkan leftjoin mempertahankan seluruh baris tabel kiri, dan baris kiri tanpa pasangan mendapat nilai missing pada kolom tabel kanan. Kolom kunci disebut melalui argumen kata kunci on. Urutan baris hasil join tidak dijamin oleh DataFrames.jl, sehingga contoh di bawah mengurutkannya dengan sort sebelum ditampilkan.
using DataFrames
siswa = DataFrame(
id = [1, 2, 3, 4],
nama = ["Ali", "Budi", "Citra", "Dewi"],
)
nilai = DataFrame(
id = [2, 3, 5],
nilai = [85, 91, 77],
)
gabung = sort(innerjoin(siswa, nilai; on = :id), :id)
show(stdout, gabung; allrows = true, allcols = true)
println()
kiri = sort(leftjoin(siswa, nilai; on = :id), :id)
show(stdout, kiri; allrows = true, allcols = true)2×3 DataFrame
Row │ id nama nilai
│ Int64 String Int64
─────┼──────────────────────
1 │ 2 Budi 85
2 │ 3 Citra 91
4×3 DataFrame
Row │ id nama nilai
│ Int64 String Int64?
─────┼────────────────────────
1 │ 1 Ali missing
2 │ 2 Budi 85
3 │ 3 Citra 91
4 │ 4 Dewi missingPerhatikan tipe kolom nilai pada hasil leftjoin yang tertulis Int64?. Tanda tanya menandai kolom yang boleh memuat missing di samping bilangan bulat, karena Ali dan Dewi tidak memiliki pasangan pada tabel nilai. Pada hasil innerjoin, keduanya tidak muncul dan tipe kolom kembali menjadi Int64 murni. Pemilihan jenis join dengan demikian turut menentukan tipe kolom hasilnya.
DataFrames.jl menyediakan jenis join lain di samping keduanya, yaitu outerjoin yang mempertahankan baris dari kedua tabel, semijoin dan antijoin yang menyaring baris tabel kiri tanpa menambahkan kolom kanan, serta crossjoin yang memasangkan setiap baris tabel pertama dengan setiap baris tabel kedua. Topik ini membatasi pembahasannya pada innerjoin dan leftjoin karena keduanya paling sering dipakai pada pekerjaan data.
Menyaring baris dengan subset#
Fungsi subset menyaring baris berdasarkan syarat per kolom. Setiap pasangan :kolom => fungsi menuntut fungsi yang mengembalikan vektor Bool dengan panjang yang sama dengan jumlah baris, dan baris yang berpasangan dengan true dipertahankan. Operator perbandingan ditulis dengan titik di depannya, yaitu .>=, sehingga perbandingan berjalan secara broadcast untuk seluruh elemen kolom. Sama seperti select, fungsi subset mengembalikan salinan, sedangkan subset! memutasi DataFrame aslinya.
using DataFrames
nilai = DataFrame(
nama = ["Ali", "Budi", "Citra", "Dewi", "Eko", "Fajar"],
kelas = ["7A", "7A", "7A", "7B", "7B", "7B"],
nilai = [78, 85, 91, 82, 74, 88],
)
lulus = subset(nilai, :nilai => n -> n .>= 80)
show(stdout, lulus; allrows = true, allcols = true)4×3 DataFrame
Row │ nama kelas nilai
│ String String Int64
─────┼───────────────────────
1 │ Budi 7A 85
2 │ Citra 7A 91
3 │ Dewi 7B 82
4 │ Fajar 7B 88Empat baris tersisa adalah Budi, Citra, Dewi, dan Fajar, dengan urutan baris aslinya dipertahankan. Ali dengan nilai 78 dan Eko dengan nilai 74 tersaring karena berada di bawah ambang batas. Syarat berlapis ditulis dengan menambahkan pasangan kolom berikutnya, misalnya :kelas => k -> k .== "7A" untuk menyaring kelasnya sekaligus.
Menambah kolom dengan transform#
Fungsi transform menambah atau mengganti kolom dari hasil fungsi tanpa mengubah jumlah baris. Pembungkus ByRow menjalankan fungsi skalar per baris, sehingga ekspresi bersyarat seperti penentuan status lulus cukup ditulis untuk satu nilai dan diterapkan ke seluruh kolom. Pola :kolom => fungsi => :kolom_baru pada transform sama dengan pola yang dipakai oleh combine, sehingga keduanya terasa konsisten.
using DataFrames
nilai = DataFrame(
nama = ["Ali", "Budi", "Citra", "Dewi", "Eko", "Fajar"],
kelas = ["7A", "7A", "7A", "7B", "7B", "7B"],
nilai = [78, 85, 91, 82, 74, 88],
)
berstatus = transform(
nilai,
:nilai => ByRow(n -> n >= 80 ? "Lulus" : "Remedial") => :status,
)
println(berstatus.status)["Remedial", "Lulus", "Lulus", "Lulus", "Remedial", "Lulus"]Kolom status bernilai Remedial untuk Ali dan Eko, sedangkan empat siswa lainnya bernilai Lulus. Ekspresi n >= 80 ? "Lulus" : "Remedial" memakai operator ternary Julia yang mengembalikan salah satu dari dua nilai sesuai syaratnya. Kolom baru diletakkan pada posisi terakhir, dan nama kolomnya ditentukan melalui pasangan penamaan di ujung argumen. Fungsi transform menerima beberapa pasangan dalam satu panggilan, sehingga beberapa kolom baru dapat dibentuk sekaligus, dan varian transform! memutasi DataFrame aslinya.
Menyusun pipeline transformasi#
Operasi satu langkah jarang cukup, sehingga DataFrames.jl dirancang agar hasil satu fungsi menjadi masukan fungsi berikutnya. Pipeline pada contoh berikut terdiri atas empat tahap yang masing-masing menyimpan hasil antara ke variabel, sehingga setiap tahap dapat diperiksa sebelum dilanjutkan. Package DataFramesMeta menyediakan makro @chain untuk menulis rangkaian semacam ini dengan gaya berantai, tetapi contoh di sini sengaja memakai fungsi bawaan DataFrames.jl agar dependensinya minim.
# pipeline.jl
# Pipeline transformasi: transform, subset, groupby, combine, select.
using DataFrames, Statistics
nilai = DataFrame(
nama = ["Ali", "Budi", "Citra", "Dewi", "Eko", "Fajar"],
kelas = ["7A", "7A", "7A", "7B", "7B", "7B"],
nilai = [78, 85, 91, 82, 74, 88],
)
# 1. tambahkan kolom status dari nilai per baris
berstatus = transform(
nilai,
:nilai => ByRow(n -> n >= 80 ? "Lulus" : "Remedial") => :status,
)
# 2. ambil baris siswa yang lulus saja
lulus = subset(berstatus, :status => s -> s .== "Lulus")
# 3. agregasi per kelas
ringkasan = combine(
groupby(lulus, :kelas),
:nilai => mean => :rata_rata,
:nilai => length => :jumlah,
)
# 4. susun kolom yang disajikan
hasil = select(ringkasan, :kelas, :jumlah, :rata_rata)
println(names(hasil))
show(stdout, hasil; allrows = true, allcols = true)["kelas", "jumlah", "rata_rata"]
2×3 DataFrame
Row │ kelas jumlah rata_rata
│ String Int64 Float64
─────┼───────────────────────────
1 │ 7A 2 88.0
2 │ 7B 2 85.0Keluarannya menunjukkan dua baris hasil, yaitu kelas 7A dengan dua siswa lulus dan rata-rata 88.0, serta kelas 7B dengan dua siswa lulus dan rata-rata 85.0. Ali dan Eko tersaring pada tahap subset karena statusnya Remedial. Tahap terakhir select menyusun ulang kolom agar jumlah tampil sebelum rata_rata, dan names(hasil) pada keluaran mengonfirmasi urutannya.
Kesalahan umum#
Tiga kesalahan berikut paling sering muncul saat operasi tabel dirangkai. Yang pertama dapat direproduksi pada contoh berikut.
using DataFrames
siswa = DataFrame(
id = [1, 2, 3, 4],
nama = ["Ali", "Budi", "Citra", "Dewi"],
)
nilai = DataFrame(
id = [2, 3, 5],
nilai = [85, 91, 77],
)
try
innerjoin(siswa, nilai; on = :nomor)
catch e
println(typeof(e))
endArgumentError- Menulis nama kolom kunci yang tidak ada pada salah satu tabel. Contoh di atas memanggil
innerjoindengan:nomor, dan eksekusinya berhenti denganArgumentError. Cocokkan kunci kedua tabel melaluinamesterlebih dahulu. - Lupa operator titik pada fungsi
subset. Fungsi yang mengembalikan nilai tunggal sepertin -> n >= 80berhenti denganMethodError, sedangkan bentuk broadcastn -> n .>= 80mengembalikan vektorBoolyang diminta. - Memproses kolom hasil
leftjointanpa menanganimissing. Bungkus perhitungannya denganskipmissingapabila baris tanpa pasangan memang ingin dikesampingkan.
Ringkasan#
innerjoinmengambil pasangan yang cocok pada kedua tabel, sedangkanleftjoinmempertahankan seluruh baris kiri dan mengisi kolom kanan denganmissing.subsetmenyaring baris dengan fungsi yang mengembalikan vektorBool, dantransformmenambah kolom, baik per kolom maupun per baris melaluiByRow.- Pipeline transformasi dirangkai dari fungsi yang mengembalikan salinan, sehingga setiap tahap dapat disimpan dan diperiksa.
- Urutan baris hasil join tidak dijamin, sehingga hasilnya diurutkan dengan
sortapabila urutannya penting.
Referensi#
- DataFrames.jl — JuliaData
— Dokumentasi resmi
innerjoin,leftjoin,subset,transform, dancombine. - DataFramesMeta.jl — JuliaData
— Dokumentasi resmi makro
@chainyang menyusun operasi DataFrames.jl secara berantai.