Skip to content Skip to sidebar Skip to footer

How To Randomly Shuffle "tiles" In A Numpy Array

I have an nxn numpy array, and I would like to divide it evenly into nxn tiles and randomly shuffle these, while retaining the pattern inside the tiles. For example, if I have an a

Solution 1:

here is my solution using loop

import numpy as np

arr = np.arange(36).reshape(6,6)

defsuffle_section(arr, n_sections):

    assert arr.shape[0]==arr.shape[1], "arr must be square"assert arr.shape[0]%n_sections == 0, "arr size must divideable into equal n_sections"

    size = arr.shape[0]//n_sections


    new_arr = np.empty_like(arr)
    ## randomize section's row index

    rand_indxes = np.random.permutation(n_sections*n_sections)

    for i inrange(n_sections):
        ## randomize section's column indexfor j inrange(n_sections):

            rand_i = rand_indxes[i*n_sections + j]//n_sections
            rand_j = rand_indxes[i*n_sections + j]%n_sections

            new_arr[i*size:(i+1)*size, j*size:(j+1)*size] = \
                arr[rand_i*size:(rand_i+1)*size, rand_j*size:(rand_j+1)*size]

    return new_arr


result = suffle_section(arr, 3)


display(arr)
display(result)

array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23],
       [24, 25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34, 35]])

array([[ 4,  5, 16, 17, 24, 25],
       [10, 11, 22, 23, 30, 31],
       [14, 15,  2,  3,  0,  1],
       [20, 21,  8,  9,  6,  7],
       [26, 27, 12, 13, 28, 29],
       [32, 33, 18, 19, 34, 35]])

Solution 2:

If you have access to skimage (it comes with Spyder) you could use view_as_blocks:

from skimage.util import view_as_blocks

defshuffle_tiles(arr, m, n):
    a_= view_as_blocks(arr,(m,n)).reshape(-1,m,n)
    # shuffle works along 1st dimension and in-place
    np.random.shuffle(a_)
    return a_

Solution 3:

We will use np.random.shuffle alongwith axes permutations to achieve the desired results. There are two interpretations to it. Hence, two solutions.

Shuffle randomly within each block

Elements in each block are randomized and that same randomized order is maintaiined in all blocks.

def randomize_tiles_shuffle_within(a, M, N):
    # M,N are the height and width of the blocks
    m,n = a.shape
    b = a.reshape(m//M,M,n//N,N).swapaxes(1,2).reshape(-1,M*N)
    np.random.shuffle(b.T)
    return b.reshape(m//M,n//N,M,N).swapaxes(1,2).reshape(a.shape)

Shuffle randomly blocks w.r.t each other

Blocks are randomized w.r.t each other, while keeping the order within each block same as in the original array.

def randomize_tiles_shuffle_blocks(a, M, N):    
    m,n = a.shape
    b = a.reshape(m//M,M,n//N,N).swapaxes(1,2).reshape(-1,M*N)
    np.random.shuffle(b)
    return b.reshape(m//M,n//N,M,N).swapaxes(1,2).reshape(a.shape)

Sample runs -

In [47]: a
Out[47]: 
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23],
       [24, 25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34, 35]])

In [48]: randomize_tiles_shuffle_within(a, 3, 3)
Out[48]: 
array([[ 1,  7, 13,  4, 10, 16],
       [14,  8, 12, 17, 11, 15],
       [ 0,  6,  2,  3,  9,  5],
       [19, 25, 31, 22, 28, 34],
       [32, 26, 30, 35, 29, 33],
       [18, 24, 20, 21, 27, 23]])

In [49]: randomize_tiles_shuffle_blocks(a, 3, 3)
Out[49]: 
array([[ 3,  4,  5, 18, 19, 20],
       [ 9, 10, 11, 24, 25, 26],
       [15, 16, 17, 30, 31, 32],
       [ 0,  1,  2, 21, 22, 23],
       [ 6,  7,  8, 27, 28, 29],
       [12, 13, 14, 33, 34, 35]])

Solution 4:

Here is an approach that tries hard to avoid unnecessary copies:

import numpy as np

def f_pp(a,bs):
    i,j = a.shape
    k,l = bs
    esh = i//k,k,j//l,l
    bc = esh[::2]
    sh1,sh2 = np.unravel_index(np.random.permutation(bc[0]*bc[1]),bc)
    ns1,ns2 = np.unravel_index(np.arange(bc[0]*bc[1]),bc)
    out = np.empty_like(a)
    out.reshape(esh)[ns1,:,ns2] = a.reshape(esh)[sh1,:,sh2]
    return out

Timings:

pp 0.41529153706505895
dv 1.3133141631260514
br 1.6034217830747366

Test script (continued)

# Divakar
def f_dv(a,bs):
    M,N = bs
    m,n = a.shape
    b = a.reshape(m//M,M,n//N,N).swapaxes(1,2).reshape(-1,M*N)
    np.random.shuffle(b)
    return b.reshape(m//M,n//N,M,N).swapaxes(1,2).reshape(a.shape)

from skimage.util import view_as_blocks

# Brenlla shape fixed by pp
def f_br(arr,bs):
    m,n = bs
    a_= view_as_blocks(arr,(m,n))
    sh = a_.shape
    a_ = a_.reshape(-1,m,n)
    # shuffle works along 1st dimension and in-place
    np.random.shuffle(a_)
    return a_.reshape(sh).swapaxes(1,2).reshape(arr.shape)

ex = np.arange(100000).reshape(1000,100)
bs = 10,10
tst = np.tile(np.arange(np.prod(bs)).reshape(bs),np.floor_divide(ex.shape,bs))

from timeit import timeit
for n,f in list(globals().items()):
    if n.startswith('f_'):
        assert (tst==f(tst,bs)).all()
        print(n[2:],timeit(lambda:f(ex,bs),number=1000))

Solution 5:

Here's code to shuffle row order but keep row items exactly as is:

import numpy as np 
np.random.seed(0)

#creates a 6x6 array
a = np.random.randint(0,100,(6,6))
a
array([[44, 47, 64, 67, 67,  9],
       [83, 21, 36, 87, 70, 88],
       [88, 12, 58, 65, 39, 87],
       [46, 88, 81, 37, 25, 77],
       [72,  9, 20, 80, 69, 79],
       [47, 64, 82, 99, 88, 49]])

#creates a number for each row index, 0,1,2,3,4,5
order = np.arange(6)

#shuffle index array
np.random.shuffle(order)

#make new array in shuffled order
shuffled = np.array([a[y] for y in order])
shuffled
array([[46, 88, 81, 37, 25, 77],
       [88, 12, 58, 65, 39, 87],
       [83, 21, 36, 87, 70, 88],
       [47, 64, 82, 99, 88, 49],
       [44, 47, 64, 67, 67,  9],
       [72,  9, 20, 80, 69, 79]])

Post a Comment for "How To Randomly Shuffle "tiles" In A Numpy Array"