Generieren Sie Zufallszahlen mit einer gegebenen (numerischen) Verteilung

Ich habe eine Datei mit einigen Wahrscheinlichkeiten für verschiedene Werte zB:

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

Ich möchte mit dieser Verteilung zufällige Zahlen generieren. Gibt es ein bestehendes Modul? Es ist ziemlich einfach, auf eigene Faust zu kodieren (bau die kumulative Dichtefunktion, generiere einen zufälligen Wert [0,1] und wähle den entsprechenden Wert) aber es scheint, dass dies ein häufiges Problem sein sollte und wahrscheinlich jemand eine Funktion / Modul für erstellt hat es.

Ich brauche das, weil ich eine Liste von Geburtstagen generieren möchte (die keiner Verteilung im Standard- random folgen).

12 Solutions collect form web for “Generieren Sie Zufallszahlen mit einer gegebenen (numerischen) Verteilung”

scipy.stats.rv_discrete könnte sein, was du willst. Sie können Ihre Wahrscheinlichkeiten über den values . Sie können dann die rvs() Methode des Verteilungsobjekts verwenden, um Zufallszahlen zu generieren.

Wie von Eugene Pakhomov in den Kommentaren hervorgehoben, können Sie auch einen p Schlüsselwort-Parameter an numpy.random.choice() , zB

 numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2]) 

Ein Vorteil für die Erstellung der Liste mit CDF ist, dass Sie die binäre Suche verwenden können. Während du O (n) Zeit und Raum für die Vorverarbeitung benötigst, kannst du k Zahlen in O (k log n) bekommen. Da normale Python-Listen ineffizient sind, können Sie array Modul verwenden.

Wenn Sie auf konstantem Raum bestehen, können Sie folgendes tun; O (n) Zeit, O (1) Raum.

 def random_distr(l): r = random.uniform(0, 1) s = 0 for item, prob in l: s += prob if s >= r: return item return item # Might occur because of floating point inaccuracies 

(Ok, ich weiß, dass du nach Schrumpffolie gefragt hast, aber vielleicht waren die heimischen Lösungen einfach nicht kurz genug für deine Lust.

 pdf = [(1, 0.1), (2, 0.05), (3, 0.05), (4, 0.2), (5, 0.4), (6, 0.2)] cdf = [(i, sum(p for j,p in pdf if j < i)) for i,_ in pdf] R = max(i for r in [random.random()] for i,c in cdf if c <= r) 

Ich habe pseudo-bestätigt, dass dies funktioniert durch Augapfel die Ausgabe dieses Ausdrucks:

 sorted(max(i for r in [random.random()] for i,c in cdf if c <= r) for _ in range(1000)) 

Vielleicht ist es spät. Aber du kannst verwenden:

Numpy.random.choice ()

http://docs.scipy.org/doc/numpy/reference/generated/numpy.random.choice.html#numpy.random.choice

Seit Python 3.6 gibt es eine Lösung dafür in der Python-Standardbibliothek, nämlich random.choices .

Beispielverwendung: Lassen Sie uns eine Population und Gewichte, die mit denen in der OP-Frage übereinstimmen:

 >>> from random import choices >>> population = [1, 2, 3, 4, 5, 6] >>> weights = [0.1, 0.05, 0.05, 0.2, 0.4, 0.2] 

Jetzt choices(population, weights) erzeugt eine einzelne Probe:

 >>> choices(population, weights) 4 

Das optionale Keyword-only-Argument k erlaubt es, mehr als ein Sample gleichzeitig anzufordern. Das ist wertvoll, denn es gibt einige vorbereitende Arbeiten, die random.choices jedes Mal, wenn es genannt wird, vor dem Erzeugen von Samples machen muss; Durch die Erzeugung von vielen Proben auf einmal, müssen wir nur diese Vorbereitungsarbeiten einmal machen. Hier erzeugen wir eine Million Proben und verwenden collections.Counter , um zu überprüfen, dass die Verteilung, die wir grob mit den Gewichten übereinstimmen, die wir gaben.

 >>> million_samples = choices(population, weights, k=10**6) >>> from collections import Counter >>> Counter(million_samples) Counter({5: 399616, 6: 200387, 4: 200117, 1: 99636, 3: 50219, 2: 50025}) 

Vielleicht möchten Sie einen Blick auf NumPy Random Sampling Distributionen

Machen Sie eine Liste von Artikeln, basierend auf ihren weights :

 items = [1, 2, 3, 4, 5, 6] probabilities= [0.1, 0.05, 0.05, 0.2, 0.4, 0.2] # if the list of probs is normalized (sum(probs) == 1), omit this part prob = sum(probabilities) # find sum of probs, to normalize them c = (1.0)/prob # a multiplier to make a list of normalized probs probabilities = map(lambda x: c*x, probabilities) print probabilities ml = max(probabilities, key=lambda x: len(str(x)) - str(x).find('.')) ml = len(str(ml)) - str(ml).find('.') -1 amounts = [ int(x*(10**ml)) for x in probabilities] itemsList = list() for i in range(0, len(items)): # iterate through original items itemsList += items[i:i+1]*amounts[i] # choose from itemsList randomly print itemsList 

Eine Optimierung kann sein, um die Mengen durch den größten gemeinsamen Teiler zu normalisieren, um die Zielliste kleiner zu machen.

Auch das könnte interessant sein

Eine andere Antwort, wohl schneller 🙂

 distribution = [(1, 0.2), (2, 0.3), (3, 0.5)] # init distribution dlist = [] sumchance = 0 for value, chance in distribution: sumchance += chance dlist.append((value, sumchance)) assert sumchance == 1.0 # not good assert because of float equality # get random value r = random.random() # for small distributions use lineair search if len(distribution) < 64: # don't know exact speed limit for value, sumchance in dlist: if r < sumchance: return value else: # else (not implemented) binary search algorithm 

Basierend auf anderen Lösungen, erzeugen Sie akkumulative Verteilung (als Integer oder Float was auch immer Sie mögen), dann können Sie bisect verwenden, um es schnell zu machen

Dies ist ein einfaches Beispiel (ich habe ganze Zahlen hier verwendet)

 l=[(20, 'foo'), (60, 'banana'), (10, 'monkey'), (10, 'monkey2')] def get_cdf(l): ret=[] c=0 for i in l: c+=i[0]; ret.append((c, i[1])) return ret def get_random_item(cdf): return cdf[bisect.bisect_left(cdf, (random.randint(0, cdf[-1][0]),))][1] cdf=get_cdf(l) for i in range(100): print get_random_item(cdf), 

Die get_cdf Funktion würde sie von 20, 60, 10, 10 in 20, 20 + 60, 20 + 60 + 10, 20 + 60 + 10 + 10 umwandeln

Jetzt wählen wir eine zufällige Zahl bis zu 20 + 60 + 10 + 10 mit random.randint dann verwenden wir bisect, um den tatsächlichen Wert auf eine schnelle Weise zu bekommen

Keine dieser Antworten ist besonders klar oder einfach.

Hier ist eine klare, einfache Methode, die garantiert zu arbeiten ist.

Accumulate_normalize_probabilities nimmt ein Wörterbuch p , das Symbole auf Wahrscheinlichkeiten ODER- Frequenzen abbildet. Es gibt eine verwendbare Liste von Tupeln aus, aus denen die Auswahl erfolgen soll.

 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 

Erträge:

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

Warum es funktioniert

Der Akkumulationsschritt verwandelt jedes Symbol in ein Intervall zwischen sich und der vorherigen Symbole Wahrscheinlichkeit oder Frequenz (oder 0 im Falle des ersten Symbols). Diese Intervalle können verwendet werden, um aus der Liste auszuwählen (und damit die vorgesehene Verteilung zu testen), indem man einfach durch die Liste geht, bis die Zufallszahl im Intervall 0,0 -> 1,0 (früher vorbereitet) kleiner oder gleich dem Intervallendpunkt des aktuellen Symbols ist.

Die Normalisierung gibt uns von der Notwendigkeit, sicherzustellen, dass alles auf etwas Wert summt. Nach der Normalisierung summiert der "Vektor" der Wahrscheinlichkeiten auf 1,0.

Der Rest des Codes für die Auswahl und die Erzeugung eines beliebig langen Samples aus der Verteilung ist unten:

 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 

Verwendung :

 >>> 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 
 from __future__ import division import random from collections import Counter def num_gen(num_probs): # calculate minimum probability to normalize min_prob = min(prob for num, prob in num_probs) lst = [] for num, prob in num_probs: # keep appending num to lst, proportional to its probability in the distribution for _ in range(int(prob/min_prob)): lst.append(num) # all elems in lst occur proportional to their distribution probablities while True: # pick a random index from lst ind = random.randint(0, len(lst)-1) yield lst[ind] 

Überprüfung:

 gen = num_gen([(1, 0.1), (2, 0.05), (3, 0.05), (4, 0.2), (5, 0.4), (6, 0.2)]) lst = [] times = 10000 for _ in range(times): lst.append(next(gen)) # Verify the created distribution: for item, count in Counter(lst).iteritems(): print '%d has %f probability' % (item, count/times) 1 has 0.099737 probability 2 has 0.050022 probability 3 has 0.049996 probability 4 has 0.200154 probability 5 has 0.399791 probability 6 has 0.200300 probability 

Hier ist eine effektivere Art , dies zu tun:

Rufen Sie einfach die folgende Funktion mit Ihrem 'Gewichte' Array (unter der Annahme der Indizes als die entsprechenden Elemente) und die Nr. Der benötigten Proben Diese Funktion kann leicht modifiziert werden, um das geordnete Paar zu behandeln.

Rückgabe von Indizes (oder Items), die mit ihren jeweiligen Wahrscheinlichkeiten abgetastet / ausgetauscht werden (mit Ersatz)

 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 

Eine kurze Anmerkung zum Konzept, das in der while-Schleife verwendet wird. Wir reduzieren das Gewicht des aktuellen Gegenstands von der kumulativen Beta, die ein kumulativer Wert ist, der gleichmäßig zufällig aufgebaut ist, und den aktuellen Index inkrementiert, um das Element zu finden, dessen Gewicht dem Wert von Beta entspricht.

  • Gibt es eine zufällige Funktion in Python, die Variablen akzeptiert?
  • Schnellste Weg, um 1.000.000 + Zufallszahlen in Python zu generieren
  • Generiere ein zufälliges Datum zwischen zwei anderen Terminen
  • Extensible voreingenommener Zahlengenerator - Python
  • AttributeError: 'Modul' Objekt hat kein Attribut 'Auswahl'
  • Wie kann ich das folgende Programm ändern, damit ich sicher bin, dass jeder Brief, den es ersetzt, eindeutig ist?
  • Probleme haben, eine Simulation deterministisch mit random.Random (0) in Python zu halten
  • Lesen Sie zufällige Zeilen aus großer CSV-Datei in Python
  • Wie würde ich einen zufälligen Hexdigit-Code-Generator mit .join und für Loops machen?
  • Wie man eine Liste von zufälligen Integer-Vektor erstellt, deren Summe x ist
  • Effiziente Möglichkeit, Millionen von Zufallszahlen in Python zu generieren und zu verwenden
  • Python ist die beste Programmiersprache der Welt.