【问题标题】:Aggregate Numpy Array By Summing通过求和聚合 Numpy 数组
【发布时间】:2014-09-30 04:35:01
【问题描述】:

我有一个形状为 (4320,8640) 的 Numpy 数组。我想要一个形状数组(2160,4320)

您会注意到新数组的每个单元格都映射到旧数组中的一组 2x2 单元格。我希望新数组中单元格的值是旧数组中此块中值的总和。

我可以这样实现:

import numpy

#Generate an example array
arr = numpy.random.randint(10,size=(4320,8640))

#Perform the transformation
arrtrans = numpy.array([ [ arr[y][x]+arr[y+1][x]+arr[y][x+1]+arr[y+1][x+1] for x in range(0,8640,2)] for y in range(0,4320,2)])

但这很慢,而且有点难看。

有没有办法使用 Numpy(或可互操作的包)来做到这一点?

【问题讨论】:

    标签: python arrays numpy


    【解决方案1】:

    当窗口完全适合数组时,使用 np.sum 重塑更多维度并折叠额外维度是使用 numpy 执行此操作的规范方式:

    >>> a = np.random.rand(4320,8640)
    >>> a.shape
    (4320, 8640)
    >>> a_small = a.reshape(2160, 2, 4320, 2).sum(axis=(1, 3))
    >>> a_small.shape
    (2160, 4320)
    >>> np.allclose(a_small[100, 203], a[200:202, 406:408].sum())
    True
    

    【讨论】:

    • 非常酷,这与使用 as_strided 操作步幅具有相同的性能。您可以使用np.einsum('ijkl -> ik', a.reshape(2160, 2, 4320, 2)) 获得更快的速度
    • np.einsum 的优势通常来自求和和乘积运算的矢量化 (SIMD)。我认为最新版本的 numpy 在np.sum 中也具有相同的功能。
    【解决方案2】:

    我不确定是否存在您想要的包,但这段代码的计算速度会更快。

    >>> arrtrans2 = arr[::2, ::2] + arr[::2, 1::2] + arr[1::2, ::2] + arr[1::2, 1::2]
    >>> numpy.allclose(arrtrans, arrtrans2)
    True
    

    其中::21::2 分别由0, 2, 4, ...1, 3, 5, ... 翻译。

    【讨论】:

      【解决方案3】:

      您正在对原始数组的滑动窗口进行操作。关于 SO 有很多问题和答案。滑动窗口和 numpy 和 python。通过操纵数组的步长,这个过程可以大大加快。这是一个通用函数,它将返回数组的 (x,y) 窗口,有或没有重叠。使用这个 stride 技巧 似乎比@mskimm 的解决方案慢了一点点。在您的工具包中拥有它是一件好事。这个函数不是我的 - 它是在Efficient Overlapping Windows with Numpy找到的

      import numpy as np
      from numpy.lib.stride_tricks import as_strided as ast
      from itertools import product
      
      def norm_shape(shape):
          '''
          Normalize numpy array shapes so they're always expressed as a tuple, 
          even for one-dimensional shapes.
      
          Parameters
              shape - an int, or a tuple of ints
      
          Returns
              a shape tuple
      
          from http://www.johnvinyard.com/blog/?p=268
          '''
          try:
              i = int(shape)
              return (i,)
          except TypeError:
              # shape was not a number
              pass
      
          try:
              t = tuple(shape)
              return t
          except TypeError:
              # shape was not iterable
              pass
      
          raise TypeError('shape must be an int, or a tuple of ints')
      
      
      def sliding_window(a,ws,ss = None,flatten = True):
          '''
          Return a sliding window over a in any number of dimensions
      
          Parameters:
              a  - an n-dimensional numpy array
              ws - an int (a is 1D) or tuple (a is 2D or greater) representing the size 
                   of each dimension of the window
              ss - an int (a is 1D) or tuple (a is 2D or greater) representing the 
                   amount to slide the window in each dimension. If not specified, it
                   defaults to ws.
              flatten - if True, all slices are flattened, otherwise, there is an 
                        extra dimension for each dimension of the input.
      
          Returns
              an array containing each n-dimensional window from a
      
          from http://www.johnvinyard.com/blog/?p=268
          '''
      
          if None is ss:
              # ss was not provided. the windows will not overlap in any direction.
              ss = ws
          ws = norm_shape(ws)
          ss = norm_shape(ss)
      
          # convert ws, ss, and a.shape to numpy arrays so that we can do math in every 
          # dimension at once.
          ws = np.array(ws)
          ss = np.array(ss)
          shape = np.array(a.shape)
      
      
          # ensure that ws, ss, and a.shape all have the same number of dimensions
          ls = [len(shape),len(ws),len(ss)]
          if 1 != len(set(ls)):
              error_string = 'a.shape, ws and ss must all have the same length. They were{}'
              raise ValueError(error_string.format(str(ls)))
      
          # ensure that ws is smaller than a in every dimension
          if np.any(ws > shape):
              error_string = 'ws cannot be larger than a in any dimension. a.shape was {} and ws was {}'
              raise ValueError(error_string.format(str(a.shape),str(ws)))
      
          # how many slices will there be in each dimension?
          newshape = norm_shape(((shape - ws) // ss) + 1)
          # the shape of the strided array will be the number of slices in each dimension
          # plus the shape of the window (tuple addition)
          newshape += norm_shape(ws)
          # the strides tuple will be the array's strides multiplied by step size, plus
          # the array's strides (tuple addition)
          newstrides = norm_shape(np.array(a.strides) * ss) + a.strides
          strided = ast(a,shape = newshape,strides = newstrides)
          if not flatten:
              return strided
      
          # Collapse strided so that it has one more dimension than the window.  I.e.,
          # the new array is a flat list of slices.
          meat = len(ws) if ws.shape else 0
          firstdim = (np.product(newshape[:-meat]),) if ws.shape else ()
          dim = firstdim + (newshape[-meat:])
          # remove any dimensions with size 1
          dim = filter(lambda i : i != 1,dim)
          return strided.reshape(dim)
      

      用法:

      # 2x2 windows with NO overlap
      b = sliding_window(arr, (2,2), flatten = False)
      c = b.sum((1,2))
      

      使用numpy.einsum 可提高大约 24% 的性能

      c = np.einsum('ijkl -> ij', b)
      

      一个 SO Q&A 示例How can I efficiently process a numpy array in blocks similar to Matlab's blkproc (blockproc) function,所选答案对您有用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-09-19
        • 2023-03-06
        • 2021-12-27
        • 2021-12-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-13
        相关资源
        最近更新 更多