# DuckDB VS Pandas VS Polars 实测

*   什么是 pyarrow？
    

**pyarrow 是一个用于处理大规模列式数据的高性能 Python 库**

它可以帮助你：

高效地在Python和其他系统之间交换数据，例如Pandas DataFrame到Apache Spark

使用Apache Arrow内存格式，这是一种语言无关的数据格式，优化了分析工作负载

读取和写入多种数据格式，包括Parquet、Feather、ORC和CSV

*   应用场景
    

pyarrow 广泛应用于以下实际场景：

大数据分析: 在数据湖中读写Parquet文件，提高数据加载和处理速度

跨系统数据交换: 在Python数据科学工具（如Pandas）和Java/Spark等大数据平台之间高效传输数据

内存优化: 减少数据在不同库之间复制的开销，尤其是在处理大型数据集时

*   python 版本与 pyarrow 兼容性问题
    

已经安装的虚拟环境 Python 3.14.6

而 pandas/pyarrow/pandas-stubs 都不是为 3.14 明确对齐时，就很容易出现各种“导入/类型扩展/二进制不兼容”的连锁报错

前面也出现过 pyarrow 二进制不兼容的报错

解决办法：用 Python 3.11 或 3.12 建一个干净虚拟环境

```plaintext
$ conda env list

# conda environments:
#
# * -> active
# + -> frozen
base                 *   /miniconda3
python312              /miniconda3/envs/python312
python314               /miniconda3/envs/python314

$ conda activate python312

$ python -V
Python 3.12.13
```

使用 pip 安装各种库

```plaintext
$ pip install polars -i https://pypi.tuna.tsinghua.edu.cn/simple

$ pip install duckdb -i https://pypi.tuna.tsinghua.edu.cn/simple

$ pip install pyarrow -i https://pypi.tuna.tsinghua.edu.cn/simple

$ pip install fastparquet -i https://pypi.tuna.tsinghua.edu.cn/simple
```

*   数据准备
    

```plaintext
# 导入各种库
import numpy as np
import pandas as pd
import polars as pl
import duckdb
import pyarrow

# 使用下列代码生成具有五百万行记录的简单示例数据，并分别导出为 .csv 和 .parquet 格式
generated_df = pd.DataFrame(
    {
        '类别': np.random.choice(list('ABCDEF'), 5000000),
        '数值': np.round(np.random.uniform(0, 1000000, 5000000), 3)
    }
)

# 分别导出为csv、parquet格式
generated_df.to_csv('./demo_data.csv', index=False)
generated_df.to_parquet('./demo_data.parquet')
```

*   实测
    

源代码及输出

```plaintext
# 针对两种格式的文件，分别比较默认情况下 DuckDB、pandas、polars 的读取速度
# .csv格式 读取速度
%%timeit
duckdb.read_csv('./demo_data.csv')
61.5 ms ± 2.35 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
pd.read_csv('./demo_data.csv')
3.14 s ± 94.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
pl.read_csv('./demo_data.csv')
692 ms ± 59.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# .parquet格式 读取速度

%%timeit
duckdb.read_parquet('./demo_data.parquet')
742 μs ± 11 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

%%timeit
pd.read_parquet('./demo_data.parquet')
549 ms ± 22.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
pl.read_parquet('./demo_data.parquet')
63.4 ms ± 9.89 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

# duckdb 通过SQL语句的方式进行等价操作
duckdb.sql("select * from 'demo_data.csv' limit 5")
┌─────────┬────────────┐
│  类别   │    数值    │
│ varchar │   double   │
├─────────┼────────────┤
│ E       │  815191.43 │
│ E       │  420200.68 │
│ C       │ 735058.475 │
│ A       │ 182173.148 │
│ E       │ 685964.784 │
└─────────┴────────────┘

duckdb.sql("select * from 'demo_data.parquet' limit 5")
┌─────────┬────────────┐
│  类别   │    数值    │
│ varchar │   double   │
├─────────┼────────────┤
│ E       │  815191.43 │
│ E       │  420200.68 │
│ C       │ 735058.475 │
│ A       │ 182173.148 │
│ E       │ 685964.784 │

pandas_df = pd.read_parquet('demo_data.parquet')
pandas_df.head()
类别	数值
0	E	815191.430
1	E	420200.680
2	C	735058.475
3	A	182173.148
4	E	685964.784

polars_df = pl.read_parquet('demo_data.parquet')
polars_df.head()
shape: (5, 2)
类别	数值
str	f64
"E"	815191.43
"E"	420200.68
"C"	735058.475
"A"	182173.148
"E"	685964.784

duckdb.sql("select * from polars_df limit 5")
┌─────────┬────────────┐
│  类别   │    数值    │
│ varchar │   double   │
├─────────┼────────────┤
│ E       │  815191.43 │
│ E       │  420200.68 │
│ C       │ 735058.475 │
│ A       │ 182173.148 │
│ E       │ 685964.784 │
└─────────┴────────────┘

# 针对 DuckDB 默认读取到内存中的对象 DuckDB 中称作关系
demo_r = duckdb.read_parquet('demo_data.parquet')
type(demo_r)
_duckdb.DuckDBPyRelation

# 通过 duckdb.sql() 直接将关系当作表名，书写 SQL 语句
duckdb.sql("select * from demo_r limit 5")
┌─────────┬────────────┐
│  类别   │    数值    │
│ varchar │   double   │
├─────────┼────────────┤
│ E       │  815191.43 │
│ E       │  420200.68 │
│ C       │ 735058.475 │
│ A       │ 182173.148 │
│ E       │ 685964.784 │
└─────────┴────────────┘

# 统计数据记录数
duckdb.sql("select count(1) from demo_r")
┌──────────┐
│ count(1) │
│  int64   │
├──────────┤
│  5000000 │
└──────────┘

# 计算类别字段唯一值数量
duckdb.sql("select count(distinct 类别) from demo_r")
┌────────────────────────┐
│ count(DISTINCT "类别") │
│         int64          │
├────────────────────────┤
│                      6 │
└────────────────────────┘

# 分组统计平均值
duckdb.sql("""select 类别, mean(数值) from demo_r group by 类别""")
┌─────────┬────────────────────┐
│  类别   │    mean("数值")    │
│ varchar │       double       │
├─────────┼────────────────────┤
│ E       │ 500412.84510126716 │
│ C       │  500224.4367560996 │
│ A       │ 500017.72415612906 │
│ B       │  500642.8727381058 │
│ F       │  500388.8039331317 │
│ D       │ 499870.52028870437 │
└─────────┴────────────────────┘

# 比较一下与pandas、polars之间执行相同任务的耗时差异
%%timeit
duckdb.sql("""select 类别, mean(数值) from demo_r group by 类别""")
669 μs ± 37.4 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

%%timeit
pandas_df.groupby('类别')['数值'].mean()
291 ms ± 22.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
polars_df.group_by('类别').agg(pl.col('数值').mean())
154 ms ± 15.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# DuckDB 将计算结果转换为Python对象、pandas数据框、polars数据框、numpy数组等常用格式
# 转为 python 对象
duckdb.sql("select * from demo_r limit 5").fetchall()
[('E', 815191.43),
 ('E', 420200.68),
 ('C', 735058.475),
 ('A', 182173.148),
 ('E', 685964.784)]
 
 # 转为 pandas 数据框
duckdb.sql("select * from demo_r limit 5").df()
类别	数值
0	E	815191.430
1	E	420200.680
2	C	735058.475
3	A	182173.148
4	E	685964.784

# 转为 ploars 数据框
duckdb.sql("select * from demo_r limit 5").pl()
shape: (5, 2)
类别	数值
str	f64
"E"	815191.43
"E"	420200.68
"C"	735058.475
"A"	182173.148
"E"	685964.784

# 转为 numpy 数组
duckdb.sql("select * from demo_r limit 5").fetchnumpy()
{'类别': array(['E', 'E', 'C', 'A', 'E'], dtype=object),
 '数值': array([815191.43 , 420200.68 , 735058.475, 182173.148, 685964.784])}
 
 # 输出为 .csv 格式，比较时间
 %%timeit
demo_r.write_csv('duckdb_out_test.csv')
3.94 s ± 295 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
pandas_df.to_csv('pandas_out_test.csv', index=False)
19.2 s ± 445 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
polars_df.write_csv('polars_out_test.csv')
834 ms ± 19.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# 输出为 .parquet 格式，比较时间
%%timeit
demo_r.write_parquet('duckdb_out_test.parquet')
1.28 s ± 66 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
pandas_df.to_parquet('pandas_out_test.parquet', index=False)
The slowest run took 35.59 times longer than the fastest. This could mean that an intermediate result is being cached.
5 s ± 9.94 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
polars_df.write_parquet('polars_out_test.parquet')
884 ms ± 141 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

*   参考文章
    

```plaintext
https://zhuanlan.zhihu.com/p/713902144
性能碾压pandas、polars的数据分析神器来了
```
