【问题标题】:NumPy: Execute function over each ndarray elementNumPy:对每个 ndarray 元素执行函数
【发布时间】:2012-06-15 23:21:42
【问题描述】:

我有一个二维坐标的三维ndarray,例如:

[[[1704 1240]
  [1745 1244]
  [1972 1290]
  [2129 1395]
  [1989 1332]]

 [[1712 1246]
  [1750 1246]
  [1964 1286]
  [2138 1399]
  [1989 1333]]

 [[1721 1249]
  [1756 1249]
  [1955 1283]
  [2145 1399]
  [1990 1333]]]

最终目标是从 5 个坐标的每个“组”中删除最接近给定点 ([1989 1332]) 的点。我的想法是产生一个类似形状的距离数组,然后使用 argmin 来确定要删除的值的索引。但是,我不确定如何应用一个函数,例如计算到给定点的距离的函数,至少以 NumPythonic 方式应用于 ndarray 中的每个元素。

【问题讨论】:

    标签: python arrays multidimensional-array numpy


    【解决方案1】:

    列表推导式是处理 numpy 数组的一种非常低效的方法。对于距离计算,它们是一个特别糟糕的选择。

    要找出数据和点之间的差异,您只需执行data - point。然后,您可以使用np.hypot 计算距离,或者如果您愿意,可以将其平方、求和,然后取平方根。

    不过,如果将其设为 Nx2 数组以进行计算,会更容易一些。

    基本上,你想要这样的东西:

    import numpy as np
    
    data = np.array([[[1704, 1240],
                      [1745, 1244],
                      [1972, 1290],
                      [2129, 1395],
                      [1989, 1332]],
    
                     [[1712, 1246],
                      [1750, 1246],
                      [1964, 1286],
                      [2138, 1399],
                      [1989, 1333]],
    
                     [[1721, 1249],
                      [1756, 1249],
                      [1955, 1283],
                      [2145, 1399],
                      [1990, 1333]]])
    
    point = [1989, 1332]
    
    #-- Calculate distance ------------
    # The reshape is to make it a single, Nx2 array to make calling `hypot` easier
    dist = data.reshape((-1,2)) - point
    dist = np.hypot(*dist.T)
    
    # We can then reshape it back to AxBx1 array, similar to the original shape
    dist = dist.reshape(data.shape[0], data.shape[1], 1)
    print dist
    

    这会产生:

    array([[[ 299.48121811],
            [ 259.38388539],
            [  45.31004304],
            [ 153.5219854 ],
            [   0.        ]],
    
           [[ 290.04310025],
            [ 254.0019685 ],
            [  52.35456045],
            [ 163.37074401],
            [   1.        ]],
    
           [[ 280.55837182],
            [ 247.34186868],
            [  59.6405902 ],
            [ 169.77926846],
            [   1.41421356]]])
    

    现在,删除最近的元素比简单地获取最近的元素要困难一些。

    使用 numpy,您可以使用布尔索引相当轻松地做到这一点。

    但是,您需要稍微担心一下轴的对齐方式。

    关键是要了解 numpy 沿 last 轴“广播”操作。在这种情况下,我们要沿中轴进行广播。

    另外,-1 可以用作轴大小的占位符。当-1作为轴的大小放入时,numpy会计算允许的大小。

    我们需要做的看起来有点像这样:

    #-- Remove closest point ---------------------
    mask = np.squeeze(dist) != dist.min(axis=1)
    filtered = data[mask]
    
    # Once again, let's reshape things back to the original shape...
    filtered = filtered.reshape(data.shape[0], -1, data.shape[2])
    

    你可以把它写成一行,我只是为了便于阅读而将它分解。关键是dist != something 会生成一个布尔数组,然后您可以使用它来索引原始数组。

    所以,把它们放在一起:

    import numpy as np
    
    data = np.array([[[1704, 1240],
                      [1745, 1244],
                      [1972, 1290],
                      [2129, 1395],
                      [1989, 1332]],
    
                     [[1712, 1246],
                      [1750, 1246],
                      [1964, 1286],
                      [2138, 1399],
                      [1989, 1333]],
    
                     [[1721, 1249],
                      [1756, 1249],
                      [1955, 1283],
                      [2145, 1399],
                      [1990, 1333]]])
    
    point = [1989, 1332]
    
    #-- Calculate distance ------------
    # The reshape is to make it a single, Nx2 array to make calling `hypot` easier
    dist = data.reshape((-1,2)) - point
    dist = np.hypot(*dist.T)
    
    # We can then reshape it back to AxBx1 array, similar to the original shape
    dist = dist.reshape(data.shape[0], data.shape[1], 1)
    
    #-- Remove closest point ---------------------
    mask = np.squeeze(dist) != dist.min(axis=1)
    filtered = data[mask]
    
    # Once again, let's reshape things back to the original shape...
    filtered = filtered.reshape(data.shape[0], -1, data.shape[2])
    
    print filtered
    

    产量:

    array([[[1704, 1240],
            [1745, 1244],
            [1972, 1290],
            [2129, 1395]],
    
           [[1712, 1246],
            [1750, 1246],
            [1964, 1286],
            [2138, 1399]],
    
           [[1721, 1249],
            [1756, 1249],
            [1955, 1283],
            [2145, 1399]]])
    

    附带说明,如果多个点同样接近,这将不起作用。 Numpy 数组在每个维度上必须具有相同数量的元素,因此在这种情况下您需要重新进行分组。

    【讨论】:

    • 啊,不知怎的,我在发帖之前没有看到这个。我想过使用apply_along_axis,但我测试了它,这要快得多。
    • apply_along_axis 应该使用更少的内存,所以这两种方法仍然有用!
    【解决方案2】:

    如果我正确理解您的问题,我认为您正在寻找apply_along_axis。使用numpy的内置广播,我们可以简单地从数组中减去点:

    >>> a - numpy.array([1989, 1332])
    array([[[-285,  -92],
            [-244,  -88],
            [ -17,  -42],
            [ 140,   63],
            [   0,    0]],
    
           [[-277,  -86],
            [-239,  -86],
            [ -25,  -46],
            [ 149,   67],
            [   0,    1]],
    
           [[-268,  -83],
            [-233,  -83],
            [ -34,  -49],
            [ 156,   67],
            [   1,    1]]])
    

    那么我们可以给它申请numpy.linalg.norm

    >>> dist = a - numpy.array([1989, 1332])
    >>> numpy.apply_along_axis(numpy.linalg.norm, 2, dist)
    array([[ 299.48121811,  259.38388539,   45.31004304,  
             153.5219854 ,    0.        ],
           [ 290.04310025,  254.0019685 ,   52.35456045,  
             163.37074401,    1.        ],
           [ 280.55837182,  247.34186868,   59.6405902 ,  
             169.77926846,    1.41421356]])
    

    最后,一些布尔掩码技巧,以及几个reshape 调用:

    >>> a[normed != normed.min(axis=1).reshape((-1, 1))].reshape((3, 4, 2))
    array([[[1704, 1240],
            [1745, 1244],
            [1972, 1290],
            [2129, 1395]],
    
           [[1712, 1246],
            [1750, 1246],
            [1964, 1286],
            [2138, 1399]],
    
           [[1721, 1249],
            [1756, 1249],
            [1955, 1283],
            [2145, 1399]]])
    

    不过,Joe Kington 的回答更快。那好吧。我会把这个留给后代。

    def joes(data, point):
        dist = data.reshape((-1,2)) - point
        dist = np.hypot(*dist.T)
        dist = dist.reshape(data.shape[0], data.shape[1], 1)
        mask = np.squeeze(dist) != dist.min(axis=1)
        return data[mask].reshape((3, 4, 2))
    
    def mine(a, point):
        dist = a - point
        normed = numpy.apply_along_axis(numpy.linalg.norm, 2, dist)
        return a[normed != normed.min(axis=1).reshape((-1, 1))].reshape((3, 4, 2))
    
    >>> %timeit mine(data, point)
    1000 loops, best of 3: 586 us per loop
    >>> %timeit joes(data, point)
    10000 loops, best of 3: 48.9 us per loop
    

    【讨论】:

      【解决方案3】:

      有多种方法可以做到这一点,但这里有一种使用列表推导的方法:

      距离函数:

      In [35]: from numpy.linalg import norm
      
      In [36]: dist = lambda x,y:norm(x-y)
      

      输入数据:

      In [39]: GivenMatrix = scipy.rand(3, 5, 2)
      
      In [40]: GivenMatrix
      Out[40]: 
      array([[[ 0.83798666,  0.90294439],
              [ 0.8706959 ,  0.88397176],
              [ 0.91879085,  0.93512921],
              [ 0.15989245,  0.57311869],
              [ 0.82896003,  0.53589968]],
      
             [[ 0.0207089 ,  0.9521768 ],
              [ 0.94523963,  0.31079109],
              [ 0.41929482,  0.88559614],
              [ 0.87885236,  0.45227422],
              [ 0.58365369,  0.62095507]],
      
             [[ 0.14757177,  0.86101539],
              [ 0.58081214,  0.12632764],
              [ 0.89958321,  0.73660852],
              [ 0.3408943 ,  0.45420989],
              [ 0.42656333,  0.42770216]]])
      
      In [41]: q = scipy.rand(2)
      
      In [42]: q
      Out[42]: array([ 0.03280889,  0.71057403])
      

      计算输出距离:

      In [44]: distances = [[dist(x, q) for x in SubMatrix] 
                            for SubMatrix in GivenMatrix]
      
      In [45]: distances
      Out[45]: 
      [[0.82783910695733931,
        0.85564093542511577,
        0.91399620574915652,
        0.18720096539588818,
        0.81508758596405939],
       [0.24190557184498068,
        0.99617079746515047,
        0.42426891258164884,
        0.88459501973012633,
        0.55808740166908177],
       [0.18921712490174292,
        0.80103146210692744,
        0.86716521557255788,
        0.40079819635686459,
        0.48482888965287363]]
      

      对每个子矩阵的结果进行排名:

      In [46]: scipy.argsort(distances)
      Out[46]: 
      array([[3, 4, 0, 1, 2],
             [0, 2, 4, 3, 1],
             [0, 3, 4, 1, 2]])
      

      至于删除,我个人认为最简单的方法是将GivenMatrix转换为list,然后使用del

      >>> GivenList = GivenMatrix.tolist()
      
      >>> del GivenList[1][2] # delete third row from the second 5-by-2 submatrix
      

      【讨论】:

        猜你喜欢
        • 2014-02-15
        • 1970-01-01
        • 1970-01-01
        • 2021-03-19
        • 2016-11-30
        • 2021-01-12
        • 2019-09-14
        • 2013-04-04
        • 2018-09-24
        相关资源
        最近更新 更多