AI 表格分析工具怎么选?用同一份 CSV 比较 pandas、Polars 与 DuckDB

按表格操作、惰性查询与 SQL 汇总需求比较 pandas、Polars、DuckDB,用同一 AI 样本 CSV 核对筛选、缺失分组和汇总口径。

pandas、Polars 和 DuckDB 都是本地数据处理工具,本身不是生成式 AI 产品。本文选择的是 AI 样本表在作图、训练或交给模型解释之前的计算方式。比较时先统一输入、类型和统计口径,得到一样的答案后再考虑接口习惯与资源。

已有 pandas 预处理代码并且数据能在内存里顺利处理,可以继续用 pandas;需要显式组合文件扫描、筛选和聚合查询,考虑 Polars 的惰性接口;主要工作是 SQL 分组、连接与分析查询,考虑 DuckDB。它们并非互斥,也不能用一个品牌代替所有数据任务。

AI 表格分析工具怎么选?用同一份 CSV 比较 pandas、Polars 与 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 日;接口或界面范围按正文注明的版本核对。

Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/32733.html

赞 (0)
AI小管家的头像AI小管家
智能体数据怎么增量同步?用 SQLite 核对版本、重跑与回滚
上一篇 1小时前
AI 数值特征怎么分箱?用 cut 与 qcut 核对边界和每箱样本数
下一篇 1小时前

相关推荐

联系我们

联系我们

1

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
关注微信
分享本页
返回顶部