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

如何在Polars中正确实现返回并拼接DataFrame的UDF?

如何在Polars中对每行应用UDF并合并行数可变的结果DataFrame?

问题背景

现有如下Polars DataFrame,需要对每行应用自定义函数(UDF),每个UDF返回schema相同但行数可变的DataFrame,最终将所有结果合并为单个DataFrame:

import polars as pl

df = pl.DataFrame(
    {
        "foo": [1, 2, 3],
        "bar": [6.0, 7.0, 8.0],
        "ham": ["a", "b", "c"],
    }
)

DataFrame结构:

shape: (3, 3)
┌─────┬─────┬─────┐
│ foo ┆ bar ┆ ham │
│ --- ┆ --- ┆ --- │
│ i64 ┆ f64 ┆ str │
╞═════╪═════╪═════╡
│ 1   ┆ 6.0 ┆ a   │
│ 2   ┆ 7.0 ┆ b   │
│ 3   ┆ 8.0 ┆ c   │
└─────┴─────┴─────┘

现有尝试及问题

最初尝试直接返回DataFrame,但结果会嵌套成object类型列,无法直接合并:

def myUDF(row_tuple):
    foo, bar, ham = row_tuple
    result = pl.DataFrame({
        "a": foo + bar,
        "b": ham
    })
    return (result,)

df.map_rows(myUDF)

输出:

shape: (3, 1)
┌────────────────┐
│ column_0       │
│ ---            │
│ object         │
╞════════════════╡
│ shape: (1, 2)  │
│ ┌─────┬─────┐  │
│ │ a …          │
│ shape: (1, 2)  │
│ ┌─────┬─────┐  │
│ │ a …          │
│ shape: (1, 2)  │
│ ┌──────┬─────┐ │
│ │ a…           │
└────────────────┘

后续改用to_dict()转换后再unnest+explode,虽然能得到正确结果,但频繁的字典转换会带来性能损耗:

def myUDF(row_tuple):
    foo, bar, ham = row_tuple
    result = pl.DataFrame({
        "a": foo + bar,
        "b": ham
    })
    return (result.to_dict(),)

df.map_rows(myUDF).unnest("column_0").explode("a", "b")

输出:

shape: (3, 2)
┌──────┬─────┐
│ a    ┆ b   │
│ ---  ┆ --- │
│ f64  ┆ str │
╞══════╪═════╡
│ 7.0  ┆ a   │
│ 9.0  ┆ b   │
│ 11.0 ┆ c   │
└──────┴─────┘

补充说明(实际场景)

实际操作中UDF逻辑更复杂,例如从大的参考数据集中切片并添加标识列:

reference_data = pl.DataFrame({
    "x": range(0, 10000000),
    "y": [chr(ord('a') + (i%26)) for i in range(0, 10000000)]
})

def myUDF(row_tuple):
    foo, bar, ham = row_tuple
    result = (
        reference_data
        .slice(foo, int(bar / 2))
        .with_columns(name=pl.lit(ham))
    )
    return (result.to_dict(),)

期望最终合并后的结果为:

shape: (10, 3)
┌─────┬─────┬──────┐
│ x   ┆ y   ┆ name │
│ --- ┆ --- ┆ ---  │
│ i64 ┆ str ┆ str  │
╞═════╪═════╪══════╡
│ 1   ┆ b   ┆ a    │
│ 2   ┆ c   ┆ a    │
│ 3   ┆ d   ┆ a    │
│ 2   ┆ c   ┆ b    │
│ 3   ┆ d   ┆ b    │
│ 4   ┆ e   ┆ b    │
│ 3   ┆ d   ┆ c    │
│ 4   ┆ e   ┆ c    │
│ 5   ┆ f   ┆ c    │
│ 6   ┆ g   ┆ c    │
└─────┴─────┴──────┘

正确实现方式

方案1:优化map_rows返回值(减少转换开销)

让UDF直接返回各列的列表,而非DataFrame或字典,避免不必要的类型转换:

def myUDF(row_tuple):
    foo, bar, ham = row_tuple
    sliced_data = reference_data.slice(foo, int(bar / 2))
    # 直接返回各列的列表,以及重复的name列
    return (
        sliced_data["x"].to_list(),
        sliced_data["y"].to_list(),
        [ham] * len(sliced_data)
    )

# 重命名列后直接explode所有列
result = (
    df.map_rows(myUDF)
    .rename({"column_0": "x", "column_1": "y", "column_2": "name"})
    .explode(pl.all())
)
print(result)

方案2:用apply+pl.concat实现更高效的合并(推荐)

避免逐行处理的额外开销,直接用apply返回每个行对应的DataFrame,再通过pl.concat合并所有结果:

result = (
    df
    .apply(
        lambda row: reference_data.slice(row["foo"], int(row["bar"]/2)).with_columns(name=pl.lit(row["ham"])),
        return_dtype=pl.DataFrame
    )
    .to_list()  # 将列中的DataFrame转为列表
    .pipe(pl.concat)  # 合并所有DataFrame
)
print(result)

这两种方案都能得到你期望的结果,其中方案2的性能更优,因为它利用了Polars的批量处理能力,避免了map_rows的逐行Python循环开销,尤其适合大数据量场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 13:25:53