Generating a list of random numbers, summing to 1
The simplest solution is indeed to take N random values and divide by the sum.
A more generic solution is to use the Dirichlet distribution
which is available in numpy.
By changing the parameters of the distribution you can change the "randomness" of individual numbers
>>> import numpy as np, numpy.random
>>> print np.random.dirichlet(np.ones(10),size=1)
[[ 0.01779975 0.14165316 0.01029262 0.168136 0.03061161 0.09046587
0.19987289 0.13398581 0.03119906 0.17598322]]
>>> print np.random.dirichlet(np.ones(10)/1000.,size=1)
[[ 2.63435230e-115 4.31961290e-209 1.41369771e-212 1.42417285e-188
0.00000000e+000 5.79841280e-143 0.00000000e+000 9.85329725e-005
9.99901467e-001 8.37460207e-246]]
>>> print np.random.dirichlet(np.ones(10)*1000.,size=1)
[[ 0.09967689 0.10151585 0.10077575 0.09875282 0.09935606 0.10093678
0.09517132 0.09891358 0.10206595 0.10283501]]
Depending on the main parameter the Dirichlet distribution will either give vectors where all the values are close to 1./N where N is the length of the vector, or give vectors where most of the values of the vectors will be ~0 , and there will be a single 1, or give something in between those possibilities.
EDIT (5 years after the original answer): Another useful fact about the Dirichlet distribution is that you naturally get it, if you generate a Gamma-distributed set of random variables and then divide them by their sum.
Python - Generate a list of 40 random numbers summing to 1, with min and max weight
Perhaps using np.random.normal would give better results. You could scale down the distribution to the 0.005-0.045 range using the proportion of 80% that is variable (above 0.005). Because normal distributions can still have outliers, it will be necessary to "retry" the calculation if the values go out of bounds (but that shouldn't happen too frequently unless you give a large standard deviation):
import numpy as np
def randStock(count=40,minR=0.005,maxR=0.045,sd=3):
iterations = 0
while True:
iterations += 1
r = np.random.normal(1,sd,count) #normal distribution
r -= min(r) # offset to zero
r /= max(r) # scale to 0..1
r = minR + r/sum(r)*(maxR-minR)/(maxR+minR) # scale to range
if min(r)>=minR and max(r)<=maxR: return r, iterations
Output:
for _ in range(10):
s,i = randStock()
print(*map("{:6.4f}".format,(sum(s),min(s),max(s))),i,"iterations")
[sum] [min] [max] [mean]
1.0000 0.0050 0.0404 0.0250 1 iterations
1.0000 0.0050 0.0409 0.0250 2 iterations
1.0000 0.0050 0.0395 0.0250 1 iterations
1.0000 0.0050 0.0411 0.0250 4 iterations
1.0000 0.0050 0.0410 0.0250 2 iterations
1.0000 0.0050 0.0428 0.0250 1 iterations
1.0000 0.0050 0.0433 0.0250 1 iterations
1.0000 0.0050 0.0424 0.0250 1 iterations
1.0000 0.0050 0.0371 0.0250 1 iterations
1.0000 0.0050 0.0446 0.0250 1 iterations
Note that this could be improved to randomize the lower bound a bit more and you can chose a different standard deviations
Generating random numbers to obtain a fixed sum(python)
This might not be the most efficient way but it will work
totals = [54, 1536, 36, 14]
nums = []
x = np.random.randint(0, i, size=(6,))
for i in totals:
while sum(x) != i: x = np.random.randint(0, i, size=(6,))
nums.append(x)
print(nums)
[array([ 3, 19, 21, 11, 0, 0]), array([111, 155, 224, 511, 457,
78]), array([ 8, 5, 4, 12, 2, 5]), array([3, 1, 3, 2, 1, 4])]
This is a way more efficient way to do this
totals = [54,1536,36,14,9,360, 0]
nums = []
for i in totals:
if i == 0:
nums.append([0 for i in range(6)])
continue
total = i
temp = []
for i in range(5):
val = np.random.randint(0, total)
temp.append(val)
total -= val
temp.append(total)
nums.append(temp)
print(nums)
[[22, 4, 16, 0, 2, 10], [775, 49, 255, 112, 185, 160], [2, 10, 18, 2,
0, 4], [10, 2, 1, 0, 0, 1], [8, 0, 0, 0, 0, 1], [330, 26, 1, 0, 2, 1],
[0, 0, 0, 0, 0, 0]]
How to generate three random numbers, whose sum is 1?
Just get 3 random numbers and then calculate a factor which is 1 / [sum of your numbers]. Finally multiply each of the random numbers with that factor. The sum will be 1.
Generating random floats, summing to 1, with minimum value
IIUC, you want to generate an array of k
values, with minimum value of low=0.055
.
It is easier to generate numbers from 0 that sum up to 1-low*k
, and then to add low
so that the final array sums to 1. Thus, this guarantees both the lower bound and the sum.
Regarding the high
, I am pretty sure it is mathematically impossible to add this constraint as once you fix the lower bound and the sum, there is not enough degrees of freedom to chose an upper bound. The upper bound will be 1-low*(k-1)
(here 0.505).
Also, be aware that, with a minimum value, you necessarily enforce a maximum k
of 1//low
(here 18 values). If you set k
higher, the low bound won't be correct.
# parameters
low = 0.055
k = 10
a = np.random.rand(k)
a = (a/a.sum()*(1-low*k))
weights = a+low
# checking that the sum is 1
assert np.isclose(weights.sum(), 1)
Example output:
array([0.13608635, 0.06796974, 0.07444545, 0.1361171 , 0.07217206,
0.09223554, 0.12713463, 0.11012871, 0.1107402 , 0.07297022])
Generating a list of random numbers, summing to a fixed amount using SQL
While the @Squirrel answer is interesting but numbers here is more random
here is the code:
DECLARE @s INT=1,
@k FLOAT=0,
@final FLOAT=9.917,
@sum FLOAT =0,
@min FLOAT=1,
@max FLOAT=9.917
BEGIN
WHILE (@sum <> @final)
BEGIN
WHILE (@s <= 10)
BEGIN
SET @k =
(
SELECT ROUND(RAND(CHECKSUM(NEWID())) * (@max - @min) + @min,3)
);
PRINT (CONCAT('random: ',@k));
IF(@sum+@k <=@final)
SET @sum+=@k;
SET @max=@final-@sum;
PRINT (CONCAT('computed sum: ',@k));
IF(@max>1) SET @min=1 ELSE SET @min=0;
IF(@sum=@final)
BREAK;
SET @s = @s + 1;
SET @k = @k + 0;
END;
PRINT (CONCAT('final', @final))
PRINT (CONCAT('sum', @sum))
IF(@sum<>@final)--force stop if after 10 try the sum not match with final
BEGIN
PRINT(CONCAT('final random number:',@final-@sum))
SET @sum=@final;
END;
SET @s=0;
IF(@sum=@final)
BEGIN
PRINT('****************************DONE****************************')
BREAK;
END
END;
PRINT ('end');
END;
Generate random list of numbers that add up to 1
One option would be to use generate
to fill the vector with random numbers, then using accumulate
to sum up the values, and finally dividing all the values in the vector by the sum to normalize the sum to one. This is shown here:
std::vector<double> vec(23);
std::generate(vec.begin(), vec.end(), /* some random source */);
const double total = std::accumulate(vec.begin(), vec.end(), 0.0);
for (double& value: vec) value /= total;
Hope this helps!
Related Topics
"Importerror: No Module Named Site" on Windows
What Is the Easiest Way to Remove All Packages Installed by Pip
Find and Replace String Values in List
Remove Punctuation from Unicode Formatted Strings
How to Get Rid of "Unnamed: 0" Column in a Pandas Dataframe Read in from CSV File
Multi-Level Defaultdict with Variable Depth
Understanding Matplotlib.Subplots Python
Class Method Decorator with Self Arguments
Combine Pool.Map with Shared Memory Array in Python Multiprocessing
Selenium Compound Class Names Not Permitted
Why Does Python Code Use Len() Function Instead of a Length Method
Why Is It String.Join(List) Instead of List.Join(String)
How to Manually Install a Pypi Module Without Pip/Easy_Install
How to Remove Nan Value While Combining Two Column in Panda Data Frame
Basic Http File Downloading and Saving to Disk in Python
Matplotlib Colorbar for Scatter