Polars DataFrame 余弦相似度矩阵的构建方法

Polars DataFrame 余弦相似度矩阵的构建方法

本教程详细介绍了如何在 Polars DataFrame 中高效计算列表类型列之间的余弦相似度,并将其结果转换为一个类似相关系数矩阵的宽格式 DataFrame。文章将通过 join_where 生成数据组合,利用 Polars 原生表达式计算余弦相似度,并最终通过 pivot 操作构建出完整的对称相似度矩阵。

引言

在数据分析和机器学习领域,我们经常需要计算数据点之间的相似度。当数据以向量(或列表)的形式存储在 dataframe 的列中时,余弦相似度是一种常用的度量标准。polars 作为一种高性能的 dataframe 库,提供了强大的表达式引擎来处理这类计算。然而,直接将自定义的 python 函数应用于 polars 的聚合操作(如 pivot)可能会遇到 attributeerror: ‘function’ object has no attribute ‘_pyexpr’ 等问题,这通常是因为 polars 期望接收其内部表达式而不是普通的 python 函数。本文将展示如何利用 polars 的原生特性,优雅地解决这一问题,从而生成一个完整的余弦相似度矩阵。

数据准备

首先,我们定义一个包含列表数据的 Polars DataFrame,这是我们进行相似度计算的基础。

import polars as plfrom numpy.linalg import norm # 虽然这里引入了norm,但在Polars原生表达式中我们有更优解data = {    "col1": ["a", "b", "c", "d"],    "col2": [[-0.06066, 0.072485, 0.548874, 0.158507],             [-0.536674, 0.10478, 0.926022, -0.083722],             [-0.21311, -0.030623, 0.300583, 0.261814],             [-0.308025, 0.006694, 0.176335, 0.533835]],}df = pl.DataFrame(data)print("原始 DataFrame:")print(df)

输出:

原始 DataFrame:shape: (4, 2)┌──────┬─────────────────────────────────┐│ col1 ┆ col2                            ││ ---  ┆ ---                             ││ str  ┆ list[f64]                       │╞══════╪═════════════════════════════════╡│ a    ┆ [-0.06066, 0.072485, … 0.15850… ││ b    ┆ [-0.536674, 0.10478, … -0.0837… ││ c    ┆ [-0.21311, -0.030623, … 0.2618… ││ d    ┆ [-0.308025, 0.006694, … 0.5338… │└──────┴─────────────────────────────────┘

我们的目标是计算 col1 中每个唯一值(例如 ‘a’, ‘b’, ‘c’, ‘d’)对应的 col2 列表之间的余弦相似度,并最终生成一个交叉矩阵。

Polars 中的余弦相似度计算原理

余弦相似度的数学公式为:$ text{cosine_similarity}(A, B) = frac{A cdot B}{|A| cdot |B|} $其中,$A cdot B$ 是向量 $A$ 和 $B$ 的点积,$|A|$ 和 $|B|$ 分别是向量 $A$ 和 $B$ 的欧几里得范数(L2 范数)。

在 Polars 中,我们可以将这个公式转化为表达式。值得注意的是,从 Polars 1.8.0 版本开始,Polars 引入了原生的列表算术操作,使得余弦相似度的计算更加高效和简洁。

# 定义 Polars 表达式形式的余弦相似度函数def calculate_cosine_similarity_expr(x: pl.Expr, y: pl.Expr) -> pl.Expr:    """    计算两个列表列之间的余弦相似度 Polars 表达式。    要求 Polars 版本 >= 1.8.0 以获得最佳性能。    """    dot_product = (x * y).list.sum()    norm_x = (x * x).list.sum().sqrt()    norm_y = (y * y).list.sum().sqrt()    return dot_product / (norm_x * norm_y)# 示例使用:# cosine_similarity_expr = calculate_cosine_similarity_expr(pl.col("col2"), pl.col("col2_right"))

这个表达式利用了 Polars 的列表乘法 (x * y) 来实现元素级别的乘积,然后通过 list.sum() 求和得到点积。欧几里得范数通过 (x * x).list.sum().sqrt() 来计算。这种方式完全在 Polars 的表达式引擎中执行,避免了 Python UDF 的性能开销。

生成数据组合

为了计算所有可能的 pairwise 相似度,我们需要将 DataFrame 中的每一行与所有其他行(包括自身)进行组合。with_row_index() 和 join_where() 是实现这一目标的强大工具

添加行索引: 使用 with_row_index() 为每一行添加一个唯一的索引。条件连接: 使用 join_where() 进行自连接,并设置条件 pl.col.index

# 转换为 lazy DataFrame 以优化性能lazy_df = df.with_row_index().lazy()# 生成组合combinations_df = lazy_df.join_where(lazy_df, pl.col.index <= pl.col.index_right).collect()print("n生成的所有组合 (部分):")print(combinations_df.head())

输出:

生成的所有组合 (部分):shape: (5, 6)┌───────┬──────┬─────────────────────────────────┬─────────────┬────────────┬─────────────────────────────────┐│ index ┆ col1 ┆ col2                            ┆ index_right ┆ col1_right ┆ col2_right                      ││ ---   ┆ ---  ┆ ---                             ┆ ---         ┆ ---        ┆ ---                             ││ u32   ┆ str  ┆ list[f64]                       ┆ u32         ┆ str        ┆ list[f64]                       │╞═══════╪══════╪═════════════════════════════════╪═════════════╪════════════╪═════════════════════════════════╡│ 0     ┆ a    ┆ [-0.06066, 0.072485, … 0.15850… ┆ 0           ┆ a          ┆ [-0.06066, 0.072485, … 0.15850… ││ 0     ┆ a    ┆ [-0.06066, 0.072485, … 0.15850… ┆ 1           ┆ b          ┆ [-0.536674, 0.10478, … -0.0837… ││ 0     ┆ a    ┆ [-0.06066, 0.072485, … 0.15850… ┆ 2           ┆ c          ┆ [-0.21311, -0.030623, … 0.2618… ││ 0     ┆ a    ┆ [-0.06066, 0.072485, … 0.15850… ┆ 3           ┆ d          ┆ [-0.308025, 0.006694, … 0.5338… ││ 1     ┆ b    ┆ [-0.536674, 0.10478, … -0.0837… ┆ 1           ┆ b          ┆ [-0.536674, 0.10478, … -0.0837… │└───────┴──────┴─────────────────────────────────┴─────────────┴────────────┴─────────────────────────────────┘

这个 DataFrame 包含了所有需要计算相似度的向量对。col2 和 col2_right 分别代表了组合中的两个向量。

计算所有组合的余弦相似度

现在,我们将上面定义的余弦相似度表达式应用于 combinations_df 中的 col2 和 col2_right 列。

# 计算余弦相似度similarity_results = (    lazy_df.join_where(lazy_df, pl.col.index <= pl.col.index_right)    .select(        col="col1",        other="col1_right",        cosine=calculate_cosine_similarity_expr(            x=pl.col.col2,            y=pl.col.col2_right        )    )).collect()print("n计算出的余弦相似度 (部分):")print(similarity_results)

输出:

计算出的余弦相似度 (部分):shape: (10, 3)┌─────┬───────┬──────────┐│ col ┆ other ┆ cosine   ││ --- ┆ ---   ┆ ---      ││ str ┆ str   ┆ f64      │╞═════╪═══════╪══════════╡│ a   ┆ a     ┆ 1.0      ││ a   ┆ b     ┆ 0.856754 ││ a   ┆ c     ┆ 0.827877 ││ a   ┆ d     ┆ 0.540282 ││ b   ┆ b     ┆ 1.0      ││ b   ┆ c     ┆ 0.752199 ││ b   ┆ d     ┆ 0.411564 ││ c   ┆ c     ┆ 1.0      ││ c   ┆ d     ┆ 0.889009 ││ d   ┆ d     ┆ 1.0      │└─────┴───────┴──────────┘

similarity_results DataFrame 包含了每对 col1 值的余弦相似度。由于我们使用了 index

构建相似度矩阵

为了得到一个完整的对称相似度矩阵,我们需要处理非对角线元素的对称性(即 cosine(A, B) 等于 cosine(B, A))。我们可以通过以下步骤完成:

复制并反转非对角线元素: 筛选出 col != other 的行,然后交换 col 和 other 列的值,形成反向的组合。合并结果: 将原始的 similarity_results 与反转后的结果合并。透视: 使用 pivot() 方法将数据从长格式转换为宽格式,形成最终的矩阵。

final_similarity_matrix = (    pl.concat(        [            similarity_results,            # 筛选非对角线元素,并反转 col 和 other            similarity_results.filter(pl.col.col != pl.col.other)                               .select(col="other", other="col", cosine="cosine")        ]    )    .pivot(        values="cosine",        index="col",        columns="other"    ))print("n最终的余弦相似度矩阵:")print(final_similarity_matrix)

输出:

最终的余弦相似度矩阵:shape: (4, 5)┌─────┬──────────┬──────────┬──────────┬──────────┐│ col ┆ a        ┆ b        ┆ c        ┆ d        ││ --- ┆ ---      ┆ ---      ┆ ---      ┆ ---      ││ str ┆ f64      ┆ f64      ┆ f64      ┆ f64      │╞═════╪══════════╪══════════╪══════════╪══════════╡│ a   ┆ 1.0      ┆ 0.856754 ┆ 0.827877 ┆ 0.540282 ││ b   ┆ 0.856754 ┆ 1.0      ┆ 0.752199 ┆ 0.411564 ││ c   ┆ 0.827877 ┆ 0.752199 ┆ 1.0      ┆ 0.889009 ││ d   ┆ 0.540282 ┆ 0.411564 ┆ 0.889009 ┆ 1.0      │└─────┴──────────┴──────────┴──────────┴──────────┘

现在我们得到了一个与期望输出完全一致的余弦相似度矩阵,其中行和列都由 col1 的唯一值表示,矩阵中的每个元素代表相应两个向量的余弦相似度。

注意事项与性能优化

Polars 版本: 上述余弦相似度表达式利用了 Polars 1.8.0 及更高版本中引入的原生列表算术功能。如果使用较旧的 Polars 版本,可能需要采用不同的方法(例如使用 apply 配合 Python UDF,但这会牺牲性能)。强烈建议升级到最新版本的 Polars 以获得最佳性能和功能。避免 Python UDFs: 尽量避免在 Polars 中使用 Python 用户自定义函数(UDFs),尤其是在性能敏感的场景。Polars 的表达式引擎经过高度优化,能够利用多核并行计算,而 UDFs 会强制数据在 Polars 内部和 Python 解释器之间来回移动,导致性能下降。本教程中的方法完全避免了 UDFs。惰性计算 (.lazy()): 在处理大型数据集时,将 DataFrame 转换为惰性模式 (.lazy()) 可以让 Polars 优化查询计划,从而提高内存效率和执行速度。在最终 collect() 之前,Polars 不会实际执行计算。内存管理: 对于非常大的数据集,生成所有组合可能会消耗大量内存。join_where 配合 lazy() 已经相对高效,但仍需注意数据集大小。

总结

本教程展示了在 Polars 中构建余弦相似度矩阵的完整流程。通过巧妙地结合 with_row_index()、join_where() 生成数据组合,利用 Polars 原生表达式高效计算余弦相似度,并最终通过 pl.concat() 和 pivot() 将结果转换为易于理解的矩阵形式。这种方法不仅解决了直接使用 Python 函数作为聚合器时的错误,而且充分利用了 Polars 的高性能特性,为处理大规模向量相似度计算提供了专业且高效的解决方案。

以上就是Polars DataFrame 余弦相似度矩阵的构建方法的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1376735.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月14日 16:09:32
下一篇 2025年12月14日 16:09:48

相关推荐

  • Python中处理和保存从HTTP响应获取的Excel文件

    本教程详细介绍了如何在Python中处理从HTTP响应获取的Excel文件字节流。文章区分了两种主要场景:一是直接将完整的Excel字节流保存为文件,适用于无需进一步处理的原始文件;二是使用Pandas解析Excel文件,并选择性地将每个工作表保存为独立的Excel文件或CSV文件。通过代码示例,帮…

    2025年12月14日
    000
  • PyCharm文件移动重构中未使用的导入移除机制解析及临时对策

    PyCharm的文件移动重构功能在更新导入路径时,会意外自动移除文件中未使用的导入。目前尚无全局设置可禁用此行为,这可能对某些开发流程造成困扰。本文将深入探讨这一现象,并提供一种使用特定注释来保护单个导入不被移除的临时解决方案,帮助开发者应对此问题。 PyCharm文件移动重构的导入处理行为 pyc…

    2025年12月14日
    000
  • Python集成Alpaca交易API:正确安装与初始化指南

    本教程旨在解决在AWS等环境中连接Alpaca交易API时常见的ModuleNotFoundError问题。核心在于明确正确的Python库为alpaca_trade_api而非alpaca_py。文章将指导用户完成正确的安装、API初始化配置,并提供示例代码,确保您能顺利地在Python程序中与A…

    2025年12月14日
    000
  • Python pathlib:高效提取当前目录名称

    本文将介绍如何利用 Python 的 pathlib 模块,从完整的路径中高效地提取出当前目录的名称。通过使用 Path 对象的 .name 属性,开发者可以避免手动解析字符串,从而简化代码并提高可读性,实现快速准确地获取所需目录名。 引言 在 python 开发中,我们经常需要处理文件系统路径。p…

    2025年12月14日
    000
  • Python中实现用户输入验证与循环重试:避免常见陷阱

    本教程探讨Python中如何有效处理用户输入验证场景。针对用户在循环中输入不符合预期条件时,程序未能正确重试或陷入死循环的问题,本文将详细阐述一种健壮的解决方案。核心在于,当输入不满足条件时,必须在循环内部再次提示用户输入,以确保循环控制变量得到更新,从而实现正确的输入验证和重试机制,避免程序意外终…

    2025年12月14日
    000
  • Numba函数中break语句导致性能下降的深入分析与优化

    在Numba优化代码时,添加break语句有时会导致意想不到的性能下降,甚至比不使用break的版本慢数倍。这主要是因为Numba底层依赖的LLVM编译器在存在break时难以进行循环向量化(SIMD优化),导致代码从高效的并行处理退化为低效的标量处理。此外,分支预测失误也会加剧性能问题。本文将深入…

    2025年12月14日
    000
  • 无需TensorBoard服务:程序化解析TensorFlow事件日志数据

    本文详细介绍了如何在不启动TensorBoard服务的情况下,通过TensorFlow的EventFileReader API程序化地解析模型训练生成的事件日志文件。通过Python脚本,您可以直接读取日志中的步数、时间戳和标量值等关键数据,实现自定义的数据提取、处理与分析,尤其适用于需要将日志数据…

    2025年12月14日
    000
  • 使用Polars高效构建余弦相似度矩阵:从数据准备到结果可视化

    本文详细介绍了如何在Polars中计算并构建余弦相似度矩阵。针对Polars的表达式限制,教程首先展示了如何通过with_row_index和join_where生成所有数据对组合,然后利用Polars原生的列表算术和表达式高效地计算余弦相似度,避免了自定义Python函数的性能瓶颈。最后,通过co…

    2025年12月14日
    000
  • Pandas DataFrame中复杂日期字符串的清洗与格式化教程

    本教程详细介绍了如何在Pandas DataFrame中处理包含特殊字符和冗余信息的日期字符串。文章提供了两种核心策略:一是利用pd.to_datetime函数的exact=False参数直接将复杂字符串转换为日期时间对象,二是结合正则表达式str.extract和str.replace方法,从混乱…

    2025年12月14日
    000
  • Pandas DataFrame中不规则日期字符串的清洗与标准化

    本文详细介绍了如何在Pandas DataFrame中处理包含特殊字符或不规则格式的日期字符串。通过利用pd.to_datetime函数的exact=False参数进行灵活转换,或结合正则表达式str.extract和str.replace方法精确提取并标准化日期格式,即使面对复杂多变的日期字符串,…

    2025年12月14日
    000
  • Python字符串格式化进阶:解包与f-string的巧妙应用

    本文深入探讨了Python中字符串格式化的多种方法,重点讲解了元组解包与f-string的结合使用。通过示例代码,详细比较了%操作符、str.format()方法以及f-string在元组解包场景下的应用,并提供了在f-string中使用斜杠分隔符的更简洁方案,旨在帮助读者掌握更高效、更易读的字符串…

    2025年12月14日
    000
  • 解决Python mysqlclient 安装中的 mysql.h 缺失问题

    在Python 3.12.1及更高版本中安装 mysqlclient 时,开发者常会遇到因缺少 mysql.h 文件导致的编译错误。本文将深入探讨这一常见问题,解释其根本原因,并提供针对Linux(Ubuntu/CentOS)和Windows平台的详细解决方案,确保您能顺利安装并使用 mysqlcl…

    2025年12月14日
    000
  • Pandas DataFrame:高效获取行级最小值及其对应关联列值

    本教程详细介绍了如何在 Pandas DataFrame 中高效地查找每一行的最小值,并进一步提取与该最小值对应的非数值型关联列值(例如,项目名称)。文章通过一个具体的示例,展示了如何利用 idxmin、str.replace 和 get_indexer_for 等 Pandas 功能,以简洁且性能…

    2025年12月14日
    000
  • PyTorch DataLoader 目标形状异常解析与正确处理方法

    本文深入探讨了PyTorch DataLoader在批处理过程中,当__getitem__方法返回Python列表作为目标标签时,可能出现的批次目标形状异常问题。通过分析DataLoader的默认批处理机制,揭示了导致目标维度错位的原因,并提供了将目标数据转换为torch.Tensor的有效解决方案…

    2025年12月14日
    000
  • 解决Selenium与GitHub搜索栏交互难题:深入理解动态元素操作

    本教程旨在解决使用Selenium自动化测试时,GitHub搜索栏出现ElementNotInteractableException的问题。核心在于识别GitHub搜索功能并非直接的输入框,而是需要先点击一个搜索按钮才能激活真正的输入字段。文章将详细指导如何利用Selenium的显式等待和正确的元素…

    2025年12月14日
    000
  • Docker容器中高效管理与切换Python多版本:构建时动态选择策略

    本文探讨了在Docker镜像中管理和切换多个Python版本的高效策略。针对在CI/CD流程中根据需求选择特定Python版本的场景,我们推荐使用Docker的构建参数(ARG)动态指定基础镜像,从而避免在单个镜像中安装多个Python版本并进行复杂的符号链接管理。这种方法简化了Dockerfile…

    2025年12月14日
    000
  • Python asyncio 异步编程:理解与实现任务的顺序执行

    asyncio 模块设计用于实现并发,asyncio.gather() 会同时运行多个任务,而非按序执行。若需确保异步任务严格依照特定顺序完成,例如当任务间存在数据依赖时,应避免使用 asyncio.gather() 进行并发调度,转而通过在循环中逐个 await 任务来强制实现串行执行,确保前一个…

    2025年12月14日
    000
  • 从完整路径中提取当前目录名称:Python pathlib 实践

    本教程旨在指导如何在Python中利用pathlib模块,从一个完整的路径对象中高效地提取出当前(最末级)目录的名称。通过pathlib.Path对象的.name属性,开发者可以简洁、优雅地获取所需目录名,避免手动字符串处理的繁琐与潜在错误,提升代码的可读性和跨平台兼容性。 理解路径与目录名提取的需…

    2025年12月14日
    000
  • 解决Selenium自动化GitHub搜索栏“元素不可交互”问题

    本教程详细讲解如何使用Selenium自动化GitHub网站的搜索功能,重点解决常见的“元素不可交互”问题。通过分析GitHub搜索栏的动态特性,我们将学习如何正确识别并操作作为按钮的搜索入口,进而与实际的输入框进行交互,并提供完整的Python代码示例及最佳实践。 引言:理解“元素不可交互”错误 …

    2025年12月14日
    000
  • 比较带有浮点数和NaN的DataFrame列:处理精度与缺失值

    本教程详细介绍了如何在Pandas DataFrame中比较两列浮点数,并准确计算差异行数。文章重点解决了浮点数精度问题(通过四舍五入)和NaN值(缺失值)的特殊处理,确保NaN与NaN不被计为差异,而NaN与数值则被计为差异,从而实现精确的数据对比和差异统计。 挑战:浮点数比较与NaN处理 在数据…

    2025年12月14日
    000

发表回复

登录后才能评论
关注微信