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 参数可以并行执行多个懒计算,大数据量下性能优势会更明显。


参考文献

相关推荐
祀爱1 小时前
定时任务之BackgroundService的详细教程
后端·c#·asp.net
E等于MC平方1 小时前
用 Rust 写一个工业级 POSP 支付系统
后端·rust·消费·8583·交易·posp·银联
程序员阿明2 小时前
spring boot + vue3 实现RSA加密解密
java·spring boot·后端
明月_清风2 小时前
Redis 数据类型全景解析:从基础到高阶,一文掌握九大核心结构与应用场景
redis·后端
明月_清风2 小时前
深入浅出 Elasticsearch:核心概念、工具链与底层原理全解析
后端·elasticsearch
彭于晏Yan2 小时前
HttpServletRequest 如何读取JSON请求体
spring boot·后端·json
小李云雾2 小时前
慧校坊-二手校园交易平台-------项目总结
数据库·后端·程序人生·fastapi·项目
IT_陈寒2 小时前
被JavaScript的隐式类型转换坑到怀疑人生,记录这次离谱经历
前端·人工智能·后端