
Parquet格式火了!量化分析师用DuckDB和Polars读取,TB级数据秒处理,CSV时代彻底拜拜!
你们还在为TB级tick数据、因子库、回测数据集头秃吗?
每天打开终端:
醒醒吧!2026年了,还在用行式CSV?Parquet格式 + DuckDB/Polars组合拳,直接把数据处理速度干到10倍以上,内存占用直降80%!
今天这篇爆款干货,我把Parquet在量化分析里的核心玩法扒得干干净净,还手把手教你DuckDB和Polars两种读取姿势,新增重磅:Parquet分区优化技巧(实测能再提速3-10倍!),最后给出真实场景硬核对比,帮你选出最香方案!
点赞+收藏,读完直接上手,效率起飞!🚀
Parquet是Apache基金会的列式存储格式,专为大数据分析而生,和CSV(行式)完全不是一个维度。
核心优势(量化场景直击痛点):
close和volume)直接拉取,不用全表扫,IO狂降。date/symbol分区,Hive风格目录结构,查询特定股票特定日期像查数据库一样快。量化人真实使用场景:
一句话:Parquet不是工具,是量化数据基础设施的降维打击。
分区是Parquet在量化时间序列数据上的杀手级特性。正确分区能让引擎直接跳过无关文件和row group,大幅减少IO和扫描量。错误分区反而会制造“小文件问题”,导致元数据爆炸、查询变慢。
量化场景最优分区策略(A股/美股/期货通用):
date/year/month分区最香。date=YYYY-MM-DD分区(适合日级回测)。year=YYYY/month=MM/day=DD(Hive风格,便于按月/季过滤)。date=YYYY-MM-DD/symbol=600519 或先按date再按symbol子分区(单日全市场数据或单股历史数据查询极快)。symbol粗分区 + 文件内按timestamp排序(充分利用row group统计信息)。写入分区Parquet实战代码(Polars最灵活):
import polars as pl
# 假设df已有 date 和 symbol 列
df.write_parquet(
"data/quant_db/",
compression="zstd", # 压缩率最高,量化数据推荐
partition_by=["date", "symbol"], # Hive风格:data/date=2024-01-01/symbol=600519/xxx.parquet
row_group_size=128_000, # 行组大小,平衡压缩与谓词下推(DuckDB默认约122k)
statistics=True # 必须开启!让min/max统计生效,加速过滤
)DuckDB写入分区:
COPY (SELECT * FROM df)
TO 'data/quant_db/'
(FORMAT parquet, PARTITION_BY (date, symbol), COMPRESSION 'zstd');读取优化(自动利用分区):
pl.scan_parquet("data/quant_db/date=2024-01-01/*/*.parquet") 或直接scan_parquet("data/quant_db/") + filter,引擎自动pruning。SELECT * FROM 'data/quant_db/**' 或 read_parquet('data/quant_db/date=2024-*/symbol=6005*/*.parquet')。额外提速技巧:
timestamp或symbol排序,提升row group统计准确性。PER_THREAD_OUTPUT加速写入。避坑:不要按高基数列(如每笔timestamp、order_id)分区;不要产生成千上万小文件(<50MB)。
DuckDB是嵌入式OLAP神器,零配置、向量量化执行,直接把Parquet当数据库查,不用全加载到内存。分区后谓词下推更猛!
安装(一行搞定):
pip install duckdb polars pyarrow代码示例(超级实用,带分区):
import duckdb
query = """
SELECT
symbol,
timestamp,
open,
high,
low,
close,
volume
FROM 'data/quant_db/**'
WHERE date = '2024-01-01'
AND symbol = '600519'
AND volume > 1000000
ORDER BY timestamp DESC
LIMIT 1000
"""
df = duckdb.sql(query).df()
print(df.head())Polars是Rust底层下一代DataFrame,懒加载+查询优化,配合分区如虎添翼。
代码示例(Lazy模式 + 分区):
import polars as pl
lf = pl.scan_parquet("data/quant_db/date=2024-01-01/*/*.parquet") # 精确到分区目录,超快
result = (
lf
.filter(pl.col("volume") > 1_000_000)
.select(["symbol", "timestamp", "close", "volume"])
.sort("timestamp")
.head(1000)
.collect(streaming=True) # 大数据推荐
)
print(result)场景维度 | DuckDB(SQL党) | Polars(DataFrame党) | 量化推荐场景 |
|---|---|---|---|
数据规模 | 10GB~TB级,超内存也能跑 | 1GB~100GB+,懒加载极致 | >100GB选DuckDB;<50GB Polars更快 |
查询类型 | 复杂SQL、JOIN、多表聚合无敌 | 表达式API、特征工程、Pipeline超爽 | 回测多因子JOIN → DuckDB特征衍生 → Polars |
性能(实测反馈) | SQL优化器强,聚合/窗口函数更快 | Rust单线程/多线程经常更快 | Polars单机DataFrame胜;DuckDB分析查询胜 |
内存占用 | 极低,可落盘;分区后降8倍 | 极低,懒执行;分区后降4倍 | 两者都吊打Pandas |
分区利用 | 自动Hive pruning极强 | scan_parquet + glob支持优秀 | 两者均优秀,混合用最强 |
学习成本 | SQL老手0成本 | Python DataFrame老手0成本 | 已会SQL选DuckDB;已会Pandas选Polars |
最强玩法 | DuckDB读取+Polars加工 | - | DuckDB ETL → Polars特征工程 |
总结一句话:
duckdb.sql(...).pl() 一行搞定)pl.write_parquet(..., compression="zstd", partition_by=["date", "symbol"], statistics=True)pl.show_graph() 或 DuckDB EXPLAIN 看优化计划 + 分区pruning效果写在最后:
Parquet + 正确分区,不是一个格式,是量化效率的降维武器。用上DuckDB和Polars,你会发现以前的“数据等待”时间全部变成了“策略迭代”时间。