J'avais besoin d'écrire une version pondérée de random.choice (chaque élément de la liste a une probabilité différente d'être sélectionné). Voici ce que j'ai trouvé:
def weightedChoice(choices):
"""Like random.choice, but each element can have a different chance of
being selected.
choices can be any iterable containing iterables with two items each.
Technically, they can have more than two items, the rest will just be
ignored. The first item is the thing being chosen, the second item is
its weight. The weights can be any numeric values, what matters is the
relative differences between them.
"""
space = {}
current = 0
for choice, weight in choices:
if weight > 0:
space[current] = choice
current += weight
rand = random.uniform(0, current)
for key in sorted(space.keys() + [current]):
if rand < key:
return choice
choice = space[key]
return None
Cette fonction me semble trop complexe et moche. J'espère que tout le monde ici pourra offrir des suggestions pour l'améliorer ou d'autres façons de le faire. L'efficacité n'est pas aussi importante pour moi que la propreté et la lisibilité du code.
la source
random.choices
pour les appels individuels. Si vous avez besoin de beaucoup de résultats aléatoires, il est vraiment important de les choisir tous en même temps en les ajustantnumber_of_items_to_pick
. Si vous le faites, c'est un ordre de grandeur plus rapide.len(list_of_candidates)
-à- dire , puis fairelist_of_candidates[draw]
Depuis Python 3.6 il existe une méthode
choices
de larandom
module.Notez que
random.choices
l'échantillon avec remplacement , selon les documents :Si vous avez besoin d'échantillonner sans remplacement, alors, comme l' indique la réponse brillante de @ ronan-paixão , vous pouvez utiliser
numpy.choice
, dont l'replace
argument contrôle un tel comportement.la source
random.choices
ne le font pas, donc bien sûr, il est plus lent sur une liste minuscule de 8 éléments, et si vous choisissez 10 000 fois dans une telle liste, vous avez raison. Mais pour les cas où la liste est plus longue (selon la façon dont vous testez, je vois des points de rupture entre 100-300 éléments),np.random.choice
commence à surperformerrandom.choices
par un écart assez large. Par exemple, y compris l'étape de normalisation avec l'appel numpy, j'obtiens une accélération de près de 4xrandom.choices
pour une liste d'éléments de 10k.la source
upto +=w; if upto > r
if r < 0
r <= 0
. Considérons un ensemble d'entrée de 1 éléments et un rouleau de 1,0. L'assertion échouera alors. J'ai corrigé cette erreur dans la réponse.# pragma: no branch
0.0 <= x < total
.Si vous devez faire plus d'un choix, divisez-le en deux fonctions, une pour construire les poids cumulatifs et une autre pour diviser en deux jusqu'à un point aléatoire.
la source
O(n)
raison du calcul de la distribution cumulée.random()
ne peut pas renvoyer 1.0. D'après la documentation, il renvoie un résultat dans l'intervalle semi-ouvert[0.0, 1.0)
, c'est-à-dire qu'il peut retourner exactement 0,0, mais ne peut pas retourner exactement 1,0. La plus grande valeur qu'il peut renvoyer est 0,99999999999999988897769753748434595763683319091796875 (que Python imprime en 0,9999999999999999, et est le plus grand flottant 64 bits inférieur à 1).Si cela ne vous dérange pas d'utiliser numpy, vous pouvez utiliser numpy.random.choice .
Par exemple:
Si vous savez combien de sélections vous devez faire à l'avance, vous pouvez le faire sans boucle comme ceci:
la source
Brut, mais peut être suffisant:
Est-ce que ça marche?
Tirages:
Suppose que tous les poids sont des nombres entiers. Ils n'ont pas besoin d'ajouter jusqu'à 100, je viens de le faire pour rendre les résultats des tests plus faciles à interpréter. (Si les poids sont des nombres à virgule flottante, multipliez-les tous par 10 jusqu'à ce que tous les poids> = 1.)
la source
[[]]*10
- tous les éléments de la liste externe pointent vers la même liste.int
vous obtenez toujours beaucoup de références au même objet en faisant quelque chose comme[id(x) for x in ([99**99] * 100)]
et observez queid
renvoie la même adresse mémoire à chaque appel.Si vous avez un dictionnaire pondéré au lieu d'une liste, vous pouvez écrire ceci
Notez que
[k for k in items for dummy in range(items[k])]
produit cette liste['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'b', 'b', 'b', 'b', 'b']
la source
À partir de Python
v3.6
,random.choices
pourrait être utilisé pour renvoyer unlist
des éléments de taille spécifiée à partir de la population donnée avec des poids facultatifs.population :
list
contenant des observations uniques. (Si vide, soulèveIndexError
)poids : plus précisément les poids relatifs requis pour effectuer des sélections.
cum_weights : poids cumulatifs requis pour effectuer des sélections.
k : taille (
len
) de lalist
sortie. (Par défautlen()=1
)Quelques mises en garde:
1) Il utilise un échantillonnage pondéré avec remplacement afin que les éléments tirés soient remplacés plus tard. Les valeurs de la séquence de poids en elles-mêmes n'ont pas d'importance, mais leur rapport relatif le fait.
Contrairement à
np.random.choice
ce qui ne peut prendre que des probabilités comme poids et qui doit également assurer la somme des probabilités individuelles jusqu'à 1 critère, il n'y a pas de telles réglementations ici. Tant qu'ils appartiennent à des types numériques (int/float/fraction
sauf leDecimal
type), ceux-ci fonctionneront toujours.2) Si ni poids ni cum_weights ne sont spécifiés, les sélections sont effectuées avec une probabilité égale. Si une séquence de poids est fournie, elle doit être de la même longueur que la population séquence de .
La spécification à la fois des poids et des cum_weights soulève a
TypeError
.3) cum_weights sont généralement le résultat d'une
itertools.accumulate
fonction qui est vraiment pratique dans de telles situations.Donc, soit l'approvisionnement
weights=[12, 12, 4]
soitcum_weights=[12, 24, 28]
notre cas artificiel produit le même résultat et ce dernier semble être plus rapide / efficace.la source
Voici la version qui est incluse dans la bibliothèque standard de Python 3.6:
Source: https://hg.python.org/cpython/file/tip/Lib/random.py#l340
la source
la source
Je suis probablement trop tard pour apporter quelque chose d'utile, mais voici un extrait simple, court et très efficace:
Pas besoin de trier vos probabilités ou de créer un vecteur avec votre cmf, et il se termine une fois qu'il a trouvé son choix. Mémoire: O (1), temps: O (N), avec un temps de fonctionnement moyen ~ N / 2.
Si vous avez des poids, ajoutez simplement une ligne:
la source
np.random.choice
. Mais plus intéressant, il existe un mode de défaillance où cela déclenche une exception. Faireprobabilities = weights / sum(weights)
ne garantit pas que celaprobabilities
se résumera à 1; par exemple, siweights
est[1,1,1,1,1,1,1]
alorsprobabilities
ne fera que la somme de 0,9999999999999998, inférieure à la plus grande valeur de retour possiblerandom.random
(qui est 0,9999999999999999). Alorschoice <= cmf
n'est jamais satisfait.Si votre liste de choix pondérés est relativement statique et que vous souhaitez un échantillonnage fréquent, vous pouvez effectuer une étape de prétraitement O (N), puis effectuer la sélection dans O (1), en utilisant les fonctions de cette réponse associée .
la source
J'ai regardé l'autre thread pointé et j'ai trouvé cette variation dans mon style de codage, cela retourne l'index de choix à des fins de comptage, mais il est simple de renvoyer la chaîne (alternative de retour commentée):
la source
Cela dépend du nombre de fois que vous souhaitez échantillonner la distribution.
Supposons que vous souhaitiez échantillonner la distribution K fois. Ensuite, la complexité de temps à l' aide
np.random.choice()
chaque fois estO(K(n + log(n)))
quandn
est le nombre d'éléments dans la distribution.Dans mon cas, j'ai dû échantillonner la même distribution plusieurs fois de l'ordre de 10 ^ 3 où n est de l'ordre de 10 ^ 6. J'ai utilisé le code ci-dessous, qui pré-calcule la distribution cumulative et l'échantillonne
O(log(n))
. La complexité globale du temps estO(n+K*log(n))
.la source
Si vous possédez Python 3 et avez peur d'installer
numpy
ou d'écrire vos propres boucles, vous pouvez faire:Parce que vous pouvez tout construire à partir d'un sac d'adaptateurs de plomberie! Bien que ... je dois admettre que la réponse de Ned, bien que légèrement plus longue, est plus facile à comprendre.
la source
Une solution générale:
la source
Voici une autre version de weighted_choice qui utilise numpy. Passez le vecteur de poids et il renverra un tableau de 0 contenant un 1 indiquant quel bac a été choisi. Par défaut, le code ne fait qu'un seul tirage, mais vous pouvez transmettre le nombre de tirages à effectuer et les décomptes par bac tiré seront retournés.
Si le vecteur de poids n'est pas égal à 1, il sera normalisé pour qu'il le fasse.
la source
Une autre façon de procéder, en supposant que nous avons des poids au même index que les éléments du tableau d'éléments.
Supposons maintenant que nous devons échantillonner 3 éléments dans 1 essai. Vous pouvez supposer qu'il y a trois boules R, G, B présentes en grande quantité par rapport à leurs poids donnés par le tableau de poids, le résultat suivant pourrait être possible:
vous pouvez également penser le nombre d'éléments à sélectionner comme le nombre d'essais binomiaux / multinomiaux dans un ensemble. Ainsi, l'exemple ci-dessus peut toujours fonctionner comme
la source
Il y a une conférence à ce sujet par Sébastien Thurn dans le cours gratuit Udacity AI for Robotics. Fondamentalement, il fait un tableau circulaire des poids indexés à l'aide de l'opérateur mod
%
, définit une variable bêta à 0, choisit au hasard un index, pour les boucles via N où N est le nombre d'indices et dans la boucle for, incrémente d'abord bêta par la formule:beta = beta + échantillon uniforme de {0 ... 2 * Weight_max}
puis imbriqué dans la boucle for, une boucle while ci-dessous:
Passez ensuite à l'indice suivant pour rééchantillonner en fonction des probabilités (ou probabilité normalisée dans le cas présenté dans le cours).
Le lien de la conférence: https://classroom.udacity.com/courses/cs373/lessons/48704330/concepts/487480820923
Je suis connecté à Udacity avec mon compte d'école donc si le lien ne fonctionne pas, c'est la leçon 8, vidéo numéro 21 d'Intelligence Artificielle pour la Robotique où il donne des cours sur les filtres à particules.
la source
Une façon consiste à randomiser sur le total de tous les poids, puis à utiliser les valeurs comme points limites pour chaque var. Voici une implémentation brute en tant que générateur.
la source
Utiliser numpy
la source
np.random.choice
, comme mentionné dans la réponse acceptée qui est ici depuis 2014. Quel est l'intérêt de rouler le vôtre?J'avais besoin de faire quelque chose comme ça très rapidement très simple, à partir de la recherche d'idées, j'ai finalement construit ce modèle. L'idée est de recevoir les valeurs pondérées sous la forme d'un json de l'api, qui est ici simulé par le dict.
Ensuite, traduisez-le dans une liste dans laquelle chaque valeur se répète proportionnellement à son poids, et utilisez simplement random.choice pour sélectionner une valeur dans la liste.
Je l'ai essayé avec 10, 100 et 1000 itérations. La distribution semble assez solide.
la source
Je n'aimais pas la syntaxe de ceux-là. Je voulais vraiment préciser quels étaient les articles et quelle était leur pondération. Je me rends compte que j'aurais pu utiliser
random.choices
mais à la place j'ai rapidement écrit le cours ci-dessous.la source
Fournissez à random.choice () une liste pré-pondérée:
Solution et test:
Production:
la source