Polars 多 DataFrame 合并操作全指南

从 pandas 迁移过来的人,最常问的一句话是:"concat 和 merge 在 Polars 里怎么写?"------答案不只是"有替代品",而是"比你想象的更完整"。


一、pl.concat() ------ 拼接这件事,Polars 想得很周全

pandas 的 concat 能做的,Polars 全都有;pandas 做不到的,Polars 也补上了。核心函数是 pl.concat(),通过一个 how 参数切换行为,覆盖了从最简单的纵向堆叠到多表对齐合并的几乎所有场景。

函数签名

python 复制代码
pl.concat(
    items: Iterable[DataFrame | LazyFrame | Series],
    *,
    how: str = 'vertical',
    rechunk: bool = False,
    parallel: bool = True,
)

📌 vertical --- 最朴素的纵向堆叠

列名和类型必须完全一致,行数叠加变长。这是默认模式,也是日常用得最多的。

python 复制代码
import polars as pl

df1 = pl.DataFrame({"a": [1], "b": [3]})
df2 = pl.DataFrame({"a": [2], "b": [4]})

result = pl.concat([df1, df2])
# shape: (2, 2)
# │ a ┆ b │
# │ 1 ┆ 3 │
# │ 2 ┆ 4 │

列名对不上会直接报错,没有静默处理。


📌 vertical_relaxed --- 类型不完全一致?自动升档

vertical 逻辑相同,但遇到类型冲突时会自动提升为公共超类型,比如 Int32 遇上 Float64,结果列统一变成 Float64,省去手动转换的麻烦。

python 复制代码
df1 = pl.DataFrame({"a": [1], "b": [3]})
df2 = pl.DataFrame({"a": [2.5], "b": [4]})

result = pl.concat([df1, df2], how="vertical_relaxed")
# 列 a 自动从 i64 提升为 f64

📌 horizontal --- 横着拼,变宽不变长

把多个 DataFrame 的列并排摆在一起,行数不同时短的那边用 null 补齐。列名不能重叠,否则报错。

python 复制代码
df_h1 = pl.DataFrame({"l1": [1, 2], "l2": [3, 4]})
df_h2 = pl.DataFrame({"r1": [5, 6, 7], "r2": [8, 9, 10]})

result = pl.concat([df_h1, df_h2], how="horizontal")
# shape: (3, 4),df_h1 的两列用 null 补到第 3 行

📌 diagonal --- 列结构不同?取并集,缺的填 null

这是处理"列不对齐"场景最直接的方式。两张表的列名取并集,各自没有的列填 null,行数叠加。

python 复制代码
df_d1 = pl.DataFrame({"a": [1], "b": [3]})
df_d2 = pl.DataFrame({"a": [2], "c": [4]})

result = pl.concat([df_d1, df_d2], how="diagonal")
# shape: (2, 3)
# │ a ┆ b    ┆ c    │
# │ 1 ┆ 3    ┆ null │
# │ 2 ┆ null ┆ 4    │

diagonal_relaxed 在此基础上额外支持类型自动提升,两者搭配基本能覆盖绝大多数"脏数据合并"的场景。


📌 align 系列 ------ 多表按键对齐,一步到位 ✨

这是 Polars 相对新的能力,也是最有意思的一个。它会自动识别多个 DataFrame 的公共键列,按键对齐后横向合并------本质上是帮你把"先 join 再 concat"这两步合成了一步。

python 复制代码
df_a1 = pl.DataFrame({"id": [1, 2], "x": [3, 4]})
df_a2 = pl.DataFrame({"id": [2, 3], "y": [5, 6]})
df_a3 = pl.DataFrame({"id": [1, 3], "z": [7, 8]})

pl.concat([df_a1, df_a2, df_a3], how="align")
# shape: (3, 4)
# │ id ┆ x    ┆ y    ┆ z    │
# │ 1  ┆ 3    ┆ null ┆ 7    │
# │ 2  ┆ 4    ┆ 5    ┆ null │
# │ 3  ┆ null ┆ 6    ┆ 8    │

align 系列有四种变体,对应不同的保留策略:

how 参数 等效 join 类型
align / align_full full outer join
align_left left join
align_right right join
align_inner inner join

pl.concat() 全策略速查

how 参数 行为 pandas 对应 适用场景
vertical 纵向堆叠,列必须一致 concat(axis=0) 同结构多批数据合并
vertical_relaxed 纵向堆叠,类型自动提升 concat(axis=0) 类型略有差异的同结构数据
horizontal 横向并列,列不可重叠 concat(axis=1) 不同特征列拼接
diagonal 取列并集,缺失填 null concat(axis=0, join='outer') 列结构不同的数据合并
diagonal_relaxed 同上 + 类型提升 同上 列结构和类型均不同
align 按公共键对齐横向合并 merge + concat 多 df 按 key 对齐

二、df.join() ------ 关联合并,SQL 那套逻辑全都在

如果说 concat 是"把表摆在一起",那 join 就是"按条件把两张表的行配对"。Polars 的 join() 覆盖了 SQL 里几乎所有标准 join 类型,语法也比 pandas 的 merge 更简洁。

函数签名

python 复制代码
df.join(
    other: DataFrame,
    on: str | list | None = None,
    how: str = 'inner',
    *,
    left_on=None,
    right_on=None,
    suffix: str = '_right',
    validate: str = 'm:m',
    join_nulls: bool = False,
    coalesce: bool | None = None,
)

六种 join 策略,逐一拆解

inner join(默认) --- 只留两边都能匹配上的行,是最常见的用法。

python 复制代码
df.join(other_df, on="ham")

left join --- 左表全保留,右表没匹配到的位置填 null

python 复制代码
df.join(other_df, on="ham", how="left", coalesce=True)

full join --- 两边都全保留,互相没匹配到的填 null。数据探查时很好用。

python 复制代码
df.join(other_df, on="ham", how="full")

cross join --- 笛卡尔积,不需要 on 参数,生成所有行的两两组合。数据量小时偶尔有用,大表慎用。

python 复制代码
df.join(other_df, how="cross")

semi join --- 只保留左表中能在右表找到匹配的行,但不附加右表的列。本质上是个过滤器,比先 join 再 select 列更高效。

python 复制代码
df.join(other_df, on="ham", how="semi")

anti join --- semi join 的反面,只保留左表中在右表找不到匹配的行。做数据差异比对时非常顺手。

python 复制代码
df.join(other_df, on="ham", how="anti")

几个容易忽略的参数

参数 说明
left_on / right_on 两表键列名不同时分别指定,不必提前重命名
suffix 重名列的后缀,默认 _right,可自定义
validate 校验关系类型:'1:1''1:m''m:1''m:m',数据质量把关利器
join_nulls 是否将 null 视为相等进行匹配,默认 False
coalesce 是否将两边的键列合并为一列输出

validate 这个参数在生产环境里尤其值得打开------它能在数据出现意外的一对多或多对多关系时直接报错,而不是悄悄给你生成一张膨胀的结果表。


三、进阶武器:join_asof()join_where()

join_asof() --- 时序数据的专属利器

时序场景里经常遇到这样的问题:两张表的时间戳对不上,但你想找"最近的那条记录"。join_asof() 就是为此而生的,逻辑类似 left join,但匹配的是最近的键值而非精确相等。两表必须提前按键列排好序。

python 复制代码
df.join_asof(
    other,
    on="timestamp",
    strategy="backward",   # 'backward'(默认) | 'forward' | 'nearest'
    tolerance="1d",        # 超出容差范围则不匹配
    by="stock_id",         # 先按此列分组,再做 asof join
)

三种匹配策略的含义直白明了:

  • backward:找右表中 ≤ 左表时间戳的最近一行
  • forward:找右表中 ≥ 左表时间戳的最近一行
  • nearest:找距离最近的那行,不管方向

tolerance 支持数值,也支持时间字符串,比如 "3d12h4m25s" 这样的写法,处理金融或日志数据时相当省心。


join_where() --- 非等值条件匹配

标准 join 只能做等值匹配,但现实中经常需要"价格在某个区间内""日期晚于某个时间点"这类条件。join_where() 接受任意谓词表达式,找出所有满足条件的行对组合。

python 复制代码
df.join_where(
    other,
    pl.col("price_left") >= pl.col("price_min"),
    pl.col("price_left") <= pl.col("price_max"),
)

pandas 里没有直接对应的方法,通常要绕道 mergequery 或者手写循环,Polars 把这个场景单独封装成一个函数,思路很清晰。


四、Polars vs pandas 对照速查

从 pandas 迁移过来,下面这张表应该能省不少查文档的时间:

需求场景 pandas 写法 Polars 写法
纵向堆叠(同结构) pd.concat([df1,df2]) pl.concat([df1,df2])
纵向堆叠(列不同) pd.concat([df1,df2], join='outer') pl.concat([df1,df2], how="diagonal")
横向拼接 pd.concat([df1,df2], axis=1) pl.concat([df1,df2], how="horizontal")
inner join pd.merge(df1,df2,on='key') df1.join(df2, on='key')
left join pd.merge(...,how='left') df1.join(df2, on='key', how='left')
full outer join pd.merge(...,how='outer') df1.join(df2, on='key', how='full')
时序近似匹配 pd.merge_asof(...) df1.join_asof(df2, on='ts')
非等值条件匹配 无直接对应 df1.join_where(df2, 条件表达式)

五、选哪个?几条实用判断原则

多批同结构数据合并 ,直接用 pl.concat([...]) 默认的 vertical 模式,性能极高,没有悬念。

列结构不统一 ,用 diagonaldiagonal_relaxed,自动补 null,不用提前对齐列名。

按键关联两表 ,用 df.join(),记得打开 validate 参数做数据质量校验,生产环境里这一步能帮你挡掉很多隐患。

时序数据对齐join_asof() 是专门为此设计的,配合 tolerance 参数避免匹配到过远的记录。

LazyFrame 场景pl.concat()parallel=True 参数可以并行执行多个懒计算,大数据量下性能优势会更明显。


参考文献

相关推荐
猪猪拆迁队8 小时前
虚拟工厂仿真引擎的架构设计:让一条产线可编程、可观测、可干预
后端·ai编程
字节跳动数据库9 小时前
文章分享——相似函数处理方法
人工智能·后端·程序员
云技纵横9 小时前
@Transactional 失效的 7 种场景:第 5 种最难排查
后端
用户6757049885029 小时前
你知道 Go 结构体和结构体指针调用的区别吗?一文带你彻底搞懂!
后端·go
程序员cxuan9 小时前
读懂 Claude Code 架构分析系列,第一篇,开始!
人工智能·后端·架构
用户6757049885029 小时前
面试官问“装饰器模式”,这样回答薪资多要 3000!
后端
tntxia9 小时前
Geo Scene域名修改引起的一些问题
后端
用户298698530149 小时前
Java 实现 Word 文档加密与权限解除
java·后端
vanuan10 小时前
给你的A2A-Agent加把锁-认证鉴权实战指南
后端
Yeats_Liao10 小时前
14:Servlet中的页面跳转-Java Web
java·后端·架构