You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Polars中如何无需循环实现基于starts_with前缀的首匹配分类赋值

问题描述

现有两个Polars DataFrame:

import polars as pl

df = pl.DataFrame({
    "url": ["https//abc.com", "https//abcd.com", "https//abcd.com/aaa", "https//abc.com/abcd"]
})

conditions_df = pl.DataFrame({
    "url": ["https//abc.com", "https//abcd.com", "https//abcd.com/aaa", "https//abc.com/aaa"],
    "category": [["a"], ["b"], ["c"], ["d"]]
})

需求:为df的每条url,匹配conditions_df中**按行顺序第一个满足前缀匹配(starts_with)**的url,将对应的category赋值给该url。预期输出:

urlcategory
https//abc.com['a']
https//abcd.com['b']
https//abcd.com/aaa['b']
https//abc.com/abcd['a']

目前已有基于循环的实现:

def add_category_column(df: pl.DataFrame, conditions_df) -> pl.DataFrame:
    # 初始化category列为空列表
    df = df.with_columns(pl.Series("category", [[] for _ in range(len(df))], dtype=pl.List(pl.String)))
    
    # 应用条件填充category列
    for row in conditions_df.iter_rows():
        url_start, category = row
        df = df.with_columns(
            pl.when(
                (pl.col("url").str.starts_with(url_start)) & (pl.col("category").list.len() == 0)
            )
            .then(pl.lit(category))
            .otherwise(pl.col("category"))
            .alias("category")
        )
    
    return df

提问:是否可以无需循环实现该需求?能否使用join_where?尝试用join_where处理前缀匹配时未能成功。


解决方案

可以不用循环实现,直接利用Polars的向量化操作完成。join_where本身可以实现条件匹配,但它会返回所有符合条件的结果,无法直接返回「第一个匹配项」,需要结合排序或窗口函数进一步处理。

方法1:交叉连接+排序取首个匹配

核心思路是给条件表添加顺序标记,交叉连接后过滤出前缀匹配的行,再按条件表的顺序排序,最后分组取每个url的第一个匹配结果:

import polars as pl

df = pl.DataFrame({
    "url": ["https//abc.com", "https//abcd.com", "https//abcd.com/aaa", "https//abc.com/abcd"]
})

conditions_df = pl.DataFrame({
    "url": ["https//abc.com", "https//abcd.com", "https//abcd.com/aaa", "https//abc.com/aaa"],
    "category": [["a"], ["b"], ["c"], ["d"]]
})

# 给条件表添加匹配顺序列,标记行的先后顺序
conditions_with_order = conditions_df.with_columns(
    pl.int_range(0, len(conditions_df)).alias("match_order")
)

# 交叉连接后过滤前缀匹配,按匹配顺序排序,分组取首个结果
result = df.join(conditions_with_order, how="cross") \
          .filter(pl.col("url").str.starts_with(pl.col("url_right"))) \
          .sort("match_order") \
          .group_by("url") \
          .first() \
          .select("url", "category")

# 左连接回原表,确保所有原始url都被保留
final_result = df.join(result, on="url", how="left")

print(final_result)

输出结果:

shape: (4, 2)
┌─────────────────────┬───────────┐
│ url                 ┆ category  │
│ ---                 ┆ ---       │
│ str                 ┆ list[str] │
╞═════════════════════╪═══════════╡
│ https//abc.com      ┆ ["a"]     │
│ https//abcd.com     ┆ ["b"]     │
│ https//abcd.com/aaa ┆ ["b"]     │
│ https//abc.com/abcd ┆ ["a"]     │
└─────────────────────┴───────────┘

方法2:交叉连接+窗口函数取最小匹配顺序

另一种方式是用窗口函数找到每个url对应的最小匹配顺序,再过滤出对应行:

result = df.join(conditions_with_order, how="cross") \
          .filter(pl.col("url").str.starts_with(pl.col("url_right"))) \
          .with_columns(pl.min("match_order").over("url").alias("min_order")) \
          .filter(pl.col("match_order") == pl.col("min_order")) \
          .select("url", "category")

final_result = df.join(result, on="url", how="left")

关于join_where的说明

join_where可以用来实现前缀匹配的连接,但它会返回所有符合条件的匹配对。例如:

temp = df.join(conditions_df, how="left", on=pl.col("url").str.starts_with(pl.col("url_right")))

这段代码会给https//abcd.com/aaa返回两个匹配结果(https//abcd.com和https//abcd.com/aaa),无法直接得到我们需要的「第一个匹配项」。因此需要额外步骤(如排序、窗口函数)来筛选出优先级最高的匹配。


内容的提问来源于stack exchange,提问作者apostofes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 11:21:21