【问题标题】:Find Start / Stop Index Range For Values in NumPy Array Greater Than N查找 NumPy 数组中大于 N 的值的开始/停止索引范围
【发布时间】:2020-06-20 03:17:36
【问题描述】:

假设我有一个 NumPy 数组:

x = np.array([2, 3, 4, 0, 0, 1, 1, 4, 6, 5, 8, 9, 9, 4, 2, 0, 3])

对于x >= 2 中的所有值,我需要找到x >=2 的连续值的开始/停止索引(即不计算一个大于或等于2 的单个值的运行)。然后,我对x >= 3x >=4、...、x >= x.max() 重复此操作。 输出应该是一个 NumPy 数组 三列(第一列是最小值,第二列是包含开始索引,第三列是停止索引),看起来像:

[[2,  0,  2],
 [2,  7, 14],
 [3,  1,  2],
 [3,  7, 13],
 [4,  7, 13],
 [5,  8, 12],
 [6, 10, 12],
 [8, 10, 12],
 [9, 11, 12]
]

天真地,我可以查看每个唯一值,然后搜索开始/停止索引。但是,这需要对x 进行多次传递。完成此任务的最佳 NumPy 矢量化方式是什么?是否有不需要多次传递数据的解决方案?

更新

我意识到我还需要计算单个实例。所以,我的输出应该是:

[[2,  0,  2],
 [2,  7, 14],
 [2, 16, 16],  # New line needed
 [3,  1,  2],
 [3,  7, 13],
 [3, 16, 16],  # New line needed
 [4,  2,  2],  # New line needed
 [4,  7, 13],
 [5,  8, 12],
 [6,  8,  8],  # New line needed
 [6, 10, 12],
 [8, 10, 12],
 [9, 11, 12]
]

【问题讨论】:

  • np.where(x>2) 可能是一个开始。
  • ...和tests = np.arange(2,x.max()+1); q = np.greater(x,tests[:,None]); np.argwhere(q)
  • 不应该 [7, 10, 12] 也在结果数组中,它来自 x >=7?
  • @AndreasK。我看到您已经为这两种情况提供了解决方案。确实,我可能两者都需要。谢谢!

标签: python numpy


【解决方案1】:

这是另一个解决方案(我相信可以改进):

import numpy as np
from numpy.lib.stride_tricks import as_strided

x = np.array([2, 3, 4, 0, 0, 1, 1, 4, 6, 5, 8, 9, 9, 4, 2, 0, 3])

# array of unique values of x bigger than 1
a = np.unique(x[x>=2])

step = len(a)  # if you encounter memory problems, try a smaller step
result = []
for i in range(0, len(a), step):
    ai = a[i:i + step]
    c = np.argwhere(x >= ai[:, None])
    c[:,0] = ai[c[:,0]]
    c =  np.pad(c, ((1,1), (0,0)), 'symmetric')

    d = np.where(np.diff(c[:,1]) !=1)[0]

    e = as_strided(d, shape=(len(d)-1, 2), strides=d.strides*2).copy()
    # e = e[(np.diff(e, axis=1) > 1).flatten()]
    e[:,0] = e[:,0] + 1 

    result.append(np.hstack([c[:,0][e[:,0, None]], c[:,1][e]]))

result = np.concatenate(result)

# array([[ 2,  0,  2],
#        [ 2,  7, 14],
#        [ 2, 16, 16],
#        [ 3,  1,  2],
#        [ 3,  7, 13],
#        [ 3, 16, 16],
#        [ 4,  2,  2],
#        [ 4,  7, 13],
#        [ 5,  8, 12],
#        [ 6,  8,  8],
#        [ 6, 10, 12],
#        [ 8, 10, 12],
#        [ 9, 11, 12]])

很抱歉没有评论每个步骤的作用——如果以后有时间我会修复它。

【讨论】:

  • 另外,在e = as_strided(d, shape=(len(d)-1, 2), strides=(8, 8))strides(8,8) 的相关性是什么。这取决于什么?实际上,我的输入数组更大并且具有更多值。我猜目前的解决方案可能不是接受不同数组的最通用形式?
  • 您可以将(8, 8) 替换为d.strides*2,但由于dnp.where 的结果,它返回dtype int64 的数组,所以d.strides 无论如何都是8(字节)。
  • @slaw 我已经编辑了我的答案,以便它也计算单个实例(只需评论一行)。
  • 有没有办法避免/替换b = (x >= a[:,None])?对于大型数组,这种密集矩阵会消耗大量内存。
  • @slaw 也许您可以将ax 大于 1 的唯一值数组)拆分为子数组并进行迭代。例如f = []; for i in range(0, len(a), 100): ai = a[i:i + 100]; b = (x >= ai[:, None]);,其余代码相同,除了最后一行f.append(np.hstack([c[:,0][e[:,0, None]], c[:,1][e]]))。最后你可以做f = np.concatenate(f)
【解决方案2】:

确实,这是一个非常有趣的问题。我试图把它分成三个部分来解决。

分组:

import numpy as np
import pandas as pd
x = np.array([2, 3, 4, 0, 0, 1, 1, 4, 6, 5, 8, 9, 9, 4, 2, 0, 3])
groups = pd.DataFrame(x).groupby([0]).indices

所以组是字典{0: [3, 4, 15], 1: [5, 6], 2: [0, 14], 3: [1, 16], 4: [2, 7, 13], 5: [9], 6: [8], 8: [10], 9: [11, 12]},它的值是numpy 数组dtype=int64

屏蔽:

在这一部分中,我按降序遍历多个掩码数组 x>=i 的每个唯一值 i

mask_array = np.zeros(x.size).astype(int)
for group in list(groups)[::-1]:
    mask = mask_array[groups[group]] = 1
    # print(group, ':', mask_array)
    # output = find_slices(mask)

这些面具看起来像这样:

9 : [0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0]
8 : [0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0]
6 : [0 0 0 0 0 0 0 0 1 0 1 1 1 0 0 0 0]
5 : [0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0]
4 : [0 0 1 0 0 0 0 1 1 1 1 1 1 1 0 0 0]
3 : [0 1 1 0 0 0 0 1 1 1 1 1 1 1 0 0 1]
2 : [1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 0 1]
1 : [1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 0 1]
0 : [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]

从蒙版中提取切片

我希望构造一些名为find_slices 的函数,该函数从掩码数组中提取切片位置(如果您取消注释)。这就是我所做的:

def find_slices(m):
    m1 = np.r_[0, m]
    m2 = np.r_[m, 0]
    starts, = np.where(~m1 & m2)
    ends, = np.where(m1 & ~m2)
    return np.c_[starts, ends - 1]

例如,数组[0 1 1 0 0 0 0 1 1 1 1 1 1 1 0 0 1] 的切片位置将是[[1, 2], [7, 13], [16, 16]]。请注意,这不是返回切片的标准方式,结束位置通常递增 1。

最终脚本

毕竟,要做出预期的输出,需要一些技巧,这里就像是最后的样子:

import numpy as np
import pandas as pd
x = np.array([2, 3, 4, 0, 0, 1, 1, 4, 6, 5, 8, 9, 9, 4, 2, 0, 3])
groups = pd.DataFrame(x).groupby([0]).indices
mask_array = np.zeros(x.size).astype(bool)

m = []
for group in list(groups)[::-1]:
    mask_array[groups[group]] = True
    s = find_slices(mask_array)
    group_output = np.c_[np.repeat(group, s.shape[0]), s] #insert first column
    m.append(group_output) 
output = np.concatenate(m[::-1])
output = output[output[:,1]!= output[:,2]] #elimate slices with unit length

输出:

 [[ 0  0 16]
 [ 1  0  2]
 [ 1  5 14]
 [ 2  0  2]
 [ 2  7 14]
 [ 3  1  2]
 [ 3  7 13]
 [ 4  7 13]
 [ 5  8 12]
 [ 6 10 12]
 [ 8 10 12]
 [ 9 11 12]]

【讨论】:

    猜你喜欢
    • 2018-07-27
    • 2016-10-30
    • 1970-01-01
    • 1970-01-01
    • 2012-12-01
    • 2021-10-12
    • 2019-06-21
    • 1970-01-01
    • 2022-01-23
    相关资源
    最近更新 更多