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 transformasiMethod pandas utamaHasil
rapikan_cabang()assign() + strNama 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.

python
"""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:

text
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 144000

Mari kita bedah keputusan pentingnya:

  1. rapikan_cabang() menjalankan df["cabang"].str.strip().str.title(). Nilai jakarta dan JAKARTA sama-sama berubah menjadi Jakarta; langkah ini wajib dikerjakan sebelum groupby() atau merge(), karena pandas menganggap string yang berbeda sebagai kelompok yang berbeda.
  2. beri_label_kategori() memakai map() dengan kamus KODE_KATEGORI. Method map() menerapkan kamus nilai-per-nilai; kode yang tidak terdaftar akan menjadi NaN, sehingga kamus ini juga berfungsi sebagai daftar kode resmi.
  3. hitung_total() melakukan rename(columns={"harga": "harga_satuan"}) sebelum assign(total=...). Penamaan yang jelas membuat rumus jumlah * harga_satuan langsung terbaca.
  4. buang_duplikat() memanggil drop_duplicates(subset="id_pesanan", keep="first"). Argumen subset membatasi pemeriksaan pada kolom id_pesanan, dan keep="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 cabang harus selesai sebelum groupby() dijalankan; sebaliknya ringkasan terpecah antara JAKARTA dan Jakarta.
  • Buang duplikat sebelum menghitung total omzet. Duplikat P005 bernilai 90000; bila drop_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 .