马儿慢些走

人生惶惶,悲喜一场。

Polars coalesce操作(取第一个非null值)(KIMI)

本文为和KIMI的对话记录,仅供参考:


Polars join结合coalesce填充内容

User: 我有两个polars的dataframe,分别是df和df_task,两者都有id、text和labels字段,df的id应该包含df_task,我希望将df_task中的结果填写到df中,跳过df中labels字段已有的结果,id作为键

Kimi: 你可以使用 Polars 的 joincoalesce 来实现这个需求:

  • id 做键,把 df_task 中的 labels 合并到 df 中;
  • 如果 dflabels 已有值(非空),则保留原值,否则用 df_task 中的值填充。

✅ 示例代码如下:

import polars as pl

# 假设 df 和 df_task 已经存在
# df: 目标 DataFrame
# df_task: 来源 DataFrame,包含更新值

df_updated = (
    df.join(
        df_task.select("id", pl.col("labels").alias("labels_task")),
        on="id",
        how="left"
    )
    .with_columns(
        pl.coalesce("labels", "labels_task").alias("labels")
    )
    .drop("labels_task")
)

🔍 说明:

  • join(..., how="left"):保留 df 的所有行;
  • pl.coalesce("labels", "labels_task"):如果 labels 非空就用它,否则用 labels_task
  • 最后清理掉中间列 labels_task

✅ 示例数据验证:

df = pl.DataFrame({
    "id": [1, 2, 3],
    "text": ["a", "b", "c"],
    "labels": ["X", None, "Z"]
})

df_task = pl.DataFrame({
    "id": [1, 2, 3],
    "text": ["a", "b", "c"],
    "labels": ["A", "B", "C"]
})

# 执行上面的代码后
# df_updated["labels"] 将是 ["X", "B", "Z"]

如果你有更多字段要合并,或者 labels 是列表/结构体类型,也可以扩展这个逻辑。需要我帮你处理更复杂的情况吗?

Join和coalesce类似的拼接和填充操作

User: 还有和join、coalesce类似的polars操作吗?

Kimi: 有,而且很多 —— Polars 把“SQL 里的常用套路”几乎都搬进来了。
下面把和 join / coalesce “功能类似、场景相近”的操作按用途分组列出来,一眼就能知道下次该用哪个。


  1. 横向拼接(join 家族)

名称 一句话区别 典型场景
join 通用等值连接 任何需要按 key 横向拼列的场景
inner / left / right / full / cross / semi / anti_join 缩写函数,语义与 SQL 完全一致 不想写 .join(..., how="xxx") 时直接用
asof_join (≥0.20 叫 join_asof) 最近邻、非精确匹配 按时间戳往前找“最新一条”行情、日志
join_where 任意表达式当 join key key 不是单列,而是复杂表达式

  1. 空值填充(coalesce 家族)

名称 一句话区别 用法示例
coalesce 返回第一个非空值 pl.coalesce("a", "b", "c")
fill_null 用固定值或策略填充 .fill_null("UNKNOWN") / .fill_null("forward")
fill_nan 仅针对 NaN 数值列里把 NaN 换成 0
forward_fill / backward_fill 向下/向上补值 时序缺值延续
interpolate 线性插值 数值列缺值平滑

  1. “更新/覆盖”语义(UPSERT 手感)

模式 核心函数 做法
有则更新,无则保留 join + coalesce 你刚才已经用过了
有则更新,无则新增 join + coalesce + concat 先拼出“新增行”,再 union
完全替换 join + select 把右表列重命名后直接覆盖左表列
按行去重 unique / distinct 更新完后去重

  1. 纵向拼接(union 家族)

名称 一句话区别
concat 把多个 df 纵向堆叠,支持 how="vertical"/"diagonal"
union SQL 语义,等价于 concat + unique

  1. 行号/索引类(做完 join 后常跟)

名称 用途
row_index 给 df 加一列连续行号(0.20+)
with_row_count 老版本同名函数

  1. 字段改名 / 选择性丢弃

名称 用途
rename 批量改列名
select / drop 只保留或丢掉指定列,做完 join 后清理中间列

  1. 窗口函数(join 完后做“同组内回填”)

名称 举例
over pl.col("labels").forward_fill().over("id")
rolling_* 滚动窗口均值、最大最小值

记忆口诀


“横向先 join,空值用 coalesce / fill,更新靠 select,纵向 concat,时序用 asof,同组 over。”

下次遇到“要把 B 表内容合进 A 表,但别动 A 表已有值”——
先想到 left-join + coalesce
如果 key 不是精确相等,就换成 join_asof
如果还要把 B 表的新行插进来,再 concat 一把即可。

Coalesce操作

User: coalesce这种操作有什么先例吗?为什么这么设计?

Kimi: coalesce 并不是 Polars 独创,它几乎原封不动地借用了 SQL 标准(ANSI SQL-92) 里早已存在 30 多年的同名函数,在关系型数据库(PostgreSQL、SQL Server、Oracle、MySQL、SQLite 等)、Spark、Pandas、Qlik 等系统里都能见到,行为完全一致:
“按书写顺序取第一个非 NULL 值,全 NULL 才返回 NULL”

一、先例时间线

  • 1970s
    System R(IBM 最早的 SQL 原型)就提供了 VALUE(),后来标准化为 COALESCE
  • 1992
    ANSI SQL-92 把 COALESCE 写进标准,所有主流数据库陆续实现。
  • 2000s
    商业 BI 工具(QlikView、OBIEE、Tableau)把 COALESCE 做成表达式,用于仪表盘计算。
  • 2010s
    Spark、Pandas 通过 coalesce/combine_first 引入 DataFrame 世界;Polars 2020 年直接复用 SQL 语义。

二、为什么这样设计

  1. 符合直觉的“降级”逻辑
    业务上常把“优先级列”依次排开:先用 A,A 缺失用 B,B 缺失用 C……顺序阅读即可,无需嵌套 IFNULL/NVL
  2. 与三值逻辑(TRUE/FALSE/NULL)自洽
    SQL 任何运算碰到 NULL 都传播 NULLCOALESCE 提供“短路”出口,让查询继续。
  3. 可变参数,一次扫描
    函数内部只需顺序判断,找到第一个非 NULL 立即返回,O(k) 时间、无需额外内存。
  4. 语法中性,跨平台无痛
    同一条 COALESCE(col1, col2, 0) 在 Oracle、PostgreSQL、Spark、Pandas、Polars 上结果相同,降低迁移成本。

三、小结

Polars 的 coalesce 并不是新发明,而是把 SQL 里经过 30 年考验的 NULL 处理套路 原样搬进 DataFrame 世界,既保持性能,又让熟悉 SQL 的人零学习成本。

posted on 2025-09-18 20:04  马儿慢些走  阅读(20)  评论(0)    收藏  举报

导航