Data yang bersih belum berarti siap analisis. Pertanyaan bisnis jarang jatuh tepat pada kolom mentah — “omzet per produk”, “nilai transaksi terbesar”, dan “nama produk pada laporan penjualan” semuanya adalah turunan dari data mentah. Transformasi data adalah tahap menambah nilai: menghitung field baru, mengelompokkan, mengagregasi, mengurutkan, mengubah bentuk, dan menggabungkan. Semuanya adalah operasi pada list of dictionary — bentuk data yang telah kita pakai sejak chapter Membaca Data CSV.

Data Latihan: Transaksi Penjualan #

Simpan berikut sebagai penjualan.csv — dua belas transaksi selama lima hari:

text
tanggal,sku,qty,harga
2025-06-13,P001,2,85000
2025-06-13,P005,3,15000
2025-06-14,P002,1,45000
2025-06-14,P001,2,85000
2025-06-14,P008,10,3500
2025-06-15,P001,1,85000
2025-06-15,P003,2,32000
2025-06-15,P005,5,15000
2025-06-16,P002,3,45000
2025-06-16,P004,1,78000
2025-06-16,P001,2,85000
2025-06-17,P005,2,15000

Kita baca dengan pola konversi tipe dari chapter sebelumnya:

python
import csv

def baca_penjualan(nama_berkas):
    hasil = []
    with open(nama_berkas, newline="", encoding="utf-8") as berkas:
        for baris in csv.DictReader(berkas):
            hasil.append({
                "tanggal": baris["tanggal"],
                "sku": baris["sku"],
                "qty": int(baris["qty"]),
                "harga": int(baris["harga"]),
            })
    return hasil

penjualan = baca_penjualan("penjualan.csv")
print(f"{len(penjualan)} transaksi")
print(penjualan[0])

Output yang Dihasilkan:

text
12 transaksi
{'tanggal': '2025-06-13', 'sku': 'P001', 'qty': 2, 'harga': 85000}

Memetakan dan Menyaring dengan Comprehension #

Operasi transformasi paling dasar adalah map dan filter, dan pada list of records keduanya paling jelas ditulis sebagai list comprehension (chapter List Comprehensions). Menyaring hanya menyembunyikan tanggal — perhatikan bahwa tanggal format ISO membuat perbandingan string bekerja seperti perbandingan tanggal:

python
akhir_pekan = [p for p in penjualan if p["tanggal"] >= "2025-06-15"]
print(f"Transaksi sejak 15 Juni: {len(akhir_pekan)}")

Output yang Dihasilkan:

text
Transaksi sejak 15 Juni: 7

Field Turunan: Diskon dan Nilai Transaksi #

Field turunan dihitung dari field yang ada. Kombinasi {**p, "kolom_baru": nilai} menyalin record lalu menambah kolom — record aslinya tidak tersentuh:

python
HARGA_DISKON = {"P001": 0.10, "P005": 0.20}

def dengan_diskon(p):
    potongan = HARGA_DISKON.get(p["sku"], 0)
    harga_akhir = round(p["harga"] * (1 - potongan))
    return {**p, "harga_akhir": harga_akhir, "hemat": p["harga"] - harga_akhir}

penjualan_diskon = [dengan_diskon(p) for p in penjualan]
print(penjualan_diskon[0])
print(penjualan_diskon[1])

Output yang Dihasilkan:

text
{'tanggal': '2025-06-13', 'sku': 'P001', 'qty': 2, 'harga': 85000, 'harga_akhir': 76500, 'hemat': 8500}
{'tanggal': '2025-06-13', 'sku': 'P005', 'qty': 3, 'harga': 15000, 'harga_akhir': 12000, 'hemat': 3000}

Produk P001 mendapat potongan 10% (85000 menjadi 76500, hemat 8500), sedangkan produk tanpa entri di HARGA_DISKON lolos dengan potongan 0 karena dict.get() mengembalikan nilai bawaan. Field seperti hemat gratis kita dapatkan karena seluruh bahan bakunya sudah ada di record.

Pengelompokan dan Agregasi per Grup #

Pertanyaan “berapa omzet per produk?” membutuhkan dua langkah: kelompokkan baris berdasarkan sku, lalu agregasi tiap kelompok. Pengelompokan standarnya memakai setdefault():

python
def kelompokkan(baris_list, kolom):
    hasil = {}
    for baris in baris_list:
        hasil.setdefault(baris[kolom], []).append(baris)
    return hasil

per_sku = kelompokkan(penjualan, "sku")
ringkasan = {
    sku: {
        "jumlah": len(rows),
        "qty": sum(r["qty"] for r in rows),
        "omzet": sum(r["qty"] * r["harga"] for r in rows),
    }
    for sku, rows in per_sku.items()
}
for sku, info in sorted(ringkasan.items(), key=lambda x: -x[1]["omzet"])[:3]:
    print(sku, info)

Output yang Dihasilkan:

text
P001 {'jumlah': 4, 'qty': 7, 'omzet': 595000}
P002 {'jumlah': 2, 'qty': 4, 'omzet': 180000}
P005 {'jumlah': 3, 'qty': 10, 'omzet': 150000}

Mari kita bedah inti polanya:

  1. Baris 5 (hasil.setdefault(baris[kolom], [])): untuk setiap baris, fungsi mengambil list milik nilai kolomnya — membuat list kosong saat pertama kali — lalu meng-append baris. Hasil akhirnya dictionary {"P001": [baris, ...], "P005": [...]}.
  2. Baris 8-14 (dictionary comprehension): setiap kelompok diringkas menjadi tiga angka: jumlah transaksi (len), total unit (sum qty), dan omzet (sum qty × harga). Struktur dictionary di dalam comprehension membaca persis seperti rumus bisnisnya.
  3. Baris 15 (sorted(..., key=lambda x: -x[1]["omzet"])): urutkan berdasarkan omzet menurun lewat nilai negatif; [:3] mengambil tiga teratas.

Perhatikan bahwa omzet P001 (595000) adalah penjumlahan 170000 + 170000 + 85000 + 170000 — angka yang sama akan kita verifikasi ulang di chapter Membangun Data Pipeline.

Mengurutkan Record dan Mengubah Bentuk #

Mengurutkan record memakai sorted() dengan key berbentuk lambda (chapter reversed dan sorted). Karena key adalah fungsi, kita bebas mengurutkan berdasarkan nilai hasil hitung:

python
terbesar = sorted(penjualan, key=lambda p: p["qty"] * p["harga"], reverse=True)[:3]
for p in terbesar:
    total = p["qty"] * p["harga"]
    print(f"{p['tanggal']} {p['sku']} Rp{total:,}".replace(",", "."))

Output yang Dihasilkan:

text
2025-06-13 P001 Rp170.000
2025-06-14 P001 Rp170.000
2025-06-16 P001 Rp170.000

Kadang alat analisis berikutnya (menggambar grafik, matriks, atau API eksternal) lebih cocok menerima data per kolom daripada per baris. Dictionary comprehension mengubah list of dicts menjadi dict of lists:

python
kolom = {kunci: [b[kunci] for b in penjualan] for kunci in penjualan[0]}
print(kolom["sku"])
print(f"Total qty: {sum(kolom['qty'])}")

Output yang Dihasilkan:

text
['P001', 'P005', 'P002', 'P001', 'P008', 'P001', 'P003', 'P005', 'P002', 'P004', 'P001', 'P005']
Total qty: 34

Setiap kunci diambil dari record pertama, lalu nilainya dipungut dari setiap baris — urutan dan panjang setiap list tetap sejajar, sehingga kolom["sku"][3] dan kolom["qty"][3] masih merujuk transaksi yang sama.

Menggabungkan Dua List Berdasarkan Kunci #

Record penjualan hanya menyimpan sku; laporan yang bisa dibaca manusia butuh nama produk. Data nama tersimpan di file lain, jadi kita gabungkan: bangun dictionary index dari list produk, lalu akses per transaksi:

python
import csv

def baca_produk(nama_berkas):
    hasil = []
    with open(nama_berkas, newline="", encoding="utf-8") as berkas:
        for baris in csv.DictReader(berkas):
            try:
                hasil.append({
                    "id": baris["id"],
                    "nama": baris["nama"],
                    "harga": int(baris["harga"]),
                    "kategori": baris["kategori"],
                })
            except ValueError:
                continue  # baris rusak dilewati (pola chapter Membaca Data CSV)
    return hasil

produk = baca_produk("produk.csv")
indeks_produk = {p["id"]: p for p in produk}

laporan = [{**p, "nama": indeks_produk[p["sku"]]["nama"]} for p in penjualan]
for baris in laporan[:3]:
    print(f"{baris['tanggal']} {baris['sku']} - {baris['nama']}")

Output yang Dihasilkan:

text
2025-06-13 P001 - Kopi Gayo
2025-06-13 P005 - Keripik Singkong
2025-06-14 P002 - Teh Melati

Kuncinya ada di baris indeks_produk = {p["id"]: p for p in produk}: dictionary comprehension mengubah pencarian dari O(n) per transaksi menjadi O(1). Pola join ini kita perdalam pada chapter Menggabungkan Dataset, termasuk penanganan kunci yang tidak cocok.

Padanan pandas #

Seluruh operasi di atas punya padanan satu baris di pandas — assign, groupby, sort_values, merge:

python
import pandas as pd

df = pd.read_csv("penjualan.csv")
df["total"] = df["qty"] * df["harga"]
omzet_sku = df.groupby("sku")["total"].sum().sort_values(ascending=False)
print(omzet_sku.head(3))

Output yang Dihasilkan:

text
sku
P001    595000
P002    180000
P005    150000
Name: total, dtype: int64

Angka 595000 untuk P001 sama persis dengan hasil standard library kita — mekanisme dan hasil saling menguatkan. pandas membahas pendalaman transformasi di section Data Science; standard library tetap andalan saat dependensi harus minim atau saat kita perlu mengendalikan setiap langkah secara eksplisit.

Dengan membaca, membersihkan, dan mentransformasi data, tiga tahap utama pengolahan data sudah di tangan. Tugas tersisa memahami cara kerjanya saat skalanya membesar.