Python: check if columns of array are within boundaries, if not pick a random number within the boundary

919 views Asked by At

I have an array of the form

a = np.array([[1,2],[3,4],[5,6]])

and I have a "domain" or boundary, which is again an array of the form

b = np.array([[0, 4], [3,7]])

Basically I want to check that a[:,0] is within the first row of b and that a[:,1] is within the second row of b. For instance in this example a[:,0]=[1,3,5] and we can see that they all work, except for 5 that is bigger than 4. Similarly a[:,1] = [2,4,6] and so we see that 2 fails because 2<3.

So basically I want 0 <= a[:,0] <= 4 and 3 <= a[:,1]<=7. When a number goes out of this boundaries, I want to basically replace it with a random number within the boundary.

My try

a[:,0][~np.logical_and(b[0][0] <= a[:,0], a[:,0] <= b[0][1])] = np.random.uniform(b[0][0], b[0][1])

a[:,1][~np.logical_and(b[1][0] <= a[:,1], a[:,1] <= b[1][1])] = np.random.uniform(b[1][0], b[1][1])

Is there a faster/better way?

2

There are 2 answers

5
Divakar On BEST ANSWER

Approach #1 : Here's one approach -

# Invalid mask where new values are to be put
mask = (a < b[:,0]) | (a > b[:,1])

# Number of invalid ones per column of a
count = mask.sum(0)

# Get lengths for range limits set by b
lens = b[:,1] - b[:,0]

# Scale for uniform random number generation
scale = np.repeat(lens, count)

# Generate random numbers in [0,1)
rand_num = np.random.rand(count.sum())

# Get offset for each set of random numbers. Scale and add offsets to get
#equivalent of all the original code uniform rand number generation
offset = np.repeat(b[:,0], count)
put_num = rand_num*scale + offset

# Finally make a copy as a float array and assign using invalid mask
out = a.copy().astype(float)
out.T[mask.T] = put_num

Sample run -

In [1004]: a
Out[1004]: 
array([[1, 2],
       [7, 4],
       [5, 6]])

In [1005]: b
Out[1005]: 
array([[ 2,  6],
       [ 5, 12]])

In [1006]: out
Out[1006]: 
array([[ 2.9488404 ,  8.97938277],
       [ 4.51508777,  5.69467752],
       [ 5.        ,  6.        ]])
# limits:  [2, 6]       [5, 12]

Approach #2 : Another approach would be to generate scaled and offsetted random numbers of the same shape as a and simply use np.where alongwith the invalid mask to choose between the generated random numbers and a. The implementation would be a simpler one, like so -

rand_nums = np.random.rand(*a.shape)*(b[:,1] - b[:,0]) + b[:,0]
mask = (a < b[:,0]) | (a > b[:,1])
out = np.where(mask, rand_nums, a)
0
Mengliu On
import numpy as np
a = np.array([[1,2],[3,4],[5,6]])
b = np.array([[0,4], [3,7]])
for iter in range(np.size(a,1)):
    index = np.where(np.logical_or(a[:,iter]<b[0,iter], a[:,iter]>b[1,iter]))
    if len(index)!=0:
        a[index,iter] = np.random.random_integers(b[0,iter], b[1,iter], size=[len(index),1])

This should give you what you need :)