Sampai titik ini kita memfilter baris dengan boolean masking: df[(df["harga"] > 40000) & (df["jumlah"] >= 5)] menuntut penulisan nama DataFrame berulang dan tanda & alih-alih and. Method query() menyaring baris dengan satu string ekspresi yang jauh lebih ringkas, dan eval() menghitung aritmetika antarkolom dengan cara yang sama. Di balik keduanya berdiri mesin evaluasi numexpr yang bisa memangkas waktu dan memori saat DataFrame membesar. Chapter ini menguji kedua method pada DataFrame 60.000 baris transaksi, membedah sintaksnya, lalu menetapkan batas kapan keduanya benar-benar menguntungkan.
query(): Filter dengan Ekspresi String #
Script berikut membangun transaksi dengan random.default_rng(42) agar hasil deterministik, lalu memakai query() dalam beberapa varian.
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 60_000
kota_pool = np.array(["Jakarta", "Bandung", "Surabaya", "Medan"])
df = pd.DataFrame({
"kota": rng.choice(kota_pool, n),
"harga": rng.integers(5_000, 50_000, n),
"jumlah": rng.integers(1, 10, n),
"diskon": rng.random(n).round(2),
})
print("numexpr aktif (default):", pd.get_option("compute.use_numexpr"))
print()
# query() memfilter baris dengan ekspresi string
terpilih = df.query("harga > 40000 and jumlah >= 5")
print("baris terpilih:", len(terpilih))
print(terpilih.head(3))
print()
# variabel Python dirujuk dengan awalan @
batas = 40_000
print("dengan @batas :", len(df.query("harga > @batas")))
print("dengan literal:", len(df.query("harga > 40000")))
print()
# in dan not in untuk keanggotaan list
prioritas = ["Jakarta", "Surabaya"]
print("in :", len(df.query("kota in @prioritas")))
print("not in:", len(df.query("kota not in @prioritas")))
print()
# and/or ditulis apa adanya; & | juga sah
a = df.query("diskon > 0.9 or harga < 6000")
b = df.query("(diskon > 0.9) | (harga < 6000)")
print("or == | :", a.equals(b))
print()
# kolom dengan spasi dibungkus backtick
df2 = df.rename(columns={"harga": "harga jual"})
print("backtick:", len(df2.query("`harga jual` > 45000")))
print()
# eval() menghitung kolom baru dari ekspresi antarkolom
df.eval("total = harga * jumlah * (1 - diskon)", inplace=True)
print(df.head(3).round(2))
print()
# referensi nama yang tidak dikenal memunculkan UndefinedVariableError
try:
df.query("harca > 1000")
except pd.errors.UndefinedVariableError as e:
print("UndefinedVariableError:", str(e))Output yang Dihasilkan:
numexpr aktif (default): True
baris terpilih: 7520
kota harga jumlah diskon
12 Surabaya 46692 5 0.51
18 Medan 46480 7 0.06
25 Surabaya 43024 5 0.71
dengan @batas : 13407
dengan literal: 13407
in : 30104
not in: 29896
or == | : True
backtick: 6682
kota harga jumlah diskon total
0 Jakarta 9000 8 0.10 64800.00
1 Medan 20620 2 0.74 10722.40
2 Surabaya 37313 7 0.29 185445.61
UndefinedVariableError: name 'harca' is not definedMari kita bedah bagian pentingnya:
- Baris 18 (
anddi dalam string):df.query("harga > 40000 and jumlah >= 5")menyaring 7.520 baris; operator logikaand,or, dannotditulis apa adanya, tanpa tanda kurung penjelas di tiap kondisi. - Baris 25-26 (awalan
@): variabel Pythonbatasdirujuk sebagai@batas; hasilnya identik dengan literal13407baris, dan pemakaian variabel menjaga satu nilai ambang tetap konsisten antar script. - Baris 31-32 (
in/not in): keanggotaan list ditulis seperti Python biasa;kota in @prioritasmengambil 30.104 baris, dannot inmenjadi kebalikannya29.896. - Baris 36-38 (setara
|):orpada parser pandas diartikan sebagai|elementwise, sehingga kedua versi menghasilkan DataFrame yangequals. - Baris 43 (backtick): nama kolom yang mengandung spasi dibungkus tanda kutip terbalik (backtick) di dalam ekspresi, sehingga kolom
harga jualbisa dirujuk dengan aman; versinya menyaring 6.682 baris.
eval(): Aritmetika Antarkolom #
Baris 47 menambahkan kolom total lewat df.eval("total = harga * jumlah * (1 - diskon)", inplace=True). Bentuk penugasan di dalam string hanya diterima eval() dengan argumen inplace=True; tanpa penugasan, eval() mengembalikan Series hasil perhitungan. Angka pada output bisa diverifikasi manual: 9000 × 8 × (1 − 0.10) = 64800.00. Ekspresi juga boleh membandingkan kolom dengan kolom — df.eval("jumlah > diskon * 20") menghasilkan mask boolean tanpa menulis nama DataFrame berulang kali, pola yang pernah kita lihat sebagai kelemahan masking biasa pada ekspresi panjang.
Mesin numexpr di Balik Layar #
Baris 14 mencetak pd.get_option("compute.use_numexpr") yang bernilai True di lingkungan kita karena paket numexpr (versi 2.14.1) terpasang; itu engine default untuk eval(), query(), dan perbandingan antar-Series. Manfaatnya baru terasa pada skala besar: pada pengukuran sederhana dengan ekspresi aritmetika bertingkat di lima juta baris, engine numexpr menyelesaikan perhitungan sekitar dua kali lebih cepat daripada mode Python, dan puncak penggunaan memori turun dari sekitar 120 MB menjadi 80 MB karena array antara yang besar tidak dibuat penuh. Sebaliknya, untuk operasi sederhana atau data kecil, boolean masking biasa justru paling cepat — jadi pakailah query()/eval() demi kejelasan sintaks, dan biarkan numexpr menjadi bonus pada data besar.
Dengan query() dan eval(), ekspresi atas kolom kini bisa ditulis ringkas pada skala apa pun. Chapter berikutnya menyajikan cara baru menulis rumus kolom lewat pd.col(), fitur pandas 3.0, sebelum kita membedah fondasi di balik banyak fasilitas modern pandas: sistem extension dtypes yang menampung str, category, Int64, dtype pyarrow, dan sparse sekaligus.
Referensi resmi untuk chapter ini: pandas.DataFrame.query , pandas.DataFrame.eval , dan pandas.errors.UndefinedVariableError .