Pandas Basics

4 jam10 min baca
Tujuan

Pandas = "Excel di code". Tools standar untuk data manipulation tabular di Python.

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:

  1. Baca file (CSV/Excel/JSON) menjadi DataFrame.
  2. Lakukan inspect (head/info/describe) untuk mengenal struktur.
  3. Pilih subset (loc/iloc/boolean), modifikasi kolom, dan bersihkan (missing/dtype/duplicate).
  4. 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:

  1. Identifikasi index (label baris) — bisa default integer atau custom.
  2. Tiap kolom adalah Series 1D dengan nama dan dtype sendiri.
  3. 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 &, |, ~ (bukan and, 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: apply lebih 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")
  1. Inspect: shape, columns, dtypes
  2. Statistik per species
  3. Filter setosa dengan petal_length > 1.5
  4. Tambah kolom area = petal_length * petal_width
  5. Group by species, hitung mean tiap kolom

Challenge 2 — Titanic Dataset

df = sns.load_dataset("titanic")
  1. Berapa missing data per kolom?
  2. Fill missing age dengan median
  3. Drop kolom dengan > 50% missing
  4. Buat kolom family_size = sibsp + parch + 1
  5. Buat kolom is_alone (boolean)
  6. Survival rate per Pclass
  7. Survival rate per gender per Pclass

Challenge 3 — Cleaning Real Data

Cari dataset Kaggle (e.g. "Sales Data"):

  1. Read CSV
  2. Inspect quality
  3. Handle missing
  4. Drop duplicates
  5. Convert types
  6. Fix obvious errors (negative prices, etc.)
  7. 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],
})
  1. Tambah kolom total = math + eng
  2. Tambah kolom avg = (math + eng) / 2
  3. Filter siswa dengan avg >= 80
  4. Sort by total descending
  5. 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"]
})
  1. Clean email: trim, lowercase
  2. Validate email format (regex)
  3. Normalize phone: format +62 XXX-XXXX-XXXX
  4. 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),
})
  1. Set tanggal sebagai index
  2. Resample ke weekly
  3. Rolling mean 7 hari
  4. Pct change harian

Selanjutnya: 03-pandas-advanced.md