Jak zrobić ważoną losową próbkę kategorii w Pythonie

Biorąc pod uwagę listę krotek, gdzie każda krotka składa się z prawdopodobieństwa i elementu, chciałbym wypróbować element zgodnie z jego prawdopodobieństwem. Na przykład, podaj listę [(.3, "a"), (.4, "b"), (.3, "c")] chciałbym spróbować " b " 40% czasu.

Jaki jest kanoniczny sposób robienia tego w Pythonie?

Spojrzałem na moduł losowy, który nie wydaje się mieć odpowiednią funkcję i na numpy.losowe, które mimo że ma wielomianową funkcję nie wydaje się zwracać wyniki w ładnej formie dla tego problemu. Szukam czegoś takiego jak mnrnd w matlab.

Wielkie dzięki.

Dzięki za odpowiedzi tak szybko. Aby wyjaśnić, nie szukam wyjaśnień, jak napisać schemat próbkowania, ale raczej, aby być wskazanym na łatwy sposób próbkowania z wielomianowego rozkładu, biorąc pod uwagę zbiór obiektów i wag, lub aby powiedzieć, że taka funkcja nie istnieje w standardowej bibliotece, a więc należy napisać własną.

Author: ninjagecko, 2011-06-22

9 answers

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']

Jak dokładnie chcesz otrzymywać wyniki?

 10
Author: phant0m,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-22 15:40:04

To może zrobić to, co chcesz:

numpy.array([.3,.4,.3]).cumsum().searchsorted(numpy.random.sample(5))
 18
Author: sholte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-22 04:42:27

Ponieważ nikt nie używał numpy.przypadkowe.wybór funkcja, oto jedna, która wygeneruje to, czego potrzebujesz w jednej, zwartej linii:

numpy.random.choice(['a','b','c'], size = 20, p = [0.3,0.4,0.3])
 9
Author: JP_smasher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-30 06:20:53

Są hacki, które możesz zrobić, jeśli na przykład Twoje prawdopodobieństwa pasują ładnie do procentów itp.

Na przykład, jeśli nie masz nic przeciwko procentom, następujące działania będą działać (kosztem wysokiego narzutu pamięci):

Ale "prawdziwym" sposobem na zrobienie tego z dowolnymi prawdopodobieństwami zmiennoprzecinkowymi jest pobranie próbki z rozkładu kumulacyjnego po jego skonstruowaniu. Jest to równoważne podzieleniu przedziału jednostkowego [0,1] na 3 segmenty liniowe oznaczone "a", " b " I "c"; następnie wybranie losowego wskaż interwał jednostki i zobacz, która linia go segment.

#!/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))

Należy uważać na metody, które zwracają wartości, nawet jeśli ich prawdopodobieństwo wynosi 0. Na szczęście ta metoda nie, ale na wszelki wypadek można by wstawić if prob==0: continue.


Dla przypomnienia, oto hakerski sposób, aby to zrobić:

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

Jednakże, jeśli nie masz problemów z rozwiązaniem... jest to prawdopodobnie najszybszy możliwy sposób. =)

 3
Author: ninjagecko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-21 23:24:37

Jak utworzyć 3 "a", 4 " b "I 3" c " na liście a następnie po prostu losowo wybrać jeden. Z wystarczającą liczbą iteracji otrzymasz pożądane prawdopodobieństwo.

 1
Author: Fredrik Pihl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-21 22:04:32

Myślę, że funkcja wielomianowa jest wciąż dość łatwym sposobem na uzyskanie próbek rozkładu w losowej kolejności. To jest tylko jeden sposób

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

Gdzie dane wejściowe są określone [(.2, 'a'), (.4, 'b'), (.3, 'c')], A rozmiar to liczba próbek, których potrzebujesz.

 1
Author: Dunes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-19 03:44:30

Nie jestem pewien, czy to jest pythoniczny sposób robienia tego, o co prosisz, ale możesz użyć random.sample(['a','a','a','b','b','b','b','c','c','c'],k) gdzie k jest liczbą próbek, które chcesz.

Dla bardziej solidnej metody, podzielić przedział jednostek na sekcje w oparciu o skumulowane prawdopodobieństwo i wyciągnąć z równomiernego rozkładu (0,1) za pomocą Losowego.random(). W tym przypadku podinterwale byłyby (0,.3)(.3,.7)(.7,1). Wybieramy element w oparciu o subinterval, do którego należy.

 0
Author: Marty B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-21 22:04:55

Zainspirowany bardzo prostą (i poprawną) odpowiedzią sholte: zademonstruję, jak łatwo będzie rozszerzyć ją o dowolne elementy, takie jak:

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']

Update :
Na podstawie opinii phant0m okazuje się, że jeszcze prostsze rozwiązanie można zaimplementować w oparciu o multinomial, np.:

In []: s= multinomial(54, [.3, .4, .3])
In []: [item* s[i] for i, item in enumerate('abc')]
Out[]: ['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccc']

IMHO tutaj mamy ładne podsumowanie empirical cdf i multinomial na podstawie próbkowania dającego podobne wyniki. Więc w podsumowaniu wybierz ten, który najlepiej pasuje dla Twoich celów.

 0
Author: eat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-22 22:11:32

To może być marginalne korzyści, ale zrobiłem to w ten sposób:

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

Jest to podobne do odpowiedzi @eat.

 0
Author: Astrid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-24 17:06:00