number generator

Duncan Smith buzzard at urubu.freeserve.co.uk
Tue Mar 13 22:24:06 EDT 2007


greg wrote:
> Gabriel Genellina wrote:
> 
>> The 5th number is not "random".
> 
> 
> More precisely, the fifth number is not *independent*
> of the others. You can't have five independent random
> numbers that sum to 50; only four independent numbers
> plus a dependent one.
> 
> -- 
> Greg

In the interests of precision, that should probably read "are
constrained to sum to 50"; it's quite possible to generate a sequence of
independent random variates that just happen to sum to 50 (as I'm sure
you know).

A fairly efficient way of generating random multinomial variates (which
would satisfy the OP's problem) is based on generating sequences of
independent Poisson variates (with suitably chosen parameters) and then
incrementing the counts in an appropriate fashion.  A fair chunk of code
is pasted below.  (If anyone knows of any more efficient methods I'd be
interested.)

Duncan

----------------------------------------------------

import math
import random

def multinomial_variate(probs, n):
    """
    Returns a random multinomial variate
    with parameters I{probs} and I{n}.

    @type  probs: sequence of C{floats}
    @param probs: multinomial probabilities
    @type      n: C{int}
    @param     n: total
    @rtype:       C{list} of C{floats}
    @return:      multinomial variate
    """
    k = len(probs)
    root_n = round(n**0.5)
    if k > root_n:
        lambda_hat = n - root_n - (k - root_n)**0.5
    else:
        lambda_hat = n - k
    gens = [gen_poisson(p * lambda_hat) for p in probs]
    gen_obs = gen_discrete(probs)
    while True:
        samp = [gen.next() for gen in gens]
        tot = sum(samp)
        if tot == n:
            break
        elif tot < n:
            for _ in xrange(n - tot):
                samp[gen_obs.next()] += 1
            break
    return samp

def _alias_table(probs):
    """
    Returns lists of aliases and cutoffs (see Kronmal and
    Peterson, 1979, I{The American Statistician}, Vol.33,
    No.4., pp. 214-218).

    @type  probs: sequence of I{floats}
    @param probs: sequence of probabilities
    @rtype:       C{tuple} of C{lists}
    @return:      a C{list} of aliases and a C{list}
                  of cutoffs
    """
    n = len(probs)
    r = [n * p for p in probs] # mean of r is 1
    a = [0] * n
    G = set()
    S = set()
    # partition r into values lower and higher than mean
    for i, val in enumerate(r):
        if val < 1:
            S.add(i)
        else:
            G.add(i)
    while True:
        try:
            j, k = S.pop(), G.pop()
        except KeyError:
            break
        a[j] = k
        r[k] += r[j] - 1
        if r[k] < 1:
            S.add(k)
        else:
            G.add(k)
    return a, r

def gen_discrete(probs):
    """
    Generates discrete (0, 1, ..., I{n}-1) variates from the
    density defined by I{probs} by the alias method.

    @type   probs: sequence of C{floats}
    @param  probs: probabilities of corresponding values
    @rtype:        generator of I{ints}
    @return:       generator of independent discrete variates
    """
    a, r = _alias_table(probs)
    n = len(probs)
    rand = random.random
    while True:
        rnd_n = rand() * n
        i = int(rnd_n)
        if rnd_n - i < r[i]:
            yield i
        else:
            yield a[i]

def gen_poisson(lambd):
    """
    Generates random Poisson variates with parameter I{lambd}.

    @type   lambd: C{float}
    @param  lambd: mean of Poisson pmf
    @rtype:        generator of I{ints}
    @return:       generator of independent Poisson variates
    """
    if lambd < 10:
        rand = random.random
        L = math.exp(-lambd)
        while True:
            k = 0
            p = 1
            while p >= L:
                k += 1
                p *= rand()
            yield k - 1
    else:
        alpha = int(7 * lambd / 8)
        gamma_var = random.gammavariate
        while True:
            X = gamma_var(alpha, 1)
            if X < lambd:
                new_lambd = lambd - X
                yield alpha + poisson_variate(new_lambd)
            else:
                yield binomial_variate(alpha - 1, lambd / X)

def poisson_variate(lambd):
    """
    Returns a random Poisson variate with parameter I{lambd}.

    @type   lambd: C{float}
    @param  lambd: mean of Poisson pmf
    @rtype:        I{int}
    @return:       Poisson variate
    """
    if lambd < 10:
        rand = random.random
        L = math.exp(-lambd)
        k = 0
        p = 1
        while p >= L:
            k += 1
            p *= rand()
        return k - 1
    else:
        a = int(7 * lambd / 8)
        X = random.gammavariate(a, 1)
        if X < lambd:
            new_lambd = lambd - X
            return a + poisson_variate(new_lambd)
        else:
            return binomial_variate(a - 1, lambd / X)

def binomial_variate(n, p):
    """
    Returns a random binomial variate with parameters I{n}
    and I{p}.

    @type  n: C{int}
    @param n: number of trials
    @type  p: C{float}
    @param p: probability of success
    @rtype:   I{int}
    @return:  number of successful trials
    """
    if n < 10:
        rand = random.random
        res = 0
        for _ in range(n):
            if rand() < p:
                res += 1
        return res
    else:
        a = 1 + n // 2
        b = n - a + 1
        X = random.betavariate(a, b)
        if X >= p:
            return binomial_variate(a - 1, p / X)
        else:
            return a + binomial_variate(b - 1, (p - X) / (1 - X))

------------------------------------------------------



More information about the Python-list mailing list