Ketiga sumber dari Bagian 1 kini berdiri di titik yang sama: CSV dengan tanggal DD/MM/YYYY, JSON dengan harga_total dan label kota campur, serta tabel SQLite dengan kolom kasir yang tidak diperlukan. Tugas Bagian 2 adalah mengubah ketiganya menjadi satu DataFrame bersih yang mengikuti satu skema, lalu menghitung omzet per transaksi. Kita melakukan ini dalam enam langkah yang berurutan: menstandardisasi tiap sumber, memilih kolom bersama, menggabungkan dengan pd.concat(), membuang duplikat lintas sumber, merapikan index, dan menambahkan kolom total. Hasil akhirnya 19 baris bersih dari 25 baris mentah, lengkap dengan kolom sumber yang mencatat asal tiap baris.

Skema Bersama sebagai Target Transformasi #

Transformasi bermakna bila targetnya jelas. Kita menetapkan sembilan kolom berikut sebagai skema bersama; setiap sumber lalu diperlakukan sesuai jaraknya ke skema itu.

Kolom targetMaknaAsal di tiap sumber
id_transaksiunique key gabungankode_trx, order_id, trx_id
tanggaldatetime64[us]DD/MM/YYYY, created_at + jam, tgl_trx teks
produk, kategorikatalog tetap enam barangproduk, item, nama_barang
kotapenulisan Title Casekota, kota_tujuan, wilayah
qtyint64 positifqty, jml_barang, jml
harga_satuanint64 rupiah per barangharga_satuan, harga, hasil bagi harga_total
sumberlineage: sistem asal barisditambahkan saat standarisasi

Tiga langkah pertama skrip menyeragamkan tiap sumber secara terpisah. Laporan CSV hanya butuh satu rename() (kode_trx menjadi id_transaksi), pd.to_datetime() dengan format="%d/%m/%Y", normalisasi kota dengan str.strip().str.title(), dan filter harga_satuan > 0 yang membuang satu baris kesalahan perekaman. Sumber JSON adalah yang paling jauh dari skema: setelah rename(), kita saring qty > 0, hitung harga_satuan = harga_total // qty, ubah created_at menjadi tanggal dengan pd.to_datetime().dt.normalize() untuk membuang komponen jam, lalu buang kolom perantara created_at dan harga_total dengan drop(). Sumber SQLite dinormalisasi dengan cara yang sama, ditambah pembuangan kolom kasir.

Menggabungkan dan Membuang Duplikat Lintas Sumber #

Langkah keempat memilih kolom skema bersama dari ketiga DataFrame, lalu pd.concat() menyatukannya dengan ignore_index=True agar index baru berjalan 0 sampai 22 tanpa duplikasi nomor. Langkah kelima adalah inti pembersihan proyek ini: drop_duplicates(subset="id_transaksi", keep="last") mengenali empat salinan berlebih — satu baris yang tercatat ganda di dalam laporan CSV dan tiga transaksi Cabang B yang muncul di laporan maupun di database kasir. Argumen keep="last" membuat versi dari kasir_sqlite yang tersisa, karena database kasir dianggap sumber resmi transaksi Cabang B. Karena penghapusan meninggalkan index bolong, reset_index(drop=True) menata ulang nomor baris sebelum kolom total dihitung sebagai qty * harga_satuan.

python
"""Standardisasi tiga sumber, gabungkan, lalu bersihkan menjadi satu DataFrame."""
from __future__ import annotations

import tempfile
from pathlib import Path

import pandas as pd

from data_sumber import muat_csv, muat_json, muat_sqlite


def main() -> None:
    with tempfile.TemporaryDirectory() as tmp:
        folder = Path(tmp)
        csv_raw = muat_csv(folder)
        json_raw = muat_json()
        sqlite_raw = muat_sqlite(folder)

        # 1. Laporan CSV: seragamkan nama kolom, ubah format tanggal, saring harga 0.
        csv_df = csv_raw.rename(columns={"kode_trx": "id_transaksi"})
        csv_df["tanggal"] = pd.to_datetime(csv_df["tanggal"], format="%d/%m/%Y")
        csv_df["kota"] = csv_df["kota"].str.strip().str.title()
        csv_df = csv_df[csv_df["harga_satuan"] > 0]
        csv_df["sumber"] = "laporan_csv"

        # 2. JSON marketplace: seragamkan nama kolom, saring qty 0,
        #    turunkan harga satuan dari harga total.
        json_df = json_raw.rename(
            columns={
                "order_id": "id_transaksi",
                "item": "produk",
                "kota_tujuan": "kota",
                "jml_barang": "qty",
            }
        )
        json_df = json_df[json_df["qty"] > 0]
        json_df["harga_satuan"] = json_df["harga_total"] // json_df["qty"]
        json_df["tanggal"] = pd.to_datetime(json_df["created_at"]).dt.normalize()
        json_df["kota"] = json_df["kota"].str.strip().str.title()
        json_df = json_df.drop(columns=["created_at", "harga_total"])
        json_df["sumber"] = "marketplace_json"

        # 3. SQLite kasir: seragamkan nama kolom, ubah tanggal teks, buang kolom kasir.
        sqlite_df = sqlite_raw.rename(
            columns={
                "trx_id": "id_transaksi",
                "tgl_trx": "tanggal",
                "nama_barang": "produk",
                "wilayah": "kota",
                "jml": "qty",
                "harga": "harga_satuan",
            }
        )
        sqlite_df["tanggal"] = pd.to_datetime(sqlite_df["tanggal"])
        sqlite_df = sqlite_df.drop(columns="kasir")
        sqlite_df["sumber"] = "kasir_sqlite"

        print("== Standarisasi per sumber ==")
        print(f"laporan_csv      : {len(csv_raw)} -> {len(csv_df)} baris (harga_satuan 0 dibuang)")
        print(f"marketplace_json : {len(json_raw)} -> {len(json_df)} baris (qty 0 dibuang)")
        print(f"kasir_sqlite     : {len(sqlite_raw)} -> {len(sqlite_df)} baris (sudah lengkap)")

        # 4. Pilih kolom skema bersama, lalu gabungkan ketiganya.
        kolom_inti = [
            "id_transaksi", "tanggal", "produk", "kategori", "kota", "qty", "harga_satuan",
        ]
        csv_df = csv_df[kolom_inti + ["sumber"]]
        json_df = json_df[kolom_inti + ["sumber"]]
        sqlite_df = sqlite_df[kolom_inti + ["sumber"]]
        gabung = pd.concat([csv_df, json_df, sqlite_df], ignore_index=True)

        # 5. Deduplikasi lintas sumber memakai id transaksi.
        bersih = gabung.drop_duplicates(subset="id_transaksi", keep="last")
        dibuang = gabung[gabung.duplicated(subset="id_transaksi", keep="last")]
        print("\n== Penggabungan dan deduplikasi ==")
        print(f"gabungan 3 sumber     : {len(gabung)} baris")
        print(
            f"duplikat id_transaksi : {len(dibuang)} dibuang"
            f" ({dibuang['sumber'].value_counts().to_dict()})"
        )
        print(f"data bersih           : {len(bersih)} baris")
        bersih = bersih.reset_index(drop=True)

        # 6. Kolom total sebagai omzet tiap baris, lalu rapikan urutan kolom.
        bersih["total"] = bersih["qty"] * bersih["harga_satuan"]
        bersih = bersih[
            ["id_transaksi", "tanggal", "produk", "kategori", "kota",
             "qty", "harga_satuan", "total", "sumber"]
        ]

        print("\n== Lima baris pertama data bersih ==")
        print(bersih.head(5).to_string(index=False))
        print(f"\nomzet keseluruhan: {int(bersih['total'].sum())}")


if __name__ == "__main__":
    main()

Output yang Dihasilkan:

text
== Standarisasi per sumber ==
laporan_csv      : 10 -> 9 baris (harga_satuan 0 dibuang)
marketplace_json : 8 -> 7 baris (qty 0 dibuang)
kasir_sqlite     : 7 -> 7 baris (sudah lengkap)

== Penggabungan dan deduplikasi ==
gabungan 3 sumber     : 23 baris
duplikat id_transaksi : 4 dibuang ({'laporan_csv': 4})
data bersih           : 19 baris

== Lima baris pertama data bersih ==
id_transaksi    tanggal                  produk    kategori    kota  qty  harga_satuan  total      sumber
   TRX-A-001 2026-01-14 Kopi Robusta Bubuk 200g     Minuman Bandung    4         38500 154000 laporan_csv
   TRX-A-002 2026-01-16          Gula Pasir 1kg Bahan Pokok Bandung    2         16000  32000 laporan_csv
   TRX-A-003 2026-01-21  Beras Pandan Wangi 5kg Bahan Pokok Bandung    6         72000 432000 laporan_csv
   TRX-A-005 2026-02-18     Teh Celup Melati 25     Minuman Bandung    5         12500  62500 laporan_csv
   TRX-A-006 2026-02-21   Mie Goreng Instan 85g Bahan Pokok Bandung    5          3500  17500 laporan_csv

omzet keseluruhan: 2261000

Mari kita telaah hasilnya:

  1. Rekap standarisasi: laporan CSV berubah 10 menjadi 9 baris, JSON 8 menjadi 7, dan SQLite tetap 7 — dua baris tidak valid terbuang di sumbernya masing-masing.
  2. Penggabungan: 23 baris masuk ke pd.concat(), lalu 4 duplikat id_transaksi dibuang; value_counts() pada baris yang dibuang menunjukkan keempatnya berasal dari laporan_csv.
  3. Data bersih 19 baris: lima baris pertama memperlihatkan kolom tanggal yang kini seragam, total yang terisi, dan kolom sumber sebagai penanda lineage.
  4. Omzet keseluruhan 2261000: penjumlahan kolom total yang menjadi angka rujukan validasi di Bagian 3.

Jembatan ke Validasi #

Skema sudah tertata, tetapi “sudah tertata” adalah klaim, bukan bukti. Bagian 3 membangun gerbang validasi yang menguji keberadaan kolom, tipe data, rentang nilai, keunikan id_transaksi, dan persamaan hasil dengan ekspektasi beku — sehingga klaim tersebut berubah menjadi pemeriksaan yang bisa diulang kapan pun.

Sumber bacaan resmi: pandas.DataFrame.rename , pandas.concat , dan pandas.DataFrame.drop_duplicates .