Extract a block from an 2d array

Viewed 264

Suppose you have a 2D array filled with integers in a continuous manner, going from left to right and top to bottom. Hence it would look like

[[ 0,  1,  2,  3,  4],
 [ 5,  6,  7,  8,  9],
 [10, 11, 12, 13, 14],
 [15, 16, 17, 18, 19]]

Suppose now you have a 1D array of some of the integers shown in the array above. Lets say this array is [6,7,11]. I want to extract the block/chunk of the 2D array that contains the elements of the list. With these two inputs the result should be

[[ 6.,  7.],
 [11., nan]]

(I am padding with np.nan is it cannot be reshaped)

This is what I have written. Is there a simpler way please?

import numpy as np
def my_fun(my_list):
    ids_down = 4
    ids_across = 5
    layout = np.arange(ids_down * ids_across).reshape((ids_down, ids_across))

    ids = np.where((layout >= min(my_list)) & (layout <= max(my_list)), layout, np.nan)
    r,c = np.unravel_index(my_list, ids.shape)
    out = np.nan*np.ones(ids.shape)
    for i, t in enumerate(zip(r,c)):
        out[t] = my_list[i]

    ax1_mask = np.any(~np.isnan(out), axis=1)
    ax0_mask = np.any(~np.isnan(out), axis=0)
    out = out[ax1_mask, :]
    out = out[:, ax0_mask]

    return out

Then trying my_fun([6,7,11]) returns

[[ 6.,  7.],
 [11., nan]]
3 Answers

One approach is to look for the bounding boxes by checking which elements in the array are contained in the second list. We can use scipy.ndimage:

from scipy import ndimage

m = np.isin(a, b)
a_components, _ = ndimage.measurements.label(m, np.ones((3, 3)))
bbox = ndimage.measurements.find_objects(a_components)
out = a[bbox[0]]
np.where(np.isin(out, b), out, np.nan)

array([[ 6.,  7.],
       [11., nan]])

Setup -

a = np.array([[ 0,  1,  2,  3,  4],
              [ 5,  6,  7,  8,  9],
              [10, 11, 12, 13, 14],
              [15, 16, 17, 18, 19]])

b = np.array([6,7,11])

Or for b = np.array([10,12,16]) we'd get:

m = np.isin(a, b)
a_components, _ = ndimage.measurements.label(m, np.ones((3, 3)))
bbox = ndimage.measurements.find_objects(a_components)
out = a[bbox[0]]
np.where(np.isin(out, b), out, np.nan)

array([[10., nan, 12.],
       [nan, 16., nan]])

We could also adapt the above for multiple bounding boxes by doing:

b = np.array([5, 11, 8, 14])

m = np.isin(a, b)
a_components, _ = ndimage.measurements.label(m, np.ones((3, 3)))
bbox = ndimage.measurements.find_objects(a_components)

l = []
for box in bbox:
    out = a[box]
    l.append(np.where(np.isin(out, b), out, np.nan))

print(l)

[array([[ 5., nan],
        [nan, 11.]]), 
 array([[ 8., nan],
        [nan, 14.]])]

This 100% NumPy solution works for both contiguous and non-contiguous arrays of wanted numbers.

a = np.array([[ 0,  1,  2,  3,  4],
              [ 5,  6,  7,  8,  9],
              [10, 11, 12, 13, 14],
              [15, 16, 17, 18, 19]])
n = np.array([6, 7, 11])

Identify the locations of the wanted numbers:

mask = np.isin(a, n)

Select the rows and columns that have the wanted numbers:

np.where(mask, a, np.nan)\
       [mask.any(axis=1)][:, mask.any(axis=0)]
#array([[ 6.,  7.],
#       [11., nan]])

Taking advantage of the specific form of template array A we can directly transform the test values to coordinates:

A = np.arange(20).reshape(4,5)
test = [6,7,11]

y,x = np.unravel_index(test,A.shape)
yl,yr = y.min(),y.max()
xl,xr = x.min(),x.max()
out = np.full((yr-yl+1,xr-xl+1),np.nan)
out[y-yl,x-xl]=test
out
# array([[ 6.,  7.],
#        [11., nan]])
Related