Janusz
Janusz

Reputation: 183

Fill larger matrix from smaller matrix

I have to fill a larger 2D array with blocks made from a smaller 2D array but still take into account a smaller amount of rows of columns than the width of the block at the edge of the new big 2D array. For example to create the array on the right from the array on the left using a 3x3 block.

[[ 1  2  3  4]         [[  1.   1.   1.   2.   2.   2.   3.   3.   3.   4.   4.]
 [ 5  6  7  8]    ->    [  1.   1.   1.   2.   2.   2.   3.   3.   3.   4.   4.]
 [ 9 10 11 12]]         [  1.   1.   1.   2.   2.   2.   3.   3.   3.   4.   4.]
                        [  5.   5.   5.   6.   6.   6.   7.   7.   7.   8.   8.]
                        [  5.   5.   5.   6.   6.   6.   7.   7.   7.   8.   8.]
                        [  5.   5.   5.   6.   6.   6.   7.   7.   7.   8.   8.]
                        [  9.   9.   9.  10.  10.  10.  11.  11.  11.  12.  12.]]

I implemented this as seen below but I'm looking for a smarter way of doing it.

# Smaller array
a=np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])

# The size of the blocks for filling in the matrix
blockSize = 3

# The number of columns and rows of the new matrix (b)
cols = 11
rows = 7
b = np.zeros([rows, cols])

for i in range(0, rows, blockSize):
    ii = i/blockSize
    if i + blockSize < rows:
        numRows = blockSize
    else:
        numRows = rows - i
    for j in range(0, cols, blockSize):
        jj= j/blockSize
        if j + blockSize < cols:
            numCols = blockSize
        else:
            numCols = cols - j

        b[i:i+numRows,j:j+numCols] = a[ii,jj]

Upvotes: 4

Views: 1781

Answers (3)

Alex Riley
Alex Riley

Reputation: 176880

You could repeat in two stages; once for the columns, once for the rows.

a = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
blockSize = 3
cols = 11
rows = 7

Each time, a list of the number of repetitions for each element is calculated from blockSize and either rows or cols (e.g. [3, 3, 3, 2] for the columns). This creates an array of the required size:

>>> result = a.repeat([blockSize]*(cols//blockSize) + [cols % blockSize], axis=1)
>>> result = result.repeat([blockSize]*(rows//blockSize) + [rows % blockSize], axis=0)
>>> result
array([[ 1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4],
       [ 1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4],
       [ 1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4],
       [ 5,  5,  5,  6,  6,  6,  7,  7,  7,  8,  8],
       [ 5,  5,  5,  6,  6,  6,  7,  7,  7,  8,  8],
       [ 5,  5,  5,  6,  6,  6,  7,  7,  7,  8,  8],
       [ 9,  9,  9, 10, 10, 10, 11, 11, 11, 12, 12]])

An alternative would be to use something like np.kron to create blocks of each element and then slice the array to the required size. However, this creates an array first that may be much larger than needed (and may be inefficient with memory).

np.kron(a, np.ones((blockSize, blockSize)))[:rows, :cols]

Upvotes: 5

Stuart Berg
Stuart Berg

Reputation: 18161

I like @acjr's answer, but here's an alternative method, using scikit-image's nifty view_as_blocks()

import numpy as np
import skimage

a = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])

# Create output array (including columns which will be trimmed later)
result = np.zeros((9,12))

# Make a special view that's addressable by tile index
# This view can be indexed like so:
# result_as_tiles[tile_index_y, tile_index_x, within_tile_y, within_tile_x]
result_as_tiles = skimage.util.view_as_blocks(result, (3,3))
assert result_as_tiles.shape == (3,3) + a.shape

# Now use simple broadcasting to assign each element
# of your input matrix to an entire tile of the output
result_as_tiles[:] = a[:, :, np.newaxis, np.newaxis]

# Finally, slice off the rows/columns you don't need
trimmed_result = result[:7,:11]
print trimmed_result

prints:

[[  1.   1.   1.   2.   2.   2.   3.   3.   3.   4.   4.]
 [  1.   1.   1.   2.   2.   2.   3.   3.   3.   4.   4.]
 [  1.   1.   1.   2.   2.   2.   3.   3.   3.   4.   4.]
 [  5.   5.   5.   6.   6.   6.   7.   7.   7.   8.   8.]
 [  5.   5.   5.   6.   6.   6.   7.   7.   7.   8.   8.]
 [  5.   5.   5.   6.   6.   6.   7.   7.   7.   8.   8.]
 [  9.   9.   9.  10.  10.  10.  11.  11.  11.  12.  12.]]

Upvotes: 0

Kasravnd
Kasravnd

Reputation: 107307

You can use np.repeat to repeat your matrix then use a simple slicing to remove the extra rows and np.delete to remove the column.

So as a more general way you can use following function :

>>> def array_crator(arr,new_shape):
...       repeat_dims=np.divide(new_shape,arr.shape)+1
...       return np.delete(a.repeat(repeat_dims[1],axis=1).repeat(repeat_dims[0],axis=0),np.s_[new_shape[1]:],1)[:new_shape[0]]

Demo:

>>> a=np.arange(1,13).reshape(3,4)
>>> array_crator(a,(7,11))
array([[ 1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4],
       [ 1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4],
       [ 1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4],
       [ 5,  5,  5,  6,  6,  6,  7,  7,  7,  8,  8],
       [ 5,  5,  5,  6,  6,  6,  7,  7,  7,  8,  8],
       [ 5,  5,  5,  6,  6,  6,  7,  7,  7,  8,  8],
       [ 9,  9,  9, 10, 10, 10, 11, 11, 11, 12, 12]])

Another example :

>>> a=np.arange(2,22).reshape(4,5)
>>> a
array([[ 2,  3,  4,  5,  6],
       [ 7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16],
       [17, 18, 19, 20, 21]])
>>> array_crator(a,(7,11))
array([[ 2,  2,  2,  3,  3,  3,  4,  4,  4,  5,  5],
       [ 2,  2,  2,  3,  3,  3,  4,  4,  4,  5,  5],
       [ 7,  7,  7,  8,  8,  8,  9,  9,  9, 10, 10],
       [ 7,  7,  7,  8,  8,  8,  9,  9,  9, 10, 10],
       [12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15],
       [12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15],
       [17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20]])
>>> array_crator(a,(9,17))
array([[ 2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  4,  4,  5,  5,  5,  5,  6],
       [ 2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  4,  4,  5,  5,  5,  5,  6],
       [ 2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  4,  4,  5,  5,  5,  5,  6],
       [ 7,  7,  7,  7,  8,  8,  8,  8,  9,  9,  9,  9, 10, 10, 10, 10, 11],
       [ 7,  7,  7,  7,  8,  8,  8,  8,  9,  9,  9,  9, 10, 10, 10, 10, 11],
       [ 7,  7,  7,  7,  8,  8,  8,  8,  9,  9,  9,  9, 10, 10, 10, 10, 11],
       [12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16],
       [12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16],
       [12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16]])

Upvotes: 2

Related Questions