Sebuah job ETL memindahkan data dari satu bentuk ke bentuk lain: baris hasil query menjadi record, kumpulan record menjadi batch, dan batch menjadi berkas yang ditulis ke storage. Python menyediakan empat struktur inti untuk itu — list, dict, set, dan tuple — dan struktur yang dipilih menentukan mudah atau sulitnya kode transformasi dibaca dan diubah. Data engineer juga jarang menerima data bersih: baris duplikat, harga nol, dan kapitalisasi nama yang tidak konsisten. Chapter ini memetakan keempat struktur dari sudut pandang data engineering, lalu mengujinya lewat satu contoh transformasi list record penjualan.

Peta Struktur Data untuk Data Engineer #

Dalam kode pipeline, keempat struktur muncul dengan peran yang hampir tetap. Tabel berikut merangkum perannya; dokumentasi resmi seluruh method ada di https://docs.python.org/3/tutorial/datastructures.html .

StrukturSifat utamaPeran khas di pipeline
listterurut, bisa diubahkumpulan baris (batch) yang diproses berurutan
dictkunci-nilai, menjaga urutan sisipansatu record dengan nama kolom
setanggota unik, tanpa urutanpenanda id yang sudah diproses (deduplikasi)
tupleterurut, tidak bisa diubahbaris posisional dan kunci gabungan

Cuplikan berikut menampilkan keempatnya dalam satu pandangan:

python
# tuple: satu baris posisional, bentuk tetap
baris_db = ("TRX-001", "Budi", 3, 15000)

# dict: satu record dengan nama kolom
record = {"id": "TRX-001", "kasir": "Budi", "qty": 3, "harga": 15000}

# list: kumpulan banyak record (batch)
batch = [record, {"id": "TRX-002", "kasir": "Siti", "qty": 1, "harga": 25000}]

# set: kumpulan id untuk deduplikasi
id_terlihat = {"TRX-001", "TRX-002"}

print(baris_db)
print(record["kasir"])
print(len(batch))
print("TRX-001" in id_terlihat)

Output yang Dihasilkan:

text
('TRX-001', 'Budi', 3, 15000)
Budi
2
True

Dict diakses lewat nama kolom; keanggotaan set dites dengan in dan berjalan konstan berapa pun jumlah anggotanya.

Dict untuk Record, List untuk Kumpulan Baris #

Dict adalah wujud paling natural dari satu baris tabel: setiap kolom dipanggil dengan namanya, bukan dengan posisinya. Penambahan kolom baru pada data sumber tidak merusak kode lama. Inilah alasan hasil csv.DictReader dan sebagian besar hasil API berbentuk dict.

List berperan sebagai wadah batch: satu job membaca ribuan record, menampungnya dalam list, lalu mengiterasinya untuk validasi dan transformasi. Tuple kurang cocok untuk record karena memaksa kita menghafal posisi kolom; saat urutan kolom sumber berubah, semua indeks baris_db[2] harus diperiksa satu per satu. Tuple tetap berguna untuk baris mentah dari driver database yang memang posisional.

Contoh: Transformasi List Record Penjualan #

Data mentah berikut memuat tiga masalah khas: TRX-001 terkirim dua kali, TRX-003 memiliki harga nol dari sistem sumber, dan semua nama ditulis dengan huruf kecil. Kita proses dalam tiga tahap: deduplikasi dengan set, validasi dengan list comprehension, normalisasi dengan loop.

python
penjualan = [
    {"id": "TRX-001", "kasir": "budi santoso", "produk": "kopi lampung", "qty": 2, "harga": 24000},
    {"id": "TRX-002", "kasir": "siti rahayu", "produk": "teh melati", "qty": 3, "harga": 15000},
    {"id": "TRX-001", "kasir": "budi santoso", "produk": "kopi lampung", "qty": 2, "harga": 24000},
    {"id": "TRX-003", "kasir": "dewi lestari", "produk": "gula aren", "qty": 1, "harga": 0},
]

# 1) Deduplikasi: lacak id yang sudah diproses memakai set
dilihat = set()
unik = []
for baris in penjualan:
    if baris["id"] in dilihat:
        continue
    dilihat.add(baris["id"])
    unik.append(baris)

# 2) Validasi: buang baris dengan qty atau harga <= 0
valid = [b for b in unik if b["qty"] > 0 and b["harga"] > 0]

# 3) Normalisasi + penghitungan
bersih = []
for baris in valid:
    record = dict(baris)
    record["kasir"] = baris["kasir"].title()
    record["produk"] = baris["produk"].title()
    record["total"] = baris["qty"] * baris["harga"]
    bersih.append(record)

print("Baris masuk  :", len(penjualan))
print("Setelah dedup:", len(unik))
print("Setelah valid:", len(valid))
for record in bersih:
    print(f"  {record['id']} {record['produk']} ({record['kasir']}) Rp{record['total']}")
print("Total pendapatan:", sum(r["total"] for r in bersih))

Output yang Dihasilkan:

text
Baris masuk  : 4
Setelah dedup: 3
Setelah valid: 2
  TRX-001 Kopi Lampung (Budi Santoso) Rp48000
  TRX-002 Teh Melati (Siti Rahayu) Rp45000
Total pendapatan: 93000

Mari kita telaah baris-per-baris kode di atas:

  1. Baris 8-15 (deduplikasi): set dilihat mencatat id yang pernah lolos; baris kedua TRX-001 di-skip lewat continue, jadi tidak pernah masuk ke unik.
  2. Baris 18 (validasi): list comprehension menyaring baris dengan qty dan harga positif; TRX-003 dengan harga 0 terbuang di sini.
  3. Baris 23 (dict(baris)): baris disalin menjadi dict baru, jadi normalisasi tidak menyentuh data mentah.
  4. Baris 24-26 (normalisasi): .title() merapikan kapitalisasi nama, lalu kolom total dihitung dari qty * harga.
  5. Baris 34: sum() menjumlahkan kolom total; hasilnya 4 baris masuk, 3 lolos dedup, 2 lolos validasi.

Panduan Memilih Struktur #

KebutuhanGunakanHindari
Satu record dengan nama kolomdicttuple posisional
Batch baris yang diproses berurutanlistset (tanpa urutan)
Penanda id yang sudah diprosessetlist yang dites in (lambat pada data besar)
Kunci gabungan agregasituple sebagai kunci dictlist (tidak bisa jadi kunci)

Aturan praktisnya: bila kode membaca baris[3] untuk mengambil harga, pertimbangkan beralih ke baris["harga"]. Bila sebuah list hanya menampung id unik dan dites dengan in, pertimbangkan beralih ke set.

Struktur data kini menjadi wadah yang benar. Langkah berikutnya adalah membungkus logika transformasi menjadi unit yang bisa diuji dan dipakai ulang antar job — dikerjakan di chapter berikutnya, Fungsi dan Modul.