7

I saw a function numpy.fill_diagonal which assigns same value for diagonal elements. But I want to assign different random values for each diagonal elements. How can I do it in python ? May be using scipy or other libraries ?

Divakar
  • 218,885
  • 19
  • 262
  • 358
Shyamkkhadka
  • 1,438
  • 4
  • 19
  • 29

3 Answers3

5

That the docs call the fill val a scalar is an existing documentation bug. In fact, any value that can be broadcasted here is OK.

Fill diagonal works fine with array-likes:

>>> a = np.arange(1,10).reshape(3,3)
>>> a
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
>>> np.fill_diagonal(a, [99, 42, 69])
>>> a
array([[99,  2,  3],
       [ 4, 42,  6],
       [ 7,  8, 69]])

It's a stride trick, since the diagonal elements are regularly spaced by the array's width + 1.

From the docstring, that's a better implementation than using np.diag_indices too:

Notes
-----
.. versionadded:: 1.4.0

This functionality can be obtained via `diag_indices`, but internally
this version uses a much faster implementation that never constructs the
indices and uses simple slicing.
wim
  • 338,267
  • 99
  • 616
  • 750
  • This Q&A is meant for older NumPy versions that didn't have that functionality. So, for newer versions, they won't be asking such a question, as OP has already mentioned that in the question that they have tried `np.fill_diagonal`. So, just a comment would have sufficed. There's also on older Q&A on the same, linked as a comment. – Divakar Dec 19 '19 at 19:32
  • @Divakar That doesn't matter - people finding this question from search should be able to see the most up to date info avail in the answers without needing to dig through comments (otherwise many users will just copy and paste suboptimal code intended for older numpy version). It's not specified any numpy version in the question. – wim Dec 19 '19 at 19:35
  • As I ready mentioned - OP says `np.fill_diagonal` doesn't work, as they have some old version. If you just want to convery that it works on newer versions, simply leave a comment or link them to older Q&A that talks about the same (fill_diagonal) for the future readers who have access to the new functionality. One more post with `np.fill_diagonal` doesn't make sense. – Divakar Dec 19 '19 at 19:46
  • @Divakar That does makes sense, and I'll explain why. I found this question through the search wanting to easily find the syntax for populating a diagonal with some sort of slice assignment operation. I read your answer, and thought, "hmm, this doesn't look quite right, surely there must be a better way than generating indices first", and indeed I was right, so posted the answer. The next person who get here from search can see another answer. Comments are ephemeral and have a habit of disappearing quietly on stackoverflow. – wim Dec 19 '19 at 19:56
  • You are missing my point. So, stating again. For the sake of this question, OP doesn't have the intended functionality available with their `np.fill_diagonal`. To use OP's words - `I saw a function numpy.fill_diagonal which assigns same value for diagonal elements.` Hence, they are posting this question. For people with newer NumPY version that allows ndarray assigning with fill_diagonal won't have this question, as they can use `fill_diagonal` and we already have an older Q&A on the same. – Divakar Dec 19 '19 at 20:21
  • So, for all the people that you are worried about that are wanting to easily find the syntax for populating a diagonal with slice assignment and if they have newer NumPy version, should lookup that older Q&A. Rest of the people, that are still with older versions, could use this Q&A. So, I don't see the point of this post in this Q&A. – Divakar Dec 19 '19 at 20:21
  • Please keep in mind that Q&A stands as title+body, not just title. – Divakar Dec 19 '19 at 20:26
  • I understand your point, I just disagree with it. Q&A is to help future searchers too, not just the OP. It’s OK to have multiple answers on question if they’re showing different ways, and outdated info should be **clearly indicated as such** or just removed. – wim Dec 19 '19 at 20:42
  • Don't think you understood my point. I specifically said a question post includes the body too. Future searchers won't end up here after reading the body, if they have the ndarray assignment functionality. Again, I am just repeating myself. – Divakar Dec 19 '19 at 20:50
  • I know for a fact that searchers will end up here looking for the functionality, because that's exactly how I ended up here. Note that [`np.fill_diagonal`](https://docs.scipy.org/doc/numpy/reference/generated/numpy.fill_diagonal.html) still says `val : scalar` (arguably a docs bug). – wim Dec 19 '19 at 20:56
  • @Divakar You wrote *"This Q&A is meant for older NumPy versions that didn't have that functionality"*. Which versions is that? – wim Dec 19 '19 at 21:12
  • Yeah, that was my bad. Should have worked throughout all, as the older Q&A posted in 2014 worked too. – Divakar Dec 19 '19 at 21:19
2

You can use np.diag_indices to get those indices and then simply index into the array with those and assign values.

Here's a sample run to illustrate it -

In [86]: arr          # Input array
Out[86]: 
array([[13, 69, 35, 98, 16],
       [93, 42, 72, 51, 65],
       [51, 33, 96, 43, 53],
       [15, 26, 16, 17, 52],
       [31, 54, 29, 95, 80]])

# Get row, col indices
In [87]: row,col = np.diag_indices(arr.shape[0])

# Assign values, let's say from an array to illustrate
In [88]: arr[row,col] = np.array([100,200,300,400,500])

In [89]: arr
Out[89]: 
array([[100,  69,  35,  98,  16],
       [ 93, 200,  72,  51,  65],
       [ 51,  33, 300,  43,  53],
       [ 15,  26,  16, 400,  52],
       [ 31,  54,  29,  95, 500]])

You can also use np.diag_indices_from and probably would be more idomatic, like so -

row, col = np.diag_indices_from(arr)

Note : The tried function would work just fine. This is discussed in a previous Q&A - Numpy modify ndarray diagonal too.

Divakar
  • 218,885
  • 19
  • 262
  • 358
  • Thanks Divakar. But I want it to be done in matrix. Not in array. Further how can I give main diagonal elements random values, not specified in array ? – Shyamkkhadka Oct 26 '16 at 11:54
  • 1
    @Shyamkkhadka Think it should work just as well on NumPy matrices without any change. Did you even try out the code? Also, on the assigning part, that `np.array([100,200,300,400,500])` is just an example. We can assign anything there, like `arr[row,col] = np.random.randint(0,9,(5))`. – Divakar Oct 26 '16 at 12:03
  • This seems to work with [`np.fill_diagonal`](https://docs.scipy.org/doc/numpy/reference/generated/numpy.fill_diagonal.html) but the documentation states that the second argument must be a scalar. Any reason not to simply use this function instead? – pbreach Jan 10 '17 at 20:50
  • @pbreach Ah yes, that works too and is idiomatic for this task! Thanks! – Divakar Jan 10 '17 at 22:19
  • For which "older NumPy version" didn't allow an ndarray to be assigned here? I checked minor versions from v1.17 all the way back to numpy v1.4 (released many years before this question was posted) and it's working fine every step of the way. I could not build older than NumPy v1.4 any more. – wim Dec 19 '19 at 21:03
  • @wim Yup, that should have worked too. That older Q&A had `np.fill_diagonal` for array-assignment. Think OP just didn't try enough or got confused with docs. – Divakar Dec 19 '19 at 21:07
  • So you are showing suboptimal method just because O.P. got confused by a documentation error? – wim Dec 19 '19 at 21:07
  • @wim I am proposing alternatives as listed in the question. I don't see OP looking for performance. – Divakar Dec 19 '19 at 21:08
  • But it is better to correct misconception from O.P. than propose a more complicated alternative, don't you think? – wim Dec 19 '19 at 21:09
  • @wim OP is asking for alternatives as I got that, which I did. OP is not asking to be corrected for any misconception, if any. So, I don't think I can call that as a better thing, when OP aren't looking for it. – Divakar Dec 19 '19 at 21:18
  • It is important to correct the misconception, otherwise future readers might just assume the misconception was right. Especially if they see accepted answer which demonstrates alternative. The goal is to have best up-to-date information on site, not just give OP whatever they asked for without using your own judgement. I can see I'm not going to have much success to convince you here, so let's just agree to disagree! – wim Dec 19 '19 at 21:29
0

Create an identity matrix with n dimensions (take input from the user). Fill the diagonals of that matrix with the multiples of the number provided by the user.

arr=np.eye(4)
j=3
np.fill_diagonal(arr,6)
for i,x in zip(range(4),range(1,5)):
    arr[i,i]=arr[i,i]*x
    arr[i,j]=6*(j+1)
    j-=1
arr

output:

array([[ 6.,  0.,  0., 24.],
       [ 0., 12., 18.,  0.],
       [ 0., 12., 18.,  0.],
       [ 6.,  0.,  0., 24.]])