6

I'm trying to reshape an array from its original shape, to make the elements of each row descend along a diagonal:

np.random.seed(0) 
my_array = np.random.randint(1, 50, size=(5, 3))
array([[45, 48,  1],
       [ 4,  4, 40],
       [10, 20, 22],
       [37, 24,  7],
       [25, 25, 13]])

I would like the result to look like this:

my_array_2 = np.array([[45,  0,  0],
                       [ 4, 48,  0],
                       [10,  4,  1],
                       [37, 20, 40],
                       [25, 24, 22],
                       [ 0, 25,  7],
                       [ 0,  0, 13]])

This is the closest solution I've been able to get:

my_diag = []
for i in range(len(my_array)):
    my_diag_ = np.diag(my_array[i], k=0)
    my_diag.append(my_diag_)
my_array1 = np.vstack(my_diag)
array([[45,  0,  0],
       [ 0, 48,  0],
       [ 0,  0,  1],
       [ 4,  0,  0],
       [ 0,  4,  0],
       [ 0,  0, 40],
       [10,  0,  0],
       [ 0, 20,  0],
       [ 0,  0, 22],
       [37,  0,  0],
       [ 0, 24,  0],
       [ 0,  0,  7],
       [25,  0,  0],
       [ 0, 25,  0],
       [ 0,  0, 13]])

From here I think it might be possible to remove all zero diagonals, but I'm not sure how to do that.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
XioHu
  • 123
  • 4

4 Answers4

2

One way using numpy.pad:

n = my_array.shape[1] - 1
np.dstack([np.pad(a, (i, n-i), "constant") 
           for i, a in enumerate(my_array.T)])

Output:

array([[[45,  0,  0],
        [ 4, 48,  0],
        [10,  4,  1],
        [37, 20, 40],
        [25, 24, 22],
        [ 0, 25,  7],
        [ 0,  0, 13]]])
Chris
  • 29,127
  • 3
  • 28
  • 51
1

There's probably a shift capability in numpy, but I'm not familiar w/it, so here's a solution using pandas. You concat np.zeros to the original array with the number of rows being equal to ncols - 1. Then iterate over each col and shift it down by the number equal to the column number.

import numpy as np
import pandas as pd
np.random.seed(0) 
my_array = np.random.randint(1,50, size=(5,3))
df = pd.DataFrame(np.concatenate((my_array,np.zeros((my_array.shape[1]-1, 
                                  my_array.shape[1])))))

for col in df.columns:
    df[col] = df[col].shift(int(col))

df.fillna(0).values

Output

array([[45.,  0.,  0.],
       [ 4., 48.,  0.],
       [10.,  4.,  1.],
       [37., 20., 40.],
       [25., 24., 22.],
       [ 0., 25.,  7.],
       [ 0.,  0., 13.]])
Chris
  • 15,819
  • 3
  • 24
  • 37
1
In [134]: arr = np.array([[45, 48,  1],
     ...:        [ 4,  4, 40],
     ...:        [10, 20, 22],
     ...:        [37, 24,  7],
     ...:        [25, 25, 13]])
In [135]: res= np.zeros((arr.shape[0]+arr.shape[1]-1, arr.shape[1]), arr.dtype)

Taking a hint from how np.diag indexes a diagonal, iterate on the rows of arr:

In [136]: for i in range(arr.shape[0]):
     ...:     n = i*arr.shape[1]
     ...:     m = arr.shape[1]
     ...:     res.flat[n:n+m**2:m+1] = arr[i,:]
     ...: 
In [137]: res
Out[137]: 
array([[45,  0,  0],
       [ 4, 48,  0],
       [10,  4,  1],
       [37, 20, 40],
       [25, 24, 22],
       [ 0, 25,  7],
       [ 0,  0, 13]])
hpaulj
  • 221,503
  • 14
  • 230
  • 353
1

You can create a fancy index for the output using simple broadcasting and padding. First pad the end of your data:

a = np.concatenate((a, np.zeros((a.shape[1] - 1, a.shape[1]), a.dtype)), axis=0)

Now make an index that gets the elements using their negative index. This will make it trivial to roll around the end:

cols = np.arange(a.shape[1])
rows = np.arange(a.shape[0]).reshape(-1, 1) - cols

Now just simply index:

result = a[rows, cols]

For large arrays, this may not be as efficient as running a small loop. At the same time, this avoids actual looping, and allows you to write a one-liner (but please don't):

result = np.concatenate((a, np.zeros((a.shape[1] - 1, a.shape[1]), a.dtype)), axis=0)[np.arange(a.shape[0] + a.shape[1] - 1).reshape(-1, 1) - np.arange(a.shape[1]), np.arange(a.shape[1])]
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264