Home > Mobile >  Slicing 2D numpy array periodically
Slicing 2D numpy array periodically

Time:11-09

I have a numpy array of 300x300 where I want to keep all elements periodically. Specifically, for both axes I want to keep the first 5 elements, then discard 15, keep 5, discard 15, etc. This should result in an array of 75x75 elements. How can this be done?

CodePudding user response:

You can created a 1D mask, that carries out the keep/discard function, and then repeat the mask and apply the mask to the array. Here is an example.

import numpy as np

size = 300
array = np.arange(size).reshape((size, 1)) * np.arange(size).reshape((1, size))

mask = np.concatenate((np.ones(5), np.zeros(15))).astype(bool)
period = len(mask)

mask = np.repeat(mask.reshape((1, period)), repeats=size // period, axis=0)
mask = np.concatenate(mask, axis=0)

result = array[mask][:, mask]

print(result.shape)

CodePudding user response:

You can view the array as series of 20x20 blocks, of which you want to keep the upper-left 5x5 portion. Let's say you have

keep = 5
discard = 15

This only works if

assert all(s % (keep   discard) == 0 for s in arr.shape)

First compute the shape of the view and use it:

block = keep   discard
shape1 = (arr.shape[0] // block, block, arr.shape[1] // block, block)
view = arr.reshape(shape1)[:, :keep, :, :keep]

The following operation will create a copy of the data because the view creates a non-contiguous buffer:

shape2 = (shape1[0] * keep, shape1[2] * keep)
result = view.reshape(shape2)

You can compute shape1 and shape2 in a more general manner with something like

shape1 = tuple(
    np.stack((np.array(arr.shape) // block,
              np.full(arr.ndim, block)), -1).ravel())
shape2 = tuple(np.array(shape1[::2]) * keep)

I would recommend packaging this into a function.

CodePudding user response:

Here is my first thought of a solution. Will update later if I think of one with fewer lines. This should work even if the input is not square:

output = []
for i in range(len(arr)):
    tmp = []
    if i % (15 5) < 5:         # keep first 5, then discard next 15
        for j in range(len(arr[i])):
            if j % (15 5) < 5: # keep first 5, then discard next 15
                tmp.append(arr[i,j])
        output.append(tmp)

Update:

Building off of Yang's answer, here is another way which uses np.tile, which repeats an array a given number of times along each axis. This relies on the input array being square in dimension.

import numpy as np

# Define one instance of the keep/discard box
keep, discard = 5, 15
mask = np.concatenate([np.ones(keep), np.zeros(discard)])
mask_2d = mask.reshape((keep discard,1)) * mask.reshape((1,keep discard))

# Tile it out -- overshoot, then trim to match size
count = len(arr)//len(mask_2d)   1
tiled = np.tile(mask_2d, [count,count]).astype('bool')
tiled = tiled[:len(arr), :len(arr)]

# Apply the mask to the input array
dim = sum(tiled[0])
output = arr[tiled].reshape((dim,dim))

CodePudding user response:

Another option using meshgrid and a modulo:

# MyArray = 300x300 numpy array
r      = np.r_[0:300]              # A slide from 0->300
xv, yv = np.meshgrid(r, r)         # x and y grid
mask   = ((xv%20)<5) & ((yv%20)<5) # We create the boolean mask
result = MyArray[mask].reshape((75,75)) # We apply the mask and reshape the final output
  • Related