如何在Polars中执行不重复使用右侧DataFrame行的最近匹配Asof连接?
如何在Polars中执行不重复使用右侧DataFrame行的最近匹配Asof连接?
嘿,这个需求我之前做项目的时候刚好碰到过!Polars自带的join_asof虽然支持strategy="nearest"做最近匹配,但确实没限制右侧行的复用,得自己加点小逻辑来实现“右侧每行只能用一次”的要求。我给你分享两种实用的方法,一个是容易理解的遍历方式,另一个是更高效的批量处理方式,你按需选~
先补全咱们的示例测试数据,方便你上手验证:
import polars as pl df1 = pl.from_repr(""" ┌───────┐ │ value │ │ --- │ │ i64 │ ├───────┤ │ 1 │ │ 3 │ │ 6 │ │ 8 │ └───────┘ """) df2 = pl.from_repr(""" ┌───────┐ │ value │ │ --- │ │ i64 │ ├───────┤ │ 2 │ │ 4 │ │ 7 │ │ 9 │ └───────┘ """)
方法一:贪心遍历匹配(适合小数据量)
这个思路特别直接,就是按顺序给左侧每一行找右侧还没被用的最近值,匹配上就标记右侧行已使用,避免重复:
# 先给两个DataFrame按匹配列排序,最近匹配的前提是数据有序 df1_sorted = df1.sort("value") df2_sorted = df2.sort("value").with_columns(used=pl.lit(False)) # 准备存储匹配结果的列表 matches = [] # 逐个处理左侧的每一行 for left_row in df1_sorted.iter_rows(named=True): left_val = left_row["value"] # 只筛选右侧还没被匹配过的行 available_right = df2_sorted.filter(~pl.col("used")) if available_right.is_empty(): # 右侧没有可用行时,就存一个空匹配结果 matches.append({"left_value": left_val, "right_value": None}) continue # 计算每个可用右侧行和当前左侧值的差值绝对值 diffs = available_right.with_columns( diff=pl.abs(pl.col("value") - left_val) ) # 挑出差值最小的那一行(也就是最近匹配的目标行) nearest = diffs.sort("diff").row(0, named=True) # 记录这次的匹配结果 matches.append({ "left_value": left_val, "right_value": nearest["value"] }) # 把右侧这行标记为已使用,防止后续再被匹配 df2_sorted = df2_sorted.with_columns( used=pl.when(pl.col("value") == nearest["value"]) .then(pl.lit(True)) .otherwise(pl.col("used")) ) # 把匹配结果转换成Polars DataFrame格式 result = pl.DataFrame(matches) print(result)
这种方法逻辑简单,一眼就能看懂,但如果你的数据量特别大,Python循环的速度会有点跟不上,这时候就可以用下面的批量处理方法。
方法二:批量高效匹配(适合大数据量)
利用Polars的批量操作能力,不用写循环,通过交叉连接和窗口函数来实现,速度会快很多,大数据量场景下优势特别明显:
# 先给两个DataFrame排序,加上行索引方便后续的匹配筛选 df1_sorted = df1.sort("value").with_row_index("left_idx") df2_sorted = df2.sort("value").with_row_index("right_idx").rename({"value": "value_right"}) # 生成所有左右行的组合,计算每对组合的差值绝对值 cross_df = df1_sorted.join(df2_sorted, how="cross").with_columns( diff=pl.abs(pl.col("value") - pl.col("value_right")) ) # 按差值从小到大排序,给每个右侧行只保留第一次出现的匹配(也就是最优的那个匹配) ranked = cross_df.sort(["diff", "left_idx"]).with_columns( rn=pl.row_number().over("right_idx") ).filter(pl.col("rn") == 1) # 再给每个左侧行筛选出唯一的最优匹配 final_result = ranked.sort(["left_idx", "diff"]).with_columns( rn_left=pl.row_number().over("left_idx") ).filter(pl.col("rn_left") == 1).drop(["rn", "rn_left", "diff"]) print(final_result)
这个方法的核心是先找出每个右侧行能匹配到的最优左侧行,再给每个左侧行确定唯一的可用匹配,全程用Polars的内置函数处理,避开了Python循环的性能瓶颈。
备注:内容来源于stack exchange,提问作者miroslaavi
相关产品推荐
相关产品推荐

