7

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].

  • currently I use the random module, and choice function that looks like this: range1= np.array([choice(array1),choice(array1),choice(array1),choice(array1),choice(array1)]) then checking range1 to see if it meets the criteria; I'm wondering if there is faster way , something similar to randomArray = np.random.random() instead.

  • Would be even better if I can store this array in some library so that if I try to generate 100 of such array, that there is no repeat but this is not necessary.

1
  • Your constraint that the values add to 1 greatly limits the valid choices. Are duplicates picks allowed? If not, the only valid choice is [0, .1, .2, .3, .4], which should add up close to 1 (floating point rounding may cause problems if you're not careful). If duplicates are allowed, then there's more than one option and the problem isn't trivial. Commented Sep 9, 2013 at 4:09

3 Answers 3

7

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])
Sign up to request clarification or add additional context in comments.

1 Comment

This solution may have issues due to floating point rounding. It will never produce [0.2, 0.2, 0.2, 0, 0.4] (in that order), because when you calculate 1-(0.2+0.2+0.2) you get 0.3999999999999999 rather than 0.4. (This is why my solution uses integers :-). I'm also not sure that this algorithm generates a uniform distribution of solutions. That said, I'm actually not sure how to determine the distribution it produces, so it might do better than I think.
2

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

4 Comments

If you're using numpy already, why not just do numpy.random.choice(source, n, False)?
@Blckknght I hadn't heard of this function before but I think you're right - it's much easier that way.
high for numpy.random.randint is exclusive. You should pass len(indices) instead.
I'm also using something like this that seem to work as well range1 = 1.0* (np.random.random_integers(1,10, size=(1,5))) / 10
2

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

5 Comments

HI Blckknght: this works very well - I don't understand how it works but will study it to learn more. thank you much.
The algorithm is similar to one for making change for a certain value given a set of coin denominations. However, instead of trying to make change with the fewest coins, in this case I'm trying to pick exactly n items that add up to target. The first if is the base case, where there's just one value left to pick. If the target value is available in the list, we pick it! The loop is the recursive case, where you pick one value, then recurse to find a smaller set that add up to the remaining part of the value.
Hi Blckknght, what if order matters? Say if I also want the sequences (0, 0, 0, 1, 9) as well as (9,1,0,0,0)? thanks.
@AlexLee: It's complicated. Do you want each of the unique permutations of the result sequences to be equally likely? If so, a solution like (2,2,2,2,2) that has only a single unique permutation is going to be much rarer than the five unique permutations of (0,0,0,0,10) or the 20 unique permutations of (0,0,0,1,9). My generator can do this if you remove the slice from the recursive step: for r in picks(values, n-1, target-v): (you can get rid of the enumerate call too, if you want). This produces about 33 times as many results though (and may take much more time)!
Thanks Blckknght - you are incredible - people like u makes the internet great. Laters.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.