7
$\begingroup$

I need to generate four positive random values in the range [.1, .6] with (at most) two significant digits to the right of the decimal, and which sum to exactly 1. Here are three attempts that do not work.

x = {.15, .35, .1, .4}; While[Total[x] != 1, 
 x = Table[Round[RandomReal[{.1, .6}], .010], 4]];
x = {.25, .25, .25, .25}; While[Total[x] == 1, 
 x = Table[Round[RandomReal[{.1, .6}], .010], 4]];
NestWhileList[Total[x], 
  x = Table[Round[RandomReal[{.1, .6}], .010], 4], 
  Plus @@ x == 1][[1]]
$\endgroup$
2
  • $\begingroup$ Is the sequence {0.10, 0.20, 0.20, 0.50} considered different from {0.50, 0.20, 0.10, 0.20} ? This has the consequence that the population of sequences to be sampled from differs depending on the answer to that question. Your attempt at an answer suggests that the ordering is important (meaning {0.10, 0.20, 0.20, 0.50} is considered different from {0.50, 0.20, 0.10, 0.20} . $\endgroup$
    – JimB
    Sep 28, 2021 at 21:04
  • $\begingroup$ What do you mean by "at most two significant digits"? Do you mean that all four numbers have to be exact integers when multiplied by 100? Or can we generate numbers that have lots of "insignificant" numbers after the decimal place, so long as we round them off before reporting them? (In which case the rounded answers will not always add to exactly 1?) $\endgroup$ Sep 28, 2021 at 21:13

9 Answers 9

2
$\begingroup$

Use Normalize to find a list of random values between 0.1 and 0.6, which are rounded to two decimal places, and that total to 1.

rr = 0;
While[Total[rr] != 1,
  rr = Round[Normalize[RandomReal[{.1, .6}, 4], Total], .01]]

The random values are returned as rr. For example:

SeedRandom[1234];
rr = 0;
While[Total[rr] != 1,
  rr = Round[Normalize[RandomReal[{.1, .6}, 4], Total], .01]]
{Total[rr], rr}

{1., {0.4, 0.27, 0.11, 0.22}}

$\endgroup$
3
  • 7
    $\begingroup$ This solution doesn't guarantee that the numbers are in the [.1, .6] range. $\endgroup$ Sep 27, 2021 at 1:07
  • 3
    $\begingroup$ It also doesn't guarantee that the numbers sum to 1 (due to the rounding). $\endgroup$ Sep 27, 2021 at 15:23
  • $\begingroup$ I think this will work if you change Total[rr] != 1 to Total[rr] != 1 || Min[rr] < 0.1 || Max[rr] > 0.6. (But it's not very efficient.) $\endgroup$
    – JimB
    Sep 28, 2021 at 22:20
10
$\begingroup$

Because you have very tight constraints, the number of allowed points is not very large, so you can generate all of them and then sample.

list = Flatten[
  Table[If[10 <= (100 - i - j - k) <= 60, {i, j, k, 100 - i - j - k}/
     100., Nothing], {i, 10, 60}, {j, 10, 60}, {k, 10, 60}], 2]

Length@list
(* 38831 *)

RandomChoice[list]
(* {0.17, 0.14, 0.4, 0.29} *)

Total@%
(* 1. *)

Not very clever or very efficient method, but it does the job ...

$\endgroup$
3
  • $\begingroup$ +1 Brute force can work just fine! And to counter a claim that has now been deleted: a simple random sample from list does require the individual numbers to have a uniform distribution. The probabilities decrease as the individual values go from 0.10 to 0.60. And the resulting numbers in each position have the same marginal distribution in that Sort[Tally[list[[All, 1]]]] == Sort[Tally[list[[All, 2]]]] == Sort[Tally[list[[All, 3]]]] == Sort[Tally[list[[All, 4]]]]. $\endgroup$
    – JimB
    Sep 27, 2021 at 17:13
  • $\begingroup$ This is just a bit faster (which would allow for "larger" ranges of numbers): list = Flatten[Table[{i, j, k, 100 - i - j - k}, {i, 10, 60}, {j, 10, Min[60, 100 - i - 20]}, {k, Max[10, 100 - i - j - 60], Min[60, 100 - i - j - 10]}], 2]/100.;. $\endgroup$
    – JimB
    Sep 27, 2021 at 17:48
  • 1
    $\begingroup$ Building off of @martin's answer, Flatten[Permutations /@ IntegerPartitions[100, {4}, Range[10, 60]]/100., 1] generates the same list and is significantly faster. $\endgroup$ Sep 28, 2021 at 21:32
5
$\begingroup$
RandomSample[IntegerPartitions[100,{4},Range[10,60]]/100.,1]
$\endgroup$
5
  • 1
    $\begingroup$ Interesting solution! I would, however, add a permutation RandomSample@RandomChoice[IntegerPartitions[100, {4}, Range[10, 60]]/100.] so that all four numbers have equal distributions. Otherwise, the numbers in the list are sorted in descending order. $\endgroup$
    – Domen
    Sep 28, 2021 at 19:49
  • $\begingroup$ I agree that it is not entirely clear from the OP's question whether he just wants a set of four numbers or a random 4D point. I just wanted to point out that, for example, your solution will never return a list {0.1, 0.1, 0.2, 0.6}. $\endgroup$
    – Domen
    Sep 28, 2021 at 20:38
  • $\begingroup$ Your solution also provides a different population to sample from than @Domen 's answer. Your solution has 1,843 unique sequences which are sampled uniformly. Domen's solution has 38,831 unique sequences which when sorted match your 1,843 sequences. But those 1,843 sorted sequences occur unequal numbers of times in the population of 38,831 sequences so the simple random sample from the two candidate populations have different results (even if only the sorted order is considered). So the population from which to sample needs to be specified. $\endgroup$
    – JimB
    Sep 28, 2021 at 20:58
  • 2
    $\begingroup$ @JimB: Domen's list of 38,831 tuples can be generated from this list of 1,843 tuples via Flatten[Permutations /@ IntegerPartitions[100,{4},Range[10,60]]/100., 1]. $\endgroup$ Sep 28, 2021 at 21:20
  • $\begingroup$ @MichaelSeifert Very good! Now the remaining question is "From which of the 2 populations should a simple random sample be drawn?" $\endgroup$
    – JimB
    Sep 28, 2021 at 21:29
4
$\begingroup$
SeedRandom[1]

list = {##, 1 - +##} & @@@ Round[RandomPoint[Simplex[3], 10], .01];

Grid[Prepend[{"4-tuple", "total"}][{#, Total@#} & /@ list ], 
 Dividers -> {False, {True, True, {False}}}]

enter image description here

$\endgroup$
6
  • $\begingroup$ This is unfortunately not a correct solution as some of the values do not lie in the desired range $[0.1, 0.6]$. $\endgroup$
    – Domen
    Sep 26, 2021 at 23:22
  • $\begingroup$ It is, however, a very interesting solution to the problem. With the introduction of a helper function, call it InRange[aList_, lowLimit_, highLimit_] := And @@ (lowLimit <= # <= highLimit & /@ aList) it is trivial to select more elements for list and then choose only those InRange. Select[list, InRange[#, 0.1, 0.6] &] I hadn't known about Simplex before this answer so it was instructive. $\endgroup$
    – Mark R
    Sep 27, 2021 at 0:43
  • $\begingroup$ @Domen, thank you (totally missed the range requirement). $\endgroup$
    – kglr
    Sep 27, 2021 at 10:18
  • 1
    $\begingroup$ Rather than using Simplex[3] in @kglr's answer...if you instead define a simplex in terms of points, such as s = Simplex[{{0.1, 0.1, 0.1}, {0.6, 0.1, 0.1}, {0.1, 0.6, 0.1}, {0.1, 0.1, 0.6}}]; you can then call RandomPoint using s as the input. Everything else in @kglr's answer will still hold, but it will preserve the constraint. $\endgroup$ Sep 27, 2021 at 16:49
  • 1
    $\begingroup$ @JoshuaSchrier, I don't think this works. Sample a lot of points and then look at Histogram[Transpose@list]. You can clearly see that the fourth number has a different distribution (which should not happen as the problem is invariant under permutation), and it even exceeds the upper limit $0.6$. $\endgroup$
    – Domen
    Sep 27, 2021 at 17:10
2
$\begingroup$

I don't think anyone has yet tried FindInstance:

FindInstance[{a + b + c + d == 100 && 10 <= a <= 60 && 10 <= b <= 60 &&
10 <= c <= 60 && 10 <= d <= 60}, {a, b, c, d}, Integers, 2, 
RandomSeeding -> Round@(10^6 RandomReal[])]

If only 1 result is requested, then the result seems to always be {60,20,10,10}. The RandomSeeding option is necessary to avoid repeats. The result then gets divided by 100 to get the sig figs, I think.

Here we have 100 results, 97 of which were unique when I ran it:

results  = 
 0.01 {a, b, c, d} /. 
  FindInstance[{a + b + c + d == 100 && 10 <= a <= 60 && 
     10 <= b <= 60 && 10 <= c <= 60 && 10 <= d <= 60}, {a, b, c, d}, 
   Integers, 100, RandomSeeding -> Round@(10^6 RandomReal[])]
Short[Results]
(* {{0.5, 0.21, 0.19, 0.1}, {0.18, 0.13, 0.11, 0.58}, {0.11, 0.54, 0.18, \
0.17}, <<95>>, {0.17, 0.17, 0.11, 0.55}, {0.3, 0.13, 0.47, 0.1} *)
Sort /@ results // Union // Length
(* 97 *)
Total /@ results // Union
(* {1., 1., 1.} *)

The last result suggests to me that there might be some machine precision roundoff errors.

$\endgroup$
2
  • $\begingroup$ This solution unfortunately does not obey the constraint that the numbers should have (at most) two significant digits. $\endgroup$
    – Domen
    Sep 28, 2021 at 19:52
  • $\begingroup$ @domen I think my update addresses sig figs. $\endgroup$ Sep 28, 2021 at 20:05
1
$\begingroup$

Building on @kglr's response—the way to do this is to incorporate some of the additional constraints into the definition of the region from which sampling occurs (it is a bit more complicated than a Simplex). We can define a region where the 3 variables go over the appropriate range. The implicit fourth variable means that the sum of the 3 cannot be less than 0.4 nor greater than 0.9 (i.e., the fourth variable also goes from 0.1 to 0.6)

h = ImplicitRegion[
   0.4 <= x1 + x2 + x3 <= 0.9,
   {{x1, 0.1, 0.6}, {x2, 0.1, 0.6}, {x3, 0.1, 0.6}}];


RegionPlot3D[h] (*take a look before we proceed*)

the region

Notice how this extra constraint cuts off some corners that would have been present in the simplex...

Now we can continue with @kglr's solution:

list = {##, 1 - +##} & @@@ Round[RandomPoint[h, 10], 0.01];

Grid[
 Prepend[{"4-tuple", "total"}][{#, Total@#} & /@ list], 
 Dividers -> {False, {True, True, {False}}}]

table of results

$\endgroup$
4
  • $\begingroup$ Nope, once again this does not work. Generate a large sample and check MinMax[list]. It will give you {0.09, 0.61}. I have a (gut) feeling that sampling reals and then using Round just cannot give proper results. $\endgroup$
    – Domen
    Sep 27, 2021 at 17:57
  • $\begingroup$ All of the issues with the proposed solution occur in the 4th position (what @Domen mentions). Take a look at Sort[DeleteDuplicates[list[[All, 4]]]]. There's an article back in the 70's about only two-thirds of the table in articles should add up exactly to 100% when rounding (no matter how many decimals are used). So if you see an article with lots of tables (of real numbers rather than integers with constraints), then if all tables sum exactly to 100%, something is fishy. (I'll see if I can dig up that article - either in the American Statistician or the JASA.) $\endgroup$
    – JimB
    Sep 27, 2021 at 18:08
  • $\begingroup$ Yeah, is definitely a rounding error related to the last place, as @JimB says. I suspect that you (@Domen) could just change the inequality bounds so that it rounds the way you desire...or do a little post-processing. $\endgroup$ Sep 27, 2021 at 19:11
  • $\begingroup$ This would be the correct way to do it if the numbers were supposed to add to exactly 1 but be reported out to only two significant figures. (Which honestly makes more sense to me than constraining the values to be exact integers when multiplied by 100, which seems to be how most folks have interpreted the question.) $\endgroup$ Sep 28, 2021 at 21:28
1
$\begingroup$

You could useDirichletDistribution

For example:

rv = Select[{##, 1 - Total[{##}]} & @@@ 
    RandomVariate[DirichletDistribution[{1, 1, 1, 1}], 100000], 
   Min[#] > 0.1 && Max[#] < 0.6 &];

Illustrating the truncated region alluded to in other posts for first three components:

Show[ListPointPlot3D[rv[[All, {1, 2, 3}]], 
  PlotRange -> Table[{0, 1}, 3]], 
 RegionPlot3D[x + y + z < 1, {x, 0, 1}, {y, 0, 1}, {z, 0, 1}, 
  PlotStyle -> Opacity[0.1], Mesh -> None]]
Grid[{##, Total[{##}]} & @@@ rv[[1 ;; 10]], 
 Dividers -> {{{False}, True, False}, None}]

enter image description here

$\endgroup$
-2
$\begingroup$

Strategy:

  1. Offset the target value range by 0.1
  2. Generate 4x random values in the range [0-1]
  3. Rebase range based on sum of values generated
  4. Rebase values to reflect offset (0.1) x4 values => 0.6
  5. Round to 2 digits (excepting floating point error)

Array(4).fill(0).map(X=>Math.random()).map((X,ii,A)=>0.1 + (Math.round((100*X/A.reduce((C,I)=>C+I, 0.0)*0.6)) /100))

I doubt if the distribution is truly random, but it might help, depending on the application.

$\endgroup$
1
  • $\begingroup$ That looks like Python code. This forum is for Wolfram Language related questions. $\endgroup$ Sep 27, 2021 at 16:08
-2
$\begingroup$

If the four numbers are to add to 1, then only three of them can be 'random'.

 In[98]:= sim = {1, 1, 1};
          While[6/10 > Total[sim] || Total[sim] > 9/10, 
             sim = RandomReal[{1/10, 6/10}, 3, WorkingPrecision -> 2]
          ]

In[100]:= sim

Out[100]= {0.18, 0.37, 0.23}

In[101]:= res = Append[sim, 1 - Total[sim]]

Out[101]= {0.18, 0.37, 0.23, 0.22}

In[102]:= Total[res]

Out[102]= 1.0
$\endgroup$
9
  • $\begingroup$ Your statement about randomness is not really correct. Also, unfortunately your solution is not working properly as it can produce $0.09$. $\endgroup$
    – Domen
    Sep 28, 2021 at 17:51
  • $\begingroup$ Just to echo @Domen 's comment: Yes, it is true that with the restriction that the 4 numbers sum to 1, knowing 3 of the numbers will tell you what the 4th number is. But that doesn't mean that the 4th number doesn't come from some probability distribution. You're mixing up a conditional event with an unconditional event. $\endgroup$
    – JimB
    Sep 28, 2021 at 18:41
  • $\begingroup$ @Domen please explain how? $\endgroup$
    – Gosia
    Sep 28, 2021 at 21:08
  • $\begingroup$ @JimB but it is a conditional event, there is condition that all four numbers add up to 1. This does not mean that the 'fourth' number does not have a distribution... $\endgroup$
    – Gosia
    Sep 28, 2021 at 21:13
  • 2
    $\begingroup$ All 4 are discrete random variables with the restriction that they sum to 1. Those random variables are therefore not independent. The random sample is taken from their joint distribution. To say that "only three can be 'random'" does not make sense. All are random variables. If $X\sim \text{Bernoulli}(p)$ would you say that $Y=1-X$ means that only one of $X$ and $Y$ are random variables just because $X+Y=1$? Maybe we're just using a different terminology (random variables vs "numbers being random"? $\endgroup$
    – JimB
    Sep 28, 2021 at 21:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.