pandas、Polars 和 DuckDB 都是本地数据处理工具,本身不是生成式 AI 产品。本文选择的是 AI 样本表在作图、训练或交给模型解释之前的计算方式。比较时先统一输入、类型和统计口径,得到一样的答案后再考虑接口习惯与资源。
已有 pandas 预处理代码并且数据能在内存里顺利处理,可以继续用 pandas;需要显式组合文件扫描、筛选和聚合查询,考虑 Polars 的惰性接口;主要工作是 SQL 分组、连接与分析查询,考虑 DuckDB。它们并非互斥,也不能用一个品牌代替所有数据任务。

按相同维度看三种选择
| 维度 | pandas | Polars | DuckDB |
|---|---|---|---|
| 本题接口 | 读取 DataFrame,再筛选和 groupby | scan_csv 构建惰性查询,collect 取结果 | SQL read_csv、WHERE、GROUP BY |
| 类型核对 | read_csv 显式 dtype | scan_csv 显式 schema_overrides | read_csv 显式 columns |
| 空来源字段 | 本例保留空字符串,再改为 unknown | 空或 null 显式改为 unknown | NULLIF/COALESCE 显式改为 unknown |
| 适合开始的情境 | 复用现有 Python 表格处理代码 | 组织文件扫描、列选择和查询计划 | 团队主要用 SQL 表达分析口径 |
| 本题不证明的能力 | 任意大表都能放内存 | 惰性查询一定最快 | 所有 SQL 都能自动优化到最好 |
Polars scan_csv 的读取层可以进行谓词和列选择下推;这是可用的查询优化方式,不是本文测出的性能提升数字。DuckDB 可以用内存连接直接分析 CSV,本例不持久化数据库。
先给这份 AI 样本表定口径
六条 AI 样本元数据为人工构造,没有调用模型、计算真实 token 或训练模型;text_chars 是给定的字符数量。需求固定为:只保留 split=train 且 text_chars>0 的记录,按 source 计算样本条数和字符合计,空来源保留在 unknown 组。
s04 为 test,排除;s06 字符数为 0,也排除。s05 的来源为空,但属于 train 且有 15 字符,不能因来源缺失就悄悄丢掉。字符数是测量列,不能把它当作请求费用、token 数或模型成绩。
本例实际环境为 Windows、Python 3.11.15、pandas 2.3.3、Polars 1.44.2、DuckDB 1.5.6。
在空的练习目录里创建独立环境。以下为 Windows PowerShell 命令;macOS/Linux 的激活命令用 source .venv/bin/activate。输出文件只用于本次演示,重复执行会更新这些演示文件。
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install pandas==2.3.3 polars==1.44.2 duckdb==1.5.6
将代码保存为 table_tools_comparison.py,运行 python table_tools_comparison.py;它创建 tool_comparison/samples.csv,再由三个工具读取同一文件。
from pathlib import Path
import pandas as pd
import polars as pl
import duckdb
out = Path('tool_comparison')
out.mkdir(exist_ok=True)
src = out / 'samples.csv'
src.write_text('sample_id,source,split,text_chars\n'
's01,web,train,12\ns02,web,train,18\ns03,docs,train,20\n'
's04,docs,test,9\ns05,,train,15\ns06,web,train,0\n', encoding='utf-8')
p = pd.read_csv(src, dtype={'sample_id': 'string', 'source': 'string',
'split': 'string', 'text_chars': 'int64'}, keep_default_na=False)
p['source'] = p['source'].replace('', 'unknown')
p = p.loc[p['split'].eq('train') & p['text_chars'].gt(0)]
p_result = p.groupby('source', as_index=False).agg(
samples=('sample_id', 'size'), chars=('text_chars', 'sum')
).sort_values('source')
p_rows = list(p_result.itertuples(index=False, name=None))
lazy = pl.scan_csv(src, schema_overrides={
'sample_id': pl.String, 'source': pl.String, 'split': pl.String, 'text_chars': pl.Int64,
})
pl_result = (
lazy.with_columns(
pl.when(pl.col('source').is_null() | pl.col('source').eq(''))
.then(pl.lit('unknown')).otherwise(pl.col('source')).alias('source')
).filter((pl.col('split') == 'train') & (pl.col('text_chars') > 0))
.group_by('source').agg(pl.len().alias('samples'), pl.col('text_chars').sum().alias('chars'))
.sort('source').collect()
)
pl_rows = pl_result.rows()
con = duckdb.connect()
duck_rows = con.sql('''
SELECT COALESCE(NULLIF(source, ''), 'unknown') AS source,
COUNT(*) AS samples, SUM(text_chars) AS chars
FROM read_csv('tool_comparison/samples.csv', header=true,
columns={'sample_id':'VARCHAR','source':'VARCHAR',
'split':'VARCHAR','text_chars':'BIGINT'})
WHERE split='train' AND text_chars>0
GROUP BY 1 ORDER BY 1
''').fetchall()
con.close()
expected = [('docs', 1, 20), ('unknown', 1, 15), ('web', 2, 30)]
assert p_rows == pl_rows == duck_rows == expected
print('pandas:', p_rows)
print('polars:', pl_rows)
print('duckdb:', duck_rows)
print('included_rows:', sum(x[1] for x in expected), 'total_chars:', sum(x[2] for x in expected))
逐行比对,而不只看总和
pandas: [('docs', 1, 20), ('unknown', 1, 15), ('web', 2, 30)]
polars: [('docs', 1, 20), ('unknown', 1, 15), ('web', 2, 30)]
duckdb: [('docs', 1, 20), ('unknown', 1, 15), ('web', 2, 30)]
included_rows: 4 total_chars: 65
三份结果都应为 docs 1 条/20 字符、unknown 1 条/15 字符、web 2 条/30 字符,总计 4 条/65 字符。总和相等还不够,分组名称、各组行数和各组字符数都应一致;代码标准化排序后比较完整结果。
若你换入真实文件后出现差异,先核对类型、空字符串与 null、数字格式、分组空值政策以及筛选条件。不能只把不一致的那组删除,或把某工具的默认结果未经核对就称为标准答案。
选型时再核对这些成本
已有很多 pandas 代码时,迁移意味着重写接口和回归检查;SQL 熟悉的团队用 DuckDB 可能更容易审阅查询;Polars 惰性表达式则需要明确执行发生在 collect。选择时把维护成本、团队能力和部署环境一起考虑。
这六行 CSV 只验证语义一致,没有测试吞吐、峰值内存或大数据性能,不能据此宣称某款最快。惰性执行不等于完全不占内存;collect、结果表和后续 Python 操作仍需资源。真实数据量、类型与查询不同,应另做同条件测试。
本题读取成功不证明输入就是合法训练数据。真实任务还要验证编号唯一、字符数是否有限且非负、split 取值和样本划分来源。不要让自然语言助手自动放宽这些输入规则;让它解释已复算的表格结果更容易追溯。
官方资料
以下资料实际读取于 2026 年 10 月 1 日;接口或界面范围按正文注明的版本核对。
- pandas groupby:分组与聚合
- Polars scan_csv:类型与惰性读取
- Polars collect:执行惰性查询
- DuckDB CSV:显式读取类型
- DuckDB Python:连接与查询
Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/32733.html