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.