Comment faire échantillon aléatoire pondéré de catégories en python
Donné une liste de tuples où chaque tuple se compose d'une probabilité et un point que j'aimerais échantillon d'un élément en fonction de sa probabilité. Par exemple, donner la liste [ (.3, "a"), (.4, "b"), (.3, 'c')] j'aimerais échantillonner 'B' 40% du temps.
Quelle est la façon canonique de faire cela en python?
j'ai regardé le module aléatoire qui ne semble pas avoir une fonction appropriée et nul.aléatoire qui, bien qu'il ait une fonction multinomiale ne semble pas retournez les résultats sous une forme agréable pour ce problème. Je cherche quelque chose comme mnrnd à matlab.
merci Beaucoup.
Merci pour toutes ces réponses si rapidement. Pour clarifier, Je ne cherche pas des explications sur la façon d'écrire un plan d'échantillonnage, mais plutôt un moyen facile d'échantillonner à partir d'une distribution multinomiale à partir d'un ensemble d'objets et de poids, ou d'être dit qu'il n'existe pas de telle fonction dans une bibliothèque standard et donc on devrait écrire ses propres propre.
9 réponses
import numpy
n = 1000
pairs = [(.3, 'a'), (.3, 'b'), (.4, 'c')]
probabilities = numpy.random.multinomial(n, zip(*pairs)[0])
result = zip(probabilities, zip(*pairs)[1])
# [(299, 'a'), (299, 'b'), (402, 'c')]
[x[0] * x[1] for x in result]
# ['aaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccc']
Comment voulez-vous recevoir les résultats?
Cela peut faire ce que tu veux:
numpy.array([.3,.4,.3]).cumsum().searchsorted(numpy.random.sample(5))
puisque personne n'a utilisé le numpy.aléatoire.choix function, en voici un qui va générer ce dont vous avez besoin en un seul, compact ligne:
numpy.random.choice(['a','b','c'], size = 20, p = [0.3,0.4,0.3])
il y a des piratages que vous pouvez faire si, par exemple, vos probabilités correspondent bien aux pourcentages, etc.
par exemple, si vous êtes d'accord avec les pourcentages, ce qui suit fonctionnera (au prix d'une mémoire aérienne élevée):
mais la façon "réelle" de le faire avec des probabilités de flottement arbitraires est d'échantillonner à partir de la distribution cumulative, après l'avoir construite. Cela équivaut à subdiviser l'intervalle unitaire [0,1] en trois segments de ligne étiquetés "a", " b " et "c"; puis choisir un point aléatoire sur l'intervalle de l'unité et voir quel segment de ligne il il.
#!/usr/bin/python3
def randomCategory(probDict):
"""
>>> dist = {'a':.1, 'b':.2, 'c':.3, 'd':.4}
>>> [randomCategory(dist) for _ in range(5)]
['c', 'c', 'a', 'd', 'c']
>>> Counter(randomCategory(dist) for _ in range(10**5))
Counter({'d': 40127, 'c': 29975, 'b': 19873, 'a': 10025})
"""
r = random.random() # range: [0,1)
total = 0 # range: [0,1]
for value,prob in probDict.items():
total += prob
if total>r:
return value
raise Exception('distribution not normalized: {probs}'.format(probs=probDict))
il faut faire attention aux méthodes qui renvoient des valeurs même si leur probabilité est 0. Heureusement cette méthode ne fait pas, mais juste au cas, on pourrait insérer if prob==0: continue
.
Pour mémoire, voici la hackish façon de le faire:
import random
def makeSampler(probDict):
"""
>>> sampler = makeSampler({'a':0.3, 'b':0.4, 'c':0.3})
>>> sampler.sample()
'a'
>>> sampler.sample()
'c'
"""
oneHundredElements = sum(([val]*(prob*100) for val,prob in probDict.items()), [])
def sampler():
return random.choice(oneHundredElements)
return sampler
Toutefois, si vous n'avez pas de problèmes de résolution... c'est probablement la voie la plus rapide possible. =)
comment créer 3 "a", 4" b "et 3" c " dans une liste, puis en choisir une au hasard. Avec assez d'itérations vous obtiendrez la probabilité désirée.
je pense que la fonction multinomiale est un moyen encore assez facile d'obtenir des échantillons d'une distribution dans l'ordre aléatoire. C'est une façon de
import numpy
from itertools import izip
def getSamples(input, size):
probabilities, items = zip(*input)
sampleCounts = numpy.random.multinomial(size, probabilities)
samples = numpy.array(tuple(countsToSamples(sampleCounts, items)))
numpy.random.shuffle(samples)
return samples
def countsToSamples(counts, items):
for value, repeats in izip(items, counts):
for _i in xrange(repeats):
yield value
Où les entrées est comme spécifié [(.2, 'a'), (.4, 'b'), (.3, 'c')]
et la taille est le nombre d'échantillons dont vous avez besoin.
Je ne suis pas sûr que ce soit la façon pythonique de faire ce que vous demandez, mais vous pourriez utiliser
random.sample(['a','a','a','b','b','b','b','c','c','c'],k)
où k est le nombre d'échantillons que vous voulez.
pour une méthode plus robuste, diviser l'intervalle unitaire en sections basées sur la probabilité cumulative et tirer de la distribution uniforme (0,1) en utilisant aléatoire.aléatoire.)( Dans ce cas, les sous-intervalles seraient (0,.3) (.3, de.7) (.7,1). Vous choisissez l'élément en fonction duquel il tombe.
inspiré de
In []: s= array([.3, .4, .3]).cumsum().searchsorted(sample(54))
In []: c, _= histogram(s, bins= arange(4))
In []: [item* c[i] for i, item in enumerate('abc')]
Out[]: ['aaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccccccc']
mise à Jour:
Sur la base des commentaires phant0m
, il s'avère qu'une solution encore plus simple peut être implémentée basée sur multinomial
, comme ceci:
In []: s= multinomial(54, [.3, .4, .3])
In []: [item* s[i] for i, item in enumerate('abc')]
Out[]: ['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccc']
IMHO nous avons ici un bon résumé de empirical cdf
et multinomial
Echantillonnage basé donnant des résultats similaires résultat. Donc, en résumé, le ramasser celui qui convient le mieux à vos besoins.
ceci peut être d'avantage marginal mais je l'ai fait de cette façon:
import scipy.stats as sps
N=1000
M3 = sps.multinomial.rvs(1, p = [0.3,0.4,0.3], size=N, random_state=None)
M3a = [ np.where(r==1)[0][0] for r in M3 ] # convert 1-hot encoding to integers
c'est similaire à la réponse de @eat.