Chapter Schema Validation membedah apa yang harus diperiksa pada sebuah skema; chapter Great Expectations menyediakan framework lengkap dengan suite dan laporan. Kadang kita hanya butuh satu hal: memvalidasi DataFrame di dalam fungsi pipeline, secepat pembuatan tipe, dengan galat yang bisa ditangkap seperti exception biasa. Pandera mengisi celah tersebut — skema dideklarasikan sebagai objek Python, validasi berjalan satu panggilan, dan hasil kegagalan berbentuk DataFrame yang mudah dianalisis. Chapter ini membangun skema tabel pesanan TokoKita, mengujinya pada data yang bermasalah, lalu memasangnya sebagai penjaga pintu masuk pipeline.

Skema sebagai Objek yang Bisa Dijalankan #

Inti pandera adalah DataFrameSchema: kamus kolom dengan tipe dan batasan. Setiap kolom dinyatakan satu Column, dan setiap batasan satu Check — Check.ge(1) untuk nilai minimum, Check.in_range untuk rentang, Check.isin untuk kosakata tetap, Check.str_matches untuk format teks. Opsi strict=True menuntut tidak ada kolom liar di luar skema, sehingga kolom baru dari upstream tidak lolos diam-diam.

Filosofi pandera adalah fail fast: data yang tidak sesuai skema ditolak sebelum sempat mengalir ke transformasi berikutnya, bukan dibiarkan lewat lalu muncul sebagai angka aneh di dashboard. Untuk data yang hampir sesuai — misalnya angka yang dikirim sebagai teks — opsi coerce=True pada level kolom atau skema akan mengonversi tipe terlebih dahulu sebelum memeriksa batasan.

Validasi Data Baik dan Buruk #

Instalasi cukup pip install pandera. Skrip berikut mendefinisikan skema, memvalidasikan DataFrame sehat, lalu memvalidasikan DataFrame rusak dengan lazy=True — mode yang mengumpulkan seluruh pelanggaran sebelum melaporkan, alih-alih berhenti di pelanggaran pertama. DataFrame df_buruk sengaja memuat empat jenis masalah sekaligus: id_pesanan ganda, status PAID di luar kosakata, total yang keluar rentang di dua arah, dan email yang bukan format alamat sah. Seluruh skrip dijalankan nyata dengan pandera 0.34 di lingkungan penulisan materi:

python
"""Chapter pandera: DataFrameSchema + Check, lazy validation, DataFrameModel,
dan integrasi ke fungsi pipeline."""

import pandera.pandas as pa
from pandera import Check, Column, Field
import pandas as pd

schema_pesanan = pa.DataFrameSchema(
    {
        "id_pesanan": Column(int, Check.ge(1), unique=True),
        "status": Column(str, Check.isin(["lunas", "belum", "dibatalkan"])),
        "total": Column(float, Check.in_range(0, 1_000_000), nullable=True),
        "email": Column(str, Check.str_matches(r"^[\w.+-]+@example\.com$"), nullable=True),
    },
    strict=True,
)

df_baik = pd.DataFrame({
    "id_pesanan": [101, 102, 103],
    "status": ["lunas", "belum", "lunas"],
    "total": [250_000.0, 90_000.0, None],
    "email": ["[email protected]", "[email protected]", None],
})

df_buruk = pd.DataFrame({
    "id_pesanan": [201, 202, 202, 204],
    "status": ["lunas", "PAID", "belum", "lunas"],
    "total": [310_000.0, 1_250_000.0, 45_000.0, -2_000.0],
    "email": ["[email protected]", "bukan-email", "[email protected]", None],
})

print("== Validasi df_baik (lazy=False, lempar galat bila gagal) ==")
df_bersih = schema_pesanan.validate(df_baik)
print(f"lolos: {len(df_bersih)} baris, kolom {list(df_bersih.columns)}")

print()
print("== Validasi df_buruk (lazy=True, kumpulkan semua kegagalan) ==")
try:
    schema_pesanan.validate(df_buruk, lazy=True)
except pa.errors.SchemaErrors as err:
    print(f"total kegagalan: {len(err.failure_cases)}")
    print(err.failure_cases[["schema_context", "column", "check", "failure_case"]]
          .to_string(index=False))

print()
print("== DataFrameModel: skema sebagai class ==")


class SkemaPesanan(pa.DataFrameModel):
    id_pesanan: pa.typing.Series[int] = Field(unique=True, ge=1)
    status: pa.typing.Series[str] = Field(isin=["lunas", "belum", "dibatalkan"])
    total: pa.typing.Series[float] = Field(in_range=(0, 1_000_000), nullable=True)
    email: pa.typing.Series[str] = Field(
        str_matches=r"^[\w.+-]+@example\.com$", nullable=True
    )

    class Config:
        strict = True


df_model = SkemaPesanan.validate(df_baik)
print(f"lolos lewat model: {len(df_model)} baris")

print()
print("== Integrasi ke pipeline: tolak data tidak valid di batas ingestion ==")


def muat_pesanan(df: pd.DataFrame) -> pd.DataFrame:
    """Pintu masuk data: validasi dulu, baru lanjut ke transform."""
    try:
        return schema_pesanan.validate(df, lazy=True)
    except pa.errors.SchemaErrors as err:
        print(f"REJECTED: {len(err.failure_cases)} pelanggaran skema "
              f"di kolom {sorted(err.failure_cases['column'].unique())}")
        raise


print("muat df_baik :", "OK" if muat_pesanan(df_baik) is not None else "-")
try:
    muat_pesanan(df_buruk)
except pa.errors.SchemaErrors:
    print("df_buruk ditolak, data mentah tidak pernah masuk ke tabel hasil")

Output yang Dihasilkan:

text
== Validasi df_baik (lazy=False, lempar galat bila gagal) ==
lolos: 3 baris, kolom ['id_pesanan', 'status', 'total', 'email']

== Validasi df_buruk (lazy=True, kumpulkan semua kegagalan) ==
total kegagalan: 6
schema_context     column                                  check failure_case
        Column id_pesanan                       field_uniqueness          202
        Column id_pesanan                       field_uniqueness          202
        Column     status isin(['lunas', 'belum', 'dibatalkan'])         PAID
        Column      total                   in_range(0, 1000000)    1250000.0
        Column      total                   in_range(0, 1000000)      -2000.0
        Column      email str_matches('^[\w.+-]+@example\.com$')  bukan-email

== DataFrameModel: skema sebagai class ==
lolos lewat model: 3 baris

== Integrasi ke pipeline: tolak data tidak valid di batas ingestion ==
muat df_baik : OK
REJECTED: 6 pelanggaran skema di kolom ['email', 'id_pesanan', 'status', 'total']
df_buruk ditolak, data mentah tidak pernah masuk ke tabel hasil

Mari kita telaah bagian-bagian kuncinya:

  1. Baris 8-16 (schema_pesanan): setiap kolom memuat tipe, daftar Check, dan sifat tambahan; unique=True dan nullable=True adalah sifat kolom, bukan Check, sehingga keduanya tidak muncul sebagai baris kegagalan biasa melainkan sebagai field_uniqueness.
  2. lazy=False versus lazy=True: tanpa lazy, pandera mengangkat exception di pelanggaran pertama dan kita tidak tahu sisa masalah; dengan lazy=True, seluruh pelanggaran dikumpulkan ke err.failure_cases — sebuah DataFrame yang bisa difilter, dihitung, dan dicatat ke log.
  3. err.failure_cases: satu baris per pelanggaran dengan kolom column, check, dan failure_case; nilai 202 muncul dua kali karena duplikasi melibatkan dua baris data.
  4. SkemaPesanan (DataFrameModel): skema yang sama ditulis sebagai class dengan type annotation pa.typing.Series[int] dan Field(...); pendekatan ini menghidupkan static type checking dan lebih rapi saat skema dipakai lintas modul.
  5. muat_pesanan: validasi dipasang di batas ingestion — data mentah yang gagal skema tidak pernah menyentuh transformasi, sehingga tabel hasil hanya berisi data yang sudah dinyatakan sah.

Blok REJECTED pada output menunjukkan pola pelaporan yang ramah operasi: alih-alih menumpuk traceback, fungsi mencetak ringkasan jumlah pelanggaran beserta kolom yang terlibat sebelum mengangkat exception. Ringkasan semacam ini pas disalurkan sebagai event WARNING atau ERROR ke structured logging dari chapter Pipeline Logging, sehingga setiap penolakan terekam dengan run_id-nya dan bisa dijadikan metric tingkat penolakan per kolom.

Pandera atau Great Expectations #

AspekPanderaGreat Expectations
Bentuk aturankode Python di dalam pipelineexpectation suite sebagai artefak
Laporanexception + failure_casesstatistics, Data Docs HTML
Eksekusisinkron di fungsi, milidetikproses validasi tersendiri
Cocok untukpintu masuk pipeline, unit testvalidasi terjadwal lintas tim

Keduanya tidak saling menggantikan: banyak tim memakai pandera di dalam kode pipeline (validasi cepat, fail fast) dan GX sebagai lapisan audit terjadwal dengan laporan untuk pemilik data. Aturan praktisnya: bila aturan hanya dibaca mesin dan pipeline, pandera sudah cukup; begitu manusia non-engineer perlu membaca dan menyetujui aturan tersebut, suite GX beserta Data Docs lebih tepat. Dokumentasi lengkap pandera ada di https://pandera.readthedocs.io/ .

Skema yang ketat mencegah data buruk masuk — tetapi ketika data buruk sudah terlanjur masuk dan baru ketahuan minggu kemudian lewat angka dashboard yang salah, kita membutuhkan metode penyelidikan yang terstruktur. Chapter penutup section ini membahasnya: Investigasi Kegagalan Data.