Pandas & Data Manipulation — Data Engineering

Pandas untuk Web Developer Pandas adalah library Python yang menjadi standar untuk manipulasi data tabular. Sebagai web developer yang terbiasa dengan…

pandas adalah library Python untuk mengolah tabel di memori. Tabelnya disebut DataFrame: baris dan kolom, dan tiap kolom punya satu tipe. Satu kolomnya disebut Series. Bayangin spreadsheet yang kamu kendalikan lewat kode: tiap klik (urutkan, saring, hapus duplikat, jumlahkan) jadi satu baris kode. Besok, waktu datanya bertambah, semua langkah itu tinggal dijalankan ulang.

🧪 Tebakan cepat: extract Toko Buku berisi 23 buku. Sebelum dibersihkan, df.groupby("city")["qty"].sum() menjumlah buku per kota. Kalau semua hasilnya dijumlahkan lagi, totalnya berapa: 23, 21, atau error? Tebak dulu, jawabannya ada di bagian "groupby: GROUP BY versi pandas".

DataFrame pertama dari CSV

Kita pakai order_lines.csv dari pelajaran sebelumnya: 19 baris pesanan Toko Buku, dengan tiga baris kotornya. Semua output di sini dari pandas 3.0.6 (rilis terbaru waktu pelajaran ini ditulis), dipasang pakai pip install pandas di virtual environment.

import pandas as pd

df = pd.read_csv("order_lines.csv", parse_dates=["created_at"])
print(df.shape)
print(df.dtypes)
(19, 9)
item_id                int64
order_id               int64
created_at    datetime64[us]
customer                 str
city                     str
book_id                int64
title                    str
qty                    int64
price                  int64
dtype: object

Tipe di atas ditebak pandas dari isinya. Kamu bisa menyebutkannya sendiri dengan dtype={"price": "int64"} (sebuah dict: pasangan nama dan nilai dalam kurung kurawal). Hasilnya untuk file yang harganya Rp 89.000: ValueError: invalid literal for int() with base 10: 'Rp 89.000'. Bagus, error-nya kelihatan. Tapi untuk file yang harganya 89.000, int64 pun gak menolong: pandas membacanya 89. Yang menolong itu thousands=".", yang ngasih tahu pandas bahwa titik itu pemisah ribuan.

Membersihkan tiga baris kotor

print(df["city"].isna().sum())
print(df[df.duplicated(subset=["order_id", "book_id"], keep=False)][["item_id", "order_id", "book_id", "qty"]])

df = df.drop_duplicates(subset=["order_id", "book_id"])
df["city"] = df["city"].fillna("Tidak diketahui")
2
   item_id  order_id  book_id  qty
7        8         6        2    2
8        9         6        2    2

merge: harga dari katalog

Harga 950000 kita betulkan dengan mengambil harga dari books.csv (kolom book_id, title, author, catalog_price):

buku = pd.read_csv("books.csv")
df = df.merge(buku[["book_id", "catalog_price"]], on="book_id", how="left")
df["total"] = df["qty"] * df["catalog_price"]

Jangan tertukar dengan concat (menumpuk dua tabel atas-bawah atau kiri-kanan, tanpa mencocokkan kunci) dan join (menggabungkan lewat index). append sudah dihapus sejak pandas 2.0.

groupby: GROUP BY versi pandas

print(df.groupby("city").agg(terjual=("qty", "sum"), omzet=("total", "sum")))
                 terjual   omzet
city                            
Bandung                5  550000
Jakarta                9  957000
Surabaya               5  493000
Tidak diketahui        2  214000

groupby("city") membagi baris per kota, .agg(nama=("kolom", "fungsi")) menghitung tiap kelompok. Totalnya 21 buku dan Rp 2.214.000, cocok dengan hasil SQL di pelajaran pertama.

Sekarang jalankan groupby yang sama sebelum dibersihkan:

city
Bandung      5
Jakarta     11
Surabaya     5
Name: qty, dtype: int64

5 + 11 + 5 = 21, padahal datanya 23 buku. Itu jawaban tebakannya: gak ada error, 2 buku Maya hilang. SQL bikin satu kelompok NULL, tapi groupby di pandas membuang baris yang kuncinya kosong. Pakai fillna dulu, atau groupby("city", dropna=False) biar kelompok NaN muncul.

Sekarang pertanyaan pemilik: buku terlaris per kota per minggu.

df["minggu"] = df["created_at"].dt.to_period("W").dt.start_time
per_judul = df.groupby(["minggu", "city", "title"], as_index=False).agg(terjual=("qty", "sum"))
terlaris = (
    per_judul.sort_values(["minggu", "city", "terjual", "title"], ascending=[True, True, False, True])
    .groupby(["minggu", "city"])
    .head(1)
)
print(terlaris.to_string(index=False))
    minggu            city                title  terjual
2026-09-28         Bandung         Bumi Manusia        2
2026-09-28         Jakarta       Laskar Pelangi        3
2026-09-28        Surabaya      Cantik Itu Luka        2
2026-09-28 Tidak diketahui       Laskar Pelangi        1
2026-10-05         Bandung Ronggeng Dukuh Paruk        2
2026-10-05         Jakarta      Cantik Itu Luka        2
2026-10-05        Surabaya       Laskar Pelangi        1
2026-10-05 Tidak diketahui         Bumi Manusia        1

Copy-on-Write di pandas 3.0

Di pandas 3.0, Copy-on-Write jadi satu-satunya mode: tiap hasil saring atau ambil kolom berlaku kayak salinan. Jadi kode yang menyaring lalu mengisi dalam dua langkah gak pernah mengubah df:

df[df["city"].isna()]["city"] = "Tidak diketahui"

pandas 3.0.6 memberi peringatan ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment., dan df tetap punya 2 kota kosong. df["city"].fillna("Tidak diketahui", inplace=True) juga gak mengubah apa-apa. Tulis dalam satu langkah: df.loc[df["city"].isna(), "city"] = "Tidak diketahui", atau df["city"] = df["city"].fillna(...) kayak tadi. SettingWithCopyWarning yang dulu sering muncul di pandas 1 dan 2 sudah dihapus.

💡 Kapan Polars atau DuckDB lebih tepat

pandas memuat seluruh tabel ke memori dan sebagian besar kerjanya pakai satu core CPU. Untuk ribuan sampai beberapa juta baris itu gak masalah.

Edit di Playground pada Lab membuka versi SQL dari kode pandas-nya, karena Playground pelajaran ini menjalankan SQL.

⚠️ Jebakan yang sering ketemu

TL;DR: pandas mengolah tabel (DataFrame) di memori dengan Python. read_csv membaca file; sebutkan tipenya dengan dtype dan pemisah ribuan dengan thousands=".", karena 89.000 dibaca 89. Bersihkan dengan drop_duplicates(subset=["order_id", "book_id"]), fillna("Tidak diketahui"), dan harga katalog lewat merge(..., on="book_id", how="left"), JOIN-nya pandas. groupby(...).agg(...) itu GROUP BY, tapi dia membuang kunci kosong: sebelum dibersihkan totalnya 21, bukan 23. Di pandas 3.0 Copy-on-Write berlaku, jadi df[...]["kolom"] = ... gak pernah mengubah df; pakai .loc. Untuk jutaan baris atau kerja yang SQL banget, Polars atau DuckDB lebih cocok.

Yang akan kamu pelajari