
Why You Need a Unified Python Data Processing Toolkit
In daily Python data analysis, we almost always work with three libraries: pandas, Polars, and Apache Arrow. Pandas is the de facto standard, Polars has risen thanks to its speed, and Arrow is the industry norm for columnar memory format.
But when data volume grows, problems emerge:
# The classic pandas dilemma
df = pd.read_csv('big_file.csv') # 10GB file → Out of Memory
df = df[df['amount'] > 0] # Must load everything before filtering
result = df.groupby('category').sum() # Aggregation is painfully slow
DuckDB changes this picture. It’s not a database server you need to deploy—it’s an analytical engine that embeds directly into your Python process. With zero-copy integration alongside pandas, Polars, and Arrow, you can achieve order-of-magnitude performance gains without changing your workflow.
This article systematically covers 5 most practical DuckDB + Python integration techniques based on our latest channel push.
Technique 1: Zero-Copy pandas ↔ DuckDB Conversion
Many users don’t know that DuckDB can directly read pandas DataFrames without writing to CSV first and reading back. The entire process keeps data in memory with no serialization overhead.
Register a DataFrame as a Temporary Table
import duckdb
import pandas as pd
# Assume you already have a pandas DataFrame
df = pd.read_parquet('sales.parquet')
# Register directly as a table — zero copy
con = duckdb.connect()
con.register('sales', df)
# Process with SQL, convert results back to DataFrame
result = con.execute("""
SELECT
DATE_TRUNC('month', sale_date) AS month,
SUM(amount) AS total,
COUNT(*) AS orders
FROM sales
WHERE status = 'completed'
GROUP BY 1
ORDER BY 1
""").fetchdf()
print(result)
Key Points
| Operation | Description |
|---|---|
con.register('name', dataframe) | Registers a DataFrame as a DuckDB temporary table |
con.execute(...).fetchdf() | Query results become a pandas DataFrame directly |
| Data transfer | Zero-copy via Arrow format, no serialization overhead |
The reverse also works—DuckDB query results can be converted directly to pandas:
# Query Parquet files from S3 in one step
df_result = con.execute("""
SELECT * FROM read_parquet('s3://bucket/data/*.parquet')
WHERE date >= '2026-01-01'
""").fetchdf()
Technique 2: Polars Users — Arrow is the Bridge
Polars is built on Apache Arrow, and DuckDB natively supports Arrow. This means data can be passed between them with zero copy.
import duckdb
import polars as pl
# Polars DataFrame → DuckDB (via Arrow, zero copy)
pl_df = pl.read_parquet('sales.parquet')
con = duckdb.connect()
# Method A: Pass Arrow table directly
arrow_table = pl_df.to_arrow()
con.register('sales_arrow', arrow_table)
result = con.execute("SELECT category, AVG(amount) FROM sales_arrow GROUP BY 1").fetchdf()
# Method B: Convert DuckDB results directly back to Polars
duckdb_result = con.execute("""
SELECT category, SUM(amount) AS total_sales
FROM sales_arrow
GROUP BY 1
HAVING total_sales > 10000
ORDER BY total_sales DESC
""").fetch_arrow_table()
polars_result = pl.from_arrow(duckdb_result)
print(polars_result)
Why This Matters
- Polars excels at columnar calculations, ideal for fast per-column transformations and filtering
- DuckDB excels at complex SQL queries, window functions, CTEs, and multi-table joins
- They connect through Arrow, so data doesn’t need to be copied—zero performance loss
Use Polars for quick exploration and DuckDB for heavy lifting—that’s the true best combination.
Technique 3: Large File Processing — Never Load Everything into Memory
This is DuckDB’s killer use case. You have a 10GB CSV or Parquet file that pandas simply can’t handle?
import duckdb
con = duckdb.connect()
# Query large files on disk directly, return only what you need
top_categories = con.execute("""
SELECT
category,
SUM(amount) AS total_sales,
COUNT(*) AS order_count,
AVG(amount) AS avg_order_value
FROM read_csv_auto('/data/sales_2026.csv', header=true)
GROUP BY category
ORDER BY total_sales DESC
LIMIT 10
""").fetchdf()
print(top_categories)
Compare with the pandas approach:
# ❌ Pandas: Load the entire file into memory first
import pandas as pd
df = pd.read_csv('/data/sales_2026.csv') # 10GB file → OOM
result = df.groupby('category').agg(
total_sales=('amount', 'sum'),
order_count=('amount', 'count'),
avg_order_value=('amount', 'mean')
).nlargest(10, 'total_sales')
Core Advantages
| Dimension | pandas | DuckDB |
|---|---|---|
| Memory usage | Full file loaded | Only reads needed columns and data |
| 10GB file actual read | 10GB+ | Possibly just 200MB |
| Predicate pushdown | ❌ Not supported | ✅ Automatic optimization |
| Columnar scanning | ❌ Loads all columns | ✅ Reads only required columns |
DuckDB uses predicate pushdown and columnar scanning techniques. For a 10GB file, it may only need to read 200MB of data. Memory usage drops from 10GB+ to tens of MB. This isn’t optimization—it’s an architectural generational gap.
Technique 4: One-Click Multi-Format File Reading
DuckDB can read various formats directly without prior conversion. This is extremely practical in daily work—your boss sends an Excel file, a colleague drops a JSON on you, historical data is still in SQLite.
import duckdb
con = duckdb.connect()
# CSV
con.execute("SELECT * FROM read_csv_auto('data.csv')")
# Excel
con.execute("SELECT * FROM read_xlsx('data.xlsx')")
# JSON
con.execute("SELECT * FROM read_json_auto('data.json')")
# Parquet
con.execute("SELECT * FROM read_parquet('data.parquet')")
# Multiple Parquet files auto-merged
con.execute("SELECT * FROM read_parquet('s3://bucket/data/*.parquet')")
# Directly query SQLite database
con.execute("SELECT * FROM sqlite_scan('legacy.db', 'users')")
# Remote MySQL/PostgreSQL queries
con.execute("SELECT * FROM mysql_scan('host=localhost', 'database', 'users')")
con.execute("SELECT * FROM postgres_scan('dbname=sales host=localhost', 'orders')")
Practical Example: Turn Excel Reports into SQL Query Objects
import duckdb
con = duckdb.connect()
# Your boss sends an Excel file with 5 worksheets
sheets = con.execute("""
SELECT sheet_name
FROM read_xlsx_auto('monthly_report.xlsx')
""").fetchall()
# Query a specific worksheet
jan_data = con.execute("""
SELECT *
FROM read_xlsx('monthly_report.xlsx', sheet='January')
""").fetchdf()
# Merge multiple months
feb_data = con.execute("""
SELECT *
FROM read_xlsx('monthly_report.xlsx', sheet='February')
""").fetchdf()
combined = jan_data.vstack(feb_data) if hasattr(jan_data, 'vstack') else pd.concat([jan_data, feb_data])
Technique 5: DuckDB as the “Acceleration Engine” for pandas/Polars
You don’t have to abandon pandas or Polars. DuckDB can serve as their backend engine, making slow operations fast.
Replace Slow groupby with SQL
import duckdb
import pandas as pd
# Traditional slow pandas operation
# df.groupby('category').sum() is very slow on large datasets
# Accelerate with DuckDB
con = duckdb.connect()
con.register('df', your_dataframe)
fast_result = con.execute("""
SELECT
category,
SUM(sales) AS total_sales,
AVG(sales) AS avg_sales,
COUNT(DISTINCT user_id) AS unique_users
FROM df
GROUP BY category
""").fetchdf()
Hybrid Polars + DuckDB Usage
import duckdb
import polars as pl
con = duckdb.connect()
# Convert Polars DataFrame to Arrow then register
arrow_table = pl_df.to_arrow()
con.register('polars_df', arrow_table)
# Use DuckDB for complex SQL, Polars for column transformations
sql_result = con.execute("""
WITH monthly AS (
SELECT
DATE_TRUNC('month', date) AS month,
SUM(amount) AS revenue
FROM polars_df
GROUP BY 1
)
SELECT
month,
revenue,
LAG(revenue) OVER (ORDER BY month) AS prev_month_revenue,
ROUND((revenue - LAG(revenue) OVER (ORDER BY month)) * 100.0
/ NULLIF(LAG(revenue) OVER (ORDER BY month), 0), 2) AS growth_pct
FROM monthly
""").fetch_arrow_table()
final_df = pl.from_arrow(sql_result)
Comparison with Traditional Tools
| Operation | pandas | Polars | DuckDB + Python |
|---|---|---|---|
| Read CSV | pd.read_csv() | pl.scan_csv() | read_csv_auto() |
| Read Parquet | pd.read_parquet() | pl.scan_parquet() | read_parquet() |
| Read Excel | pd.read_excel() | ❌ Not supported | read_xlsx() |
| Read JSON | pd.read_json() | pl.scan_ndjson() | read_json_auto() |
| DataFrame→Table | Not supported | Not supported | con.register() |
| Query→DataFrame | N/A | N/A | .fetchdf() |
| Query→Polars | N/A | N/A | .fetch_arrow_table() |
| Large file aggregation | groupby().sum() OOM | group_by().sum() | SQL GROUP BY streaming |
| Window functions | transform() | over() | OVER() |
| Remote data sources | Requires extra libs | Requires extra libs | Built-in support |
Quick Reference: Common Functions
| Function | pandas | DuckDB SQL |
|---|---|---|
| Read CSV | pd.read_csv() | read_csv_auto('file.csv') |
| Read Parquet | pd.read_parquet() | read_parquet('file.parquet') |
| Read Excel | pd.read_excel() | read_xlsx('file.xlsx') |
| Read JSON | pd.read_json() | read_json_auto('file.json') |
| Register DataFrame | Not supported | con.register('name', df) |
| Result to DataFrame | N/A | .fetchdf() |
| Result to Polars | N/A | .fetch_arrow_table() + pl.from_arrow() |
| Group aggregation | df.groupby().agg() | SELECT ... GROUP BY |
| Window functions | df.transform() | LAG()/ROW_NUMBER() OVER() |
| Date truncation | dt.floor() | DATE_TRUNC('month', date) |
Monetization Suggestions
Mastering the integration of DuckDB + Python ecosystem can generate multiple business value streams:
1. Productize Data Services
Package commonly used data processing workflows into reusable Python packages or API services. Offer small and medium enterprises a one-stop “data cleaning + report generation” solution, charging ¥299-999 per month.
2. Technical Consulting and Training
Many teams are still struggling with pandas on large files, hitting out-of-memory errors regularly. You can provide DuckDB migration consulting for enterprises, helping them重构 their data processing pipelines. Single projects charge ¥5,000-20,000.
3. Automated Reporting SaaS
Build a lightweight BI platform based on DuckDB + Python that supports CSV/Excel/Parquet upload and automatically generates SQL analysis and visualization reports. Charge ¥99-299/month targeting e-commerce and operations teams.
4. Open Source Projects + Paid Value-Added Services
Open-source the utility code from this article to build personal brand. Monetize through GitHub Sponsors, paid tutorials, and priority technical support.
5. Data Engineering Template Marketplace
Package DuckDB + pandas/Polars integration patterns into reusable ETL templates. Sell them on template marketplaces or knowledge payment platforms at ¥49-199 per template.
Summary
The right way to use DuckDB + Python:
- pandas users — Use
con.register()to turn DataFrames into tables, use SQL to replace slow groupby/merge - Polars users — Pass data zero-copy through Arrow; Polars handles column transforms, DuckDB handles aggregation and window functions
- Large file processing — Never
pd.read_csv()an entire file; query directly with DuckDB - Multi-format support — CSV, Excel, JSON, Parquet, SQLite, MySQL, PostgreSQL all queryable directly
- Hybrid usage — No need to choose one; DuckDB is the acceleration engine for pandas/Polars
Remember one principle: small data with pandas, big data with DuckDB, switch seamlessly with just a few lines of code.
📖 More DuckDB Python实战 tips → duckdblab.org