Dashboard TokoKita menampilkan pendapatan harian Rp2,1 juta padahal kasir mencatat Rp3,4 juta. Data ada, pipeline berstatus sukses, tetapi angka yang dipercaya salah. Masalah seperti ini jarang disebabkan satu kesalahan besar; biasanya akumulasi nilai kosong, baris ganda, dan format yang bergeser diam-diam. Sebelum kita bisa memperbaiki kualitas data, kita perlu bahasa bersama untuk mendefinisikannya — dan itulah peran dimensi kualitas data (data quality dimensions). Chapter ini memetakan enam dimensi yang paling umum dipakai industri, lalu menghitung skor tiap dimensi pada tabel pesanan yang sengaja dibuat bermasalah.

Mengapa Kualitas Data Harus Diukur #

Kata “kualitas” tanpa definisi operasional hanya menghasilkan debat tanpa akhir: tim analytics bilang data pesanan buruk, tim kasir bilang tidak. Dimensi kualitas memecah kata “buruk” menjadi pernyataan yang bisa dihitung: berapa persen sel kosong, berapa baris melanggar format, berapa jam umur data terbaru. Angka-angka tersebut mengubah perdebatan menjadi pekerjaan: setiap dimensi yang gagal punya pemilik proses yang jelas dan langkah perbaikan yang berbeda.

Pengukuran juga memungkinkan kita menetapkan target layanan — misalnya “completeness minimal 99%” — lalu memantau trennya mingguan, persis seperti uptime untuk server. Tanpa angka, penilaian kualitas bergantung pada siapa yang paling keras bersuara di rapat.

Enam Dimensi Kualitas Data #

DimensiPertanyaan KunciContoh Pelanggaran di TokoKita
CompletenessApakah semua nilai yang seharusnya ada benar-benar ada?kolom pelanggan kosong; partisi harian gagal load
ValidityApakah nilai sesuai format dan rentang yang diharapkan?total bernilai -5000; email tanpa tanda @
UniquenessApakah satu entitas tidak tercatat ganda?ORD-002 muncul dua kali di tabel pesanan
ConsistencyApakah fakta yang sama bernilai sama di semua tabel?total pesanan berbeda dari total pembayaran
FreshnessApakah data cukup baru untuk keputusan hari ini?data berhenti di hari Jumat padahal hari Senin
AccuracyApakah nilai mencerminkan kenyataan di lapangan?alamat pelanggan sudah tidak sesuai domisili

Lima dimensi pertama dapat dihitung langsung dari data itu sendiri: kita membandingkan isi tabel dengan aturan, tabel pembanding, atau penanda waktu. Accuracy adalah dimensi yang paling sulit diotomatisasi karena menuntut sumber kebenaran dari dunia nyata — hasil audit kasir, konfirmasi pelanggan, atau sensor — sehingga di praktik biasanya diverifikasi lewat sampling manual. Enam dimensi ini juga saling bertukar: proses deduplikasi yang agresif menaikkan uniqueness tetapi bisa membuang baris pesanan sungguhan dan menurunkan completeness.

Menghitung Skor Tiap Dimensi dengan pandas #

Kita ambil tabel pesanan berisi sepuluh baris dengan masalah campuran: dua sel kosong, satu order_id ganda, dua email tidak valid, satu total negatif. Titik pemeriksaan kita kunci pada waktu tetap supaya skor freshness deterministik:

python
"""Skor dimensi kualitas data TokoKita: completeness, uniqueness, validity, freshness."""
import datetime as dt
import re

import pandas as pd

# Titik pemeriksaan tetap supaya umur data deterministik antar run.
SEKARANG = dt.datetime(2026, 10, 20, 9, 0)
SLA_FRESHNESS_JAM = 24

pesanan = pd.DataFrame(
    [
        ["ORD-001", "Budi", "[email protected]", 150000, dt.datetime(2026, 10, 20, 6, 12)],
        ["ORD-002", "Siti", "[email protected]", 320000, dt.datetime(2026, 10, 20, 5, 40)],
        ["ORD-003", "Dewi", "dewi example com", 99000, dt.datetime(2026, 10, 20, 4, 55)],
        [None, "Budi", "[email protected]", 430000, dt.datetime(2026, 10, 19, 22, 8)],
        ["ORD-005", None, "[email protected]", -5000, dt.datetime(2026, 10, 19, 18, 30)],
        ["ORD-006", "Siti", "[email protected]", 260000, dt.datetime(2026, 10, 19, 14, 2)],
        ["ORD-007", "Dewi", "[email protected]", 87000, dt.datetime(2026, 10, 19, 9, 45)],
        ["ORD-002", "Siti", "[email protected]", 320000, dt.datetime(2026, 10, 20, 5, 40)],
        ["ORD-009", "Budi", "@example.com", 175000, dt.datetime(2026, 10, 18, 20, 17)],
        ["ORD-010", "Dewi", "[email protected]", 210000, dt.datetime(2026, 10, 18, 8, 5)],
    ],
    columns=["order_id", "pelanggan", "email", "total", "created_at"],
)

# Completeness: persentase sel yang terisi.
sel_kosong = int(pesanan.isna().sum().sum())
completeness = 1 - sel_kosong / pesanan.size

# Uniqueness: order_id unik di antara baris yang terisi.
terisi = pesanan["order_id"].dropna()
uniqueness = terisi.nunique() / len(terisi)

# Validity: email sesuai pola dan total tidak negatif.
pola_email = r"^[\w.+-]+@[\w-]+\.[\w.-]+$"
email_ok = pesanan["email"].fillna("").str.match(pola_email)
total_ok = pesanan["total"] >= 0
validity = (email_ok & total_ok).sum() / len(pesanan)

# Freshness: umur baris terbaru terhadap titik pemeriksaan.
terbaru = pesanan["created_at"].max()
umur_jam = (SEKARANG - terbaru).total_seconds() / 3600

print(f"baris diperiksa : {len(pesanan)}")
print(f"{'dimensi':14}{'skor':>8}  keterangan")
print(f"{'completeness':14}{completeness:>7.1%}  {sel_kosong} sel kosong dari {pesanan.size}")
print(f"{'uniqueness':14}{uniqueness:>7.1%}  {len(terisi) - terisi.nunique()} order_id ganda dari {len(terisi)} terisi")
print(f"{'validity':14}{validity:>7.1%}  {int((~(email_ok & total_ok)).sum())} baris gagal aturan email/total")
status = "SEGAR" if umur_jam <= SLA_FRESHNESS_JAM else "TIDAK SEGAR"
print(f"{'freshness':14}{umur_jam:>6.1f}j  baris terbaru {terbaru} -> {status} (SLA {SLA_FRESHNESS_JAM} jam)")

Output yang Dihasilkan:

text
baris diperiksa : 10
dimensi           skor  keterangan
completeness    96.0%  2 sel kosong dari 50
uniqueness      88.9%  1 order_id ganda dari 9 terisi
validity        70.0%  3 baris gagal aturan email/total
freshness        2.8j  baris terbaru 2026-10-20 06:12:00 -> SEGAR (SLA 24 jam)

Mari kita telaah baris-baris kode di atas:

  1. Baris 8-9 (SEKARANG dan SLA_FRESHNESS_JAM): titik pemeriksaan dan ambang SLA ditulis sebagai konstanta. Di pipeline produksi, SEKARANG diganti datetime.now() dan SLA dibaca dari konfigurasi.
  2. Baris 11-25 (pesanan = pd.DataFrame(...)): sepuluh baris berisi lima jenis masalah — sel kosong di baris 4 dan 5, email rusak di baris 3 dan 9, total negatif di baris 5, dan ORD-002 ganda di baris 2 serta 8.
  3. Baris 28-29 (completeness): isna() menandai sel kosong, lalu jumlahnya dibagi ukuran tabel; hasil 48 dari 50 sel terisi berarti 96%.
  4. Baris 32-33 (uniqueness): dropna() membuang baris kosong lebih dulu supaya None tidak dihitung sebagai nilai ganda; sembilan baris terisi hanya punya delapan nilai unik.
  5. Baris 36-39 (validity): satu baris dinyatakan valid hanya bila email cocok pola regex dan total tidak negatif — dua aturan digabung dengan operator &.
  6. Baris 50-51 (freshness): umur dihitung dari baris created_at paling baru, lalu dibandingkan dengan SLA untuk menentukan status.

Membaca Skor dan Menindaklanjuti #

Skor di atas memperlihatkan pola yang sering muncul di dunia nyata: data sangat segar (2,8 jam) tetapi validitasnya rendah (70%). Freshness yang baik justru bisa menutupi masalah lain karena pipeline tetap berjalan dan data tetap mengalir — hanya isinya yang melanggar aturan. Setiap skor rendah mengarah ke tindakan dan pemilik yang berbeda:

  • completeness rendah: selidiki proses pengisian form di aplikasi kasir — pemiliknya tim aplikasi.
  • uniqueness rendah: periksa logika deduplikasi dan idempotensi di tahap ingestion.
  • validity rendah: tegakkan aturan format dan rentang di gerbang masuk data.
  • freshness rendah: telusuri jadwal pipeline dan kesehatan sumber upstream.

Fungsi pandas yang dipakai di sini (isna(), nunique(), str.match()) terdokumentasi lengkap di dokumentasi pandas . Mengukur dimensi setelah data jatuh ke tabel hanya separuh pekerjaan; separuh lainnya adalah mencegah data bermasalah masuk sejak awal — pekerjaan yang dibahas pada chapter berikutnya: Data Validation.