全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

NumPy 高性能技巧:基于多列条件查找最近邻行索引的向量化实现

本文深入探讨如何在numpy中高效解决一个复杂的数据查询问题:为大型数组中的每一行,查找满足特定多列(第二列和第四列)值完全匹配,且在另一列(第一列)上距离最近的n个行的原始索引。通过摒弃低效的python for循环,本教程将详细展示如何利用numpy的向量化操作,包括数据预处理、巧妙的分块策略、广播机制进行距离计算与排序,以及结果整合与还原,从而实现显著的性能提升和代码的简洁性,为处理大规模数据集提供一个专业且可复用的解决方案。

问题背景与挑战

在数据分析和科学计算中,我们经常需要从大型数据集中检索满足特定条件的记录。一个常见的挑战是,当查询条件涉及多列,并且需要根据某一列的“距离”来找出最接近的N个记录时,传统的逐行迭代(例如使用Python的for循环)会带来严重的性能问题,尤其是在处理百万级别甚至更大规模的NumPy数组时。

具体而言,我们的目标是:给定一个具有8列的NumPy数组,对于数组中的每一行,我们需要找出其他满足以下两个条件的N行:

  1. 它们的第二列(索引为1)的值与当前行的第二列值完全相同。
  2. 它们的第四列(索引为3)的值与当前行的第四列值完全相同。
  3. 在满足上述两个条件的所有行中,其第一列(索引为0)的值与当前行的第一列值最接近。

最终输出应是一个与原始数组行数相同、列数为N的数组,其中每一行包含对应原始行的N个最近邻行的原始索引。

NumPy 向量化解决方案概览

为了克服for循环的性能瓶颈,我们将采用NumPy的向量化能力。核心思路是:

  1. 添加原始索引:在对数组进行任何排序或分块操作之前,保留每行的原始位置信息。
  2. 按条件列分组:将原始数组根据其中一个等值条件列(例如第二列)进行排序,然后分块。这样,每个子块内部的所有行的该条件列值都是相同的。
  3. 在组内过滤与计算:对于每个子块,再根据另一个等值条件列(第四列)进行过滤。在过滤后的数据中,使用NumPy的广播机制高效计算第一列的绝对差值,并找出N个最小差值的索引。
  4. 整合结果:将所有子块计算出的索引合并,并根据原始索引恢复数组的初始顺序。

详细实现步骤

1. 准备数据:添加原始索引

在进行任何可能改变行顺序的操作之前,我们需要为原始数组的每一行添加一个唯一的标识符,即其在原始数组中的索引。这确保了即使数据经过排序、过滤和分块,我们也能最终追溯到其在原始数据集中的位置。

import numpy as np

# 假设 arr 是从 "data.txt" 加载的原始大型数组
# arr = np.loadtxt("data.txt")

# 示例数据(简化版,模拟原始数据结构)
# 假设原始数据有8列,这里只用4列进行演示
arr = np.array([
    [1.0, 0.0, -1.6, 2.3],  # idx 0
    [1.0, 1.0, -1.6, 2.3],  # idx 1
    [1.0, 0.0, -1.6, 2.3],  # idx 2 (col1=0, col4=2.3, col0=1.0)
    [2.0, 0.0, -1.6, 2.3],  # idx 3
    [1.1, 0.0, -1.6, 2.3],  # idx 4 (col1=0, col4=2.3, col0=1.1)
    [1.2, 0.0, -1.6, 2.3],  # idx 5 (col1=0, col4=2.3, col0=1.2)
    [1.0, 1.0, -1.6, 2.3],  # idx 6 (col1=1, col4=2.3, col0=1.0)
    [1.1, 1.0, -1.6, 2.3],  # idx 7 (col1=1, col4=2.3, col0=1.1)
    [1.0, 0.0, -1.6, 7.3],  # idx 8 (col1=0, col4=7.3, col0=1.0)
    [1.1, 0.0, -1.6, 7.3]   # idx 9 (col1=0, col4=7.3, col0=1.1)
])

# 添加原始索引作为最后一列
# np.c_ 用于按列堆叠数组
arr_indexed = np.c_[arr, np.arange(len(arr))]
print("原始数组(带索引):\n", arr_indexed)

在上述代码中,np.arange(len(arr)) 生成一个从0到 len(arr)-1 的整数序列,然后 np.c_ 将其作为新的一列添加到原始数组 arr 的右侧。

2. 按条件列排序与分块

为了高效处理“第二列和第四列值相同”的条件,我们可以首先对数组进行排序。选择第二列(索引为1)作为主要的排序键,因为它的值在问题描述中是离散的。排序后,所有第二列值相同的行将聚集在一起,这为后续的分块操作提供了便利。

# 按第二列的值进行排序
arr_sorted_by_col1 = arr_indexed[arr_indexed[:, 1].argsort()]
print("\n按第二列排序后的数组:\n", arr_sorted_by_col1)

# 根据第二列的唯一值进行分块
# np.unique(..., return_index=True) 返回唯一值及其首次出现的索引
unique_col1_values, split_indices = np.unique(arr_sorted_by_col1[:, 1], return_index=True)
# split_indices[1:] 提供了除第一个块之外的所有分界点
splitted_array = np.split(arr_sorted_by_col1, split_indices[1:])

print("\n按第二列分块后的数组(列表形式):")
for i, block in enumerate(splitted_array):
    print(f"--- 块 {i} (第二列值为 {block[0, 1]}) ---\n", block)

np.unique(arr_sorted_by_col1[:, 1], return_index=True)[1][1:] 能够找到第二列值发生变化的所有位置,这些位置作为 np.split 的参数,可以将排序后的数组精确地分割成多个子数组,每个子数组内部的第二列值都是相同的。

3. 核心逻辑:query_trks 函数

这个函数负责在每个分块(即第二列值已确定的子数组)内部,进一步根据第四列的值进行过滤,然后计算第一列的距离并找出N个最近邻的原始索引。

def query_trks(FULL_block, target_col4_value, N):
    """
    在给定的数据块中,查找满足第四列条件且第一列最接近的N个行的原始索引。

    参数:
    FULL_block (np.ndarray): 一个NumPy数组,其中第二列的值是恒定的。
                            最后一列包含原始索引。
    target_col4_value (float): 要匹配的第四列值。
    N (int): 需要查找的最近邻行数。

    返回:
    np.ndarray: 一个数组,包含N个最近邻行的原始索引。
                如果符合条件的行数不足N,则返回所有符合条件的行的索引。
    """
    # 过滤出第四列值与目标值相等的行
    # 注意:这里假设第四列是索引为4,但根据问题描述,它是索引3(0-based)
    # 在示例数据中,我们假设是索引3
    # 根据问题描述,是第四列(索引3)
    filt_mask = (FULL_block[:, 3] == target_col4_value)

    # 获取过滤后的行的索引(在FULL_block内部的相对索引)
    internal_filt_indices = np.where(filt_mask)[0]

    if len(internal_filt_indices) == 0:
        return np.array([], dtype=int) # 没有匹配的行

    # 提取过滤后的行的第一列值
    trks_col0 = FULL_block[internal_filt_indices, 0]

    # 使用广播机制计算所有过滤后的行之间第一列值的绝对差值
    # trks_col0[:, None] 将一维数组转换为列向量,与 trks_col0 进行元素级减法
    # 得到一个 (len(trks_col0), len(trks_col0)) 的差值矩阵
    diff_matrix = np.abs(trks_col0[:, None] - trks_col0)

    # 对差值矩阵按列排序,获取排序后的索引
    # argsort(axis=0) 对每一列进行排序,返回排序后的索引
    # [:N] 获取前N个最小差值的索引
    # 注意:这里需要处理自身与自身的距离为0的情况,通常我们希望排除自身
    # 一种方法是先将对角线元素设为无穷大,或者在获取N个索引后排除自身
    # 考虑到实际需求,通常N个最近邻会包含自身(距离为0),如果需要排除,需额外处理。
    # 这里的argsort会把0距离的自身排在第一位。

    # 为了排除自身,我们可以将对角线元素设置为一个大值
    np.fill_diagonal(diff_matrix, np.inf)

    # 重新排序获取前N个最小差值的索引
    # argsort(axis=0) 会对每一列进行排序,返回排序后的索引
    # [:N] 获取前N个最小差值的索引
    closest_internal_indices = diff_matrix.argsort(axis=0)[:N]

    # 将内部索引转换回 FULL_block 中的实际索引
    # 然后再通过 FULL_block 找到其原始索引

    # 最终结果需要针对每个原始行返回 N 个索引。
    # 这里的 diff_matrix 是针对 trks_col0 的,所以 closest_internal_indices 也是 trks_col0 的索引。
    # 我们需要的是 FULL_block 中对应行的原始索引。

    # 创建一个空的列表来存储每个查询行的N个最近邻原始索引
    result_indices = np.empty((len(internal_filt_indices), N), dtype=int)

    for i, current_row_internal_idx in enumerate(internal_filt_indices):
        # 对于当前行 (FULL_block[current_row_internal_idx]),
        # 找到其在 trks_col0 中的位置 (j)
        # diff_matrix 的第 j 列包含了当前行与其他行的距离

        # 找到当前行在 trks_col0 内部的索引
        j = np.where(internal_filt_indices == current_row_internal_idx)[0][0]

        # 获取第 j 列中 N 个最近邻的内部索引
        n_closest_in_trks_col0 = closest_internal_indices[:, j]

        # 将这些内部索引映射回 FULL_block 的内部索引
        n_closest_in_full_block = internal_filt_indices[n_closest_in_trks_col0]

        # 提取这些行对应的原始索引(最后一列)
        result_indices[i] = FULL_block[n_closest_in_full_block, -1].astype(int)

    return result_indices

# 假设 N = 2
N = 2

# 创建一个列表来存储每个分块计算出的结果
all_computed_indices = []

# 遍历每个分块并调用 query_trks
for block in splitted_array:
    # 在每个block中,第二列的值是相同的,我们只需获取其值
    # 并且对于block中的每一行,我们都需要找到N个最近邻

    # 假设我们为block中的每一行查找最近邻
    # 我们需要迭代block中的每一行作为“当前行”来查询

    # 获取 block 中所有行的原始索引,这些是需要填充结果的行
    original_indices_in_block = block[:, -1].astype(int)

    # 为当前 block 创建一个临时存储结果的数组
    block_results = np.empty((len(block), N), dtype=int)

    for i, row in enumerate(block):
        # 当前行的第二列值和第四列值
        current_col1_val = row[1]
        current_col4_val = row[3]
        current_col0_val = row[0]

        # 过滤出与当前行第四列值相同的行
        # 这里的 FULL_block 实际上就是当前的 block,因为我们已经在外部按 col1 分割了
        # 并且 query_trks 应该只返回与 target_col4_value 匹配的行的最近邻

        # 优化:query_trks 应该接收一个 block 和一个 target_col4_value
        # 并且它应该为 block 中所有满足 target_col4_value 的行计算最近邻

        # 重新设计 query_trks,使其能够处理一个 block 中的所有行
        # 原始的 query_trks 假设 `trk` 是一个值,而不是整个列
        # 让我们回到原始答案的逻辑,它通过 `a[:, 0]` 传入了整个第一列,
        # 这意味着 `query_trks` 内部会为 `trks` 中的每个元素找到最近邻。
        # 这里的 `trks` 是 `FULL[filt, 0]`,即过滤后的第一列。

        # 让我们按照答案的逻辑重新构建 query_trks
        # `query_trks(FULL, trk, v, N)`
        # `FULL` 是当前的 `block`
        # `trk` 在答案中被移除,因为 `trks = FULL[filt, 0]` 已经包含了所有需要比较的第一列值
        # `v` 是 `target_col4_value`


# python  # go  # 性能瓶颈 


相关文章: 潮流网站制作头像软件下载,适合母子的网名有哪些?  如何选择高效可靠的多用户建站源码资源?  最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?  宿州网站制作公司兴策,安徽省低保查询网站?  开心动漫网站制作软件下载,十分开心动画为何停播?  如何设计高效校园网站?  如何快速上传建站程序避免常见错误?  网站制作企业,网站的banner和导航栏是指什么?  如何确认建站备案号应放置的具体位置?  已有域名如何快速搭建专属网站?  实现点击下箭头变上箭头来回切换的两种方法【推荐】  学校建站服务器如何选型才能满足性能需求?  如何用wdcp快速搭建高效网站?  广州商城建站系统开发成本与周期如何控制?  油猴 教程,油猴搜脚本为什么会网页无法显示?  广东专业制作网站有哪些,广东省能源集团有限公司官网?  太原网站制作公司有哪些,网约车营运证查询官网?  如何撰写建站申请书?关键要点有哪些?  电商网站制作价格怎么算,网上拍卖流程以及规则?  东莞专业网站制作公司有哪些,东莞招聘网站哪个好?  我的世界制作壁纸网站下载,手机怎么换我的世界壁纸?  高防网站服务器:DDoS防御与BGP线路的AI智能防护方案  网站制作需要会哪些技术,建立一个网站要花费多少?  整人网站在线制作软件,整蛊网站退不出去必须要打我是白痴才能出去?  开源网站制作软件,开源网站什么意思?  网站制作公司,橙子建站是合法的吗?  如何自己制作一个网站链接,如何制作一个企业网站,建设网站的基本步骤有哪些?  关于BootStrap modal 在IOS9中不能弹出的解决方法(IOS 9 bootstrap modal ios 9 noticework)  如何通过商城免费建站系统源码自定义网站主题?  内网网站制作软件,内网的网站如何发布到外网?  C++如何使用std::optional?(处理可选值)  c# 在ASP.NET Core中管理和取消后台任务  北京网页设计制作网站有哪些,继续教育自动播放怎么设置?  香港服务器如何优化才能显著提升网站加载速度?  网站建设制作需要多少钱费用,自己做一个网站要多少钱,模板一般多少钱?  微课制作网站有哪些,微课网怎么进?  微信推文制作网站有哪些,怎么做微信推文,急?  如何在Golang中指定模块版本_使用go.mod控制版本号  MySQL查询结果复制到新表的方法(更新、插入)  如何在Windows 2008云服务器安全搭建网站?  如何在万网自助建站中设置域名及备案?  c++怎么使用类型萃取type_traits_c++ 模板元编程类型判断【方法】  香港代理服务器配置指南:高匿IP选择、跨境加速与SEO优化技巧  网站制作公司排行榜,四大门户网站排名?  网站海报制作教学视频教程,有什么免费的高清可商用图片网站,用于海报设计?  c++怎么实现高并发下的无锁队列_c++ std::atomic原子变量与CAS操作【详解】  如何快速查询域名建站关键信息?  家具网站制作软件,家具厂怎么跑业务?  香港服务器选型指南:免备案配置与高效建站方案解析  如何在IIS中新建站点并解决端口绑定冲突? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。