02 — Pandas Basics
Estimasi: 4 jam Tujuan: Pandas = "Excel di code". Tools standar untuk data manipulation tabular di Python.
Kenapa Materi Ini Penting?
Pandas adalah alat utama kamu untuk membersihkan, memformat, dan menjelajahi data sebelum masuk ke modeling. Di dunia nyata, 70-80% waktu seorang ML engineer dihabiskan untuk data wrangling — dan Pandas-lah yang melakukannya. Tanpa Pandas, kamu akan menulis ratusan baris loop manual untuk hal sederhana seperti "filter row dengan harga > 100 dan group by kategori".
Analogi: kalau Excel itu kalkulator visual, Pandas itu Excel super kuat yang bisa di-scripting. Kamu bisa replay 200 langkah cleaning yang sama di dataset baru cuma dengan run satu file. Ini critical untuk reproducibility — saat dosen atau interviewer bilang "tolong rerun dengan dataset bulan depan", kamu cukup ganti CSV-nya.
Di GenAI bootcamp nanti, kamu akan butuh Pandas untuk: load CSV jutaan baris untuk fine-tuning, parsing JSON dari API LLM jadi tabel, agregasi metric eksperimen, atau cleaning corpus untuk RAG. Kuasai dasar Pandas sekarang dan kamu akan bergerak 5x lebih cepat di fase ML/LLM.
Peta Materi Pandas Basics
Cara Membaca Diagram:
- Node kiri (sumber file) →
pd.read_*→ DataFrame. - DataFrame mempunyai 4 cabang aksi utama: Inspect, Select, Modify, Clean.
- Hasil Clean masuk ke tahap Aggregate, lalu Output.
Walkthrough Step-by-Step:
- Baca file (CSV/Excel/JSON) menjadi DataFrame.
- Lakukan inspect (head/info/describe) untuk mengenal struktur.
- Pilih subset (loc/iloc/boolean), modifikasi kolom, dan bersihkan (missing/dtype/duplicate).
- Lakukan agregasi (sum, mean, groupby) lalu simpan hasil.
Analogi Sehari-hari: seperti masak. Belanja (read), cek bahan (inspect), potong dan cuci (select/clean), masak (aggregate), sajikan (output).
Diagram statis Mermaid sebagai fallback:
flowchart TD
A["CSV / Excel / JSON"] --> B["pd.read_*"]
B --> C["DataFrame<br/>(rows x cols)"]
C --> D["Inspect<br/>head, info, describe"]
C --> E["Select<br/>loc / iloc / boolean"]
C --> F["Modify<br/>add, rename, drop"]
C --> G["Clean<br/>missing, dtype, dedup"]
G --> H["Aggregate<br/>sum, mean, groupby"]
H --> I["Output<br/>to_csv / to_parquet"]
Anatomi DataFrame
Cara Membaca Diagram:
- Node atas = DataFrame, akarnya.
- Node kiri (Index) = label baris.
- Tiga node tengah = kolom-kolom (tiap kolom = Series).
- Dua node bawah = catatan: kolom = Series, baris = record.
Walkthrough Step-by-Step:
- Identifikasi index (label baris) — bisa default integer atau custom.
- Tiap kolom adalah Series 1D dengan nama dan dtype sendiri.
- Tiap baris = satu record/sample data lintas kolom.
Analogi Sehari-hari: spreadsheet Excel. Index = nomor baris, kolom = header, baris = entry data per orang/transaksi.
Diagram statis Mermaid sebagai fallback:
flowchart TB
subgraph DF["DataFrame"]
direction LR
IDX["Index<br/>(label baris)"]
subgraph COLS["Columns"]
C1["nama<br/>(Series)"]
C2["umur<br/>(Series)"]
C3["kota<br/>(Series)"]
end
end
DF --> N1["Tiap kolom = Series<br/>(1D array berlabel)"]
DF --> N2["Tiap row = record<br/>(satu sampel data)"]
Analogi: DataFrame = spreadsheet Excel super kuat. Series = satu kolom Excel. Index = nomor baris (atau label custom seperti tanggal). Beda dengan Excel: Pandas tidak menyimpan formula, semua sudah jadi nilai konkret.
Bagian 1 — Series & DataFrame
import pandas as pd
# Series = 1D labeled array
s = pd.Series([10, 20, 30], index=["a", "b", "c"], name="values")
print(s)
# a 10
# b 20
# c 30
# Name: values, dtype: int64
# DataFrame = 2D table
data = {
"nama": ["Budi", "Ani", "Cici"],
"umur": [25, 30, 28],
"kota": ["Bandung", "Jakarta", "Surabaya"],
}
df = pd.DataFrame(data)
print(df)
Bagian 2 — Read/Write Data
# Read
df = pd.read_csv("data.csv")
df = pd.read_csv("data.csv", sep=";", encoding="utf-8")
df = pd.read_excel("data.xlsx", sheet_name="Sheet1")
df = pd.read_json("data.json")
df = pd.read_parquet("data.parquet")
# Write
df.to_csv("out.csv", index=False)
df.to_excel("out.xlsx", index=False)
df.to_json("out.json", orient="records")
df.to_parquet("out.parquet")
# Read sebagian
df = pd.read_csv("big.csv", nrows=100)
df = pd.read_csv("big.csv", usecols=["nama", "umur"])
Bagian 3 — Inspect
df.head() # 5 first rows
df.head(10) # 10 first
df.tail() # 5 last
df.shape # (n_rows, n_cols)
df.columns # column names
df.index # row labels
df.dtypes # tipe per kolom
df.info() # ringkasan
df.describe() # statistik numerik
df.describe(include='all') # all columns
df.memory_usage(deep=True)
Bagian 4 — Selection
Visualisasi: loc vs iloc
flowchart LR
DF["DataFrame"] --> LOC["df.loc[label, label]<br/>pakai NAMA index/kolom<br/>inclusive di slicing"]
DF --> ILOC["df.iloc[pos, pos]<br/>pakai POSISI integer<br/>exclusive di slicing"]
LOC --> EX1["df.loc[0:5, 'nama']<br/>(0,1,2,3,4,5)"]
ILOC --> EX2["df.iloc[0:5, 0]<br/>(0,1,2,3,4) — tanpa 5"]
Cara ingat: loc = label-based (huruf "l" untuk label). iloc = integer-based (huruf "i" untuk index).
Column
df["nama"] # Series
df[["nama", "umur"]] # DataFrame
df.nama # attribute access (kadang ambigu, hindari)
Row by Position (iloc)
df.iloc[0] # first row (Series)
df.iloc[0:3] # rows 0-2
df.iloc[-1] # last row
df.iloc[[0, 2, 5]] # specific rows
Row by Label (loc)
df.loc[0] # row dengan index 0
df.loc[df["nama"] == "Budi"] # filter
df.loc[0:5] # rows index 0-5 INCLUSIVE
df.loc[:, "nama"] # all rows, kolom nama
df.loc[df.umur > 25, ["nama", "kota"]]
Filter (Boolean)
# Single condition
df[df["umur"] > 25]
# Multiple condition
df[(df["umur"] > 25) & (df["kota"] == "Bandung")]
df[(df["umur"] < 18) | (df["umur"] > 65)]
df[~(df["kota"] == "Jakarta")] # NOT
# isin
df[df["kota"].isin(["Bandung", "Surabaya"])]
# String methods
df[df["nama"].str.startswith("B")]
df[df["nama"].str.contains("ud", case=False)]
Wajib pakai
&,|,~(bukanand,or,not) untuk boolean operasi DataFrame.
Bagian 5 — Modification
Add/Update Column
df["umur_5_tahun_lagi"] = df["umur"] + 5
df["kategori"] = ["dewasa"] * len(df)
# Conditional
df["status"] = df["umur"].apply(lambda x: "dewasa" if x >= 18 else "anak")
# Pakai numpy
import numpy as np
df["status"] = np.where(df["umur"] >= 18, "dewasa", "anak")
Rename
df.rename(columns={"nama": "name", "umur": "age"}, inplace=True)
df.columns = ["name", "age", "city"] # ganti semua
Drop
df.drop("kategori", axis=1) # drop column
df.drop(columns=["kategori", "status"])
df.drop([0, 2]) # drop rows
df.drop(index=[0, 2])
# Inplace (modifikasi original)
df.drop(columns=["x"], inplace=True)
Sort
df.sort_values("umur")
df.sort_values("umur", ascending=False)
df.sort_values(["kota", "umur"]) # multi-column
df.sort_index()
Bagian 6 — Missing Data
# Detect
df.isnull() # boolean mask
df.isnull().sum() # count per column
df.notnull()
# Drop
df.dropna() # drop rows dengan NaN
df.dropna(subset=["nama"])
df.dropna(axis=1) # drop columns dengan NaN
df.dropna(how="all") # only kalau semua NaN
# Fill
df.fillna(0)
df.fillna({"umur": 0, "nama": "Unknown"})
df["umur"].fillna(df["umur"].mean(), inplace=True)
df.fillna(method="ffill") # forward fill
df.fillna(method="bfill") # backward fill
Aturan ML: missing data harus di-handle. Drop kalau dikit, impute (fillna mean/median) kalau banyak.
Bagian 7 — Type Conversion
df["umur"] = df["umur"].astype(int)
df["harga"] = df["harga"].astype(float)
df["kategori"] = df["kategori"].astype("category") # hemat memory
# Datetime
df["tanggal"] = pd.to_datetime(df["tanggal"])
df["tanggal"] = pd.to_datetime(df["tanggal"], format="%Y-%m-%d")
Datetime Operations
df["tanggal"].dt.year
df["tanggal"].dt.month
df["tanggal"].dt.day
df["tanggal"].dt.dayofweek
df["tanggal"].dt.day_name()
# Filter by date
df[df["tanggal"] >= "2026-01-01"]
df[df["tanggal"].dt.year == 2026]
Bagian 8 — String Operations
Pandas Series punya .str accessor:
df["nama"].str.upper()
df["nama"].str.lower()
df["nama"].str.title()
df["nama"].str.strip()
df["nama"].str.replace("Budi", "Budiman")
df["nama"].str.split(" ")
df["nama"].str.split(" ", expand=True) # split jadi multiple columns
df["nama"].str.len()
df["nama"].str.contains("Bud")
df["email"].str.extract(r"(\w+)@")
Bagian 9 — Apply & Map
# Apply function ke Series
df["umur_squared"] = df["umur"].apply(lambda x: x ** 2)
df["nama_upper"] = df["nama"].apply(str.upper)
# Apply per row (axis=1)
df["full_info"] = df.apply(lambda row: f"{row['nama']} ({row['umur']})", axis=1)
# Apply per column (axis=0)
df.apply(lambda col: col.max() - col.min())
# Map untuk Series saja
df["status"] = df["umur"].map(lambda x: "dewasa" if x >= 18 else "anak")
# Map dengan dict
mapping = {"Bandung": "Jabar", "Jakarta": "DKI", "Surabaya": "Jatim"}
df["provinsi"] = df["kota"].map(mapping)
Tips:
applylebih lambat dari vectorized op. Pakai built-in kalau bisa.
Bagian 10 — Aggregation Sederhana
# Per column
df["umur"].sum()
df["umur"].mean()
df["umur"].median()
df["umur"].std()
df["umur"].min()
df["umur"].max()
df["umur"].count()
df["umur"].nunique() # jumlah unik
df["kota"].value_counts() # frequency table
# Multiple
df["umur"].agg(["mean", "std", "max"])
# Across DataFrame
df.sum()
df.mean(numeric_only=True)
Bagian 11 — Index
# Set index
df.set_index("nama", inplace=True)
df.loc["Budi"] # access by index
# Reset index
df.reset_index(inplace=True)
# Multi-index
df.set_index(["kota", "nama"])
df.loc[("Bandung", "Budi")]
Bagian 12 — Common Patterns
Pattern 1: Filter + Modify
df.loc[df["umur"] < 18, "kategori"] = "anak"
Pattern 2: Konversi Tipe Beberapa Kolom
df = df.astype({"umur": int, "harga": float, "tanggal": "datetime64"})
Pattern 3: Cek Quality Data
def data_quality(df):
print("Shape:", df.shape)
print("\nMissing:")
print(df.isnull().sum())
print("\nDuplicates:", df.duplicated().sum())
print("\nDtypes:")
print(df.dtypes)
print("\nDescribe:")
print(df.describe(include="all"))
Pattern 4: Drop Duplicates
df.drop_duplicates()
df.drop_duplicates(subset=["email"], keep="last")
Common Mistakes / FAQ
1. SettingWithCopyWarning
# JELEK — chained indexing, ambigu apakah view atau copy
df[df["umur"] > 25]["status"] = "senior" # SettingWithCopyWarning!
# BAIK — pakai .loc dengan satu langkah
df.loc[df["umur"] > 25, "status"] = "senior"
Analogi: chained indexing seperti minta foto dari teman lalu coret-coret di foto itu — tidak jelas apakah perubahanmu masuk ke foto asli atau bukan. Pakai .loc = langsung coret di foto aslinya.
2. inplace=True vs Reassignment
Kedua cara ini valid, tapi jangan dicampur:
df = df.dropna() # cara 1: reassign
df.dropna(inplace=True) # cara 2: in-place
df.dropna() # SALAH — tidak menyimpan hasil!
Komunitas Pandas modern lebih merekomendasikan reassignment karena lebih aman dengan method chaining.
3. Boolean dengan and/or → Error
# df[(df["umur"] > 18) and (df["kota"] == "Bandung")] # ValueError
df[(df["umur"] > 18) & (df["kota"] == "Bandung")] # benar
Pakai &, |, ~ dan kurung — sama seperti NumPy.
4. df["col"] vs df[["col"]]
df["nama"] # Series (1D)
df[["nama"]] # DataFrame (2D, satu kolom)
Bedanya penting saat passing ke ML library — banyak yang minta DataFrame, bukan Series.
5. Filter Tidak Mengubah DataFrame Asli
df[df["umur"] > 18] # ini cuma view, df tetap utuh
df = df[df["umur"] > 18] # baru replace
6. apply Lambat untuk Data Besar
# JELEK
df["double"] = df["x"].apply(lambda v: v * 2)
# BAIK (vectorized)
df["double"] = df["x"] * 2
Sebagai aturan jempol: kalau bisa pakai operator NumPy/Pandas built-in, jangan pakai apply.
7. CSV Encoding Error
df = pd.read_csv("data.csv", encoding="utf-8") # default
df = pd.read_csv("data.csv", encoding="latin-1") # fallback untuk file Excel Indonesia
Cek Pemahaman
- Bisa baca CSV dan inspect dengan head/info?
- Tahu beda iloc dan loc?
- Bisa filter dengan multiple condition?
- Bisa handle missing data?
- Bisa convert ke datetime dan ekstrak komponen?
- Bisa pakai apply dan map?
Challenge 4.2
Challenge 1 — Iris Dataset
import pandas as pd
import seaborn as sns
df = sns.load_dataset("iris")
- Inspect: shape, columns, dtypes
- Statistik per species
- Filter setosa dengan petal_length > 1.5
- Tambah kolom
area = petal_length * petal_width - Group by species, hitung mean tiap kolom
Challenge 2 — Titanic Dataset
df = sns.load_dataset("titanic")
- Berapa missing data per kolom?
- Fill missing age dengan median
- Drop kolom dengan > 50% missing
- Buat kolom
family_size = sibsp + parch + 1 - Buat kolom
is_alone(boolean) - Survival rate per Pclass
- Survival rate per gender per Pclass
Challenge 3 — Cleaning Real Data
Cari dataset Kaggle (e.g. "Sales Data"):
- Read CSV
- Inspect quality
- Handle missing
- Drop duplicates
- Convert types
- Fix obvious errors (negative prices, etc.)
- Save cleaned version
Challenge 4 — Filter & Transform
df = pd.DataFrame({
"nama": ["Budi", "Ani", "Cici", "Dodi", "Eka"],
"skor_math": [85, 72, 90, 65, 78],
"skor_eng": [70, 88, 92, 60, 80],
"umur": [20, 22, 21, 19, 23],
})
- Tambah kolom
total = math + eng - Tambah kolom
avg = (math + eng) / 2 - Filter siswa dengan avg >= 80
- Sort by total descending
- Tambah ranking column
Challenge 5 — String Cleaning
df = pd.DataFrame({
"email": [" Budi@Gmail.COM ", "ani@yahoo.com ", " cici@example.com"],
"phone": ["0812-3456-7890", "+62-812-1234-5678", "08123456789"]
})
- Clean email: trim, lowercase
- Validate email format (regex)
- Normalize phone: format
+62 XXX-XXXX-XXXX - Extract domain dari email
Challenge 6 — Time Series Mini
df = pd.DataFrame({
"tanggal": pd.date_range("2026-01-01", periods=30),
"sales": np.random.randint(100, 1000, 30),
})
- Set tanggal sebagai index
- Resample ke weekly
- Rolling mean 7 hari
- Pct change harian
Selanjutnya: 03-pandas-advanced.md