【问题标题】:Create a matrix from a vector where each row is a shifted version of the vector从向量创建矩阵,其中每一行都是向量的移位版本
【发布时间】:2017-05-02 09:59:50
【问题描述】:

我有一个像这样的 numpy 数组

import numpy as np

ar = np.array([1, 2, 3, 4])

我想创建一个如下所示的数组:

array([[4, 1, 2, 3],
       [3, 4, 1, 2],
       [2, 3, 4, 1],
       [1, 2, 3, 4]])

因此,每一行对应于ar,它被行索引移动了+1。

一个简单的实现可能如下所示:

ar_roll = np.tile(ar, ar.shape[0]).reshape(ar.shape[0], ar.shape[0])

for indi, ri in enumerate(ar_roll):
    ar_roll[indi, :] = np.roll(ri, indi + 1)

这给了我想要的输出。

我的问题是是否有更聪明的方法来避免循环。

【问题讨论】:

    标签: python performance numpy vectorization


    【解决方案1】:

    这是一种使用NumPy strides 的方法,基本上用剩余元素填充,然后strides 帮助我们非常有效地创建转换后的版本 -

    def strided_method(ar):
        a = np.concatenate(( ar, ar[:-1] ))
        L = len(ar)
        n = a.strides[0]
        return np.lib.stride_tricks.as_strided(a[L-1:], (L,L), (-n,n))
    

    示例运行 -

    In [42]: ar = np.array([1, 2, 3, 4])
    
    In [43]: strided_method(ar)
    Out[43]: 
    array([[4, 1, 2, 3],
           [3, 4, 1, 2],
           [2, 3, 4, 1],
           [1, 2, 3, 4]])
    
    In [44]: ar = np.array([4,9,3,6,1,2])
    
    In [45]: strided_method(ar)
    Out[45]: 
    array([[2, 4, 9, 3, 6, 1],
           [1, 2, 4, 9, 3, 6],
           [6, 1, 2, 4, 9, 3],
           [3, 6, 1, 2, 4, 9],
           [9, 3, 6, 1, 2, 4],
           [4, 9, 3, 6, 1, 2]])
    

    运行时测试-

    In [5]: a = np.random.randint(0,9,(1000))
    
    # @Eric's soln
    In [6]: %timeit roll_matrix(a)
    100 loops, best of 3: 3.39 ms per loop
    
    # @Warren Weckesser's soln
    In [8]: %timeit circulant(a[::-1])
    100 loops, best of 3: 2.03 ms per loop
    
    # Strides method
    In [18]: %timeit strided_method(a)
    100000 loops, best of 3: 6.7 µs per loop
    

    制作副本(如果您想进行更改而不仅仅是用作只读数组)不会对 strides 方法造成太大伤害 -

    In [19]: %timeit strided_method(a).copy()
    1000 loops, best of 3: 381 µs per loop
    

    【讨论】:

    • 这使用的内存比我的解决方案少,但输出不能安全写入。我怀疑这是一个问题,但这是更好的解决方案
    • @Eric:“不可安全写入”是什么意思?
    • @Cleb:我的意思是,如果您尝试更改数组中的4s 之一,它们都会立即更改!
    • @Eric:好的,我认为创建副本应该可以避免这个问题?
    • @Cleb 是的,那应该没问题。
    【解决方案2】:

    现有的两个答案都很好;如果您已经在使用 scipy,这个答案可能只有您感兴趣。

    您描述的矩阵称为circulant matrix。如果不介意对scipy的依赖,可以使用scipy.linalg.circulant创建一个:

    In [136]: from scipy.linalg import circulant
    
    In [137]: ar = np.array([1, 2, 3, 4])
    
    In [138]: circulant(ar[::-1])
    Out[138]: 
    array([[4, 1, 2, 3],
           [3, 4, 1, 2],
           [2, 3, 4, 1],
           [1, 2, 3, 4]])
    

    【讨论】:

    • 我确实使用 scipy,所以这个答案非常有帮助,因为它也是最易读的一个(赞成)。就速度而言,它似乎效率较低,但已经内置了它,这很好!
    • 看起来 scipy 值得一个补丁来使用@Divakar 更快的实现。可以看到scipy实现here
    • @Eric 好主意! Scipy 肯定可以使用一些 strides 技巧!
    • "...对用户来说是不可见的" 是的,直到他们开始就地修改数组。然后,至少对于毫无戒心的用户来说,乐趣就开始了。
    • @Divakar:创建一个带有改进描述的新问题将是一个很好的开始。创建一个实现改进的拉取请求会更好!有关为 scipy 做出贡献的信息,请参阅 github.com/scipy/scipy/blob/master/HACKING.rst.txt
    【解决方案3】:

    这是一种方法

    def roll_matrix(vec):
        N = len(vec)
        buffer = np.empty((N, N*2 - 1))
    
        # generate a wider array that we want a slice into
        buffer[:,:N] = vec
        buffer[:,N:] = vec[:-1]
    
        rolled = buffer.reshape(-1)[N-1:-1].reshape(N, -1)
        return rolled[:,:N]
    

    在您的情况下,我们将 buffer 构建为

    array([[ 1.,  2.,  3.,  4.,  1.,  2.,  3.],
           [ 1.,  2.,  3.,  4.,  1.,  2.,  3.],
           [ 1.,  2.,  3.,  4.,  1.,  2.,  3.],
           [ 1.,  2.,  3.,  4.,  1.,  2.,  3.]])
    

    然后将其展平、修剪、重塑以获得rolled

    array([[ 4.,  1.,  2.,  3.,  1.,  2.],
           [ 3.,  4.,  1.,  2.,  3.,  1.],
           [ 2.,  3.,  4.,  1.,  2.,  3.],
           [ 1.,  2.,  3.,  4.,  1.,  2.]])
    

    最后,切掉最后一列的垃圾

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-22
      • 2012-09-04
      • 1970-01-01
      • 2014-07-27
      • 1970-01-01
      • 2021-12-05
      相关资源
      最近更新 更多