Home > Net >  Repeat 1D array to 2D array with shifted rows
Repeat 1D array to 2D array with shifted rows

Time:10-01

I want to take an array a = [1, 2, 3, 4, 5] and turn it into a 5x5 array like this:

[[1, 2, 3, 4, 5],
 [2, 3, 4, 5, 0],
 [3, 4, 5, 0, 0],
 [4, 5, 0, 0 ,0],
 [5, 0, 0, 0, 0]]

How can I do this with numpy?

I also want to be able to use an arbitrary array like a = [1, 6, 2, 3, 8] and get:

[[1, 6, 2, 3, 8],
 [6, 2, 3, 8, 0],
 [2, 3, 8, 0, 0],
 [3, 8, 0, 0, 0],
 [8, 0, 0, 0, 0]]

CodePudding user response:

With np.add.outer and np.where

import numpy as np

a = [1, 2, 3, 4, 5]
r = np.add.outer(a,a) - 1
np.where(r <= len(a), r, 0)

Output

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

More general for constantly increasing integer ranges

a = [-2, -1, 0 , 1, 2, 3]
r = np.add.outer(a,a) - np.min(a)
np.where(r <= np.max(a), r, 0)

Output

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

For arbitrary arrays

import numpy as np

a = np.array([1, 4, 3, 5, 8, 3, 8, 3, 2, 1, 8]) 
x = np.arange(len(a))
r = np.add.outer(x,x)
idx = np.where(r <= len(x)-1, r, 0)
np.where(r <= len(x)-1, np.full(r.shape, a)[np.arange(len(a)),idx], 0)

Output

array([[1, 4, 3, 5, 8, 3, 8, 3, 2, 1, 8],
       [4, 3, 5, 8, 3, 8, 3, 2, 1, 8, 0],
       [3, 5, 8, 3, 8, 3, 2, 1, 8, 0, 0],
       [5, 8, 3, 8, 3, 2, 1, 8, 0, 0, 0],
       [8, 3, 8, 3, 2, 1, 8, 0, 0, 0, 0],
       [3, 8, 3, 2, 1, 8, 0, 0, 0, 0, 0],
       [8, 3, 2, 1, 8, 0, 0, 0, 0, 0, 0],
       [3, 2, 1, 8, 0, 0, 0, 0, 0, 0, 0],
       [2, 1, 8, 0, 0, 0, 0, 0, 0, 0, 0],
       [1, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

CodePudding user response:

If you can use scipy:

from scipy import linalg

linalg.hankel([1,2,3,4,5])
# array([[1, 2, 3, 4, 5],
#        [2, 3, 4, 5, 0],
#        [3, 4, 5, 0, 0],
#        [4, 5, 0, 0, 0],
#        [5, 0, 0, 0, 0]])

If not:

def hankel(a):
    n = len(a)
    out = np.zeros((n,n),int)
    out.ravel()[:-1].reshape(n 1,n-1)[1:,0] = a[-1]
    np.copyto(out.ravel()[:1-2*n].reshape(n-1,n-1),a[:-1],where=np.tri(n-1,dtype=bool).T)
    return out                                

hankel([1,2,3,4,5])
# array([[1, 2, 3, 4, 5],
#        [2, 3, 4, 5, 0],
#        [3, 4, 5, 0, 0],
#        [4, 5, 0, 0, 0],
#        [5, 0, 0, 0, 0]])

CodePudding user response:

Disclaimer:

The following solution only works if you only need a view. This solution does not return a writeable array. If you need a writeable array, you can simply pass the view to the array constructor: np.array(array_view), but at that point, the linalg.hankel from @loopywalt's solution is a better approach, unless you want to use any of the fun features I add.

If all you need is a view

Here's a solution which works for arbitrary 1D arrays and is supremely fast:

def fast_hankel_view(a: np.ndarray) -> np.ndarray:
    arr = np.append(a, np.zeros(a.size - 1, a.dtype))
    return np.lib.stride_tricks.sliding_window_view(arr, a.size)

Output:

>>> a = np.array([1, 2, 3, 4, 5])
>>> fast_hankel_view(a)
array([[1, 2, 3, 4, 5],
       [2, 3, 4, 5, 0],
       [3, 4, 5, 0, 0],
       [4, 5, 0, 0, 0],
       [5, 0, 0, 0, 0]])

>>> a = np.array([1, 4, 3, 5, 8, 3, 8, 3, 2, 1, 8])
>>> fast_hankel_view(a)
array([[1, 4, 3, 5, 8, 3, 8, 3, 2, 1, 8],
       [4, 3, 5, 8, 3, 8, 3, 2, 1, 8, 0],
       [3, 5, 8, 3, 8, 3, 2, 1, 8, 0, 0],
       [5, 8, 3, 8, 3, 2, 1, 8, 0, 0, 0],
       [8, 3, 8, 3, 2, 1, 8, 0, 0, 0, 0],
       [3, 8, 3, 2, 1, 8, 0, 0, 0, 0, 0],
       [8, 3, 2, 1, 8, 0, 0, 0, 0, 0, 0],
       [3, 2, 1, 8, 0, 0, 0, 0, 0, 0, 0],
       [2, 1, 8, 0, 0, 0, 0, 0, 0, 0, 0],
       [1, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

Small benchmark for an array of 100,000 random integers:

In [3]: x = np.random.randint(0, 255, (1, 100000), dtype="uint8")
In [4]: %timeit fast_hankel_view(x)
25.8 µs ± 165 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Again, this is so fast because it's only constructing a view of the input array x:

In [5]: h = fast_hankel_view(x)

In [6]: h  = 1
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-ce0b251d63e1> in <module>
----> 1 h  = 1

ValueError: output array is read-only

To reiterate, however, you can get a writeable array from the output by passing the view to the np.array() constructor:

In [7]: x = np.array([1, 3, 5, 7])

In [8]: h = np.array(fast_hankel_view(x))

In [9]: h
Out[9]:
array([[1, 3, 5, 7],
       [3, 5, 7, 0],
       [5, 7, 0, 0],
       [7, 0, 0, 0]])

In [10]: h  = 1

In [11]: h
Out[11]:
array([[2, 4, 6, 8],
       [4, 6, 8, 1],
       [6, 8, 1, 1],
       [8, 1, 1, 1]])

Some fun modifications

With a small modification, you can also reflect across the diagonal, if you wish, or pad with another array where the result will be shape n x n where n is whichever is the larger length of the two arrays:

import numpy as np


def fast_hankel_view(x: np.ndarray) -> np.ndarray:
    return np.lib.stride_tricks.sliding_window_view(x, (x.size   1) // 2)


def hankel_view_builder(a: np.ndarray, pad="zeros") -> np.ndarray:
    if type(pad) == np.ndarray:
        arr = np.append(a, pad)
    elif pad == "zeros":
        arr = np.append(a, np.zeros(a.size - 1, a.dtype))
    elif pad == "reflect":
        arr = np.append(a, np.flip(a)[1:])
    else:
        raise ValueError("invalid pad type!")
    return fast_hankel_view(arr)

A nice benefit of appending to two arrays and using sliding_window_view is that you can supply arrays of differing length:

In [11]: x
Out[11]: array([1, 2, 3])

In [12]: y
Out[12]: array([4, 5, 6, 7, 8, 9])

In [13]: hankel_view_builder(x, y)
Out[13]:
array([[1, 2, 3, 4, 5],
       [2, 3, 4, 5, 6],
       [3, 4, 5, 6, 7],
       [4, 5, 6, 7, 8],
       [5, 6, 7, 8, 9]])

In [14]: hankel_view_builder(y, "reflect")
Out[14]:
array([[4, 5, 6, 7, 8, 9],
       [5, 6, 7, 8, 9, 8],
       [6, 7, 8, 9, 8, 7],
       [7, 8, 9, 8, 7, 6],
       [8, 9, 8, 7, 6, 5],
       [9, 8, 7, 6, 5, 4]])

In [15]: hankel_view_builder(y)
Out[15]:
array([[4, 5, 6, 7, 8, 9],
       [5, 6, 7, 8, 9, 0],
       [6, 7, 8, 9, 0, 0],
       [7, 8, 9, 0, 0, 0],
       [8, 9, 0, 0, 0, 0],
       [9, 0, 0, 0, 0, 0]])

CodePudding user response:

For arbitrary arrays where you want to obtain a shift along axis 0:

import numpy as np
a = np.array([1,2,3,4,5])
padded = np.append(np.zeros((1, a.size)), a)
print("padded:")
print(padded)
duplicated = np.tile(padded, a.size)
print("duplicated:")
print(duplicated)
resized = np.resize(duplicated, (a.size 1, padded.size - 1))
print("resized:")
print(resized)
flipped = resized[::-1]
print("flipped:")
print(flipped)
result = flipped[: a.size, :a.size]
print("result:")
print(result)

Output

padded:
[0. 0. 0. 0. 0. 1. 2. 3. 4. 5.]
duplicated:
[0. 0. 0. 0. 0. 1. 2. 3. 4. 5. 0. 0. 0. 0. 0. 1. 2. 3. 4. 5. 0. 0. 0. 0.
 0. 1. 2. 3. 4. 5. 0. 0. 0. 0. 0. 1. 2. 3. 4. 5. 0. 0. 0. 0. 0. 1. 2. 3.
 4. 5.]
resized:
[[0. 0. 0. 0. 0. 1. 2. 3. 4.]
 [5. 0. 0. 0. 0. 0. 1. 2. 3.]
 [4. 5. 0. 0. 0. 0. 0. 1. 2.]
 [3. 4. 5. 0. 0. 0. 0. 0. 1.]
 [2. 3. 4. 5. 0. 0. 0. 0. 0.]
 [1. 2. 3. 4. 5. 0. 0. 0. 0.]]
flipped:
[[1. 2. 3. 4. 5. 0. 0. 0. 0.]
 [2. 3. 4. 5. 0. 0. 0. 0. 0.]
 [3. 4. 5. 0. 0. 0. 0. 0. 1.]
 [4. 5. 0. 0. 0. 0. 0. 1. 2.]
 [5. 0. 0. 0. 0. 0. 1. 2. 3.]
 [0. 0. 0. 0. 0. 1. 2. 3. 4.]]
result:
[[1. 2. 3. 4. 5.]
 [2. 3. 4. 5. 0.]
 [3. 4. 5. 0. 0.]
 [4. 5. 0. 0. 0.]
 [5. 0. 0. 0. 0.]]

CodePudding user response:

arr = np. array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# Convert 1D array to a 2D numpy array of 2 rows and 3 columns.
arr_2d = np. reshape(arr, (2, 5))
print(arr_2d)

You can use reshape function. Populate the array as your requirement and then use reshape.

  • Related