Skillversitas

DuckDB with Pandas and Arrow

DuckDB is designed to live inside a Python data stack, and its two most important partners are pandas DataFrames and Apache Arrow tables. Data can flow in both directions with almost no copying, so DuckDB acts as a fast SQL engine over objects you already have, and returns results in exactly the container your next step expects.

Querying Pandas DataFrames

DuckDB supports replacement scans: local variables that hold DataFrames can be referenced by name directly in SQL. The explicit alternatives are register() and from_df():

import duckdb, pandas as pd

df = pd.DataFrame({
    "device": ["s1", "s2", "s1"],
    "temp":   [21.3, 19.8, 22.1],
})

con = duckdb.connect()
con.register("temps", df)              # optional named view
rel = duckdb.from_df(df)               # relation over the DataFrame

print(duckdb.sql("SELECT device, avg(temp) AS avg_temp "
                 "FROM df GROUP BY 1").df())

The last query works because df is a local variable, no registration needed; the same trick applies for polars, and it is the fastest way to throw a SQL question at in-memory data.

Getting Results Back

Result objects convert to the container of your choice:

out  = duckdb.sql("SELECT * FROM df WHERE temp > 20")
df2  = out.df()                        # pandas (fetchdf is an alias)
tbl  = out.arrow()                     # pyarrow.Table
rows = out.fetchall()                  # list of tuples
rec  = out.fetchnumpy()                # dict of numpy arrays

.arrow() avoids a pandas copy when the downstream consumer is Arrow-based, which is common in streaming and data lake tools.

Zero-Copy Arrow Integration

Apache Arrow is the standard columnar in-memory format, and DuckDB shares a representation with it, so scanning or producing Arrow tables is zero-copy in the common case. duckdb.from_arrow() wraps a pyarrow.Table as a relation, and register() accepts Arrow tables for a named SQL view:

import pyarrow as pa

table = pa.table({
    "region": ["west", "east"],
    "amount": [120.0, 80.0],
})

agg = duckdb.sql("SELECT region, sum(amount) AS total "
                 "FROM at GROUP BY region").df()
select

Hmm, that example is incomplete.