223 votes

Générer des nombres aléatoires avec une distribution (numérique) donnée

Je dispose d'un fichier contenant des probabilités pour différentes valeurs, par exemple :

1 0.1
2 0.05
3 0.05
4 0.2
5 0.4
6 0.2

J'aimerais générer des nombres aléatoires à l'aide de cette distribution. Existe-t-il un module qui gère cela ? C'est assez simple à coder soi-même (construire la fonction de densité cumulative, générer une valeur aléatoire [0,1] et choisir la valeur correspondante) mais il semble que ce soit un problème courant et que quelqu'un ait probablement créé une fonction/module pour cela.

J'ai besoin de cela parce que je veux générer une liste d'anniversaires (qui ne suivent aucune distribution dans le modèle standard). random ).

0voto

msalvadores Points 8768

Vous pouvez jeter un coup d'œil à NumPy Distributions d'échantillonnage aléatoire

0voto

Cris Stringfellow Points 2539

Aucune de ces réponses n'est particulièrement claire ou simple.

Voici une méthode claire et simple dont l'efficacité est garantie.

accumuler_normaliser_les_probabilités prend un dictionnaire p qui associe des symboles à des probabilités OU fréquences. Il produit une liste utilisable de tuples à partir desquels il est possible d'effectuer une sélection.

def accumulate_normalize_values(p):
        pi = p.items() if isinstance(p,dict) else p
        accum_pi = []
        accum = 0
        for i in pi:
                accum_pi.append((i[0],i[1]+accum))
                accum += i[1]
        if accum == 0:
                raise Exception( "You are about to explode the universe. Continue ? Y/N " )
        normed_a = []
        for a in accum_pi:
                normed_a.append((a[0],a[1]*1.0/accum))
        return normed_a

Rendement :

>>> accumulate_normalize_values( { 'a': 100, 'b' : 300, 'c' : 400, 'd' : 200  } )
[('a', 0.1), ('c', 0.5), ('b', 0.8), ('d', 1.0)]

Pourquoi ça marche

Les l'accumulation transforme chaque symbole en un intervalle entre lui-même et la probabilité ou la fréquence des symboles précédents (ou 0 dans le cas du premier symbole). Ces intervalles peuvent être utilisés pour effectuer une sélection (et donc échantillonner la distribution fournie) en parcourant simplement la liste jusqu'à ce que le nombre aléatoire dans l'intervalle 0,0 -> 1,0 (préparé plus tôt) soit inférieur ou égal à l'extrémité de l'intervalle du symbole actuel.

Les normalisation nous libère de la nécessité de nous assurer que chaque chose a une valeur. Après normalisation, le "vecteur" de probabilités est égal à 1,0.

Les reste du code pour la sélection et la génération d'un échantillon arbitrairement long de la distribution est inférieur à :

def select(symbol_intervals,random):
        print symbol_intervals,random
        i = 0
        while random > symbol_intervals[i][1]:
                i += 1
                if i >= len(symbol_intervals):
                        raise Exception( "What did you DO to that poor list?" )
        return symbol_intervals[i][0]

def gen_random(alphabet,length,probabilities=None):
        from random import random
        from itertools import repeat
        if probabilities is None:
                probabilities = dict(zip(alphabet,repeat(1.0)))
        elif len(probabilities) > 0 and isinstance(probabilities[0],(int,long,float)):
                probabilities = dict(zip(alphabet,probabilities)) #ordered
        usable_probabilities = accumulate_normalize_values(probabilities)
        gen = []
        while len(gen) < length:
                gen.append(select(usable_probabilities,random()))
        return gen

Utilisation :

>>> gen_random (['a','b','c','d'],10,[100,300,400,200])
['d', 'b', 'b', 'a', 'c', 'c', 'b', 'c', 'c', 'c']   #<--- some of the time

-1voto

Vaibhav Points 517

Voici un un moyen plus efficace de le faire :

Il suffit d'appeler la fonction suivante avec votre tableau de "poids" (en supposant que les indices sont les éléments correspondants) et le nombre d'échantillons nécessaires. Cette fonction peut être facilement modifiée pour traiter les paires ordonnées.

Renvoie les index (ou éléments) échantillonnés/prélevés (avec remplacement) en utilisant leurs probabilités respectives :

def resample(weights, n):
    beta = 0

    # Caveat: Assign max weight to max*2 for best results
    max_w = max(weights)*2

    # Pick an item uniformly at random, to start with
    current_item = random.randint(0,n-1)
    result = []

    for i in range(n):
        beta += random.uniform(0,max_w)

        while weights[current_item] < beta:
            beta -= weights[current_item]
            current_item = (current_item + 1) % n   # cyclic
        else:
            result.append(current_item)
    return result

Une petite note sur le concept utilisé dans la boucle while. Nous réduisons le poids de l'élément actuel à partir du bêta cumulatif, qui est une valeur cumulative construite uniformément au hasard, et nous incrémentons l'indice actuel afin de trouver l'élément dont le poids correspond à la valeur du bêta.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X