'Random-generate tuples according to probability function

I have a probability density distribution function for the angle combination (theta, phi). Theta and phi can take 360 and 180 values respectively, resulting in a total of 64800 elements, each one with its own specific probability. Is there a way in python to generate random numbers for a 2D n-tuple, as they are generated in random.choices(list, probabilities) for a list?



Solution 1:[1]

Even if the approach mentioned in jonrsharpe's comment, seems somewhat inelegant at first glance,

[…] create the lists of tuples (theta, phi) and their probabilities and use random.choices directly […]

it is surprisingly fast, so that I wouldn't expect “a more pythonic way” to do this.

The following example shows a probability distribution that is easier to grasp:

import random

# Model two dice that fall so that their number of points (a,b) is 
# always close to each other. The approach taken is to subtract the 
# difference of points from the possible maximum (5=6-1).
def make_dice_choices():

    # individual probabilities (rows and columns show a die each):
    #
    #   | 1 2 3 4 5 6
    # --+------------
    # 1 | 5 4 3 2 1 0
    # 2 | 4 5 4 3 2 1
    # 3 | 3 4 5 4 3 2
    # 4 | 2 3 4 5 4 3
    # 5 | 1 2 3 4 5 4
    # 6 | 0 1 2 3 4 5
    def probability(a, b):
        return 5-abs(a-b)
        
    population=[]
    weights=[]
    for a in range(1,7):
        for b in range(1,7):
            population.append((a, b))
            weights.append(probability(a, b))

    # We derive a function from the original function to which 
    # two parameters are already bound.
    return lambda k: random.choices(population, weights, k=k)

# Create samples following the given probability. Scaling the 
# sum (110=30+40+24+12+4) of all probabilities in the table, we 
# can observe the balancing effect of the law of large numbers [1]
# in our actual results. (We expect no couccurences of 1 and 6)
# [1] https://en.wikipedia.org/wiki/Law_of_large_numbers 
def experiment():
    dice_choices = make_dice_choices()
    for scale in (1, 10, 100, 1000):
        roll_count = 110*scale
        rolls = dice_choices(roll_count)

        # Quick "analysis" of results
        roll_delta = {}
        for r in rolls:
            k = abs(r[0]-r[1])
            roll_delta[k] = roll_delta.get(k, 0)+1

        print(f'delta: rolls (of {roll_count})')
        for k in sorted(roll_delta.keys()):
            print(f'{k:5}: {roll_delta[k]}')

experiment()

Here is a sample output of above script:

delta: rolls (of 110)
    0: 32
    1: 41
    2: 18
    3: 14
    4: 5
delta: rolls (of 1100)
    0: 293
    1: 416
    2: 235
    3: 117
    4: 39
delta: rolls (of 11000)
    0: 2947
    1: 3997
    2: 2499
    3: 1167
    4: 390
delta: rolls (of 110000)
    0: 30146
    1: 39766
    2: 23967
    3: 12032
    4: 4089

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1