September 28, 2026
Pandas: Mengolah Data Tabular.
Dasar pandas 3.0: DataFrame, membaca CSV dan SQL, filter, groupby, resample time series, merge, dan menangani missing values — dengan contoh data harga beras.
Pandas adalah library untuk data tabular di Python: baca data dari CSV/SQL, bersihkan, agregasi, lalu serahkan ke model atau chart. Di Price Prediction System pandas memegang hampir semua langkah antara API pemerintah dan model forecasting. Catatan ini memakai pandas 3.0 (butuh Python 3.11+).
pip install "pandas>=3.0" sqlalchemy pymysql
Yang berubah di pandas 3.0: Copy-on-Write aktif default (chained assignment tidak lagi mengubah data asli), kolom teks memakai dtype
stralih-alihobject, alias frekuensi lama seperti"M"dihapus (pakai"ME"), dan tanggal hasil parsing string memakai resolusi mikrodetik (datetime64[us]).
1. Series & DataFrame
Series adalah satu kolom berlabel; DataFrame adalah tabel berisi banyak Series yang berbagi index.
import pandas as pd
import numpy as np
df = pd.DataFrame({
"tanggal": pd.to_datetime(["2025-01-01", "2025-01-02", "2025-01-03",
"2025-01-01", "2025-01-02", "2025-01-03"]),
"kode_kota": ["3507", "3507", "3507", "3578", "3578", "3578"],
"komoditas": ["Beras Medium"] * 6,
"harga": [13200, 13250, np.nan, 13800, 13850, 13900],
})
df.info() # tipe kolom & jumlah non-null
df.head(3) # 3 baris pertama
df.describe() # statistik ringkas kolom numerik
df.shape # (6, 4)
df["harga"].mean() # NaN diabaikan secara default
| Akses | Contoh | Hasil |
|---|---|---|
| Satu kolom | df["harga"] | Series |
| Beberapa kolom | df[["tanggal", "harga"]] | DataFrame |
| Label | df.loc[0, "harga"] | nilai |
| Posisi | df.iloc[-1] | baris terakhir |
| Kondisi | df.loc[df["harga"] > 13500, "kode_kota"] | Series |
2. Membaca CSV dan SQL
# CSV: tentukan tipe & parsing tanggal sejak awal
df = pd.read_csv(
"harga_harian.csv",
parse_dates=["tanggal"],
dtype={"kode_kota": "str"}, # kode wilayah jangan jadi angka (buang nol di depan)
)
df.to_csv("export/harga_bersih.csv", index=False)
Untuk database, pandas menerima SQLAlchemy engine/connection. Selalu kirim parameter lewat params, jangan menyusun SQL dengan f-string:
import os
from sqlalchemy import create_engine, text
url = (f"mysql+pymysql://{os.environ['DB_USER']}:{os.environ['DB_PASSWORD']}"
f"@{os.environ['DB_HOST']}:{os.environ.get('DB_PORT', '3306')}/{os.environ['DB_NAME']}")
engine = create_engine(url, pool_pre_ping=True)
query = text("""
SELECT tanggal, harga
FROM history_data_komoditas
WHERE kode_kota = :kota AND komoditas_id = :kom
AND tanggal BETWEEN :start AND :end
ORDER BY tanggal
""")
with engine.connect() as conn:
harian = pd.read_sql(query, conn, params={
"kota": "3507", "kom": 27, "start": "2023-01-01", "end": "2025-06-30",
})
harian["tanggal"] = pd.to_datetime(harian["tanggal"])
harian = harian.set_index("tanggal")
Menulis balik: df.to_sql("nama_tabel", engine, if_exists="append", index=False). Dasar query-nya ada di Script Dasar SQL
.
3. Filter & Transformasi Kolom
# Kombinasi kondisi: pakai & | ~ dan tanda kurung
mahal = df[(df["harga"] > 13500) & (df["kode_kota"] == "3578")]
# isin untuk banyak nilai, between untuk rentang
df[df["kode_kota"].isin(["3507", "3578"])]
df[df["tanggal"].between("2025-01-01", "2025-01-02")]
# query() untuk ekspresi yang lebih mudah dibaca
df.query("harga > 13500 and kode_kota == '3578'")
# kolom baru
df = df.assign(
harga_ribu=df["harga"] / 1000,
bulan=df["tanggal"].dt.to_period("M"),
)
# transformasi per elemen di kolom teks
df["komoditas"] = df["komoditas"].str.upper()
Mengubah sebagian baris wajib lewat .loc dalam satu langkah:
df.loc[df["harga"] < 0, "harga"] = np.nan # benar
# df[df["harga"] < 0]["harga"] = np.nan # chained: tidak berefek di pandas 3
4. Missing Values
Data harga dari API sering bolong (hari libur, pasar tutup, kota tidak melapor).
df["harga"].isna().sum() # jumlah NaN
df.dropna(subset=["harga"]) # buang baris tanpa harga
# isi dengan rata-rata grupnya (pola di ETL skripsi saya)
df["harga"] = df["harga"].fillna(
df.groupby(["kode_kota", "komoditas"])["harga"].transform("mean")
)
# untuk time series: bawa nilai terakhir atau interpolasi
s = harian["harga"].ffill()
s = harian["harga"].interpolate(method="time")
| Metode | Kapan dipakai |
|---|---|
dropna() | Data cukup banyak dan baris kosong tidak bermakna |
fillna(nilai) | Ada nilai default yang masuk akal (mis. 0 untuk jumlah) |
groupby().transform("mean") | Isi dengan rata-rata kelompok yang sama |
ffill() / bfill() | Harga “tetap sama” sampai ada laporan baru |
interpolate(method="time") | Series berindeks tanggal dengan jarak tidak rata |
5. GroupBy & Agregasi
Polanya split → apply → combine:
ringkas = (
df.groupby("kode_kota")
.agg(rata=("harga", "mean"),
tertinggi=("harga", "max"),
hari=("harga", "count"))
.round(0)
.sort_values("rata", ascending=False)
.reset_index()
)
print(ringkas)
aggdengan named aggregation (nama=("kolom", "fungsi")) menghasilkan nama kolom yang rapi.transformmengembalikan hasil dengan panjang sama seperti data asli — cocok untuk kolom turunan.pct_change()dalam grup:df.groupby("kode_kota")["harga"].pct_change() * 100.
6. Time Series & Resample
Resample butuh index bertipe datetime. Data harian di skripsi diubah menjadi ±30 titik bulanan per kota × jenis beras:
idx = pd.date_range("2023-01-01", "2025-06-30", freq="D")
rng = np.random.default_rng(42)
harian = pd.DataFrame(
{"harga": 12500 + np.cumsum(rng.normal(3, 25, len(idx))).round()},
index=idx,
)
bulanan = harian["harga"].resample("ME").agg(["mean", "min", "max", "count"])
print(bulanan.head())
print(len(bulanan)) # 30 bulan
# rolling mean 7 hari & deteksi lonjakan >= 5%
harian["ma7"] = harian["harga"].rolling(7).mean()
harian["pct"] = harian["harga"].pct_change() * 100
lonjakan = harian[harian["pct"].abs() >= 5]
| Alias | Arti | Catatan |
|---|---|---|
"D" | harian | |
"W" | mingguan (Minggu) | "W-MON" untuk Senin |
"MS" / "ME" | awal / akhir bulan | "M" sudah dihapus di pandas 3 |
"QE", "YE" | akhir kuartal / tahun | pengganti "Q", "Y" |
"h", "min" | jam, menit | huruf kecil |
Data bulanan inilah yang masuk ke model — lihat Forecasting Harga dengan Exponential Smoothing .
7. Merge & Concat
kota = pd.DataFrame({
"kode_kota": ["3507", "3578"],
"nama_kota": ["Kab. Malang", "Kota Surabaya"],
})
gabung = df.merge(kota, on="kode_kota", how="left", validate="many_to_one")
# gabung vertikal beberapa batch hasil fetch
semua = pd.concat([batch_jan, batch_feb], ignore_index=True) # batch_* = DataFrame dari tiap fetch
# baris yang tidak punya pasangan (pandas 3.0+)
yatim = df.merge(kota, on="kode_kota", how="left_anti")
how | Hasil |
|---|---|
inner | hanya key yang ada di kedua sisi |
left | semua baris kiri, kanan diisi NaN bila tidak cocok |
outer | gabungan semua key |
left_anti | baris kiri yang tidak punya pasangan |
Parameter validate melempar error bila relasi tidak sesuai dugaan (mis. key duplikat di tabel master) — murah dan menyelamatkan dari baris yang tiba-tiba berlipat. Konsepnya sama dengan JOIN di Relasi Tabel dan Normalisasi
.
Kesalahan Umum
| Kesalahan | Akibat | Perbaikan |
|---|---|---|
Chained assignment df[mask]["col"] = x | Di pandas 3 tidak mengubah df sama sekali (hanya muncul ChainedAssignmentError warning) | df.loc[mask, "col"] = x |
resample("M") dari kode lama | ValueError di pandas 3 | Ganti "ME" (atau "MS") |
| Kode wilayah dibaca sebagai int | "0351" jadi 351, merge gagal | dtype={"kode": "str"} saat membaca |
Lupa pd.to_datetime | resample/.dt error, sort tanggal salah | Parse tanggal saat baca atau segera setelahnya |
iterrows() untuk hitungan | Sangat lambat | Operasi vektor, groupby, atau np.where |
| Menyusun SQL dengan f-string | SQL injection | text() + params= |
inplace=True dianggap lebih hemat memori | Tidak ada jaminan, memutus chaining | Assign ulang: df = df.dropna() |
| Menghitung error setelah normalisasi | Metrik tidak bermakna | Hitung MAPE/RMSE pada harga rupiah asli |

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.