CSV adalah format pertukaran data paling universal: hasil ekspor Excel, laporan database, hingga data sensor semuanya bisa mendarat sebagai file CSV. Membacanya terasa mudah, tetapi tiga masalah selalu muncul di data nyata: setiap nilai datang sebagai string, ada baris yang rusak, dan format penulisan (delimiter, encoding) tidak selalu standar. Chapter Membaca dan Menulis CSV sudah memperkenalkan API dasar module csv; pada chapter ini kita fokus pada alur kerja data yang sesungguhnya, mulai dari membaca baris sampai menghasilkan agregasi yang bisa dipercaya.
Data Latihan: Daftar Produk #
Sepanjang section ini kita memakai data produk sebuah toko kelontong. Simpan teks berikut sebagai produk.csv:
id,nama,harga,kategori,stok
P001,Kopi Gayo,85000,minuman,120
P002,Teh Melati,45000,minuman,200
P003,Gula Aren,32000,bahan pokok,75
P004,"Beras Pandan Wangi, 5kg",78000,bahan pokok,40
P005,Keripik Singkong,15000,camilan,250
P006,Sirup Markisa,22000,minuman,60
P007,Sambal Bawang,28000,camilan,90
P008,Mie Instan Goreng,3500,bahan pokok,500
P009,Susu Kental Manis,14500,minuman,80
P012,Cangkir Keramik,55000,aksesoris,25
P010,Kopi Luwak,Rp 250.000,minuman,10
P011,Kerupuk Udang,,camilan,150Perhatikan dua jebakan yang sengaja kita tanam. Pertama, nama "Beras Pandan Wangi, 5kg" mengandung koma sehingga wajib dikutip; memecah baris dengan split(",") akan merusak kolomnya, sedangkan module csv memotong dengan benar. Kedua, dua baris terakhir adalah data rusak yang realistis: harga berformat mata uang dan harga kosong.
Membaca Baris sebagai Dictionary #
csv.DictReader menghasilkan satu dictionary per baris, dengan baris pertama file sebagai nama kolom:
import csv
with open("produk.csv", newline="", encoding="utf-8") as berkas:
pembaca = csv.DictReader(berkas)
for baris in list(pembaca)[:3]:
print(baris)Output yang Dihasilkan:
{'id': 'P001', 'nama': 'Kopi Gayo', 'harga': '85000', 'kategori': 'minuman', 'stok': '120'}
{'id': 'P002', 'nama': 'Teh Melati', 'harga': '45000', 'kategori': 'minuman', 'stok': '200'}
{'id': 'P003', 'nama': 'Gula Aren', 'harga': '32000', 'kategori': 'bahan pokok', 'stok': '75'}Mengakses kolom berdasarkan nama jauh lebih aman daripada posisi indeks: urutan kolom boleh berubah tanpa merusak kode. Namun ada satu kebiasaan yang harus kita bongkar sejak awal — semua nilai bertipe str:
with open("produk.csv", newline="", encoding="utf-8") as berkas:
baris_pertama = next(csv.DictReader(berkas))
print(baris_pertama["nama"], "bernilai:", baris_pertama["harga"])
print("Tipe harga:", type(baris_pertama["harga"]).__name__)Output yang Dihasilkan:
Kopi Gayo bernilai: 85000
Tipe harga: strCSV adalah format teks murni; angka 85000 di file hanyalah karakter. Operasi aritmetika seperti baris["harga"] * 2 tidak akan menghasilkan 170000, melainkan pengulangan string. Konversi tipe adalah tanggung jawab kita.
Konversi Tipe dan Penanganan Baris Rusak #
Karena dua baris terakhir file kita rusak, konversi harus dibungkus try/except per baris agar satu baris buruk tidak menghentikan seluruh proses:
import csv
def baca_produk(nama_berkas):
produk_valid = []
baris_gagal = []
with open(nama_berkas, newline="", encoding="utf-8") as berkas:
for nomor, baris in enumerate(csv.DictReader(berkas), start=2):
try:
produk_valid.append({
"id": baris["id"],
"nama": baris["nama"],
"harga": int(baris["harga"]),
"kategori": baris["kategori"],
"stok": int(baris["stok"]),
})
except (ValueError, KeyError) as galat:
baris_gagal.append((nomor, baris["id"], galat))
return produk_valid, baris_gagal
produk, gagal = baca_produk("produk.csv")
print(f"Terbaca {len(produk)} baris valid dan {len(gagal)} baris gagal")
for nomor, id_produk, galat in gagal:
print(f" Baris {nomor} ({id_produk}): {galat}")Output yang Dihasilkan:
Terbaca 10 baris valid dan 2 baris gagal
Baris 12 (P010): invalid literal for int() with base 10: 'Rp 250.000'
Baris 13 (P011): invalid literal for int() with base 10: ''Mari kita bedah bagian-bagian kuncinya:
- Baris 7 (
for nomor, baris in enumerate(csv.DictReader(berkas), start=2)):enumeratememberi nomor baris file;start=2karena baris 1 adalah header, sehingga laporan galat menunjuk nomor baris yang bisa diperiksa langsung di editor. - Baris 9-16 (
produk_valid.append({...})): setiap nilai dikonversi eksplisit —int(baris["harga"])— sebelum disimpan, sehingga sisa program menerima data yang sudah bertipe benar. - Baris 17 (
except (ValueError, KeyError) as galat):ValueErrormenangkap konversi yang gagal (harga"Rp 250.000"atau kosong),KeyErrormenangkap kolom yang hilang. Baris bermasalah dicatat, bukan dibuang diam-diam. - Baris 18 (
baris_gagal.append((nomor, baris["id"], galat))): nomor baris, id produk, dan objek galat disimpan agar bisa ditindaklanjuti setelah proses selesai.
Memisahkan hasil valid dan laporan galat seperti ini adalah pola yang akan kita pakai berulang kali di sepanjang section, sampai versi terbesarnya di chapter Membangun Data Pipeline.
Agregasi Sederhana per Kolom #
Setelah data bertipe benar, pertanyaan analisis seperti “berapa produk per kategori?” atau “berapa nilai persediaan?” tinggal dijawab dengan sum() dan Counter dari chapter collections:
from collections import Counter
jumlah_per_kategori = Counter(p["kategori"] for p in produk)
print("Jumlah produk per kategori:")
for kategori, jumlah in sorted(jumlah_per_kategori.items()):
print(f" {kategori:<12} {jumlah}")
total_stok = sum(p["stok"] for p in produk)
nilai_persediaan = sum(p["harga"] * p["stok"] for p in produk)
print(f"Total unit stok : {total_stok}")
print(f"Nilai persediaan: Rp{nilai_persediaan:,}".replace(",", "."))Output yang Dihasilkan:
Jumlah produk per kategori:
aksesoris 1
bahan pokok 3
camilan 2
minuman 4
Total unit stok : 1440
Nilai persediaan: Rp36.595.000Pola sum(...) dan Counter(...) pada generator expression memproses seluruh list tanpa loop manual — ringkas karena iterasinya terjadi di level C.
Delimiter dan Encoding dari Dunia Nyata #
Dua variasi file CSV sering menjebak pembaca awal. Pertama, Excel dengan pengaturan regional tertentu mengekspor CSV berdelimiter titik koma. Deteksi otomatisnya tersedia lewat csv.Sniffer:
import csv
with open("produk_semicolon.csv", newline="", encoding="utf-8") as berkas:
contoh = berkas.read(1024)
berkas.seek(0)
dialek = csv.Sniffer().sniff(contoh, delimiters=",;|\t")
print(f"Delimiter terdeteksi: {dialek.delimiter!r}")
for baris in csv.DictReader(berkas, dialect=dialek):
print(baris["nama"], baris["harga"])Output yang Dihasilkan:
Delimiter terdeteksi: ';'
Kopi Gayo 85000
Teh Melati 45000
Gula Aren 32000
Beras Pandan Wangi, 5kg 78000
Keripik Singkong 15000Kedua, file “CSV UTF-8” dari Excel diawali byte order mark (BOM). Kalau dibaca dengan encoding="utf-8", BOM menempel di nama kolom pertama:
import csv
with open("produk_bom.csv", newline="", encoding="utf-8") as berkas:
baris_pertama = next(csv.DictReader(berkas))
print(f"Kolom pertama (utf-8) : {list(baris_pertama)[0]!r}")
with open("produk_bom.csv", newline="", encoding="utf-8-sig") as berkas:
baris_pertama = next(csv.DictReader(berkas))
print(f"Kolom pertama (utf-8-sig) : {list(baris_pertama)[0]!r}")Output yang Dihasilkan:
Kolom pertama (utf-8) : '\ufeffid'
Kolom pertama (utf-8-sig) : 'id'Kolom \ufeffid membuat baris["id"] melempar KeyError yang membingungkan. Encoding utf-8-sig memotong BOM secara otomatis, sehingga baris["id"] bekerja normal.
Kapan Berpindah ke pandas #
Untuk file kecil dan logika sederhana, standard library sudah cukup dan bebas dependensi. pandas unggul saat analisis menjadi interaktif dan berulang. Perhatikan perilakunya pada file kita yang mengandung dua baris rusak:
import pandas as pd
df = pd.read_csv("produk.csv")
print(df.dtypes)
print()
df["harga_bersih"] = pd.to_numeric(df["harga"], errors="coerce")
print(df[["id", "harga", "harga_bersih"]].tail(3).to_string(index=False))
print()
print(df.groupby("kategori")["harga_bersih"].mean().round(0).sort_index())Output yang Dihasilkan:
id object
nama object
harga object
kategori object
stok int64
dtype: object
id harga harga_bersih
P012 55000 55000.0
P010 Rp 250.000 NaN
P011 NaN NaN
kategori
aksesoris 55000.0
bahan pokok 37833.0
camilan 21500.0
minuman 41625.0Satu nilai kotor ("Rp 250.000") membuat kolom harga gagal dinferensi sebagai angka dan seluruh kolom menjadi object. Fungsi pd.to_numeric(errors="coerce") mengubah nilai rusak menjadi NaN, lalu agregasi seperti mean() melewatinya secara otomatis.
| Situasi | Pilihan |
|---|---|
| Skrip CLI tanpa dependensi, data < puluhan ribu baris | module csv |
| Eksplorasi interaktif, agregasi berlapis, banyak kolom | pandas |
| File sangat besar, melebihi memori | streaming csv (chapter Menangani Data Besar) |
pandas dibahas mendalam pada section Data Science, dimulai dari chapter Dasar Pandas. Untuk seluruh section ini kita tetap berpijak pada standard library agar mekanisme di balik layarnya terlihat jelas.