Reputation: 4949
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.
Upvotes: 7
Views: 9210
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,
>>> 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
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
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