Tutorial Ibis: API DataFrame Portabel untuk Banyak Backend

# Ibis: API Dataframe Python yang Portabel di Banyak Backend Ibis adalah library dataframe Python yang memungkinkan Anda menulis kode analitik satu kali lalu menjalankannya di banyak mesin eksekusi,...

By Ruby Abdullah · · tutorial
IbisDataFrameSQLData EngineeringAnalyticsPython

Ibis: API Dataframe Python yang Portabel di Banyak Backend

Ibis adalah library dataframe Python yang memungkinkan Anda menulis kode analitik satu kali lalu menjalankannya di banyak mesin eksekusi, mulai dari DuckDB lokal di laptop hingga BigQuery, Snowflake, atau Spark di produksi. Daripada menulis ulang logika pandas menjadi SQL ketika data sudah tidak muat di memori, Anda cukup mendeskripsikan transformasinya dalam Python, dan Ibis akan mengompilasinya ke dialek backend mana pun yang Anda hubungkan. Tutorial ini membahas konsep intinya: deferred execution, portabilitas backend, dan contoh analitik menyeluruh yang praktis.

Masalah yang Diselesaikan Ibis

Banyak tim data menghadapi kendala yang sama. Anda membuat prototipe analisis dengan pandas karena cepat ditulis dan mudah dipahami. Dataset bertambah besar, tidak lagi muat di memori, dan sekarang Anda harus menerjemahkan kode pandas tersebut menjadi SQL agar bisa berjalan di dalam data warehouse. Kedua implementasi itu mulai berbeda, bug muncul saat penerjemahan, dan Anda harus memelihara dua versi dari logika yang sama.

Ibis menawarkan satu API dataframe yang berdiri di depan mesin eksekusi. Anda menulis ekspresi dalam Python, dan:

  • Kode yang sama berjalan di mesin lokal (DuckDB) saat pengembangan dan di warehouse (BigQuery, Snowflake, Postgres) saat produksi.
  • Komputasi berat terjadi di dalam mesin, dekat dengan data, bukan di proses Python Anda.
  • Anda terhindar dari menerjemahkan pandas ke SQL secara manual, dan bisa menskalakan dari laptop ke warehouse cukup dengan mengganti koneksi.

Model berpikirnya lebih dekat ke query builder ketimbang pandas. Anda menyusun deskripsi sebuah komputasi, bukan langsung memmaterialisasi hasil antara.

Deferred Execution (Eksekusi Tertunda)

Ini adalah konsep paling penting di Ibis. Ketika Anda merangkai operasi seperti filter, select, dan groupby, tidak ada yang dihitung. Anda sedang membangun expression tree. Eksekusi baru terjadi ketika Anda secara eksplisit meminta hasilnya.

import ibis

con = ibis.duckdb.connect()

Ini membangun ekspresi. Belum ada query yang berjalan.

orders = con.table("orders")

expr = (

orders

.filter(orders.status == "completed")

.groupby("country")

.aggregate(total=orders.amount.sum())

)

Masih belum ada yang dieksekusi. expr hanyalah sebuah deskripsi.

print(type(expr)) #

Eksekusi terjadi di sini, mengembalikan pandas DataFrame.

df = expr.execute()

Titik pemicu yang benar-benar menjalankan query adalah:

  • .execute() — mengembalikan pandas DataFrame (default).
  • .topandas() — output pandas secara eksplisit.
  • .topolars() — mengembalikan Polars DataFrame.
  • .topyarrow() — mengembalikan PyArrow Table.
  • .head().execute() — melihat sampel kecil.

Karena eksekusi tertunda, Ibis dapat mendorong seluruh pipeline ke mesin sebagai satu query yang teroptimasi. Inilah yang membuatnya skalabel: Anda tidak menarik data mentah ke Python lalu memfilternya di sana.

Instalasi

Ibis dipasang bersama satu atau lebih extra backend. Setiap backend yang ingin Anda gunakan adalah dependensi opsional, sehingga instalasinya tetap ramping.

# Library inti plus backend DuckDB (titik awal yang direkomendasikan)

pip install 'ibis-framework[duckdb]'

Tambahkan backend lain sesuai kebutuhan

pip install 'ibis-framework[bigquery]'

pip install 'ibis-framework[postgres]'

pip install 'ibis-framework[snowflake]'

pip install 'ibis-framework[polars]'

Beberapa backend sekaligus

pip install 'ibis-framework[duckdb,postgres,bigquery]'

Verifikasi instalasi:

import ibis

print(ibis.version)

DuckDB adalah backend lokal yang direkomendasikan. Ia berjalan in-process, tidak butuh server, dan mendukung jangkauan fitur SQL lengkap yang dikompilasi Ibis, sehingga ideal untuk pengembangan dan pengujian.

Menghubungkan ke Backend

Koneksi adalah objek yang tahu cara berbicara dengan sebuah mesin. API ekspresi yang sama bekerja terlepas dari koneksi mana yang Anda buat.

import ibis

DuckDB in-memory (bagus untuk eksperimen)

con = ibis.duckdb.connect()

DuckDB yang dipersistensi ke file

con = ibis.duckdb.connect("analytics.ddb")

Koneksi gaya URL (berfungsi lintas backend)

con = ibis.connect("duckdb://analytics.ddb")

Untuk backend warehouse, koneksi membawa kredensial dan detail proyek. Ekspresi yang Anda bangun setelahnya tetap identik.

# BigQuery

con = ibis.bigquery.connect(

projectid="my-gcp-project",

datasetid="analytics",

)

PostgreSQL

con = ibis.postgres.connect(

host="localhost",

user="analyst",

password="secret",

database="warehouse",

)

Snowflake

con = ibis.snowflake.connect(

user="analyst",

account="abc-xy123",

database="ANALYTICS",

warehouse="COMPUTEWH",

)

Polars (in-memory, bukan mesin SQL)

con = ibis.polars.connect()

Perhatikan bahwa beberapa backend, seperti Polars dan pandas, bukan mesin SQL. Ibis tetap memberi Anda API dataframe yang sama di atasnya, mengeksekusi melalui library native alih-alih menghasilkan SQL.

Membaca Data

Ibis dapat membaca tabel yang sudah ada di backend atau memuat file secara langsung. DuckDB membuat pembacaan file menjadi sangat praktis.

import ibis

con = ibis.duckdb.connect()

Mereferensikan tabel yang sudah ada di backend

orders = con.table("orders")

Membaca file secara langsung (DuckDB, di antaranya)

sales = con.readcsv("data/sales.csv")

events = con.readparquet("data/events/.parquet")

Daftar tabel yang terdaftar di koneksi

print(con.listtables())

Anda juga dapat membawa data in-memory sebagai memtable. Ini berguna untuk tabel lookup kecil atau data uji, dan berfungsi lintas backend.

import pandas as pd

import ibis

regions = pd.DataFrame(

{

"country": ["ID", "SG", "MY"],

"region": ["SEA", "SEA", "SEA"],

}

)

Membungkus pandas DataFrame menjadi tabel Ibis

regiontable = ibis.memtable(regions)

Operasi Tabel Inti

Kosakata operasinya akan terasa akrab jika Anda menguasai SQL atau pandas, tetapi semuanya tersusun secara lazy.

Select dan Filter

import ibis

con = ibis.duckdb.connect()

orders = con.table("orders")

Memilih kolom tertentu

expr = orders.select("orderid", "country", "amount")

Memfilter baris (beberapa kondisi digabung dengan & dan |)

expr = orders.filter(

(orders.amount > 100) & (orders.status == "completed")

)

Referensi Kolom Tertunda

Ibis menyediakan objek underscore yang merujuk pada "tabel saat ini" di dalam sebuah rantai. Objek ini memungkinkan Anda menulis pipeline yang mengalir tanpa harus menyebut nama tabel berulang kali.

from ibis import 

expr = (

orders

.filter(.status == "completed")

.mutate(netamount=.amount - .discount)

.filter(.netamount > 50)

.orderby(.netamount.desc())

.limit(10)

)

Di sini selalu menunjuk ke tabel yang dihasilkan langkah sebelumnya, termasuk kolom netamount yang ditambahkan oleh mutate, yang tidak ada di tabel orders asli.

Mutate, Order, dan Limit

expr = (

orders

.mutate(amountwithtax=orders.amount 1.11)

.orderby(ibis.desc("amountwithtax"))

.limit(20)

)

Agregasi dengan groupby

from ibis import 

metrics = (

orders

.groupby("country")

.aggregate(

ordercount=.orderid.count(),

totalrevenue=.amount.sum(),

avgorder=.amount.mean(),

)

.orderby(.totalrevenue.desc())

)

Join

con = ibis.duckdb.connect()

orders = con.table("orders")

customers = con.table("customers")

joined = orders.join(

customers,

orders.customerid == customers.customerid,

how="inner",

)

result = joined.select(

"orderid",

"amount",

customers.name,

customers.segment,

)

Window Function

Window function menghitung nilai di sekumpulan baris yang berkaitan dengan baris saat ini, tanpa meringkasnya seperti yang dilakukan agregat.

from ibis import 

Memberi peringkat order berdasarkan jumlah di tiap negara

ranked = orders.mutate(

rankincountry=ibis.rank().over(

ibis.window(groupby="country", orderby=.amount.desc())

)

)

Metode String dan Tanggal

Ibis menyediakan metode string dan temporal yang dikompilasi ke fungsi backend yang tepat.

from ibis import 

expr = orders.mutate(

countrylower=.country.lower(),

nameclean=.customername.strip(),

haspromo=.notes.contains("PROMO"),

orderyear=.orderdate.year(),

ordermonth=.orderdate.month(),

dayssince=(ibis.now() - .orderdate).cast("int64"),

)

Pratinjau Secara Lazy dan Memeriksa SQL Hasil Kompilasi

Karena ekspresi tertunda, Anda dapat memeriksa apa yang akan dikirim Ibis ke mesin sebelum menjalankan sesuatu yang berat. Ini sangat berharga untuk debugging dan untuk memahami biaya.

from ibis import 

expr = (

orders

.filter(.status == "completed")

.groupby("country")

.aggregate(total=.amount.sum())

)

Pratinjau hanya beberapa baris (tetap mendorong query, dengan LIMIT)

print(expr.head(5).execute())

Memeriksa SQL hasil kompilasi tanpa mengeksekusi

print(ibis.tosql(expr))

Ekspresi yang sama dikompilasi menjadi SQL berbeda tergantung backend yang terhubung. Di bawah ini, Python yang identik menghasilkan dialek DuckDB dan BigQuery.

import ibis

expr = (

ibis.table({"country": "string", "amount": "float64"}, name="orders")

.groupby("country")

.aggregate(total=ibis..amount.sum())

)

print(ibis.tosql(expr, dialect="duckdb"))

print(ibis.tosql(expr, dialect="bigquery"))

-- dialek duckdb

SELECT

"country",

SUM("amount") AS "total"

FROM "orders"

GROUP BY 1

-- dialek bigquery

SELECT

country,

SUM(amount) AS total

FROM orders

GROUP BY 1

Perhatikan perbedaan pada quoting identifier ("..." versus ` ... ). Ibis menangani sintaks spesifik dialek, nama fungsi, dan cast tipe sehingga Anda tidak perlu memikirkannya.

Berganti Backend: Imbalan dari Portabilitas

Di sinilah Ibis membuktikan tempatnya dalam sebuah stack. Anda mengembangkan dan menguji terhadap DuckDB secara lokal, lalu mengarahkan kode yang sama ke BigQuery di produksi. Logika analitik tidak berubah, hanya koneksinya.

import ibis

from ibis import

def revenuebycountry(con, tablename="orders"):

"""Analitik agnostik-backend. Berfungsi di koneksi Ibis mana pun."""

orders = con.table(tablename)

return (

orders

.filter(.status == "completed")

.groupby("country")

.aggregate(

totalrevenue=.amount.sum(),

ordercount=.orderid.count(),

)

.orderby(.totalrevenue.desc())

)

Pengembangan: DuckDB lokal

devcon = ibis.duckdb.connect("local.ddb")

print(revenuebycountry(devcon).execute())

Produksi: BigQuery, fungsi yang sama, koneksi berbeda

prodcon = ibis.bigquery.connect(projectid="my-project", datasetid="analytics")

print(revenuebycountry(prodcon).execute())

Fungsi ini ditulis satu kali dan benar-benar portabel. Satu-satunya detail spesifik lingkungan adalah objek koneksi yang Anda berikan.

Interoperabilitas dengan pandas, Polars, dan PyArrow

Ibis dirancang untuk menyatu ke dalam stack data Python yang sudah ada, bukan menggantikannya. Anda dapat memindahkan data masuk dan keluar dari pandas, Polars, dan Arrow dengan leluasa.

import ibis

con = ibis.duckdb.connect()

orders = con.table("orders")

Keluar: pilih format output yang Anda inginkan

pdf = orders.limit(1000).topandas()

pldf = orders.limit(1000).topolars()

arrowtbl = orders.limit(1000).topyarrow()

Masuk: bungkus dataframe eksternal menjadi tabel Ibis

import pandas as pd

extra = pd.DataFrame({"id": [1, 2], "label": ["a", "b"]})

extratbl = ibis.memtable(extra)

Pola yang umum adalah menggunakan Ibis sebagai frontend yang menyerahkan SQL berat ke warehouse, lalu menarik hanya hasil agregat yang kecil ke pandas untuk plotting atau pelaporan, sehingga hanya ringkasannya yang melintasi jaringan.

Scalar Python UDF

Ketika sebuah backend tidak memiliki fungsi bawaan yang Anda butuhkan, Anda dapat mendefinisikan user-defined function skalar dalam Python. Dukungan dan performanya bergantung pada backend (DuckDB mendukung Python UDF dengan baik).

import ibis

from ibis import

@ibis.udf.scalar.python

def classifyamount(x: float) -> str:

if x >= 1000:

return "large"

elif x >= 100:

return "medium"

return "small"

con = ibis.duckdb.connect()

orders = con.table("orders")

expr = orders.mutate(sizebucket=classifyamount(.amount))

Utamakan operasi bawaan Ibis bila tersedia, karena ia dikompilasi ke fungsi native mesin dan berjalan lebih cepat dibanding UDF Python baris demi baris. Gunakan UDF hanya ketika logika tidak bisa diekspresikan dengan cara lain.

Jalan Keluar ke SQL Mentah

Ibis tidak mengunci Anda dari SQL mentah. Ketika Anda butuh konstruksi spesifik backend atau sekadar lebih suka menulis query secara manual, Anda dapat turun ke SQL dan tetap menyusun di atas hasilnya.

import ibis

from ibis import

con = ibis.duckdb.connect()

Menjalankan SQL mentah dan mendapatkan tabel Ibis kembali

top = con.sql(

"""

SELECT country, SUM(amount) AS total

FROM orders

WHERE status = 'completed'

GROUP BY country

"""

)

Lanjut membangun dengan API Ibis di atas hasil SQL

final = top.filter(.total > 10000).orderby(.total.desc())

Memberi nama (alias) ekspresi tabel untuk dipakai ulang atau di dalam con.sql

labeled = top.alias("countrytotals")

Pendekatan campuran ini bersifat praktis: gunakan API Ibis yang portabel untuk sebagian besar logika Anda, dan gunakan con.sql(...) hanya untuk bagian yang benar-benar butuh query tulisan tangan.

Contoh Menyeluruh: Analitik Penjualan di DuckDB

Contoh berikut berjalan sepenuhnya di instance DuckDB lokal. Ia membangun dua memtable, menggabungkan orders dengan customers, menghitung metrik berkelompok, dan memberi peringkat order di tiap segmen menggunakan window function.

import ibis

import pandas as pd

from ibis import

ibis.options.interactive = True # auto-eksekusi dan cetak rapi di REPL

con = ibis.duckdb.connect()

ordersdf = pd.DataFrame(

{

"orderid": [1, 2, 3, 4, 5, 6],

"customerid": [10, 10, 11, 12, 11, 12],

"amount": [120.0, 80.0, 500.0, 30.0, 220.0, 90.0],

"status": [

"completed",

"completed",

"completed",

"cancelled",

"completed",

"completed",

],

"orderdate": pd.todatetime(

[

"2026-01-05",

"2026-01-09",

"2026-02-01",

"2026-02-03",

"2026-02-15",

"2026-03-02",

]

),

}

)

customersdf = pd.DataFrame(

{

"customerid": [10, 11, 12],

"name": ["Andi", "Budi", "Citra"],

"segment": ["retail", "wholesale", "retail"],

}

)

orders = con.createtable("orders", ordersdf)

customers = con.createtable("customers", customersdf)

1) Gabungkan orders dengan customers, simpan hanya order completed

joined = (

orders

.filter(.status == "completed")

.join(customers, "customerid", how="inner")

)

2) Metrik berkelompok per segmen pelanggan

segmentmetrics = (

joined

.groupby("segment")

.aggregate(

revenue=.amount.sum(),

ordercount=.orderid.count(),

avgorder=.amount.mean(),

)

.orderby(.revenue.desc())

)

print(segmentmetrics.execute())

3) Peringkat tiap order berdasarkan amount dalam segmennya (window function)

ranked = joined.mutate(

rankinsegment=ibis.rank().over(

ibis.window(groupby="segment", orderby=.amount.desc())

)

).select("orderid", "name", "segment", "amount", "rankinsegment")

print(ranked.orderby(["segment", "rankinsegment"]).execute())

Periksa SQL yang benar-benar dijalankan DuckDB untuk langkah perankingan

print(ibis.tosql(ranked))

Pipeline ini terbaca seperti Python biasa, namun setiap langkah dieksekusi di dalam DuckDB sebagai satu query hasil kompilasi. Mengganti con dengan koneksi BigQuery akan menjalankan logika yang identik di warehouse.

Kapan Memakai Ibis vs Polars vs SQL Mentah

Setiap alat punya keunggulan masing-masing, dan keduanya sering berdampingan dalam satu proyek.

  • Gunakan Ibis ketika Anda butuh satu basis kode analitik yang berjalan di banyak mesin, ketika data Anda ada di warehouse dan Anda ingin mendorong komputasi ke sana, atau ketika Anda ingin mengembangkan secara lokal di DuckDB lalu menerapkannya ke BigQuery atau Snowflake tanpa menulis ulang. Ibis adalah frontend, bukan mesin.
  • Gunakan Polars ketika data Anda muat nyaman di satu mesin, Anda ingin performa in-process maksimal, dan Anda tidak butuh portabilitas backend. Polars adalah mesin dan sangat baik untuk kerja dataframe lokal yang cepat.
  • Gunakan SQL mentah ketika sebuah query secara alami diekspresikan dalam SQL, bersifat spesifik backend, atau ketika tim Anda sudah memelihara aset SQL. Anda tetap bisa membungkus SQL itu di Ibis lewat con.sql(...) untuk menyusun lebih lanjut.

Hibrida yang masuk akal: buat prototipe dan eksplorasi dengan Polars atau pandas, ekspresikan pipeline produksi di Ibis agar berjalan di warehouse, dan turun ke con.sql(...) untuk sesekali query yang butuh penyetelan manual.

Praktik Terbaik

  • Kembangkan di DuckDB, terapkan di warehouse Anda. DuckDB memberi loop lokal yang cepat tanpa server sambil menjaga perilakunya dekat dengan mesin SQL produksi.
  • Periksa SQL dengan ibis.tosql(expr) sebelum menjalankan query mahal. Ia menunjukkan persis apa yang akan dieksekusi mesin dan membantu Anda menalar biaya.
  • Jaga komputasi berat tetap di mesin. Filter dan agregasi sebelum memanggil .topandas(), sehingga hanya hasil kecil yang masuk ke Python.
  • Gunakan from ibis import untuk rantai yang terbaca dan mengalir yang merujuk kolom antara, dan utamakan operasi bawaan dibanding UDF karena ia dikompilasi ke fungsi native mesin dan berjalan jauh lebih cepat daripada Python per baris.
  • Berikan koneksi sebagai parameter ke fungsi analitik Anda agar logika yang sama tetap agnostik-backend dan mudah diuji.
  • Pin versi Ibis dan backend Anda di produksi, karena kompilasi dialek bisa berubah antarrilis.

Kesimpulan dan Poin Penting

Ibis memberi Anda satu API dataframe yang portabel yang dikompilasi ke dialek SQL mesin mana pun yang Anda hubungkan, sehingga Anda dapat menulis analitik satu kali dan menjalankannya dari laptop hingga warehouse.

  • Ibis adalah frontend di atas mesin eksekusi, bukan mesin itu sendiri. Ia membangun ekspresi dan menyerahkan query hasil kompilasi ke backend seperti DuckDB, BigQuery, Postgres, Snowflake, dan Spark.
  • Eksekusi bersifat tertunda. Anda menyusun expression tree, dan tidak ada yang berjalan sampai Anda memanggil .execute(), .topandas(), .topolars(), atau .topyarrow().
  • Kode yang sama berjalan di backend berbeda hanya dengan mengganti koneksi, dan inilah manfaat portabilitas yang utama.
  • Anda dapat memeriksa SQL hasil kompilasi dengan ibis.to_sql(expr) dan memastikan ekspresi yang sama menghasilkan dialek berbeda per backend.
  • Ibis berinteroperasi dengan rapi bersama pandas, Polars, dan PyArrow, serta menyediakan jalan keluar con.sql(...)` untuk SQL mentah saat dibutuhkan.
  • Pilih Ibis untuk portabilitas dan pushdown skala warehouse, Polars untuk kerja lokal satu mesin yang cepat, dan SQL mentah di mana ia paling cocok, dengan memadukan keduanya sesuai kebutuhan proyek.

Artikel Terkait

DuckDB: Database Analitik In-Process untuk Data Science

DuckDB: Database Analitik In-Process untuk Data Science DuckDB adalah database analitik in-process yang dirancang khusus...

Tutorial PostgreSQL Advanced untuk ML: Analytics dan Feature Engineering

Tutorial 17: PostgreSQL Lanjutan untuk Machine Learning Daftar Isi Pendahuluan Prasyarat Window Functions untuk Rekayasa...

Tutorial dlt: Pipeline Ingestion Data Berbasis Python

Membangun Pipeline EL Berbasis Python dengan dlt (data load tool) Sebagian besar tim data menghabiskan waktu yang tidak ...

Tutorial Pandera: Validasi Data Statistik untuk DataFrame

Pandera: Validasi Data Statistik untuk DataFrame pandas dan Polars Pipeline data sering gagal tanpa suara. Sebuah kolom ...