【问题标题】:Mapping locations satisfying condition in array在数组中映射满足条件的位置
【发布时间】:2018-10-19 19:50:12
【问题描述】:

给定一个逻辑数组(True/False 值)和一个可能不是从 0 开始的索引范围,我想创建一个与该范围相同大小的新数组,其中每个元素包含最近的前面 True 的索引.

具有逻辑数组的示例,例如:

[1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0]

以及索引子集:
[0, 1, 2, 3, 4, 5, 6, 7](此处,从 0 开始,但可能不必)

结果是:

[0, 0, 0, 3, 4, 5, 5, 7]

我有一个可行的解决方案(如下),但我正在寻找更快和/或更优雅和/或更可读的替代方案,因为数组的大小可能从数千到数百万不等。

import numpy as np

def map_nearest_preceding_true_indices(tmask, irange):

    true_indices = np.where(tmask)[0]
    mapped_indices = np.empty(len(irange), dtype=np.int)

    for i, index in enumerate(irange):
        index_loc = np.where(true_indices <= index)[0][-1]
        mapped_indices[i] = true_indices[index_loc]

    return mapped_indices

【问题讨论】:

  • 如果整个数组都是0怎么办?
  • @SruthiV 强制执行的总体方案不会在涉及该功能时发生。
  • 对更新的解决方案有任何反馈吗?
  • @Divakar 刚刚测试。效果很好!谢谢你。我用更大的数组对这两种解决方案进行了计时,tmask 大小为 5000,True/False 随机调度,idx_range 大小为 1000。您的解决方案需要 ~0.3 ms,而来自“agubelu”的解决方案需要 ~ 1.2 ms。我很惊讶后者使用生成器的速度较慢。
  • @Wall-E 我感觉到那里对 NumPy 的认识不足 :) NumPy 是为了性能,因此,我并不感到惊讶。

标签: python numpy indexing mapping conditional-statements


【解决方案1】:

这是np.searchsorted 的矢量化解决方案 -

def map_locations(tmask, irange, invalid_index=-1):
    idx = np.where(tmask)[0]
    sidx = np.searchsorted(idx, irange, 'right')-1
    return np.where(sidx==-1,invalid_index, idx[sidx])

示例运行 -

In [124]: # Considering a more generic case
     ...: tmask = np.array([1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0], dtype=bool)
     ...: irange = np.array([4, 8, 11, 18])

In [125]: map_locations(tmask, irange, invalid_index=-1)
Out[125]: array([4, 7, 9, 9])

In [129]: # Original case with first mask element being false
     ...: tmask = np.array([0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0], dtype=bool).tolist()
     ...: irange = np.array([0, 1, 2, 3, 4, 5, 6, 17]).tolist()

In [130]: map_locations(tmask, irange, invalid_index=-1)
Out[130]: array([-1, -1, -1,  3,  4,  5,  5,  9])

【讨论】:

  • 正如我对@agublu 所说,当第一个元素为 False 时可能会出现问题。数据应该以 True 开头。您的方法似乎通过提供array([9, 9, 9, 3, 4, 5, 5, 7]) 来循环数据,其中@agublu 的方法失败了,我宁愿这样做,所以我知道我的数据是错误的(这个错误现在可能)。
  • @Wall-E 添加了参数invalid_index 来处理此类情况。
【解决方案2】:

有一个单行列表理解:

data = [1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0]
indices = [0, 1, 2, 3, 4, 5, 6, 7]

nearest_true = [next(v for v in range(ind, -1, -1) if data[v]) for ind in indices]

对于索引数组中的每个索引,next 从该索引开始向后遍历数据数组,并返回包含真值的数据数组的第一个索引。

但是,正如 cmets 中所讨论的,如果任何索引在其处或后面没有至少一个真值,则此代码将失败。我们可以通过为next() 提供一个默认值来解决这个问题,将其作为第二个参数传递,在这种情况下,我们必须将生成器括起来,因为它不再是唯一的参数:

NOT_FOUND = -1
nearest_true = [next((v for v in range(ind, -1, -1) if data[v]), NOT_FOUND) 
                for ind in indices]

那么,data = [0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0] 的输出将是 [-1, -1, -1, 3, 4, 5, 5, 7]

【讨论】:

  • @SruthiV 内部的for从索引开始往回走,直到找到数据数组中的第一个True值,所以我认为它不会有很大的性能损失。
  • 我添加了以前从未使用过的生成器。它在哪些方面比例如:nearest_true = [[v for v in range(ind, -1, -1) if data[v]][0] for ind in indices] 更好?是不是因为并不是所有的值都会被计算出来,从而提高效率?
  • @Wall-E 使用这种方法,整个内部列表将在使用[0] 选择第一个元素之前生成,这不是很有效,因为我们只需要第一个元素。但是,生成器本身在调用 next() 之前不会计算任何元素,从而生成第一个匹配条件的元素。
  • 从给定的示例中,稍微更改一下,如果第一个数据元素为 False,则此方法失败:data = [0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0]。 @Divakar 方法不会循环数据,输出为array([9, 9, 9, 3, 4, 5, 5, 7])。无论如何,数据都应该以 True 开头,所以这并不是真正的问题,但我宁愿让它崩溃,也不愿给出科学上不正确的答案。
  • 好吧,实际上,如果任何所需的索引在其上或后面没有至少一个 True 值,它就会失败。我将编辑我的答案以考虑到这一点。
猜你喜欢
  • 2017-09-18
  • 2021-12-06
  • 2019-07-21
  • 2021-07-23
  • 1970-01-01
  • 2019-05-13
  • 1970-01-01
  • 1970-01-01
  • 2023-01-27
相关资源
最近更新 更多