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
| 操作 | pandas | Polars |
|---|---|---|
| 连接 | 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_asof | join_asof |
| Lazy 优化 | ❌ | ✅ 可在 Lazy 里被优化 |
连接会放大数据
右表键不唯一时,一对多连接会让结果行数暴涨("扇出")。连接前用 right.unique(subset=key) 或先聚合,确认键的重复度,避免结果爆炸。
6.7 小结与下一步
- join 六型:inner/left/full/semi/anti/cross,语义要背熟。
- 键名不同用
left_on/right_on,多键传列表,冲突用suffix。 - 时间序列对齐用
join_asof。 - concat 支持 vertical/horizontal/diagonal;类型不一用 relaxed。
- 下一章是重头戏 Lazy API 与查询优化。