random — Generadores de números pseudoaleatorios

Propósito:Implementa varios tipos de generadores de números pseudoaleatorios.

El módulo random proporciona un generador rápido de números pseudoaleatorios basado en el algoritmo Mersenne Twister. Originalmente desarrollado para producir entradas para simulaciones de Monte Carlo, Mersenne Twister genera números con distribución casi uniforme y un período grande, por lo que es adecuado para una amplia gama de aplicaciones.

Generando Números Aleatorios

La función random() devuelve el siguiente valor de coma flotante aleatorio de la secuencia generada. Todos los valores de retorno caen dentro del rango 0 <= n <1.0.

random_random.py
import random

for i in range(5):
    print('%04.3f' % random.random(), end=' ')
print()

Ejecutar el programa repetidamente produce diferentes secuencias de números.

$ python3 random_random.py

0.859 0.297 0.554 0.985 0.452

$ python3 random_random.py

0.797 0.658 0.170 0.297 0.593

Para generar números en un rango numérico específico, usa en lugar uniform().

random_uniform.py
import random

for i in range(5):
    print('{:04.3f}'.format(random.uniform(1, 100)), end=' ')
print()

Pasa los valores mínimo y máximo, y uniform() ajusta los valores de retorno de random() usando la fórmula min + (max -min) * random ().

$ python3 random_uniform.py

12.428 93.766 95.359 39.649 88.983

Sembrando

random() produce diferentes valores cada vez que se llama y tiene un período muy grande antes de repetir cualquier número. Esto es útil para producir valores únicos o variaciones, pero hay momentos en los que tener el mismo conjunto de datos disponible para ser procesado de diferentes maneras es útil. Una técnica es usar un programa para generar valores aleatorios y guárdalos para ser procesados por un paso separado. Eso puede no ser práctico para grandes cantidades de datos, así que random incluye la función seed() para inicializar el generador pseudoaleatorio para que produzca un conjunto esperado de valores.

random_seed.py
import random

random.seed(1)

for i in range(5):
    print('{:04.3f}'.format(random.random()), end=' ')
print()

El valor de semilla controla el primer valor producido por la fórmula utilizada para producir números pseudoaleatorios, y dado que la fórmula es determinista, también establece la secuencia completa producida después de la semilla sea cambiada. El argumento para seed() puede ser cualquier objeto hashable. El valor predeterminado es usar una fuente de aleatoriedad específica de la plataforma, si está disponible. De lo contrario, se usa la hora actual.

$ python3 random_seed.py

0.134 0.847 0.764 0.255 0.495

$ python3 random_seed.py

0.134 0.847 0.764 0.255 0.495

Guardando el estado

El estado interno del algoritmo pseudoaleatorio utilizado por random() se puede guardar y usar para controlar los números producidos en ejecuciones posteriores. Restaurando el estado anterior antes de continuar reduce la probabilidad de repetir valores o secuencias de valores de la entrada anterior. La función getstate() devuelve datos que se puede utilizar para reinicializar el generador de números aleatorios más tarde con setstate().

random_state.py
import random
import os
import pickle

if os.path.exists('state.dat'):
    # Restore the previously saved state
    print('Found state.dat, initializing random module')
    with open('state.dat', 'rb') as f:
        state = pickle.load(f)
    random.setstate(state)
else:
    # Use a well-known start state
    print('No state.dat, seeding')
    random.seed(1)

# Produce random values
for i in range(3):
    print('{:04.3f}'.format(random.random()), end=' ')
print()

# Save state for next time
with open('state.dat', 'wb') as f:
    pickle.dump(random.getstate(), f)

# Produce more random values
print('\nAfter saving state:')
for i in range(3):
    print('{:04.3f}'.format(random.random()), end=' ')
print()

Los datos devueltos por getstate() son un detalle de implementación, por lo que este ejemplo guarda los datos en un archivo con pickle pero de lo contrario lo trata como una caja negra. Si el archivo existe cuando el programa comienza, carga el estado anterior y continúa. Cada ejecución produce algunos números antes y después de guardar el estado, para mostrar que restaura el estado hace que el generador produzca los mismos valores nuevamente.

$ python3 random_state.py

No state.dat, seeding
0.134 0.847 0.764

After saving state:
0.255 0.495 0.449

$ python3 random_state.py

Found state.dat, initializing random module
0.255 0.495 0.449

After saving state:
0.652 0.789 0.094

Número enteros aleatorios

random() genera números de coma flotante. Es posible convertir los resultados a enteros, pero usando randint() para generar enteros directamente es más conveniente.

random_randint.py
import random

print('[1, 100]:', end=' ')

for i in range(3):
    print(random.randint(1, 100), end=' ')

print('\n[-5, 5]:', end=' ')
for i in range(3):
    print(random.randint(-5, 5), end=' ')
print()

Los argumentos para randint() son los extremos del rango inclusivo para los valores. Los números pueden ser positivos o negativos, pero el primer valor debe ser menor que el segundo.

$ python3 random_randint.py

[1, 100]: 98 75 34
[-5, 5]: 4 0 5

randrange() es una forma más general de seleccionar valores de un rango.

random_randrange.py
import random

for i in range(3):
    print(random.randrange(0, 101, 5), end=' ')
print()

randrange() admite un argumento paso, además de los valores inicio y fin, por lo que es totalmente equivalente a seleccionar un valor aleatorio desde range(inicio, fin, paso). Es más eficiente, porque el rango no está realmente construido.

$ python3 random_randrange.py

15 20 85

Eligiendo elementos aleatorios

Un uso común para generadores de números aleatorios es seleccionar un elemento aleatorio a partir de una secuencia de valores enumerados, incluso si esos valores no son números. random incluye la función choice() para hacer una selección al azar de una secuencia. Este ejemplo simula lanzar una moneda 10,000 veces para contar cuántas veces sale cara y cuántas veces sale cruz.

random_choice.py
import random
import itertools

outcomes = {
    'heads': 0,
    'tails': 0,
}
sides = list(outcomes.keys())

for i in range(10000):
    outcomes[random.choice(sides)] += 1

print('Heads:', outcomes['heads'])
print('Tails:', outcomes['tails'])

Solo se permiten dos resultados, así que en lugar de usar números y convertirlos las palabras «cabezas» y «cola» se utilizan con choice(). Los resultados se tabulan en un diccionario usando los nombres de resultados como llaves.

$ python3 random_choice.py

Heads: 5091
Tails: 4909

Permutaciones

Una simulación de un juego de cartas necesita mezclar el mazo de cartas y luego repartirlas a los jugadores, sin usar la misma tarjeta más de una vez. Usando choice() podría dar como resultado que se reparta la misma carta dos veces, por lo tanto, en su lugar, el mazo se puede mezclar con shuffle() y luego, las cartas individuales se eliminan a medida que se reparten.

random_shuffle.py
import random
import itertools

FACE_CARDS = ('J', 'Q', 'K', 'A')
SUITS = ('H', 'D', 'C', 'S')


def new_deck():
    return [
        # Always use 2 places for the value, so the strings
        # are a consistent width.
        '{:>2}{}'.format(*c)
        for c in itertools.product(
            itertools.chain(range(2, 11), FACE_CARDS),
            SUITS,
        )
    ]


def show_deck(deck):
    p_deck = deck[:]
    while p_deck:
        row = p_deck[:13]
        p_deck = p_deck[13:]
        for j in row:
            print(j, end=' ')
        print()


# Make a new deck, with the cards in order
deck = new_deck()
print('Initial deck:')
show_deck(deck)

# Shuffle the deck to randomize the order
random.shuffle(deck)
print('\nShuffled deck:')
show_deck(deck)

# Deal 4 hands of 5 cards each
hands = [[], [], [], []]

for i in range(5):
    for h in hands:
        h.append(deck.pop())

# Show the hands
print('\nHands:')
for n, h in enumerate(hands):
    print('{}:'.format(n + 1), end=' ')
    for c in h:
        print(c, end=' ')
    print()

# Show the remaining deck
print('\nRemaining deck:')
show_deck(deck)

Las cartas se representan como cadenas con el valor nominal y una letra indicando el palo. Las «manos» repartidas se crean agregando una carta a la vez a cada una de las cuatro listas, y quitándola del mazo para que no se puede repartir de nuevo.

$ python3 random_shuffle.py

Initial deck:
 2H  2D  2C  2S  3H  3D  3C  3S  4H  4D  4C  4S  5H
 5D  5C  5S  6H  6D  6C  6S  7H  7D  7C  7S  8H  8D
 8C  8S  9H  9D  9C  9S 10H 10D 10C 10S  JH  JD  JC
 JS  QH  QD  QC  QS  KH  KD  KC  KS  AH  AD  AC  AS

Shuffled deck:
 QD  8C  JD  2S  AC  2C  6S  6D  6C  7H  JC  QS  QC
 KS  4D 10C  KH  5S  9C 10S  5C  7C  AS  6H  3C  9H
 4S  7S 10H  2D  8S  AH  9S  8H  QH  5D  5H  KD  8D
10D  4C  3S  3H  7D  AD  4H  9D  3D  2H  KC  JH  JS

Hands:
1:  JS  3D  7D 10D  5D
2:  JH  9D  3H  8D  QH
3:  KC  4H  3S  KD  8H
4:  2H  AD  4C  5H  9S

Remaining deck:
 QD  8C  JD  2S  AC  2C  6S  6D  6C  7H  JC  QS  QC
 KS  4D 10C  KH  5S  9C 10S  5C  7C  AS  6H  3C  9H
 4S  7S 10H  2D  8S  AH

Muestreo

Muchas simulaciones necesitan muestras aleatorias de una población valores de entrada. La función sample() genera muestras sin repetir valores y sin modificar la secuencia de entrada. Este ejemplo imprime una muestra aleatoria de palabras del diccionario del sistema.

random_sample.py
import random

with open('/usr/share/dict/words', 'rt') as f:
    words = f.readlines()
words = [w.rstrip() for w in words]

for w in random.sample(words, 5):
    print(w)

El algoritmo para producir el conjunto de resultados tiene en cuenta los tamaños de la entrada y la muestra solicitada para producir el resultado de la manera eficiente posible.

$ python3 random_sample.py

streamlet
impestation
violaquercitrin
mycetoid
plethoretical

$ python3 random_sample.py

nonseditious
empyemic
ultrasonic
Kyurinish
amphide

Múltiples generadores simultáneos

Además de las funciones a nivel de módulo, random incluye una clase Random para gestionar el estado interno de varios generadores aleatorios de números. Todas las funciones descritas anteriormente están disponibles como métodos de las instancias de Random, y cada instancia puede ser inicializada y utilizada por separado, sin interferir con los valores devueltos por otras instancias.

random_random_class.py
import random
import time

print('Default initializiation:\n')

r1 = random.Random()
r2 = random.Random()

for i in range(3):
    print('{:04.3f}  {:04.3f}'.format(r1.random(), r2.random()))

print('\nSame seed:\n')

seed = time.time()
r1 = random.Random(seed)
r2 = random.Random(seed)

for i in range(3):
    print('{:04.3f}  {:04.3f}'.format(r1.random(), r2.random()))

En un sistema con buena creación nativa de valores aleatorios, las instancias comienzan en estados únicos. Sin embargo, si no hay un buen generador aleatorio en la plataforma, es probable que las instancias se hayan sembrado con la hora actual, y por lo tanto producen los mismos valores.

$ python3 random_random_class.py

Default initializiation:

0.862  0.390
0.833  0.624
0.252  0.080

Same seed:

0.466  0.466
0.682  0.682
0.407  0.407

SystemRandom

Algunos sistemas operativos proporcionan un generador de números aleatorios que tiene acceso a más fuentes de entropía que se pueden introducir en el generador. random expone esta característica a través de la clase SystemRandom, que tiene la misma interfaz que Random pero usa os.urandom() para generar los valores que forman la base de todos los otros algoritmos.

random_system_random.py
import random
import time

print('Default initializiation:\n')

r1 = random.SystemRandom()
r2 = random.SystemRandom()

for i in range(3):
    print('{:04.3f}  {:04.3f}'.format(r1.random(), r2.random()))

print('\nSame seed:\n')

seed = time.time()
r1 = random.SystemRandom(seed)
r2 = random.SystemRandom(seed)

for i in range(3):
    print('{:04.3f}  {:04.3f}'.format(r1.random(), r2.random()))

Las secuencias producidas por SystemRandom no son reproducibles porque la aleatoriedad proviene del sistema, no del estado del software (de hecho, seed() y setstate() no tienen efecto del todo).

$ python3 random_system_random.py

Default initializiation:

0.110  0.481
0.624  0.350
0.378  0.056

Same seed:

0.634  0.731
0.893  0.843
0.065  0.177

Distribuciones no uniformes

Si bien la distribución uniforme de los valores producidos por random() es útil para muchos propósitos, otras distribuciones modelan situaciones específicas con mayor precisión. El módulo random incluye funciones para producir valores en esas distribuciones también. Se enumeran aquí, pero no se cubren en detalle porque sus usos tienden a ser especializados y requieren ejemplos más complejos.

Normal

La distribución normal se usa comúnmente para valores continuos no uniformes tales como grados, alturas, pesos, etc. La curva producida por la distribución tiene una forma distintiva que la ha llevado a sera apodada una «curva de campana». random incluye dos funciones para generar valores con una distribución normal, normalvariate() y gauss(), ligeramente más rápida (la distribución normal también llamada distribución de gauss).

La función relacionada, lognormvariate() produce valores pseudoaleatorios donde el logaritmo de los valores está distribuido normalmente. Las distribuciones log-normales son útiles para los valores que son el producto de varias variables aleatorias que no interactúan.

Aproximada

La distribución triangular se usa como una distribución aproximada para tamaños de muestra pequeños. La «curva» de una distribución triangular tiene puntos bajos a valores mínimos y máximos conocidos, y un punto alto en el modo, que se estima en función del resultado «más probable» (reflejado por el argumento de modo a triangular()).

Exponencial

expovariate() produce una distribución exponencial útil para simular valores de llegada o de tiempo de intervalo para en procesos homogéneos Poisson tales como la tasa de descomposición radiactiva o solicitudes que vienen a un servidor web.

La distribución de Pareto, o ley de poder, coincide con muchos fenómenos observables y fue popularizada por The Long Tail, por Chris Anderson. La función paretovariate() es útil para simular la asignación de recursos para individuos (riqueza para las personas, demanda de músicos, atención a los blogs, etc.).

Angular

La distribución de von Mises, o circular normal, (producida por vonmisesvariate()) se usa para calcular las probabilidades de valores cíclicos como ángulos, días calendario y horas.

Tamaños

betavariate() genera valores con la distribución Beta, que se usa comúnmente en estadísticas de Bayes y aplicaciones como modelado de duración de tareas.

La distribución Gamma producida por gammavariate() se usa para modelar los tamaños de cosas tales como tiempos de espera, lluvia y errores computacionales.

La distribución de Weibull calculada por weibullvariate() se usa en el análisis de fallas, ingeniería industrial y pronóstico del clima. Describe la distribución de tamaños de partículas u otros objetos discretos.

Ver también