2012-07-07 10 views
32

Sto cercando una funzione semplice che possa generare una matrice di valori casuali specificati in base alle loro probabilità corrispondenti (anche specificate). Ho solo bisogno di generare valori float, ma non vedo perché non dovrebbe essere in grado di generare alcuno scalare. Posso pensare a molti modi di costruire questo da funzioni esistenti, ma penso che probabilmente mi sono appena perso un'evidente funzione SciPy o NumPy.Generazione di variabili casuali discrete con pesi specificati utilizzando SciPy o NumPy

Es .:

>>> values = [1.1, 2.2, 3.3] 
>>> probabilities = [0.2, 0.5, 0.3] 
>>> print some_function(values, probabilities, size=10) 
(2.2, 1.1, 3.3, 3.3, 2.2, 2.2, 1.1, 2.2, 3.3, 2.2) 

Nota: Ho trovato scipy.stats.rv_discrete ma non capisco come funziona. In particolare, non capisco che cosa questo (sotto) significa né che cosa si dovrebbe fare:

numargs = generic.numargs 
[ <shape(s)> ] = ['Replace with resonable value', ]*numargs 

Se rv_discrete è quello che io dovrei essere in uso, la prego di fornirmi un semplice esempio e una spiegazione di quanto sopra " forma "affermazione?

risposta

42

Il disegno da una distribuzione discreta è direttamente incorporato in numpy. La funzione è chiamata random.choice (difficile da trovare senza alcun riferimento alle distribuzioni discrete nei documenti numpy).

elements = [1.1, 2.2, 3.3] 
probabilities = [0.2, 0.5, 0.3] 
np.random.choice(elements, 10, p=probabilities) 
+3

Grande! Ma la sintassi corretta è: np.random.choice (elementi, 10, p = lista (probabilità)) – Sina

+0

Nice. Penso che questa versione sia uscita dopo che ho postato la mia domanda originale (penso che sia stata rilasciata per la prima volta in 1.7.0, che credo sia arrivata nel 2013). – TimY

+0

Molto bello! Sembra funzionare anche senza casting per elencare: np.random.scelta (elementi, 10, p = probabilità)). – zeycus

24

Ecco una funzione breve, relativamente semplice che restituisce valori ponderati, utilizza NumPy's digitize, accumulate e random_sample.

import numpy as np 
from numpy.random import random_sample 

def weighted_values(values, probabilities, size): 
    bins = np.add.accumulate(probabilities) 
    return values[np.digitize(random_sample(size), bins)] 

values = np.array([1.1, 2.2, 3.3]) 
probabilities = np.array([0.2, 0.5, 0.3]) 

print weighted_values(values, probabilities, 10) 
#Sample output: 
[ 2.2 2.2 1.1 2.2 2.2 3.3 3.3 2.2 3.3 3.3] 

funziona così:

  1. Prima usando accumulate creiamo bidoni.
  2. viene creato un po 'di numeri casuali (tra 0, e 1) utilizzando random_sample
  3. Usiamo digitize per vedere quali bidoni questi numeri rientrano.
  4. E restituire i valori corrispondenti.
+1

Sì, questo è fondamentalmente ciò che pensavo, ma ho solo pensato che ci potrebbe essere una funzione built-in che fa esattamente questo. Dal suono di ciò, non esiste una cosa del genere. Devo ammettere che non l'avrei fatto con eleganza. - Grazie – TimY

+0

NumPy offre direttamente 'numpy.cumsum()', che può essere usato al posto di 'np.add.accumulate()' ('np.add()' non è molto usato, quindi consiglio di usare 'cumsum () '). – EOL

+0

+1 per l'utile 'numpy.digitize()'! Tuttavia, SciPy offre in realtà una funzione che risponde direttamente alla domanda: vedi la mia risposta. – EOL

3

Il modo più semplice fai da te sarebbe quello di riassumere le probabilità in una distribuzione cumulativa. In questo modo, si divide l'intervallo unitario in intervalli intermedi della lunghezza pari alle probabilità originali. Ora generi una singola uniforme di numeri casuali su [0,1), e vedi a quale intervallo atterra.

+1

Sì, questo è fondamentalmente ciò a cui stavo pensando, ma ho pensato che potrebbe esserci una funzione incorporata che fa esattamente questo. Dal suono di ciò, non esiste una cosa del genere. – TimY

14

Stava andando in una buona direzione: il built-in scipy.stats.rv_discrete() crea abbastanza direttamente una variabile casuale discreta. Ecco come funziona:

>>> from scipy.stats import rv_discrete 

>>> values = numpy.array([1.1, 2.2, 3.3]) 
>>> probabilities = [0.2, 0.5, 0.3] 

>>> distrib = rv_discrete(values=(range(len(values)), probabilities)) # This defines a Scipy probability distribution 

>>> distrib.rvs(size=10) # 10 samples from range(len(values)) 
array([1, 2, 0, 2, 2, 0, 2, 1, 0, 2]) 

>>> values[_] # Conversion to specific discrete values (the fact that values is a NumPy array is used for the indexing) 
[2.2, 3.3, 1.1, 3.3, 3.3, 1.1, 3.3, 2.2, 1.1, 3.3] 

La distribuzione distrib sopra restituisce così indici dalla lista values.

Più in generale, rv_discrete() prende una sequenza di numeri interi valori dei primi elementi della sua tesi values=(…,…), e restituisce questi valori, in questo caso; non è necessario convertire valori specifici (float). Ecco un esempio:

>>> values = [10, 20, 30] 
>>> probabilities = [0.2, 0.5, 0.3] 
>>> distrib = rv_discrete(values=(values, probabilities)) 
>>> distrib.rvs(size=10) 
array([20, 20, 20, 20, 20, 20, 20, 30, 20, 20]) 

dove i valori di input (intero) vengono restituiti direttamente con la probabilità desiderata.

+4

NOTA: ho provato a eseguire timeit su di esso e sembra essere un buon 100x più lento rispetto alla versione puramente numpy di fraxel. Per caso sai perché è così? – TimY

+0

Wow, interessante! Sui 10k elementi, ottengo persino un fattore di 300x più lento. Ho dato una rapida occhiata al codice: ci sono molti controlli eseguiti, ma immagino che non possano spiegare una così grande differenza nel tempo di esecuzione; Non sono andato abbastanza in profondità nel codice Scipy per essere stato in grado di vedere da dove potrebbe provenire la differenza ... – EOL

+0

@TimY la mia ingenua ipotesi è che la lentezza è dovuta al lavoro più svolto in Python puro, meno lavoro svolto (sotto il cappuccio) in C. (i pacchetti matematico/scientifici in Python tendono a avvolgere il codice C.) – dbliss

4

È inoltre possibile utilizzare Lea, un pacchetto Python puro dedicato a distribuzioni di probabilità discrete.

>>> distrib = Lea.fromValFreqs((1.1,2),(2.2,5),(3.3,3)) 
>>> distrib 
1.1 : 2/10 
2.2 : 5/10 
3.3 : 3/10 
>>> distrib.random(10) 
(2.2, 2.2, 1.1, 2.2, 2.2, 2.2, 1.1, 3.3, 1.1, 3.3) 

Et voilà!

Problemi correlati