Chapter 06

连接与拼接

掌握 join 的六种连接方式、concat 的纵向/横向拼接、frame 对齐,并厘清与 pandas merge 的差异。

6.1 join 的六种方式

inner(内连接)
只保留两表键都匹配的行。默认方式。
left(左连接)
保留左表全部行,右表匹配不上的列填 null。最常用于"给主表补充维度信息"。
full(全外连接)
两表所有行都保留,任一侧缺失填 null(旧名 outer)。
semi(半连接)
只保留左表中在右表能找到匹配的行,但不带入右表的列。相当于"存在性过滤"。
anti(反连接)
只保留左表中在右表找不到匹配的行。用于"找出没有对应记录的行"。
cross(笛卡尔积)
左右每行两两组合,不需要键。结果行数 = 左行数 × 右行数,慎用。

6.2 基本用法

import polars as pl

users = pl.DataFrame({
    "uid": [1, 2, 3],
    "name": ["Alice", "Bob", "Carol"],
})
orders = pl.DataFrame({
    "uid": [1, 1, 2],
    "amount": [99, 50, 120],
})

# 内连接:只有下过单的用户
users.join(orders, on="uid", how="inner")

# 左连接:所有用户,Carol 的 amount 为 null
users.join(orders, on="uid", how="left")

# 反连接:找出从没下单的用户(Carol)
users.join(orders, on="uid", how="anti")

6.3 键名不同 / 多键

# 左右键名不同
left.join(right, left_on="user_id", right_on="uid")

# 多键连接
a.join(b, on=["date", "region"], how="left")

# 处理列名冲突:右表同名列加后缀
a.join(b, on="id", suffix="_right")

6.4 join_asof:按最近键连接

时间序列常用:为左表每行找右表中"时间不晚于它的最近一条",用于对齐不同频率的数据(如把行情价格贴到交易记录上)。

trades.join_asof(
    quotes,
    on="time",
    by="symbol",      # 先按 symbol 分组再 asof
    strategy="backward", # 取不晚于的最近一条
)

6.5 concat:拼接

# 纵向:堆叠行(列须一致),等价 SQL UNION ALL
pl.concat([df1, df2], how="vertical")

# 横向:并排列(行数须一致)
pl.concat([df1, df2], how="horizontal")

# diagonal:列不完全相同也能堆,缺的填 null
pl.concat([df1, df2], how="diagonal")
vertical_relaxed

纵向拼接时若同名列 dtype 略有不同(如 Int32 vs Int64),用 how="vertical_relaxed" 让 Polars 自动做超类型提升,避免报错。

6.6 vs pandas merge

操作pandasPolars
连接pd.merge(a, b, on=...)a.join(b, on=...)
连接类型参数how='inner/left/outer'how='inner/left/full/semi/anti/cross'
半/反连接需 isin/indicator 变通原生 semi / anti
纵向拼接pd.concat([a, b])pl.concat([a, b])
近邻连接pd.merge_asofjoin_asof
Lazy 优化✅ 可在 Lazy 里被优化
连接会放大数据

右表键不唯一时,一对多连接会让结果行数暴涨("扇出")。连接前用 right.unique(subset=key) 或先聚合,确认键的重复度,避免结果爆炸。

6.7 小结与下一步