1
\$\begingroup\$

Conceptually, generating item stats is the same as placing balls into buckets. An even distribution of 50 balls into 5 buckets looks like this: [10,10,10,10,10] whereas an uneven distribution might look like this: [37,0,12,0,1]. I want a very uneven distribution but not so uneven that 1 bucket has all the balls. Here are some properties that I desire:

  • Numbers are very unevenly distributed
  • Small numbers (e.g. 0-10, 10-100) appear more often than 0
  • Very large numbers (e.g. those exceeding 50% of total balls) appear frequently (e.g. >20% of the time)

One method I've thought of is this: for each ball, roll a die, if the result is below a threshold then add the ball to the previous bucket, otherwise randomly choose a new bucket. Python code as follows:

from random import randint

stats = [0] * 25
threshold = 9985

cur_bucket = randint(0,len(stats)-1)
for i in range(10000):
    c = randint(0,9999)
    if c > threshold:
        cur_bucket = randint(0,len(stats)-1)
    else:
        stats[cur_bucket] += 1

print(stats)

Example result: [0, 1693, 0, 0, 0, 0, 231, 0, 122, 0, 258, 0, 0, 1422, 0, 0, 2406, 526, 0, 0, 2759, 148, 0, 419, 0]

As you can see there are a lot of 0s and no numbers between 0 and 100, but quite a few numbers in the 1000-3000 range.

Another way is to choose a bucket for each ball where the probability of choosing a bucket is proportional to the number of balls already in the bucket. Python code as follows:

from random import randint

stats = [1]*25
cumsums = stats[:]
def recalc():
    global cumsums
    global tot
    tot = 0
    for i,v in enumerate(stats):
        tot += v
        cumsums[i] = tot

for i in range(10000):
    recalc()
    n = randint(0,tot)
    for j in range(len(cumsums)):
        if cumsums[j] > n:
            stats[j] += 1
            break

print(stats)

Example result: [766, 426, 241, 517, 345, 491, 350, 495, 1073, 8, 127, 740, 2, 91, 883, 208, 1041, 208, 49, 287, 22, 50, 16, 269, 1316]

This method (suggested by a friend) generates a much nicer distribution, notice how there are both small numbers (1, 2, 3, etc) as well as large. You can see all numbers from 0-10, 10-100, 100-1000 and higher up.

But with this method it's also very rare to see numbers above 3000 (I only saw it once in over 30 runs of this program so it does happen but doesn't appear often). Whereas with my previous method, it was quite common to see numbers above 4000.

What other methods are there for generating uneven distributions of numbers for items? Or, how can my existing methods be improved?

\$\endgroup\$
1
  • 2
    \$\begingroup\$ There's as many ways to unevenly distribute the items as there are different distributions. You've come up with a few yourself - what's wrong with what you've tried? Asking for other distributions without more info is overly broad & it's unclear why the solutions you've described are satisfactory. \$\endgroup\$
    – Pikalek
    Aug 9, 2022 at 2:11

2 Answers 2

4
\$\begingroup\$

I recommend weights randomizing with custom weight values.

Given that 10000 is a large number for 25, the random result will be somewhat close to its mathematical expectation, which means that the result will be a bit fixed.

import random

stats = [0] * 25
index_list = [x for x in range(25)]
weights = [x for x in range(25)]
for i in random.choices(index_list, weights=weights, k=10000):
    stats[i]+=1
print(stats)

Result: [0, 35, 90, 105, 146, 160, 197, 214, 270, 301, 307, 339, 405, 471, 444, 527, 529, 546, 612, 642, 679, 692, 683, 794, 812]

Based on this, we can change the generative expression of the weights, like:

weights = [(x/10)**15+500 for x in range(25)]

Or you can specify the weight value as required, like:

weights =[0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.06, 0.09, 0.16, 0.32, 0.69, 1.49, 3.16, 6.52, 13.05, 25.76, 48.00]

Result: [5, 5, 3, 5, 6, 4, 4, 6, 2, 5, 4, 4, 0, 2, 8, 8, 18, 36, 50, 141, 294, 700, 1318, 2527, 4845]

Finally, use random.shuffle to shuffle the order of the list:

random.shuffle(stats)

Result: [4, 8, 18, 141, 6, 4, 36, 2, 6, 4845, 4, 3, 4, 5, 8, 2, 5, 50, 700, 1318, 5, 294, 0, 2527, 5]

\$\endgroup\$
4
  • \$\begingroup\$ Thanks! That's a really clear answer, but what would be a good way of randomly generating unevenly distributed weights? How about using 1/random.random() to generate the weights? \$\endgroup\$ Aug 9, 2022 at 19:18
  • 1
    \$\begingroup\$ @user2108462 Sort your desired result example from smallest to largest. What shape will it be? try to fit a function to these scattered points. Totally random weights may not be what you expect, but it's worth a shot. BTW, the range of random.random() in python is [0,1), you can use 1/(1-random.random()) to avoid division by 0. \$\endgroup\$
    – Mangata
    Aug 9, 2022 at 20:22
  • \$\begingroup\$ How about the Dirichlet distribution? Using weights = sorted([1/x for x in numpy.random.default_rng().dirichlet([1]*25, 1)[0]]) seemed to give some very nice results! \$\endgroup\$ Aug 9, 2022 at 20:53
  • \$\begingroup\$ @user2108462 If the result is what you expect, then it's usable. just choose what you want. Good luck! :D \$\endgroup\$
    – Mangata
    Aug 10, 2022 at 8:07
0
\$\begingroup\$

I may be misinterpreting what you want, but heres what I'd do:

  1. Randomly sort the order of the "buckets"
  2. Loop through the list of buckets
  3. For each bucket get a % of the total remaining "balls"
  4. Subtract that amount from the remaining balls and put the amount in the bucket
  5. Move to next bucket and repeat
  6. In last bucket put all the remaining balls

This way you can set the distribution of the balls via %. If you use a fixed %, then the amount of balls will follow a curve because even though each bucket gets the same percent of balls out of the total, the total decreases after each bucket.

Using a random % within a range makes the curve no longer uniform and even more wild outputs.

Since the order of the buckets is randomized, some buckets will be put into the higher end of the curve and others into the low end meaning some will randomly have more balls than the others.

Edit: Using your 50 balls and 5 buckets example. I will say the % is fixed at 50%.

The random order of buckets is 42153

The number of balls in the first will be 50% of 50 or 25, leaving 25. The number of balls in the second will be 50% of 25, or 12.5, leaving 12.5. In the end the ball amount is 25, 12.5, 6.25, 3.125, 3.125. Now you can match up ball count #1 with bucket in spot #1 so for each bucket:

Bucket 4 has 25, bucket 2 has 12.5, bucket 1 has 6.25, bucket 5 has 3.125, bucket 3 has 3.125. Pretty random distribution.

Using a random % in between 50% and 60% each time:

4 has 58% of 50, 29 balls leaving 21 2 has 51% of 21, 10.71 balls leaving 10.29 1 has 55% of 10.25, 5.66 balls leaving 4.63 5 has 53% of 4.63, 2.45 leaving 2.18 3 has 2.18

The key is really to just randomize the order of the buckets and then iterate through each one removing balls from the pool as you go and dumping the rest into the last one (or you could start over the iteration again)

When you reorder 1-5 back into the correct numerical order it looks like this when graphed: enter image description here

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .