Tahap transform adalah tempat sebagian besar pekerjaan pipeline berlangsung. Data dari chapter sebelumnya sudah menyatu dalam satu DataFrame, tetapi isinya masih menuntut perbaikan: nama cabang ditulis dengan kapitalisasi yang berbeda-beda, kode kategori seperti MNM belum berarti bagi pembaca laporan, kolom total belum ada, dan terdapat baris duplikat. Chapter ini menyusun keempat perbaikan tersebut menjadi fungsi transformasi kecil yang berurutan, mengikuti pola fungsi-per-tahap yang kita bangun pada chapter Membangun Pipeline Data. Setiap fungsi menerima DataFrame dan mengembalikan DataFrame baru, sehingga urutan pengerjaannya mudah dipindah dan diperiksa.
Empat Transformasi Standar #
Pipeline toko Toko Berkah Abadi memakai empat transformasi yang hampir selalu muncul pada pekerjaan data nyata:
| Fungsi transformasi | Method pandas utama | Hasil |
|---|---|---|
rapikan_cabang() | assign() + str | Nama cabang seragam: Jakarta, Bandung, Surabaya |
beri_label_kategori() | map() | Kode MNM/BPK menjadi label lengkap |
hitung_total() | rename() + assign() | Kolom harga menjadi harga_satuan, kolom total ditambahkan |
buang_duplikat() | drop_duplicates() | Satu baris per id_pesanan |
Kombinasi method di atas memang sengaja dipilih dari operasi paling umum: assign(), rename(), map(), dan drop_duplicates(). Bila Anda sudah menempuh section Transformasi Data, semua method ini seharusnya terasa akrab; perbedaannya kini method tersebut dipakai sebagai bagian dari pipeline yang utuh.
Skrip Transformasi Lengkap #
Script berikut memuat data mentah tujuh baris — termasuk satu duplikat P005 dan cabang jakarta dengan spasi sisa — lalu menjalankan empat fungsi transformasi berurutan.
"""Tahap transform: fungsi-fungsi pembersihan untuk pesanan Toko Berkah Abadi."""
import pandas as pd
KODE_KATEGORI = {"MNM": "Minuman", "BPK": "Bahan Pokok"}
def muat_pesanan_mentah() -> pd.DataFrame:
"""Simulasi hasil extract: data mentah dengan cabang tidak rapi dan duplikat."""
return pd.DataFrame({
"id_pesanan": ["P001", "P002", "P003", "P004", "P005", "P005", "P006"],
"cabang": ["jakarta ", "Bandung", "Surabaya", "JAKARTA", "Bandung",
"Bandung", "Surabaya"],
"produk": ["Kopi Arabika", "Teh Melati", "Beras Premium", "Gula Pasir",
"Kopi Arabika", "Kopi Arabika", "Minyak Goreng"],
"kode": ["MNM", "MNM", "BPK", "BPK", "MNM", "MNM", "BPK"],
"jumlah": [2, 3, 1, 4, 2, 2, 3],
"harga": [45000, 18000, 62000, 14000, 45000, 45000, 32000],
})
def rapikan_cabang(df: pd.DataFrame) -> pd.DataFrame:
"""Samakan penulisan nama cabang: buang spasi dan samakan kapitalisasi."""
return df.assign(cabang=df["cabang"].str.strip().str.title())
def beri_label_kategori(df: pd.DataFrame) -> pd.DataFrame:
"""Ganti kode kategori dengan label yang mudah dibaca lewat map."""
return df.assign(kategori=df["kode"].map(KODE_KATEGORI)).drop(columns="kode")
def hitung_total(df: pd.DataFrame) -> pd.DataFrame:
"""Ganti nama kolom harga menjadi harga_satuan lalu tambahkan kolom total."""
df = df.rename(columns={"harga": "harga_satuan"})
return df.assign(total=df["jumlah"] * df["harga_satuan"])
def buang_duplikat(df: pd.DataFrame) -> pd.DataFrame:
"""Sisakan satu baris per id_pesanan."""
return df.drop_duplicates(subset="id_pesanan", keep="first")
if __name__ == "__main__":
df = muat_pesanan_mentah()
print(f"mentah : {len(df)} baris")
print(df[["id_pesanan", "cabang", "kode"]].to_string(index=False))
print()
df = rapikan_cabang(df)
print(f"rapikan_cabang : {df['cabang'].unique().tolist()}")
df = beri_label_kategori(df)
print(f"beri_label : {df['kategori'].unique().tolist()}")
df = hitung_total(df)
print(f"kolom sekarang : {list(df.columns)}")
df = buang_duplikat(df)
print(f"buang_duplikat : {len(df)} baris")
print()
df = df.sort_values(["cabang", "id_pesanan"]).reset_index(drop=True)
print("== Hasil transform ==")
print(df.to_string(index=False))
print()
ringkasan = (
df.groupby("cabang", as_index=False)
.agg(transaksi=("id_pesanan", "count"), omzet=("total", "sum"))
.sort_values("omzet", ascending=False)
.reset_index(drop=True)
)
print("== Ringkasan per cabang ==")
print(ringkasan.to_string(index=False))Output yang Dihasilkan:
mentah : 7 baris
id_pesanan cabang kode
P001 jakarta MNM
P002 Bandung MNM
P003 Surabaya BPK
P004 JAKARTA BPK
P005 Bandung MNM
P005 Bandung MNM
P006 Surabaya BPK
rapikan_cabang : ['Jakarta', 'Bandung', 'Surabaya']
beri_label : ['Minuman', 'Bahan Pokok']
kolom sekarang : ['id_pesanan', 'cabang', 'produk', 'jumlah', 'harga_satuan', 'kategori', 'total']
buang_duplikat : 6 baris
== Hasil transform ==
id_pesanan cabang produk jumlah harga_satuan kategori total
P002 Bandung Teh Melati 3 18000 Minuman 54000
P005 Bandung Kopi Arabika 2 45000 Minuman 90000
P001 Jakarta Kopi Arabika 2 45000 Minuman 90000
P004 Jakarta Gula Pasir 4 14000 Bahan Pokok 56000
P003 Surabaya Beras Premium 1 62000 Bahan Pokok 62000
P006 Surabaya Minyak Goreng 3 32000 Bahan Pokok 96000
== Ringkasan per cabang ==
cabang transaksi omzet
Surabaya 2 158000
Jakarta 2 146000
Bandung 2 144000Mari kita bedah keputusan pentingnya:
rapikan_cabang()menjalankandf["cabang"].str.strip().str.title(). NilaijakartadanJAKARTAsama-sama berubah menjadiJakarta; langkah ini wajib dikerjakan sebelumgroupby()ataumerge(), karena pandas menganggap string yang berbeda sebagai kelompok yang berbeda.beri_label_kategori()memakaimap()dengan kamusKODE_KATEGORI. Methodmap()menerapkan kamus nilai-per-nilai; kode yang tidak terdaftar akan menjadiNaN, sehingga kamus ini juga berfungsi sebagai daftar kode resmi.hitung_total()melakukanrename(columns={"harga": "harga_satuan"})sebelumassign(total=...). Penamaan yang jelas membuat rumusjumlah * harga_satuanlangsung terbaca.buang_duplikat()memanggildrop_duplicates(subset="id_pesanan", keep="first"). Argumensubsetmembatasi pemeriksaan pada kolomid_pesanan, dankeep="first"menyimpan kemunculan pertama — baris P005 kedua dihapus, tujuh baris menjadi enam.
Tahap terakhir skrip menambahkan sort_values(["cabang", "id_pesanan"]) supaya tabel hasil terbaca berurutan, lalu merangkum omzet per cabang dengan groupby() dan named aggregation. Ringkasan akhir menempatkan Surabaya di urutan pertama dengan omzet 158000, sementara selisih Jakarta (146000) dan Bandung (144000) hanya 2000 — angka yang rapat seperti ini baru bermakna bila data pembersihannya tepercaya, dan itulah alasan urutan transformasi penting.
Urutan yang Aman #
Perubahan kecil pada urutan transformasi bisa mengubah hasil akhir. Dua aturan urutan yang aman:
- Rapikan kunci sebelum agregasi. Normalisasi
cabangharus selesai sebelumgroupby()dijalankan; sebaliknya ringkasan terpecah antaraJAKARTAdanJakarta. - Buang duplikat sebelum menghitung total omzet. Duplikat P005 bernilai
90000; biladrop_duplicates()dijalankan setelah agregasi, omzet Bandung ikut terdorong naik dua kali lipat dari nilai seharusnya.
Hasil transformasi pada chapter ini baru kami perlakukan sebagai data yang “tampak bersih”. Sebelum data dikirim ke tujuan ekspor, pipeline membutuhkan gerbang pemeriksaan otomatis — schema dan aturan bisnis — yang dibangun pada chapter Validasi Data Pipeline.
Referensi resmi: pandas.DataFrame.assign , pandas.Series.map , dan pandas.DataFrame.drop_duplicates .