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.

26
demandé sur ninjagecko 2011-06-22 01:56:45

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?

10
répondu phant0m 2011-06-22 15:40:04

Cela peut faire ce que tu veux:

numpy.array([.3,.4,.3]).cumsum().searchsorted(numpy.random.sample(5))
18
répondu sholte 2011-06-22 04:42:27

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])
9
répondu JP_smasher 2015-09-30 06:20:53

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. =)

3
répondu ninjagecko 2011-06-21 23:24:37

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.

1
répondu Fredrik Pihl 2011-06-21 22:04:32

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.

1
répondu Dunes 2011-10-19 03:44:30

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.

0
répondu Marty B 2011-06-21 22:04:55

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.

0
répondu eat 2011-06-22 22:11:32

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.

0
répondu Astrid 2017-11-24 17:06:00