6

I would like to change diagonal elements from a 2d matrix. These are both main and non-main diagonals.

numpy.diagonal() In NumPy 1.10, it will return a read/write view, Writing to the returned array will alter your original array.

numpy.fill_diagonal(), numpy.diag_indices() Only works with main-diagonal elements

Here is my use case: I want to recreate a matrix of the following form, which is very trivial using diagonal notation given that I have all the x, y, z as arrays.

matrix

Alex Riley
  • 169,130
  • 45
  • 262
  • 238
FooBar
  • 15,724
  • 19
  • 82
  • 171
  • what about `numpy.diag`? – talonmies Jan 10 '15 at 12:36
  • 1
    I think `np.diag` calls `np.diagonal` which, prior to Numpy 1.10, presents [difficulties](http://docs.scipy.org/doc/numpy/reference/generated/numpy.diagonal.html) when trying to write values to the array. – Alex Riley Jan 10 '15 at 14:32
  • Maybe have a look at [`scipy.sparse.diags`](http://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.sparse.diags.html) and [`scipy.sparse.dia_matrix`](http://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.sparse.dia_matrix.htm). –  Jan 10 '15 at 14:37

3 Answers3

6

Try this:

>>> A = np.zeros((6,6))
>>> i,j = np.indices(A.shape)
>>> z = [1, 2, 3, 4, 5]

Now you can intuitively access any diagonal:

>>> A[i==j-1] = z
>>> A
array([[ 0.,  1.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  2.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  3.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  4.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  5.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

In the same way you can assign arrays to A[i==j], etc.

elyase
  • 39,479
  • 12
  • 112
  • 119
  • Haven't compared performance with @ajcrs answer, but I like this one for simplicity. – FooBar Jan 12 '15 at 09:22
  • @FooBar This method is definitely syntactically simpler than mine :-) But performance-wise, constructing a mask (with `i==j-1`) for larger matrices is not efficient. On a 1000x1000 matrix, setting the first upper diagonal is around 100 times slower than specifying the indices directly. This is because you need to do one million `==` comparisons to build the boolean mask (not to mention having the large boolean matrix in memory). Whether or not this matters depends on your use case, of course. – Alex Riley Jan 14 '15 at 20:24
5

You could always use slicing to assign a value or array to the diagonals.

Passing in a list of row indices and a list of column indices lets you access the locations directly (and efficiently). For example:

>>> z = np.zeros((5,5))
>>> z[np.arange(5), np.arange(5)] = 1 # diagonal is 1
>>> z[np.arange(4), np.arange(4) + 1] = 2 # first upper diagonal is 2
>>> z[np.arange(4) + 1, np.arange(4)] = [11, 12, 13, 14] # first lower diagonal values

changes the array of zeros z to:

array([[  1.,   2.,   0.,   0.,   0.],
       [ 11.,   1.,   2.,   0.,   0.],
       [  0.,  12.,   1.,   2.,   0.],
       [  0.,   0.,  13.,   1.,   2.],
       [  0.,   0.,   0.,  14.,   1.]])

In general for a k x k array called z, you can set the ith upper diagonal with

z[np.arange(k-i), np.arange(k-i) + i]

and the ith lower diagonal with

z[np.arange(k-i) + i, np.arange(k-i)]

Note: if you want to avoid calling np.arange several times, you can simply write ix = np.arange(k) once and then slice that range as needed:

np.arange(k-i) == ix[:-i]
Alex Riley
  • 169,130
  • 45
  • 262
  • 238
2

Here is another approach just for fun. You can write your own diagonal function to return of view of the diagonal you need.

import numpy as np

def diag(a, k=0):
    if k > 0:
        a = a[:, k:]
    elif k < 0:
        a = a[-k:, :]

    shape = (min(a.shape),)
    strides = (sum(a.strides),)
    return np.lib.stride_tricks.as_strided(a, shape, strides)

a = np.arange(20).reshape((4, 5))
diag(a, 2)[:] = 88
diag(a, -2)[:] = 99
print(a)
# [[ 0  1 88  3  4]
#  [ 5  6  7 88  9]
#  [99 11 12 13 88]
#  [15 99 17 18 19]]
Bi Rico
  • 25,283
  • 3
  • 52
  • 75