inf的Python矩阵对角线没有fill_diagonal

时间:2017-05-03 11:36:41

标签: python numpy

我需要将矩阵的对角线元素设置为Inf。

一种简单的方法是使用np.fill_diagonal

np.fill_diagonal(my_matrix, float('inf')

然而fill_diagonal修改输入矩阵而不是返回填充了对角线的新矩阵。 这对我不起作用。我需要填充对角线而不修改原始矩阵。

当然我可以克隆原始矩阵,所以我将始终保留原始矩阵的副本。但是我并不喜欢这个解决方案,因为我会经常更新我的原始矩阵,因此每次我需要对角线为inf时我都必须复制它。

是否存在与fill_diagonal相同但不修改输入矩阵的函数?类似的东西:

new_matrix = np.fill_diagonal(original_matrix, float('inf') 

为什么我需要这个:

我的矩阵是点之间的距离矩阵,我想在每一步计算两个最近的点。当然,该矩阵的对角线为0(因为从点到自身的距离为0)。所以我的解决方案是确保我没有采取相同的观点是将对角线设置为Inf。

然而,一旦找到两个点,我需要计算这两个点与其余点之间的距离的平均值,所以我实际上需要对角线为0而不是Inf。

目前我正在做的是:

  • 使用Inf
  • 填充对角线
  • 找到最近的2个点
  • 用0
  • 填充对角线
  • 计算这两点与其余点之间的平均距离。

    # fill diagonal with Inf to avoid taking the diagonals
    np.fill_diagonal(data, float('inf'))  
    # find the minimum distance
    idx = np.argmin(data)
    # fill the diagonals back to 0
    np.fill_diagonal(data, 0.0) 
    # get the coordinates of the minimum distance
    row, col =  np.unravel_index(idx,data.shape)
    # compute the new node as the average distance between the two points
    new_node = np.mean((data[:,row],data[:,col]),0)
    # replace the first node (row) with the new node
    data[:,row] = new_node
    data[row,:] = new_node.T
    # delete the second node (col) from the matrix
    data = np.delete(data, col, 0)  # delete row
    data = np.delete(data, col, 1)  # delete column
    

但是我不喜欢将对角线设置为Inf然后再回到0的想法,我宁愿只将一个函数传递给argmax,它返回带有填充Inf的对角线的数据而不实际修改矩阵数据。

类似的东西:

idx = np.argmin(return_filled_diagonals(data, float('Inf'))
# here I can operate with data as usual since it has not been modified.

5 个答案:

答案 0 :(得分:2)

方法#1

你正在寻找的魔法在NumPy strides中,它让我们看到没有对角元素的数组,因此不再占用内存空间。这是实现这种观点的实现 -

def nodiag_view(a):
    m = a.shape[0]
    p,q = a.strides
    return np.lib.stride_tricks.as_strided(a[:,1:], (m-1,m), (p+q,q))

让我们看看一个示例运行来验证它的视图 -

In [124]: a  # Input array
Out[124]: 
array([[ 0, 61, 43, 26, 21],
       [20,  0, 78, 29, 64],
       [34, 49,  0, 64, 60],
       [36, 96, 67,  0, 75],
       [36, 85, 40, 74,  0]])

# Get the no-diag view
In [125]: a_nodiag = nodiag_view(a)

# Lets's verify by changing elements in the view and that should change
# elements in the original array too
In [126]: a_nodiag[:] = 999

In [127]: a
Out[127]: 
array([[  0, 999, 999, 999, 999],
       [999,   0, 999, 999, 999],
       [999, 999,   0, 999, 999],
       [999, 999, 999,   0, 999],
       [999, 999, 999, 999,   0]])

最后,让我们看看我们如何设置它来解决您的整个问题 -

def argmin_without_diag(a):
    a_nodiag = nodiag_view(a)
    idx_nodiag = np.argmin(a_nodiag)
    m = a.shape[0]
    idx = idx_nodiag + np.unravel_index(idx_nodiag, (m-1,m))[0]+1
    return  np.unravel_index(idx, a.shape)

示例运行 -

In [142]: a
Out[142]: 
array([[ 0, 60, 79, 55, 77],
       [62,  0, 86, 84, 25],
       [32, 96,  0, 74, 89],
       [24, 33, 64,  0, 93],
       [14, 74, 30, 44,  0]])

In [143]: argmin_without_diag(a)
Out[143]: (4, 0)

方法#2

如果您担心内存和性能,可以暂时将对角线设置为infnite,然后获取argmin索引,然后放回原始对角线值。因此,有效地输入数组不变。实现看起来像这样 -

def argmin_without_diag_replacement(a):
    # Store diagonal values
    vals = a.ravel()[::a.shape[1]+1].copy()

    # Set diag ones as infinites
    a.ravel()[::a.shape[1]+1] = np.inf

    # Get argmin index
    idx = np.argmin(a)

    # Put back the original diag values
    a.ravel()[::a.shape[1]+1] = vals
    return np.unravel_index(idx, a.shape)

因此,对于(n x n)形状的数组,临时数组只有n个元素。

示例运行 -

In [237]: a
Out[237]: 
array([[  0.,  95.,  57.,  75.,  92.],
       [ 37.,   0.,  69.,  71.,  62.],
       [ 42.,  72.,   0.,  30.,  57.],
       [ 41.,  80.,  94.,   0.,  26.],
       [ 36.,  45.,  71.,  76.,   0.]])

In [238]: argmin_without_diag_replacement(a)
Out[238]: (3, 4)

运行时测试

In [271]: a = np.random.randint(11,99,(1000,1000)).astype(float)

In [272]: np.fill_diagonal(a,0)

In [273]: %timeit argmin_without_diag(a)
1000 loops, best of 3: 1.76 ms per loop

In [274]: %timeit argmin_without_diag_replacement(a)
1000 loops, best of 3: 688 µs per loop

答案 1 :(得分:1)

orig_mat = np.array([[1.2,2,3],[4,5,6],[7,8,9]])

#set diagonal to inf without making a copy of the array.
orig_mat + np.where(np.eye(orig_mat.shape[0])>0,np.inf,0)
array([[ inf,   2.,   3.],
       [  4.,  inf,   6.],
       [  7.,   8.,  inf]])

#the original array remains untorched.
print(orig_mat)
[[ 1.2  2.   3. ]
 [ 4.   5.   6. ]
 [ 7.   8.   9. ]]

答案 2 :(得分:0)

根据我的理解,你想找到矩阵的min的索引,但不包括对角元素:

orig_mat = np.array([[1.2,2,3],[4,5,6],[7,8,9]])
min_idx = np.argmin(orig_mat+np.multiply(np.eye(orig_mat.shape[0]),1e9))

答案 3 :(得分:0)

我的提名是明确的复制和填写,包含在一个函数

def foo(mat):
    a = mat.copy()
    np.fill_diagonal(a,np.inf)
    return a

这确实是一个副本,但只有一个。

orig_mat + np.where(np.eye(orig_mat.shape[0])>0,np.inf,0))  

也会返回一个新数组,但在此过程中必须添加两个临时数组。所以最终会变慢:

代表orig_mat=np.ones((1000,1000))

In [67]: timeit np.argmin(orig_mat + np.where(np.eye(orig_mat.shape[0])>0, np.inf, 0))
100 loops, best of 3: 18.7 ms per loop
In [68]: timeit np.argmin(foo(orig_mat))
100 loops, best of 3: 6.94 ms per loop

令人惊讶的是@Divakar's解决方案的速度要快得多:

In [69]: timeit argmin_without_diag(orig_mat)
100 loops, best of 3: 5.97 ms per loop

答案 4 :(得分:-2)

对于(n x n)形状的数组,首先创建一个长度等于n的np.inf列表。

inf_list = [np.inf] * n

然后,使用此inf列表作为numpy.diag()函数的输入。该条目列表将其放在对角线位置。其余条目全为零。

diag_entr = np.diag(inf_list)

如果n等于10,diag_entr看起来像:

[[inf  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0. inf  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0. inf  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0. inf  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0. inf  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0. inf  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0. inf  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0. inf  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0. inf  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0. inf]]

将原始Matix表示为MyMatrix。通过以下操作,使其对角线入口变为无穷大:

MyMatrix = MyMatrix + diag_entr