Filter dan transformasi pada data besar punya padanan vectorized yang sama cepatnya dengan aritmetika antar kolom dari Chapter sebelumnya. Prinsipnya satu: bangun Series boolean (mask) yang panjangnya sama dengan tabel, biarkan pandas memilih baris atau menulis nilai dalam satu pemanggilan C, dan jangan pernah mengulang baris demi baris. Chapter ini mengulang pola mask dari kondisi tunggal, penggabungan kondisi, isin() dan between(), sampai pembuatan label kondisional dengan np.where() dan np.select() — semuanya pada 100.000 baris transaksi yang sama.

Membangun dan Menggabungkan Mask #

Operasi perbandingan pada kolom menghasilkan Series bool, bukan nilai tunggal. Series itulah yang diberikan ke df[...] untuk memfilter baris, digabung dengan & (dan), | (atau), dan ~ (negasi):

python
import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
n = 100_000
df = pd.DataFrame(
    {
        "kota": rng.choice(["Jakarta", "Bandung", "Surabaya", "Medan", "Semarang"], size=n),
        "jumlah": rng.integers(1, 11, size=n),
        "harga": rng.choice([15000, 25000, 40000, 55000, 70000], size=n),
    }
)
df["total"] = df["jumlah"] * df["harga"]

# 1. Boolean masking: Series True/False sepanjang tabel
mask = df["total"] > 500_000
print("dtype mask:", mask.dtype)
print("jumlah True:", int(mask.sum()))
print("baris lolos:", df[mask].shape)
print()

# 2. Menggabungkan kondisi: & (dan), | (atau), ~ (negasi)
mask2 = (df["jumlah"] >= 3) & (df["harga"] <= 40_000)
print("kondisi gabungan True:", int(mask2.sum()))
print("negasi (~mask) True:", int((~mask).sum()))
print("isin Jakarta+Bandung:", int(df["kota"].isin(["Jakarta", "Bandung"]).sum()))
print("between harga 25rb-55rb:", int(df["harga"].between(25_000, 55_000).sum()))
print()

# 3. Gotcha: keyword and/or Python tidak bekerja pada mask
try:
    _ = (df["total"] > 500_000) and (df["jumlah"] >= 3)
except ValueError as e:
    print("keyword 'and' ->", type(e).__name__, "-", str(e).splitlines()[0])
print()

# 4. Label kondisional tanpa loop: np.where dan np.select
label = np.where(df["total"] >= 500_000, "premium", "reguler")
print("np.where premium:", int((label == "premium").sum()))
segmen = np.select(
    [df["total"] >= 500_000, df["total"] >= 200_000],
    ["premium", "reguler"],
    default="hemat",
)
nilai, banyak = np.unique(segmen, return_counts=True)
print("np.select:", dict(zip(nilai.tolist(), banyak.tolist())))
print()

# 5. Menulis nilai berdasarkan mask lewat .loc
df.loc[mask, "segmen"] = "premium"
print("segmen premium di df:", int((df["segmen"] == "premium").sum()))

Output yang Dihasilkan:

text
dtype mask: bool
jumlah True: 7971
baris lolos: (7971, 4)

kondisi gabungan True: 48224
negasi (~mask) True: 92029
isin Jakarta+Bandung: 39961
between harga 25rb-55rb: 60099

keyword 'and' -> ValueError - The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

np.where premium: 7971
np.select: {'hemat': 51889, 'premium': 7971, 'reguler': 40140}

segmen premium di df: 7971

Mari kita telaah baris-baris pentingnya:

  1. Baris 15-19 (mask tunggal): df["total"] > 500_000 menghasilkan Series bool dengan 7.971 nilai True. Menyerahkannya ke df[mask] mengembalikan tabel baru berukuran (7971, 4) — hanya baris yang lolos, tanpa loop.
  2. Baris 22-27 (penggabungan kondisi): setiap kondisi dibungkus tanda kurung lalu digabung &. Hasilnya 48.224 baris memenuhi kedua syarat sekaligus. Operator ~ membalik mask (92.029 baris), isin() memfilter keanggotaan kota (39.961 baris Jakarta+Bandung), dan between() memfilter rentang nilai (60.099 baris).
  3. Baris 30-34 (gotcha keyword and): menggabungkan dua mask dengan keyword Python and memunculkan ValueError: The truth value of a Series is ambiguous. Python mencoba menentukan nilai kebenaran tunggal dari Series 100.000 elemen — padahal yang kita inginkan adalah perbandingan per elemen, jadi gunakan &.
  4. Baris 37-46 (label kondisional): np.where(kondisi, "premium", "reguler") memilih nilai per baris tanpa loop (7.971 baris premium), sedangkan np.select() menerima daftar kondisi berurutan dengan nilai default untuk yang tidak lolos — hasilnya 51.889 hemat, 7.971 premium, 40.140 reguler.
  5. Baris 49-51 (penulisan via mask): df.loc[mask, "segmen"] = "premium" menambah kolom baru dan mengisi hanya baris yang lolos mask — 7.971 sel, satu pemanggilan. Pola df.loc[mask, kolom] = nilai adalah bentuk penulisan vectorized yang benar di pandas 3.0.

Peta Pola Masking #

KebutuhanPola vectorized
Filter satu syaratdf[df["total"] > 500_000]
Dua syarat sekaligusdf[(df["a"] >= 3) & (df["b"] <= 40_000)]
Salah satu dari beberapa nilaidf[df["kota"].isin(["Jakarta", "Bandung"])]
Rentang nilai inklusifdf[df["harga"].between(25_000, 55_000)]
Label dua pilihannp.where(mask, "ya", "tidak")
Label tiga pilihan atau lebihnp.select([k1, k2], [v1, v2], default=...)
Menulis nilai pada baris tertentudf.loc[mask, "kolom"] = nilai
Query teks ringkasdf.query("total > 500000")

Chapter berikutnya berpindah dari pola operasi ke pola penyimpanan: memilih dtype yang tepat — int32, float32, category, hingga dtype pyarrow — agar tabel yang sama memakan memori jauh lebih kecil.

Referensi resmi untuk pendalaman: panduan Indexing and selecting data , pandas.DataFrame.query , dan dokumentasi NumPy .