Home > front end >  NumPy: construct squares along diagonal of matrix / expand diagonal matrix
NumPy: construct squares along diagonal of matrix / expand diagonal matrix

Time:10-27

Suppose you have either two arrays:

index = [1, 2, 3]
counts = [2, 3, 2]

or a singular array

arr = [1, 1, 2, 2, 2, 3, 3]

How can I efficiently construct the matrix

[
  [1, 1, 0, 0, 0, 0, 0],
  [1, 1, 0, 0, 0, 0, 0],
  [0, 0, 2, 2, 2, 0, 0],
  [0, 0, 2, 2, 2, 0, 0],
  [0, 0, 2, 2, 2, 0, 0],
  [0, 0, 0, 0, 0, 3, 3],
  [0, 0, 0, 0, 0, 3, 3]
]

with NumPy?

I know that

square = np.zeros((7, 7))
np.fill_diagnol(square, arr) # see arr above

produces

[
  [1, 0, 0, 0, 0, 0, 0],
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 2, 0, 0, 0, 0],
  [0, 0, 0, 2, 0, 0, 0],
  [0, 0, 0, 0, 2, 0, 0],
  [0, 0, 0, 0, 0, 3, 0],
  [0, 0, 0, 0, 0, 0, 3]
]

How do I "expand" the diagonal by n where n is counts[index-1] for the values specified by index[I]

tmp = np.array((arr * N)).reshape((len(arr), len(arr)) 
np.floor( (tmp   tmp.T) / 2 ) # <-- this is closer


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

This gets what I want, but probably doesn't scale that well?

riffled = list(zip(index, counts))
riffled
# [(1, 2), (2, 3), (3, 2)]
a = np.zeros((len(arr), len(arr))) # 7, 7 square
last = 0 # <-- keep track of current sub square
for i, c in riffled:
    a[last:last c, last:last c] = np.ones((c, c)) * i 
    last  = c # <-- shift square

yield

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

CodePudding user response:

You can use scipy.linalg.block_diag to make that work:

import numpy as np
import scipy.linalg as linalg


a = 1*np.ones((2,2))
b = 2*np.ones((3,3))
c = 3*np.ones((2,2))

superBlock = linalg.block_diag(a,b,c)

print(superBlock)

#returns
#[[1. 1. 0. 0. 0. 0. 0.]
# [1. 1. 0. 0. 0. 0. 0.]
# [0. 0. 2. 2. 2. 0. 0.]
# [0. 0. 2. 2. 2. 0. 0.]
# [0. 0. 2. 2. 2. 0. 0.]
# [0. 0. 0. 0. 0. 3. 3.]
# [0. 0. 0. 0. 0. 3. 3.]]

if you want to get there from a list of values and a list of counts you can do this:

values = [1,2,3]
counts = [2,3,2]

mats = []
for v,c in zip(values,counts):
    thisMatrix = v*np.ones((c,c))
    mats.append( thisMatrix )


superBlock = linalg.block_diag(*mats)


print(superBlock)

CodePudding user response:

Try broadcasting:

idx = np.repeat(np.arange(len(counts)), counts)
np.where(idx==idx[:,None], arr, 0)
# or
# arr * (idx==idx[:,None])

Output;

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

CodePudding user response:

Here is a generic solution.

starting from the index/count:

index = [1, 2, 1]
counts = [2, 3, 2]

arr = np.repeat(index, counts)
arr2 = np.repeat(range(len(index)), counts)
np.where(arr2==arr2[:,None], arr, 0)

output:

array([[1, 1, 0, 0, 0, 0, 0],
       [1, 1, 0, 0, 0, 0, 0],
       [0, 0, 2, 2, 2, 0, 0],
       [0, 0, 2, 2, 2, 0, 0],
       [0, 0, 2, 2, 2, 0, 0],
       [0, 0, 0, 0, 0, 1, 1],
       [0, 0, 0, 0, 0, 1, 1]])

starting from the array version:

arr = np.array([1, 1, 2, 2, 2, 1, 2])

arr2 = np.cumsum(np.diff(arr,prepend=np.nan)!=0)
np.where(arr2==arr2[:,None], arr, 0)

output:

array([[1, 1, 0, 0, 0, 0, 0],
       [1, 1, 0, 0, 0, 0, 0],
       [0, 0, 2, 2, 2, 0, 0],
       [0, 0, 2, 2, 2, 0, 0],
       [0, 0, 2, 2, 2, 0, 0],
       [0, 0, 0, 0, 0, 1, 0],
       [0, 0, 0, 0, 0, 0, 2]])
  • Related