Menulis Python yang layak produksi untuk ML: NumPy & Pandas yang efisien, dataclasses & type hint sebagai kontrak data, testing sebagai jaring pengaman, dan pola refactor notebook menjadi kode modular yang bersih, testable, dan siap di-deploy

Setelah di episode 2 kita memahami siklus hidup dan arsitektur sistem ML, sekarang waktunya masuk ke dapur: menulis Python yang layak produksi. Perbedaan utama antara notebook riset dan kode produksi bukan pada algoritmanya, melainkan pada engineering di sekelilingnya: struktur, kontrak data, dan pengujian.
Notebook itu seperti lab note: sangat bagus untuk eksplorasi, sangat buruk untuk produksi. Kode produksi harus bisa dieksekusi kembali (reproducible), diuji otomatis, dan dibaca oleh orang lain — termasuk kalian sendiri enam bulan kemudian. Episode ini membangun tiga keterampilan inti: memakai NumPy/Pandas dengan benar, mendefinisikan kontrak data dengan dataclasses & type hint, dan refactor notebook menjadi modul yang bersih.
Aturan emas data science: jangan pernah memproses baris per baris dengan loop Python jika bisa divector-kan. Perbandingan kecil:
import numpy as np
x = np.random.rand(1_000_000)
# Lambat: loop Python
result_slow = [v**2 + 1 for v in x]
# Cepat: vectorized
result_fast = x**2 + 1Perbedaan kecepatannya bisa puluhan hingga ratusan kali lipat. Latency waktu komputasi ini berlipat ganda saat data berukuran jutaan baris — kebutuhan umum di produksi.
import pandas as pd
df = pd.read_parquet("data/user_events.parquet")
# Filter + kolom baru sekaligus
active = df[df["event_count"] > 10].assign(
engagement = lambda d: d["event_count"] / d["days_seen"]
)
print(active[["user_id", "engagement"]].head())Pola yang perlu dihindari: df.iterrows() untuk transformasi, pd.concat di dalam loop, dan chained assignment (df[a][b] = c) yang bisa menimbulkan SettingWithCopyWarning.
Di produksi, data mengalir antar-modul. Kesalahan tipe kolom adalah sumber bug paling umum — dan paling sulit dilacak. Dataclasses + type hint mengubah data mentah menjadi kontrak yang bisa diperiksa.
from dataclasses import dataclass
@dataclass(frozen=True)
class UserFeatures:
user_id: int
event_count: int
days_seen: int
churned: bool
@property
def engagement(self) -> float:
return self.event_count / max(self.days_seen, 1)Manfaatnya tiga kali lipat: kode bisa dibaca (nama kolom eksplisit), aman terhadap typo (field dijamin ada), dan mudah diuji. Saat data masuk dari Pandas, kalian bisa mengkonversi row ke dataclass lalu validasi dengan pydantic jika membutuhkan cek runtime.
Kode ML bukan pengecualian dari aturan software engineering — ia justru butuh test lebih banyak karena tambahan lapisan data. Minimal, uji tiga hal:
normalize, encode, aggregate menghasilkan output yang diharapkan untuk input kecil.0..1.import pandas as pd
from sklearn.preprocessing import MinMaxScaler
def scale_numeric(df: pd.DataFrame, cols: list[str]) -> pd.DataFrame:
out = df.copy()
out[cols] = MinMaxScaler().fit_transform(out[cols])
return out
def test_scale_numeric_maps_to_unit_range():
df = pd.DataFrame({"age": [10, 20, 30]})
result = scale_numeric(df, ["age"])
assert result["age"].min() == 0.0
assert result["age"].max() == 1.0Jalankan test dengan pytest — ini akan menjadi gerbang CI kalian di episode 10.
Ini keterampilan paling praktis yang bisa langsung kalian pakai. Pola refactor yang baik membagi notebook menjadi empat bagian modular:
src/
data.py # loading & cleaning
features.py # transformasi feature
train.py # training & evaluasi
predict.py # inference
tests/
test_features.pydata untuk semua loading & cleaning, features untuk transformasi, train untuk pelatihan & evaluasi.main() yang bisa dijalankan dari terminal — bukan dari notebook.from src.data import load_and_clean
from src.features import build_features
from src.train import train_model
def main():
df = load_and_clean("data/raw.parquet")
X, y = build_features(df)
model, metrics = train_model(X, y)
print(metrics)
if __name__ == "__main__":
main()Tip
Prinsip yang membantu saat refactor: jangan mengubah perilaku saat merapikan struktur. Pertama, "bekukan" hasil notebook dengan menyimpan output reference (misal metrik model) — lalu refactor dan pastikan outputnya sama persis. Refactor yang mengubah perilaku adalah fitur baru, dan harus lewat proses evaluasi tersendiri.
| Pitfall | Dampak | Pencegahan |
|---|---|---|
| Loop Python pada kolom | Lambat, sulit diskalakan | Selalu vectorize |
| Global state di notebook | Output tidak reproducible | Semua lewat parameter fungsi |
| Tanpa type hint | Typo kolom baru ketahuan saat produksi | Dataclass + type hint |
| Tidak ada test transformasi | Regresi diam-diam saat feature berubah | Test di CI |
| Konfigurasi hardcode | Eksperimen tidak bisa dibandingkan | Pisah config dari code |
Pada episode 3 ini, kalian telah mengasah keterampilan Python produksi:
Di episode 4 selanjutnya kita akan membahas Feature Engineering & Transformations — encoding, scaling, feature selection, dan cara menyusun feature pipeline yang konsisten antara training dan serving, termasuk praktik feature pipeline untuk skala produksi. Pastikan struktur modul kalian sudah rapi, karena di episode 4 kita akan membangun di atasnya! Sampai jumpa di episode 4!