9.6. random — Génère des nombres pseudo-aléatoires

Code source : Lib/random.py


Ce module implémente des générateurs de nombres pseudo-aléatoires pour différentes distributions.

Pour les entiers, il existe une sélection uniforme à partir d’une plage. Pour les séquences, il existe une sélection uniforme d’un élément aléatoire, une fonction pour générer une permutation aléatoire d’une liste sur place et une fonction pour un échantillonnage aléatoire sans remplacement.

Pour l’ensemble des réels, il y a des fonctions pour calculer des distributions uniformes, normales (gaussiennes), log-normales, exponentielles négatives, gamma et bêta. Pour générer des distributions d’angles, la distribution de von Mises est disponible.

Presque toutes les fonctions du module dépendent de la fonction de base random(), qui génère un nombre à virgule flottante aléatoire de façon uniforme dans la plage semi-ouverte [0.0, 1.0). Python utilise l’algorithme Mersenne Twister comme générateur de base. Il produit des flottants de précision de 53 bits et a une période de 2***19937-1. L’implémentation sous-jacente en C est à la fois rapide et compatible avec les programmes ayant de multiples fils d’exécution. Le Mersenne Twister est l’un des générateurs de nombres aléatoires les plus largement testés qui existent. Cependant, étant complètement déterministe, il n’est pas adapté à tous les usages et est totalement inadapté à des fins cryptographiques.

Les fonctions fournies par ce module dépendent en réalité de méthodes d’une instance cachée de la classe random.Random. Vous pouvez créer vos propres instances de Random pour obtenir des générateurs sans états partagés.

La classe Random peut également être sous-classée si vous voulez utiliser un générateur de base différent, de votre propre conception. Dans ce cas, remplacez les méthodes random(), seed(), gettsate() et setstate(). En option, un nouveau générateur peut fournir une méthode getrandbits() — ce qui permet à randrange() de produire des sélections sur une plage de taille arbitraire.

Le module random fournit également la classe SystemRandom qui utilise la fonction système os.urandom() pour générer des nombres aléatoires à partir de sources fournies par le système d’exploitation.

Avertissement

Les générateurs pseudo-aléatoires de ce module ne doivent pas être utilisés à des fins de sécurité. Pour des utilisations de sécurité ou cryptographiques, voir le module secrets.

Voir aussi

M. Matsumoto and T. Nishimura, « Mersenne Twister: A 623-dimensionally equidistributed uniform pseudorandom number generator », ACM Transactions on Modeling and Computer Simulation Vol. 8, No. 1, Janvier pp.3–30 1998.

Complementary-Multiply-with-Carry recipe pour un autre générateur de nombres aléatoires avec une longue période et des opérations de mise à jour relativement simples.

9.6.1. Fonctions de gestion d’état

random.seed(a=None, version=2)

Initialise le générateur de nombres aléatoires.

Si a est omis ou None, l’heure système actuelle est utilisée. Si des sources aléatoires sont fournies par le système d’exploitation, elles sont utilisées à la place de l’heure système (voir la fonction os.urandom() pour les détails sur la disponibilité).

Si a est un entier, il est utilisé directement.

Avec la version 2 (par défaut), un objet str, bytes ou bytearray est converti en int et tous ses bits sont utilisés.

Avec la version 1 (fournie pour reproduire des séquences aléatoires produites par d’anciennes versions de Python), l’algorithme pour str et bytes génère une gamme plus étroite de graines.

Modifié dans la version 3.2: Passée à la version 2 du schéma qui utilise tous les bits d’une graine de chaîne de caractères.

random.getstate()

Renvoie un objet capturant l’état interne actuel du générateur. Cet objet peut être passé à setstate() pour restaurer cet état.

random.setstate(state)

Il convient que state ait été obtenu à partir d’un appel précédent à getstate(), et setstate() restaure l’état interne du générateur à ce qu’il était au moment où getstate() a été appelé.

random.getrandbits(k)

Renvoie un entier Python avec k bits aléatoires. Cette méthode est fournie avec le générateur MersenneTwister. Quelques autres générateurs peuvent également la fournir en option comme partie de l’API. Lorsqu’elle est disponible, getrandbits() permet à randrange() de gérer des gammes arbitrairement larges.

9.6.2. Fonctions pour les entiers

random.randrange(stop)
random.randrange(start, stop[, step])

Renvoie un élément sélectionné aléatoirement à partir de range(start, stop, step). C’est équivalent à choice(range(start, stop, step)), mais ne construit pas réellement un objet range.

Le motif d’argument positionnel correspond à celui de range(). N’utilisez pas d’arguments nommés parce que la fonction peut les utiliser de manière inattendue.

Modifié dans la version 3.2: randrange() est plus sophistiquée dans la production de valeurs uniformément distribuées. Auparavant, elle utilisait un style comme int(random()*n) qui pouvait produire des distributions légèrement inégales.

random.randint(a, b)

Renvoie un entier aléatoire N tel que a <= N <= b. Alias pour randrange(a, b+1).

9.6.3. Fonctions pour les séquences

random.choice(seq)

Renvoie un élément aléatoire de la séquence non vide seq. Si seq est vide, lève IndexError.

random.choices(population, weights=None, *, cum_weights=None, k=1)

Renvoie une liste de taille k d’éléments choisis dans la population avec remise. Si la population est vide, lève IndexError.

Si une séquence de poids est spécifiée, les tirages sont effectués en fonction des poids relatifs. Alternativement, si une séquence cum_weights est donnée, les tirages sont faits en fonction des poids cumulés (peut-être calculés en utilisant itertools.accumulate()). Par exemple, les poids relatifs [10, 5, 30, 5] sont équivalents aux poids cumulatifs [10, 15, 45, 50]. En interne, les poids relatifs sont convertis en poids cumulatifs avant d’effectuer les tirages, ce qui vous permet d’économiser du travail en fournissant des pondérations cumulatives.

Si ni weights ni cum_weights ne sont spécifiés, les tirages sont effectués avec une probabilité uniforme. Si une séquence de poids est fournie, elle doit être de la même longueur que la séquence population. Spécifier à la fois weights et cum_weights lève une TypeError.

The weights or cum_weights can use any numeric type that interoperates with the float values returned by random() (that includes integers, floats, and fractions but excludes decimals).

Nouveau dans la version 3.6.

random.shuffle(x[, random])

Mélange la séquence x sans créer de nouvelle instance (« sur place »).

L’argument optionnel random est une fonction sans argument renvoyant un nombre aléatoire à virgule flottante dans [0.0, 1.0); par défaut, c’est la fonction random().

Pour mélanger une séquence immuable et renvoyer une nouvelle liste mélangée, utilisez sample(x, k=len(x)) à la place.

Notez que même pour les petits len(x), le nombre total de permutations de x peut rapidement devenir plus grand que la période de la plupart des générateurs de nombres aléatoires. Cela implique que la plupart des permutations d’une longue séquence ne peuvent jamais être générées. Par exemple, une séquence de longueur 2080 est la plus grande qui puisse tenir dans la période du générateur de nombres aléatoires Mersenne Twister.

random.sample(population, k)

Renvoie une liste de k éléments uniques choisis dans la séquence ou l’ensemble de la population. Utilisé pour un tirage aléatoire sans remise.

Renvoie une nouvelle liste contenant des éléments de la population tout en laissant la population originale inchangée. La liste résultante est classée par ordre de sélection de sorte que toutes les sous-tranches soient également des échantillons aléatoires valides. Cela permet aux gagnants du tirage (l’échantillon) d’être divisés en gagnants du grand prix et en gagnants de la deuxième place (les sous-tranches).

Les membres de la population n’ont pas besoin d’être hachables ou uniques. Si la population contient des répétitions, alors chaque occurrence est un tirage possible dans l’échantillon.

Pour choisir un échantillon parmi un intervalle d’entiers, utilisez un objet range() comme argument. Ceci est particulièrement rapide et économe en mémoire pour un tirage dans une grande population : échantillon(range(10000000), k=60).

Si la taille de l’échantillon est supérieure à la taille de la population, une ValueError est levée.

9.6.4. Distributions pour les nombre réels

Les fonctions suivantes génèrent des distributions spécifiques en nombre réels. Les paramètres de fonction sont nommés d’après les variables correspondantes de l’équation de la distribution, telles qu’elles sont utilisées dans la pratique mathématique courante ; la plupart de ces équations peuvent être trouvées dans tout document traitant de statistiques.

random.random()

Renvoie le nombre aléatoire à virgule flottante suivant dans la plage [0.0, 1.0).

random.uniform(a, b)

Renvoie un nombre aléatoire à virgule flottante N tel que a <= N <= b pour a <= b et b <= N <= a pour b < a.

La valeur finale b peut ou non être incluse dans la plage selon l’arrondi à virgule flottante dans l’équation a + (b-a) * random().

random.triangular(low, high, mode)

Renvoie un nombre aléatoire en virgule flottante N tel que low <= N <= high et avec le mode spécifié entre ces bornes. Les limites low et high par défaut sont zéro et un. L’argument mode est par défaut le point médian entre les bornes, ce qui donne une distribution symétrique.

random.betavariate(alpha, beta)

Distribution bêta. Les conditions sur les paramètres sont alpha > 0 et beta > 0. Les valeurs renvoyées varient entre 0 et 1.

random.expovariate(lambd)

Distribution exponentielle. lambd est 1,0 divisé par la moyenne désirée. Ce ne doit pas être zéro. (Le paramètre aurait dû s’appeler « lambda », mais c’est un mot réservé en Python.) Les valeurs renvoyées vont de 0 à plus l’infini positif si lambd est positif, et de moins l’infini à 0 si lambd est négatif.

random.gammavariate(alpha, beta)

Distribution gamma. (Ce n’est pas la fonction gamma !) Les conditions sur les paramètres sont alpha > 0 et beta > 0.

La fonction de distribution de probabilité est :

          x ** (alpha - 1) * math.exp(-x / beta)
pdf(x) =  --------------------------------------
            math.gamma(alpha) * beta ** alpha
random.gauss(mu, sigma)

Distribution gaussienne. mu est la moyenne et sigma est la écart type. C’est légèrement plus rapide que la fonction normalvariate() définie ci-dessous.

random.lognormvariate(mu, sigma)

Logarithme de la distribution normale. Si vous prenez le logarithme naturel de cette distribution, vous obtiendrez une distribution normale avec mu moyen et écart-type sigma. mu peut avoir n’importe quelle valeur et sigma doit être supérieur à zéro.

random.normalvariate(mu, sigma)

Distribution normale. mu est la moyenne et sigma est l’écart type.

random.vonmisesvariate(mu, kappa)

mu est l’angle moyen, exprimé en radians entre 0 et 2*pi, et kappa est le paramètre de concentration, qui doit être supérieur ou égal à zéro. Si kappa est égal à zéro, cette distribution se réduit à un angle aléatoire uniforme sur la plage de 0 à 2*pi.

random.paretovariate(alpha)

Distribution de Pareto. alpha est le paramètre de forme.

random.weibullvariate(alpha, beta)

Distribution de Weibull. alpha est le paramètre de l’échelle et beta est le paramètre de forme.

9.6.5. Générateur alternatif

class random.SystemRandom([seed])

Classe qui utilise la fonction os.urandom() pour générer des nombres aléatoires à partir de sources fournies par le système d’exploitation. Non disponible sur tous les systèmes. Ne repose pas sur un état purement logiciel et les séquences ne sont pas reproductibles. Par conséquent, la méthode seed() n’a aucun effet et est ignorée. Les méthodes getstate() et setstate() lèvent NotImplementedError si vous les appelez.

9.6.6. Remarques sur la reproductibilité

Il est parfois utile de pouvoir reproduire les séquences données par un générateur de nombres pseudo-aléatoires. En réutilisant la même graine, la même séquence devrait être reproductible d’une exécution à l’autre tant que plusieurs processus ne sont pas en cours.

La plupart des algorithmes et des fonctions de génération de graine du module aléatoire sont susceptibles d’être modifiés d’une version à l’autre de Python, mais deux aspects sont garantis de ne pas changer :

  • Si une nouvelle méthode de génération de graine est ajoutée, une fonction rétro-compatible sera offerte.

  • La méthode random() du générateur continuera à produire la même séquence lorsque la fonction de génération de graine compatible recevra la même semence.

9.6.7. Exemples et recettes

Exemples de base :

>>> random()                             # Random float:  0.0 <= x < 1.0
0.37444887175646646

>>> uniform(2.5, 10.0)                   # Random float:  2.5 <= x < 10.0
3.1800146073117523

>>> expovariate(1 / 5)                   # Interval between arrivals averaging 5 seconds
5.148957571865031

>>> randrange(10)                        # Integer from 0 to 9 inclusive
7

>>> randrange(0, 101, 2)                 # Even integer from 0 to 100 inclusive
26

>>> choice(['win', 'lose', 'draw'])      # Single random element from a sequence
'draw'

>>> deck = 'ace two three four'.split()
>>> shuffle(deck)                        # Shuffle a list
>>> deck
['four', 'two', 'ace', 'three']

>>> sample([10, 20, 30, 40, 50], k=4)    # Four samples without replacement
[40, 10, 50, 30]

Simulations :

>>> # Six roulette wheel spins (weighted sampling with replacement)
>>> choices(['red', 'black', 'green'], [18, 18, 2], k=6)
['red', 'green', 'black', 'black', 'red', 'black']

>>> # Deal 20 cards without replacement from a deck of 52 playing cards
>>> # and determine the proportion of cards with a ten-value
>>> # (a ten, jack, queen, or king).
>>> deck = collections.Counter(tens=16, low_cards=36)
>>> seen = sample(list(deck.elements()), k=20)
>>> seen.count('tens') / 20
0.15

>>> # Estimate the probability of getting 5 or more heads from 7 spins
>>> # of a biased coin that settles on heads 60% of the time.
>>> trial = lambda: choices('HT', cum_weights=(0.60, 1.00), k=7).count('H') >= 5
>>> sum(trial() for i in range(10000)) / 10000
0.4169

>>> # Probability of the median of 5 samples being in middle two quartiles
>>> trial = lambda : 2500 <= sorted(choices(range(10000), k=5))[2]  < 7500
>>> sum(trial() for i in range(10000)) / 10000
0.7958

Exemple de *bootstrapping* statistique utilisant le ré-échantillonnage avec remise pour estimer un intervalle de confiance pour la moyenne d’un échantillon de taille cinq :

# http://statistics.about.com/od/Applications/a/Example-Of-Bootstrapping.htm
from statistics import mean
from random import choices

data = 1, 2, 4, 4, 10
means = sorted(mean(choices(data, k=5)) for i in range(20))
print(f'The sample mean of {mean(data):.1f} has a 90% confidence '
      f'interval from {means[1]:.1f} to {means[-2]:.1f}')

Exemple d’un *resampling permutation test* pour déterminer la signification statistique ou valeur p d’une différence observée entre les effets d’un médicament et ceux d’un placebo :

# Example from "Statistics is Easy" by Dennis Shasha and Manda Wilson
from statistics import mean
from random import shuffle

drug = [54, 73, 53, 70, 73, 68, 52, 65, 65]
placebo = [54, 51, 58, 44, 55, 52, 42, 47, 58, 46]
observed_diff = mean(drug) - mean(placebo)

n = 10000
count = 0
combined = drug + placebo
for i in range(n):
    shuffle(combined)
    new_diff = mean(combined[:len(drug)]) - mean(combined[len(drug):])
    count += (new_diff >= observed_diff)

print(f'{n} label reshufflings produced only {count} instances with a difference')
print(f'at least as extreme as the observed difference of {observed_diff:.1f}.')
print(f'The one-sided p-value of {count / n:.4f} leads us to reject the null')
print(f'hypothesis that there is no difference between the drug and the placebo.')

Simulation des heures d’arrivée et des livraisons de services dans une seule file d’attente de serveurs :

from random import expovariate, gauss
from statistics import mean, median, stdev

average_arrival_interval = 5.6
average_service_time = 5.0
stdev_service_time = 0.5

num_waiting = 0
arrivals = []
starts = []
arrival = service_end = 0.0
for i in range(20000):
    if arrival <= service_end:
        num_waiting += 1
        arrival += expovariate(1.0 / average_arrival_interval)
        arrivals.append(arrival)
    else:
        num_waiting -= 1
        service_start = service_end if num_waiting else arrival
        service_time = gauss(average_service_time, stdev_service_time)
        service_end = service_start + service_time
        starts.append(service_start)

waits = [start - arrival for arrival, start in zip(arrivals, starts)]
print(f'Mean wait: {mean(waits):.1f}.  Stdev wait: {stdev(waits):.1f}.')
print(f'Median wait: {median(waits):.1f}.  Max wait: {max(waits):.1f}.')

Voir aussi

Statistics for Hackers un tutoriel vidéo par Jake Vanderplas sur l’analyse statistique en utilisant seulement quelques concepts fondamentaux dont la simulation, l’échantillonnage, le brassage et la validation croisée.

Economics Simulation simulation d’un marché par Peter Norvig qui montre l’utilisation efficace de plusieurs des outils et distributions fournis par ce module (gauss, uniform, sample, betavariate, choice, triangular, et randrange).

A Concrete Introduction to Probability (using Python) un tutoriel par Peter Norvig couvrant les bases de la théorie des probabilités, comment écrire des simulations, et comment effectuer des analyses de données avec Python.