Vectorizing creation of array of diagonal matrix, [duplicate] - python

This question already has answers here:
Vectorized creation of an array of diagonal square arrays from a liner array in Numpy or Tensorflow
(5 answers)
Closed 3 years ago.
I have a 2d array called diagonals where each row represents the diagonal of a 2d matrix. What's the fastest/best way to create a 3d array diag_matricies where the last two dimensions each consist of a diagonal matrix created using the rows of diagonals?
In a loop this is what I want:
import numpy as np
diag_matricies = np.zeros([3,3,3])
diagonals = np.array([[1,2,3],[4,5,6],[7,8,9]])
for i in range(3):
diag_matricies[i] = np.diag(diagonals[i,:])
print(diag_matricies)

One faster alternative is to use advanced indexing:
index = np.arange(3)
diag_matricies[:, index, index] = diagonals
[[[1. 0. 0.]
[0. 2. 0.]
[0. 0. 3.]]
[[4. 0. 0.]
[0. 5. 0.]
[0. 0. 6.]]
[[7. 0. 0.]
[0. 8. 0.]
[0. 0. 9.]]]
Timing with the size of each dimension being 1200:
from datetime import datetime
N = 1200
diag_matricies = np.zeros([N, N, N])
diagonals = np.arange(N * N).reshape((N, N))
start = datetime.now()
index = np.arange(N)
diag_matricies[:, index, index] = diagonals
print('advanced indexing: ', datetime.now() - start)
start = datetime.now()
for i in range(N):
diag_matricies[i] = np.diag(diagonals[i])
print('for loop: ', datetime.now() - start)
# advanced indexing: 0:00:01.537120
# for loop: 0:00:07.281833

You can use np.einsum:
>>> out = np.zeros((3,3,3))
>>> np.einsum('ijj->ij',out)[...] = diagonals
>>> out
array([[[1., 0., 0.],
[0., 2., 0.],
[0., 0., 3.]],
[[4., 0., 0.],
[0., 5., 0.],
[0., 0., 6.]],
[[7., 0., 0.],
[0., 8., 0.],
[0., 0., 9.]]])
What this does under the hood is more or less the following:
>>> out2 = np.zeros((3,3,3))
>>> out2.reshape(3,9)[:,::4] = diagonals
>>> out2
array([[[1., 0., 0.],
[0., 2., 0.],
[0., 0., 3.]],
[[4., 0., 0.],
[0., 5., 0.],
[0., 0., 6.]],
[[7., 0., 0.],
[0., 8., 0.],
[0., 0., 9.]]])
only the einsum method also works for noncontiguous arrays.

Related

Efficiently imprint an array onto another in Python

I am trying to create a function that imprints a smaller array onto another. The dimensions and center are arbitrary. For example I may want to put a 3x3 on a 5x5 at center (1, 2) or I may want to put a 5x5 on a 100x100 at center (50, 30). Ignoring indexing errors and even arrays that have no center.
Example:
arr1 =
[2, 3, 5]
[1, 5, 6]
[1, 0, 1]
arr2 =
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
arr3 = imprintarray(arr1, arr2, (1, 2))
arr3 =
[0, 2, 3, 5, 0]
[0, 1, 5, 6, 0]
[0, 1, 0, 1, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
I call the first array the smallarray (the one that is imprinted) and the second array to be the map array (the bigger array who has its values modified)
My solution was to create a 3rd array with the target indexes on the the maparray and to iterate through accessing the smallarrays values and changing the elements of the maparray directly.
import numpy as np
maparray = np.zeros((9, 9))
smallarray = np.zeros((3, 3))
smallarray[:] = 2
def createindexarray(dimensions, center):
array = []
adjustment = (dimensions[0] * -0.5) + 0.5
for row in range(dimensions[0]):
elements = []
for col in range(dimensions[1]):
elements.append((row + center[0] + int(adjustment), col + center[1] + int(adjustment)))
array.append(elements)
return array
indexarray = createindexarray((3, 3), (3, 5))
for w, x in enumerate(smallarray):
for y, z in enumerate(x):
maparray[indexarray[w][y][0]][indexarray[w][y][1]] = smallarray[w][y]
It does feel like there should be a better way or more efficient way. I looked through numpy's documentation to see if I could find something like this but I could not find it. Thoughts? Even if you think this is the best way any tips on improving my Python would be much appreciated.
Here is my adjustment to #Shahab Rahnama's solution that accounts for #Mechanic Pig's comment about the shift only working for (3x3) matrices, and my comment about the code not working if the matrix is placed with some elements out of bounds.
import numpy as np
def imprint(center, bigarray, smallarray):
half_height = smallarray.shape[0] // 2
half_width = smallarray.shape[1] // 2
top = center[0] - half_height
bottom = center[0] + half_height + 1
left = center[1] - half_width
right = center[1] + half_width + 1
bigarray[ \
max(0, top): \
min(bigarray.shape[0], bottom),
max(0, left): \
min(bigarray.shape[1], right)
] = smallarray[ \
(top < 0) * abs(top): \
smallarray.shape[0] - (bottom > bigarray.shape[0]) * (bottom - bigarray.shape[0]), \
(left < 0) * abs(left): \
smallarray.shape[1] - (right > bigarray.shape[1]) * (right - bigarray.shape[1]) \
]
return bigarray
bigarray = np.zeros((7, 7))
smallarray = 2 * np.ones((5, 5))
imprint((3, 3), bigarray, smallarray)
Create index arrays using np.meshgrid and the smallarray's shape.
bigarray = np.zeros((9, 9))
smallarray = np.zeros((3, 3)) + 2
x,y = smallarray.shape
xv,yv = np.meshgrid(np.arange(x),np.arange(y))
#print(xv)
#print(yv)
Those can be used on the left-hand-side of an assignment to imprint the smallarray. Without modification the smallarray will be positioned in the upper-left corner.
bigarray[xv,yv] = smallarray
print(bigarray)
[[2. 2. 2. 0. 0. 0. 0. 0. 0.]
[2. 2. 2. 0. 0. 0. 0. 0. 0.]
[2. 2. 2. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]]
The position can be changed by adding a scalar to the index arrays.
bigarray[xv+2,yv+3] = smallarray + 4
print(bigarray)
[[2. 2. 2. 0. 0. 0. 0. 0. 0.]
[2. 2. 2. 0. 0. 0. 0. 0. 0.]
[2. 2. 2. 6. 6. 6. 0. 0. 0.]
[0. 0. 0. 6. 6. 6. 0. 0. 0.]
[0. 0. 0. 6. 6. 6. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]]
The scalars will be the index in the bigarray where the [0,0] point of the smallarray will be placed. If the function needs to accept the position of the center of the smallarray just use its shape to calculate the appropriate scalar. Also use both arrays' shape to calculate whether the small one will fit then adjust.
The index arrays can also be made with .indices.
xv,yv = np.indices(smallarray.shape)
Ignoring your requirement for "center" alignment1, let's say you want to place the values of some array a1 into another array a2 with some offset offset. offset is the coordinate in a2 where a1[0, 0, ..., 0] ends up. The values can be negative without wrapping.
The sizes of the arrays are irrelevant. We only need to assume that the types are at least somewhat compatible, and that a1.ndim == a2.ndim == len(offset). Let's call the number of dimensions d.
The goal is to be able to create a tuple of slices for each array, so that the assignment becomes a simple indexing operation:
a2[index2] = a1[index1]
So let's start writing this function:
def emplace(a1, a2, offset):
offset = np.asanyarray(offset)
if a1.ndim != a2.ndim or a1.ndim != len(offset) or len(offset) != offset.size:
raise ValueError('All dimensions must match')
First let's filter out the cases where there is no overlap at all:
if a1.size == 0 or a2.size == 0:
return
if (offset + a1.shape <= 0).any() or (offset >= a2.shape).any():
return
Now that you know that there will be overlap, you can compute the starting indices for both arrays. If offset is negative, that truncates leading elements from a1. If it's positive, it skips elements in a2.
start1 = np.where(offset < 0, -offset, 0)
start2 = np.where(offset < 0, 0, -offset)
The ends can be computed in a similar manner, except that the bound check is done on the opposite end now:
stop1 = np.where(offset + a1.shape > a2.shape, a2.shape - offset, a1.shape)
stop2 = np.where(offset + a1.shape > a2.shape, a2.shape, offset + a1.shape)
You can construct tuples of indices from the bounds:
index1 = tuple(slice(*bounds) for bounds in np.stack((start1, stop1), axis=-1))
index2 = tuple(slice(*bounds) for bounds in np.stack((start2, stop2), axis=-1))
And so the final emplacement becomes just a simple assignment:
a2[index2] = a1[index1]
TL;DR
Here is how I would write the full function:
def emplace(a1, a2, offset):
offset = np.asanyarray(offset)
if a1.ndim != a2.ndim or a1.ndim != len(offset) or len(offset) != offset.size:
raise ValueError('All dimensions must match')
if a1.size == 0 or a2.size == 0:
return
if (offset + a1.shape <= 0).any() or (offset >= a2.shape).any():
return
noffset = -offset
omask = offset < 0
start1 = np.where(omask, noffset, 0)
start2 = np.where(omask, 0, noffset)
omask = offset + a1.shape > a2.shape
stop1 = np.where(omask, a2.shape - offset, a1.shape)
stop2 = np.where(omask, a2.shape, offset + a1.shape)
index1 = tuple(slice(*bounds) for bounds in np.stack((start1, stop1), axis=-1))
index2 = tuple(slice(*bounds) for bounds in np.stack((start2, stop2), axis=-1))
a2[index2] = a1[index1]
1 I'm not suggesting you actually ignore how you want to do things. Just convert the center alignment to a corner offset.
Yes, there is a better way :
import numpy as np
maparray = np.zeros((9, 9))
smallarray = np.zeros((3, 3))
smallarray[:] = 2
def imprint(center, maparray, smallarray):
maparray[center[0]:center[0]+smallarray.shape[0],center[1]:center[1]+smallarray.shape[1]] = smallarray
return maparray
print(imprint((3, 5), maparray, smallarray))
I used roll to overcome out-of-bounds (if the center is on the edge like [7,7] and [4,7], the smallarray will be out-of-bounds):
import numpy as np
maparray = np.zeros((9, 9))
smallarray = np.zeros((3, 3))
smallarray[:] = 2
def imprint(center, maparray, smallarray):
rollx= 0
rolly= 0
if maparray.shape[0] - center[0] < smallarray.shape[0]:
rollx = maparray.shape[0] - center[0] - smallarray.shape[0]
center[0] = center[0] - abs(rollx )
if maparray.shape[1] - center[1] < smallarray.shape[1]:
rolly = maparray.shape[1] - center[1] - smallarray.shape[1]
center[1] = center[1] - abs(rolly )
maparray[center[0]:center[0]+smallarray.shape[0],center[1]:center[1]+smallarray.shape[1]] = smallarray
return np.roll(maparray, (abs(rollx ), abs(rolly )), axis=(0, 1))
print(imprint([7, 7], maparray, smallarray))
Output:
[[2. 0. 0. 0. 0. 0. 0. 2. 2.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0.]
[2. 0. 0. 0. 0. 0. 0. 2. 2.]
[2. 0. 0. 0. 0. 0. 0. 2. 2.]]
Given:
import numpy as np
maparray = np.zeros((9, 9))
smallarray = np.ones((3, 3)) * 2
We can simply assign to a slice:
# to assign with the top-left at (1, 2)
maparray[1:4, 2:5] = smallarray
which we can generalize:
def imprint(big, small, position):
y, x = position
h, w = small.shape
big[y:y+h, x:x+w] = small
If we need to wrap around the outside, then we can take a different approach, still without needing any conditional logic. Simply roll the big array such that the "imprinting" location is at the top-left; assign the values; then roll back. Thus:
def imprint(big, small, position):
y, x = position
h, w = small.shape
copy = np.roll(big, (-y, -x), (0, 1))
copy[:h, :w] = small
big[:, :] = np.roll(copy, (y, x), (0, 1))
Let's test it:
>>> imprint(maparray, smallarray, (7, 7))
>>> maparray
array([[2., 0., 0., 0., 0., 0., 0., 2., 2.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[2., 0., 0., 0., 0., 0., 0., 2., 2.],
[2., 0., 0., 0., 0., 0., 0., 2., 2.]])
>>> imprint(maparray, smallarray, (2, 2))
>>> maparray
array([[2., 0., 0., 0., 0., 0., 0., 2., 2.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 2., 2., 2., 0., 0., 0., 0.],
[0., 0., 2., 2., 2., 0., 0., 0., 0.],
[0., 0., 2., 2., 2., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[2., 0., 0., 0., 0., 0., 0., 2., 2.],
[2., 0., 0., 0., 0., 0., 0., 2., 2.]])
We see that we can correctly leave the results from previous imprints in place, and also wrap around the edge.
It also works with negative indices and large indices, wrapping around in the expected manner:
>>> maparray[:] = 0 # continuing from before
>>> imprint(maparray, smallarray, (-1, -1))
>>> maparray
array([[2., 2., 0., 0., 0., 0., 0., 0., 2.],
[2., 2., 0., 0., 0., 0., 0., 0., 2.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[2., 2., 0., 0., 0., 0., 0., 0., 2.]])
>>> # Keep in mind the first coordinate is for the first axis, which
>>> # displays vertically here (hence labelling it `y` in the code).
>>> imprint(maparray, smallarray, (40, 0))
>>> maparray
array([[2., 2., 0., 0., 0., 0., 0., 0., 2.],
[2., 2., 0., 0., 0., 0., 0., 0., 2.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[2., 2., 2., 0., 0., 0., 0., 0., 0.],
[2., 2., 2., 0., 0., 0., 0., 0., 0.],
[2., 2., 2., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[2., 2., 0., 0., 0., 0., 0., 0., 2.]])
It will presumably be more efficient to use conditional logic explicitly (left as an exercise here), but this approach is elegant and easy to understand.

Python: Fill out edges of binary array

I'm using the following code to generate an array based on coordinates of edges:
verts = np.array(list(itertools.product((0,2), (0,2))))
arr = np.zeros((5, 5))
arr[tuple(verts.T)] = 1
plt.imshow(arr)
which gives me
or, as a numeric array:
[[1., 0., 1., 0., 0.],
[0., 0., 0., 0., 0.],
[1., 0., 1., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]]
Now, I would like to fill out the spaces in between the corners (ie. yellow squares):
so that I get the following array:
[[1., 1., 1., 0., 0.],
[1., 1., 1., 0., 0.],
[1., 1., 1., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]]
Replace (0,2) using range(0,3) (3 as ranges are inclusive-exclusive) that is
import itertools
import numpy as np
verts = np.array(list(itertools.product(range(0,3), range(0,3))))
arr = np.zeros((5, 5))
arr[tuple(verts.T)] = 1
print(arr)
output
[[1. 1. 1. 0. 0.]
[1. 1. 1. 0. 0.]
[1. 1. 1. 0. 0.]
[0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0.]]

how to access a matrix and increase specific column?

For example, I have this matrix, and I need to access the second column and increase it by 2:
m = [[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
You can do that just by accessing the 2nd column and incrementing the value. You can do that by doing this : m[:, 1] = m[:, 1] + 2
It means that you are ignoring all the rows and specifying the columns. Here, 1 refers to the 2nd column.
You can do this by using numpy library which allows you to easily do such thing.
Import numpy as import numpy as np
Convert the 2d list into numpy array
m = np.array([
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]
])
Now apply the conditioning
m[:, 1] = m[:, 1] + 2
Print the output.
print("M: ", m)
Combined Code:
import numpy as np
m = np.array([
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]
])
m[:, 1] = m[:, 1] + 2
print("M: ", m)
So, you need to increase the second element of each row by 2. You could achieve this by a for loop.
for row in m:
row[1] += 2
You could convert the matrix into a numpy array. Just in case you're looking at exploiting the optimisations that this library offers
import numpy as np
m = np.array([
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]
])
m[:, 1] += 1

Update a matrix through advanced indexing/vectorizing

I have a matrix of what is effectively counters. I would like to increment those counters based on a list of column indices - with each positional index also corresponding to a row increment.
This is straightforward with a for loop, and a little less straightforward with list comprehension. In either case, iteration is involved. But I was wondering if there is any way to vectorise this problem?
The minimal problem is:
counters = np.zeros((4,4))
counters
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
update_columns = [1,0,2,2]
for row, col in zip(range(len(update_columns)), update_columns):
counters[row, col] += 1
counters
array([[0., 1., 0., 0.],
[1., 0., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.]])
What you are looking for is called advanced numpy indexing. You can pass the row index using np.arange and column index using update_columns:
update_columns = np.array(update_columns)
counters[np.arange(update_columns.size), update_columns] += 1
output:
[[0. 1. 0. 0.]
[1. 0. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 1. 0.]]

Filling of 2D array columns according to 1D counter array

I'm looking for a numpy solution to fill each column in a 2D array ("a" in the example below) with a number of "1" values as defined in a different 1D counter array ("cnt" in the example below).
I have tried the following:
import numpy as np
cnt = np.array([1, 3, 2, 4]) # cnt array: how much elements per column are 1
a = np.zeros((5, 4)) # array that must be filled with 1s per column
for i in range(4): # for each column
a[:cnt[i], i] = 1 # all elements from top to cnt value are filled
print(a)
and gives the desired output:
[[1. 1. 1. 1.]
[0. 1. 1. 1.]
[0. 1. 0. 1.]
[0. 0. 0. 1.]
[0. 0. 0. 0.]]
Is there an easier (and faster) way with a numpy routine to do this without having a loop per column?
a = np.full((5, 4), 1, cnt)
Something like the above would be nice, but is not working.
Thank you for your time!
You can use np.where and broadcasting like so:
>>> import numpy as np
>>>
>>> cnt = np.array([1, 3, 2, 4]) # cnt array: how much elements per column are 1
>>> a = np.zeros((5, 4)) # array that must be filled with 1s per column
>>>
>>> res = np.where(np.arange(a.shape[0])[:, None] < cnt, 1, a)
>>> res
array([[1., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 0., 1.],
[0., 0., 0., 1.],
[0., 0., 0., 0.]])
Or in-place:
>>> a[np.arange(a.shape[0])[:, None] < cnt] = 1
>>> a
array([[1., 1., 1., 1.],
[0., 1., 1., 1.],
[0., 1., 0., 1.],
[0., 0., 0., 1.],
[0., 0., 0., 0.]])

Categories