Python 如何对数组中的某个范围值求和,其中每行的范围不同

Python 如何对数组中的某个范围值求和,其中每行的范围不同,python,numpy,numpy-ndarray,Python,Numpy,Numpy Ndarray,我想计算数组中某个范围的和(简单)——但我不想这样做,而是n次,应该求和的范围来自第二个数组 我有一个二维数组,其中包含0和1: count = np.array(\ [[0,1,0,0,1,0,1], [0,0,1,1,1,0,0]]) 我构造了一个2D数组,该数组有一个字段,其中包含要在计数数组上求和的范围 dtype=[..., ('ranges', 'u1', (2, 2)) , ...] 表['ranges']如下所示: [ [[1, 3], [0, 4]], [[0, 0

我想计算数组中某个范围的和(简单)——但我不想这样做,而是n次,应该求和的范围来自第二个数组

我有一个二维数组,其中包含0和1:

count = np.array(\
[[0,1,0,0,1,0,1],
 [0,0,1,1,1,0,0]])
我构造了一个2D数组,该数组有一个字段,其中包含要在计数数组上求和的范围

dtype=[..., ('ranges', 'u1', (2, 2)) , ...]
表['ranges']
如下所示:

 [
 [[1, 3], [0, 4]],
 [[0, 0], [3, 4]],
 [[0, 0], [2 4]],
 [[0, 0], [3 4]],
 [[3, 7], [1 5]]]
(通常为20到几百行)

这个例子的结果应该是

[2, # = (1 +0) + (0 + 0 +1)
 1, # = ( ) + (1)
 2,  # = ( ) + (1 + 1)
 1, # = ( ) + (1)
 5] # = (0 + 1 +0 +1 ) + (0 + 1 + 1 + 1)
首先,我从:

        result = np.zeros(table.size, dtype=np.int)

        for index, r in enumerate(table):
            for index, range in enumerate(r['ranges']):
                result[index] += np.sum(counts[index][range[0]:range[1]])

给出了正确的结果,但不是效率的例子

我还尝试了消除第二个循环,并将其进一步简化:

        result = np.zeros(table.size, dtype=np.int)

        for index, (from1, to1, from2, to2) in \
                enumerate(np.nditer(table['ranges'], flags=['external_loop'])):
            counts[index] += np.sum(counts[0][from1:to1]) +\
                np.sum(counts[1][from2:to2])
但这些代码行仍然是应用程序花费大部分时间的地方。这个应用程序比这个要大一点,但是根据profiler的数据,花在这些行上的时间大约减少了一半

所以基本上我正在寻找一种方法来摆脱循环,并在numpy中完成这一切。 我在寻找类似于

counts=np.sum(counts[1][table['ranges'][0][0]:table['ranges'][0][1])+np.sum(counts[2][table['ranges'][1][0]:table['ranges'][1][1])
但到目前为止,还没有找到一个好的方法来做到这一点

更新进行了一些时间比较:

import numpy as np
import timeit as ti

table = np.empty(5,
                 dtype=[('s1', np.int8),
                        ('ranges', 'u1', (2, 2)),
                        ('s2', np.int16)])

table["ranges"] = [((1, 3), (0, 4)),
                   ((0, 0), (3, 4)),
                   ((0, 0), (2, 4)),
                   ((0, 0), (3, 4)),
                   ((3, 7), (1, 5))]

results = np.zeros(table.size)

counts = np.array([[0, 1, 0, 0, 1, 0, 1],
                   [0, 0, 1, 1, 1, 0, 0]])


# version one
def rv1(table, counts, results):
    for row_index, r in enumerate(table):
        for index, crange in enumerate(r['ranges']):
            results[row_index] += np.sum(counts[index][crange[0]:crange[1]])


# version two
def rv2(table, counts, results):
    for rowindex, (f1, t1, f2, t2) in \
            enumerate(np.nditer(table['ranges'], flags=['external_loop'])):
        results[rowindex] += np.sum(counts[0][f1:t1]) +\
            np.sum(counts[1][f2:t2])


# version 3 (TomNash)
def rvTN(table, counts, results):
    ranges=table["ranges"]
    result=[
        sum(counts[0][slice(*ranges[i][0])]) + sum(counts[1][slice(*ranges[i][1])])
            for i in range(len(ranges))]
    results+=result


results = np.zeros(table.size)
rv1(table, counts, results)
print ("rv1 result" , results)


results = np.zeros(table.size)
rv2(table, counts, results)
print ("rv2 result", results)

results = np.zeros(table.size)
rvTN(table, counts, results)
print ("slice*(TN) result", results)



print ("double loop time " , ti.timeit(lambda : rv1(table, counts, results)))
print ("nditer time " ,  ti.timeit(lambda : rv2(table, counts, results)))
print ("slice* time " ,  ti.timeit(lambda : rv3(table, counts, results)))

我明白了


所以Tomnash版本快了30%左右。不幸的是,这仍然有点慢。

您可以使用
slice
*args
来分解开始、停止索引和切片列表

[sum(count[0][slice(*ranges[i][0])]+sum(count[1][slice(*ranges[i][1])用于范围(len(ranges))]
我认为你的预期结果与指数略有出入,这就是我得到的

结果

[3,1,2,1,5]

在遇到同样的问题后,我找到了两种方法来编写完全矢量化的版本

一种解决方案涉及使用
numpy.add
上的方法,但由于
reduceat
行为的奇怪规范,这种方法最终相当混乱和低效。关于添加一个新的ufunc方法,可以使这个任务成为一个高效的单行程序,曾经有过一些讨论,但是还没有实现任何东西

我提出的第二个解决方案似乎更有效,也更容易理解,其工作原理如下:

  • 首先,使用获取沿
    计数的长轴的累积和,
    累积计数

  • 接下来,获取要求和的范围的所有起始索引
    start
    和结束索引
    ends
    ,这可以通过在
    表['ranges']
    中切片数组来获得

  • 现在,您可以通过使用
    开始
    结束
    索引到
    累积计数
    来获取每个范围开始和结束处的累积和数组

  • 最后,要获得每个范围的和,只需从结束和中减去所有开始和

有一个小问题,那就是要使索引工作正常,
累积计数
需要在其第一个索引处有一个零,然后才是
cumsum
结果

首先将这个概念放在1D中,因为它稍微清晰一些:

范围内的i(len(计数)):
累计计数=np.empty(len(计数[i])+1,dtype=counts.dtype)
累计_计数[0]=0
累积计数[1:]=np.cumsum(计数[i])
开始,结束=表['ranges'][:,i,:].T
结果+=累积计数[结束]-累积计数[开始]
这已经更快了-循环仅在短轴上(2行
计数),其余为矢量化

但也可以消除外循环,并将相同的方法应用于2D输入,以实现示例中的完全矢量化解决方案:

cumulative_counts=np.empty((counts.shape[0],counts.shape[1]+1),dtype=counts.dtype)
累计_计数[:,0]=0
累计_计数[:,1:]=np.cumsum(计数,轴=1)
开始,结束=表['ranges'].T.swapax(1,2)
行=np.arange(len(counts))。重塑(1,-1)
行结果=累计行数[行,结束]-累计行数[行,开始]
结果+=行结果.sum(轴=1)
将此版本添加到计时比较中,它比我的系统上TomNash的切片方法运行速度快30%。我预计,对于更大的阵列,加速比会得到放大

rv1 result [3. 1. 2. 1. 5.]
rv2 result [3. 1. 2. 1. 5.]
slice*(TN) result [3. 1. 2. 1. 5.]
cumsum(ML) result [3. 1. 2. 1. 5.]
double loop time  118.32055400998797
nditer time  101.83165721700061
slice* time  50.249228183995
cumsum time  38.694901300012134

你的版本似乎比我的快33%。
rv1 result [3. 1. 2. 1. 5.]
rv2 result [3. 1. 2. 1. 5.]
slice*(TN) result [3. 1. 2. 1. 5.]
cumsum(ML) result [3. 1. 2. 1. 5.]
double loop time  118.32055400998797
nditer time  101.83165721700061
slice* time  50.249228183995
cumsum time  38.694901300012134