Ahdee
Ahdee

Reputation: 4949

filling numpy array with random element from another array

I'm not sure if this is possible but here goes. Suppose I have an array:

array1 = [0,.1,.2,.3,.4,.5,.6,.7,.8,.9,1]

and now I would like to create a numpy 1D array consisting of 5 elements that are randomly drawn from array1 AND with the condition that the sum is equal to 1. Example is something like, a numpy array that looks like [.2,.2,.2,.1,.1].

Upvotes: 7

Views: 9210

Answers (3)

falsetru
falsetru

Reputation: 368944

You can use numpy.random.choice if you use numpy 1.7.0+:

>>> import numpy as np
>>> array1 = np.array([0,.1,.2,.3,.4,.5,.6,.7,.8,.9,1])
>>> np.random.choice(array1, 5)
array([ 0. ,  0. ,  0.3,  1. ,  0.3])
>>> np.random.choice(array1, 5, replace=False)
array([ 0.6,  0.8,  0.1,  0. ,  0.4])

To get 5 elements that the sum is equal to 1,

  • generate 4 random numbers.
  • substract the sum of 4 numbers from 1 -> x
  • if x included in array1, use that as final number; or repeat

>>> import numpy as np
>>> 
>>> def solve(arr, total, n):
...     while True:
...         xs = np.random.choice(arr, n-1)
...         remain = total - xs.sum()
...         if remain in arr:
...             return np.append(xs, remain)
... 
>>> array1 = np.array([0,.1,.2,.3,.4,.5,.6,.7,.8,.9,1])
>>> print solve(array1, 1, 5)
[ 0.1  0.3  0.4  0.2  0. ]

Another version (assume given array is sorted):

EPS = 0.0000001
def solve(arr, total, n):
    while True:
        xs = np.random.choice(arr, n-1)
        t = xs.sum()
        i = arr.searchsorted(total - t)
        if abs(t + arr[i] - total) < EPS:
            return np.append(xs, arr[i])

Upvotes: 7

Blckknght
Blckknght

Reputation: 104702

If you don't care about the order of the values in the output sequences, the number of 5-value combinations of values from your list that add up to 1 is pretty small. In the specific case you proposed though, it's a bit complicated to calculate, since floating point values have rounding issues. You can more easily solve the issue if you use a set of integers (e.g. range(11))and find combinations that add up to 10. Then if you need the fractional values, just divide the values in the results by 10.

Anyway, here's a generator that yields all the possible sets that add up to a given value:

def picks(values, n, target):
    if n == 1:
        if target in values:
            yield (target,)
        return
    for i, v in enumerate(values):
        if v <= target:
            for r in picks(values[i:], n-1, target-v):
                yield (v,)+r

Here's the results for the numbers zero through ten:

>>> for r in picks(range(11), 5, 10):
    print(r)

(0, 0, 0, 0, 10)
(0, 0, 0, 1, 9)
(0, 0, 0, 2, 8)
(0, 0, 0, 3, 7)
(0, 0, 0, 4, 6)
(0, 0, 0, 5, 5)
(0, 0, 1, 1, 8)
(0, 0, 1, 2, 7)
(0, 0, 1, 3, 6)
(0, 0, 1, 4, 5)
(0, 0, 2, 2, 6)
(0, 0, 2, 3, 5)
(0, 0, 2, 4, 4)
(0, 0, 3, 3, 4)
(0, 1, 1, 1, 7)
(0, 1, 1, 2, 6)
(0, 1, 1, 3, 5)
(0, 1, 1, 4, 4)
(0, 1, 2, 2, 5)
(0, 1, 2, 3, 4)
(0, 1, 3, 3, 3)
(0, 2, 2, 2, 4)
(0, 2, 2, 3, 3)
(1, 1, 1, 1, 6)
(1, 1, 1, 2, 5)
(1, 1, 1, 3, 4)
(1, 1, 2, 2, 4)
(1, 1, 2, 3, 3)
(1, 2, 2, 2, 3)
(2, 2, 2, 2, 2)

You can select one of them at random (with random.choice), or if you plan on using many of them and you don't want to repeat yourself, you can use random.shuffle, then iterate.

results = list(picks(range(11), 5, 10))
random.shuffle(results)

for r in results:
    # do whatever you want with r

Upvotes: 2

David Wurtz
David Wurtz

Reputation: 4378

I had to do something similar a while ago.

def getRandomList(n, source):
    '''
    Returns a list of n elements randomly selected from source.
    Selection is done without replacement.

    '''

    list = source
    indices = range(len(source))
    randIndices = []
    for i in range(n):
        randIndex = indices.pop(np.random.randint(0, high=len(indices)))
        randIndices += [randIndex]

    return [source[index] for index in randIndices]


data = [1,2,3,4,5,6,7,8,9]
randomData = getRandomList(4, data)
print randomData

Upvotes: 2

Related Questions