0

0

Polars中利用列值作为字典键进行数据过滤的策略与优化

聖光之護

聖光之護

发布时间:2025-10-13 11:25:22

|

556人浏览过

|

来源于php中文网

原创

Polars中利用列值作为字典键进行数据过滤的策略与优化

在polars中,直接使用列表达式作为python字典的键会导致`typeerror: unhashable type: 'expr'`。本文将深入探讨两种有效解决此问题的方法:一是利用`map_elements`进行行级别转换,这种方法直观但效率较低;二是将嵌套字典扁平化为polars dataframe,并通过`join`操作实现高效过滤,这是处理大规模数据的推荐方案。文章将详细阐述每种方法的实现细节、适用场景及其性能考量。

在Polars进行数据处理时,我们有时会遇到需要根据DataFrame中的列值去查询一个外部Python字典的情况,特别是当字典是多层嵌套时。例如,尝试使用pl.col("cliente")和pl.col("cluster")作为nested_dict的键来过滤数据,如下所示:

df_x = (
    df_x
    .filter(pl.col("score") == nested_dict[pl.col("cliente")][pl.col("cluster")])
)

这段代码会抛出TypeError: unhashable type: 'Expr'错误。这是因为pl.col("cliente")和pl.col("cluster")返回的是Polars表达式(Expr对象),而不是实际的列值。Python字典的键必须是可哈希的(hashable),而Expr对象不可哈希,因此无法直接用作字典键进行查询。

为了解决这个问题,我们可以采用两种主要策略:一种是利用map_elements在行级别应用Python函数,另一种是将外部字典转换为Polars DataFrame并进行连接(join)操作。

策略一:使用 map_elements 进行行级别转换

map_elements方法允许我们对DataFrame中的元素应用一个Python函数。通过这种方式,我们可以在函数内部解析列值,并使用这些实际值来查询Python字典。

实现步骤

  1. 组合相关列: 使用pl.struct()将需要作为字典键的列(例如'cliente'和'cluster')组合成一个结构体(Struct)。
  2. 应用 map_elements: 对这个结构体应用map_elements函数。该函数会接收每一行的结构体作为输入,其中包含各列的实际值。
  3. 字典查询: 在map_elements内部的lambda函数中,使用结构体中的实际值来查询nested_dict。

示例代码

import polars as pl

# 示例数据和嵌套字典
df_x = pl.DataFrame({
    "cliente": ["A", "B", "A", "C"],
    "cluster": ["X", "Y", "Z", "X"],
    "score": [10, 20, 30, 40]
})

nested_dict = {
    "A": {"X": 10, "Z": 25},
    "B": {"Y": 20},
    "C": {"X": 40}
}

# 使用 map_elements 进行过滤
df_filtered_map = (
    df_x
    .filter(
        pl.col('score').eq(
            pl.struct('cliente', 'cluster')
                .map_elements(lambda x: (
                    nested_dict.get(x['cliente'], {}).get(x['cluster']) # 使用 .get 避免 KeyError
                ), return_dtype=pl.Int64) # 指定返回类型
        )
    )
)

print("使用 map_elements 过滤后的 DataFrame:")
print(df_filtered_map)

注意事项

  • 性能: map_elements会强制Polars将数据传递给Python函数进行处理,这会引入Python解释器的开销,通常比纯Polars的向量化操作效率低。对于大规模数据集,这可能成为性能瓶颈
  • 错误处理: 在lambda函数中,建议使用字典的.get()方法来安全地访问键,以防止当cliente或cluster的组合在nested_dict中不存在时引发KeyError。
  • 返回类型: 必须为map_elements指定return_dtype,否则Polars可能无法推断出正确的列类型。

策略二:扁平化字典并进行连接(Join)

更高效且Polars-idiomatic 的方法是将嵌套的Python字典转换为一个Polars DataFrame,然后通过join操作将其与主DataFrame连接起来,最后再进行过滤。这种方法将字典查询转换为Polars的向量化操作,从而显著提高性能。

Relayed AI
Relayed AI

一款AI驱动的视频会议工具,旨在帮助团队克服远程工作、繁忙的日程安排和会议疲劳。

下载

实现步骤

  1. 扁平化嵌套字典: 将nested_dict转换为一个包含cliente、cluster和cluster_value(即对应的分数)的Polars DataFrame。
  2. 执行连接操作: 使用join方法将主DataFrame与扁平化的字典DataFrame连接起来,连接键为cliente和cluster。
  3. 进行过滤: 连接后,cluster_value列将可用,可以直接用于过滤条件。

示例代码

import polars as pl

# 示例数据和嵌套字典
df_x = pl.DataFrame({
    "cliente": ["A", "B", "A", "C", "D"],
    "cluster": ["X", "Y", "Z", "X", "Y"],
    "score": [10, 20, 30, 40, 50]
})

nested_dict = {
    "A": {"X": 10, "Z": 25},
    "B": {"Y": 20},
    "C": {"X": 40}
}

# 1. 扁平化嵌套字典为 Polars DataFrame
df_nested_prelim = pl.from_dict(nested_dict)

df_nested_parts = []
for col_name in df_nested_prelim.columns:
    df_nested_parts.append(
        df_nested_prelim.lazy()
        .select(col_name).unnest(col_name) # 展开内部字典为列
        .unpivot(
            on=[], # 不指定on,对所有非id列进行unpivot
            index=[], # 没有id列,所有列都参与unpivot
            variable_name='cluster', 
            value_name='cluster_value'
        )
        .with_columns(cliente=pl.lit(col_name)) # 添加原始的cliente名称
    )

df_nested = pl.concat(df_nested_parts).collect()

# 2. 移除可能产生的null值(如果原始字典中没有对应的cluster)
df_nested = df_nested.filter(pl.col("cluster_value").is_not_null())

print("\n扁平化后的字典 DataFrame:")
print(df_nested)

# 3. 执行连接并过滤
df_filtered_join = (
    df_x
    .join(df_nested, on=['cliente', 'cluster'], how='inner') # 使用inner join确保只匹配存在的值
    .filter(pl.col('score') == pl.col('cluster_value'))
    .select(df_x.columns) # 仅保留原始 df_x 的列
)

print("\n使用 join 过滤后的 DataFrame:")
print(df_filtered_join)

扁平化字典的详细解释

上述扁平化字典的代码可能看起来有些复杂,我们来逐步解析:

  1. df_nested_prelim = pl.from_dict(nested_dict): 将nested_dict转换为Polars DataFrame。此时,nested_dict的顶层键(如"A", "B", "C")会成为DataFrame的列名,而它们对应的值(内层字典)会成为这些列中的结构体(Struct)。 例如,df_nested_prelim可能看起来像:

    shape: (1, 3)
    ┌───────────┬───────────┬───────────┐
    │ A         ┆ B         ┆ C         │
    │ struct[2] ┆ struct[1] ┆ struct[1] │
    ╞═══════════╪═══════════╪═══════════╡
    │ {10,25}   ┆ {20}      ┆ {40}      │
    └───────────┴───────────┴───────────┘
  2. for col_name in df_nested_prelim.columns:: 遍历df_nested_prelim中的每一列(即原始的cliente名称)。

  3. select(col_name).unnest(col_name): 选择当前列,并将其解嵌套。例如,对于列"A",它包含{"X": 10, "Z": 25}这个结构体。unnest("A")会将其展开为两列:"X"和"Z"。

    shape: (1, 2)
    ┌─────┬─────┐
    │ X   ┆ Z   │
    │ i64 ┆ i64 │
    ╞═════╪═════╡
    │ 10  ┆ 25  │
    └─────┴─────┘
  4. unpivot(on=[], index=[], variable_name='cluster', value_name='cluster_value'): 将宽格式的DataFrame(X, Z作为列)转换为长格式。variable_name指定了新的列名,用于存放原始的列名(X, Z),value_name指定了存放原始列值(10, 25)的列名。 结果会是:

    shape: (2, 2)
    ┌─────────┬───────────────┐
    │ cluster ┆ cluster_value │
    │ str     ┆ i64           │
    ╞═════════╪═══════════════╡
    │ X       ┆ 10            │
    │ Z       ┆ 25            │
    └─────────┴───────────────┘
  5. with_columns(cliente=pl.lit(col_name)): 添加一个名为cliente的新列,其值就是当前循环的原始cliente名称(例如"A")。

    shape: (2, 3)
    ┌─────────┬───────────────┬─────────┐
    │ cluster ┆ cluster_value ┆ cliente │
    │ str     ┆ i64           ┆ str     │
    ╞═════════╪═══════════════╪═════════╡
    │ X       ┆ 10            ┆ A       │
    │ Z       ┆ 25            ┆ A       │
    └─────────┴───────────────┴─────────┘
  6. pl.concat(df_nested_parts).collect(): 将所有cliente循环生成的DataFrame片段拼接在一起,形成最终的扁平化字典DataFrame。

优势

  • 高性能: join和filter操作都是Polars的高度优化和向量化操作,能够充分利用多核CPU,处理大规模数据集时效率远高于map_elements。
  • Polars范式: 这种方法更符合Polars的设计哲学,即尽量将操作保持在DataFrame级别,避免Python循环和UDF。

总结

当需要在Polars中根据列值查询外部Python字典时,直接使用列表达式作为字典键是不可行的。

  • map_elements方法 提供了一种快速实现的方式,尤其适用于数据量较小或逻辑复杂难以用Polars表达式表达的场景。但其性能开销较大。
  • 扁平化字典并进行join的方法 是处理大规模数据集时的首选方案。它将字典查询转换为高效的Polars DataFrame操作,提供了卓越的性能和可扩展性。

在实际应用中,应根据数据规模和性能要求选择最合适的方法。对于生产环境和大数据场景,强烈推荐采用扁平化字典并进行连接的策略。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

282

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

192

2025.07.04

lambda表达式
lambda表达式

Lambda表达式是一种匿名函数的简洁表示方式,它可以在需要函数作为参数的地方使用,并提供了一种更简洁、更灵活的编码方式,其语法为“lambda 参数列表: 表达式”,参数列表是函数的参数,可以包含一个或多个参数,用逗号分隔,表达式是函数的执行体,用于定义函数的具体操作。本专题为大家提供lambda表达式相关的文章、下载、课程内容,供大家免费下载体验。

208

2023.09.15

python lambda函数
python lambda函数

本专题整合了python lambda函数用法详解,阅读专题下面的文章了解更多详细内容。

191

2025.11.08

Python lambda详解
Python lambda详解

本专题整合了Python lambda函数相关教程,阅读下面的文章了解更多详细内容。

56

2026.01.05

AO3官网入口与中文阅读设置 AO3网页版使用与访问
AO3官网入口与中文阅读设置 AO3网页版使用与访问

本专题围绕 Archive of Our Own(AO3)官网入口展开,系统整理 AO3 最新可用官网地址、网页版访问方式、正确打开链接的方法,并详细讲解 AO3 中文界面设置、阅读语言切换及基础使用流程,帮助用户稳定访问 AO3 官网,高效完成中文阅读与作品浏览。

1

2026.02.02

主流快递单号查询入口 实时物流进度一站式追踪专题
主流快递单号查询入口 实时物流进度一站式追踪专题

本专题聚合极兔快递、京东快递、中通快递、圆通快递、韵达快递等主流物流平台的单号查询与运单追踪内容,重点解决单号查询、手机号查物流、官网入口直达、包裹进度实时追踪等高频问题,帮助用户快速获取最新物流状态,提升查件效率与使用体验。

0

2026.02.02

Golang WebAssembly(WASM)开发入门
Golang WebAssembly(WASM)开发入门

本专题系统讲解 Golang 在 WebAssembly(WASM)开发中的实践方法,涵盖 WASM 基础原理、Go 编译到 WASM 的流程、与 JavaScript 的交互方式、性能与体积优化,以及典型应用场景(如前端计算、跨平台模块)。帮助开发者掌握 Go 在新一代 Web 技术栈中的应用能力。

1

2026.02.02

PHP Swoole 高性能服务开发
PHP Swoole 高性能服务开发

本专题聚焦 PHP Swoole 扩展在高性能服务端开发中的应用,系统讲解协程模型、异步IO、TCP/HTTP/WebSocket服务器、进程与任务管理、常驻内存架构设计。通过实战案例,帮助开发者掌握 使用 PHP 构建高并发、低延迟服务端应用的工程化能力。

0

2026.02.02

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.4万人学习

Django 教程
Django 教程

共28课时 | 3.8万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号