"random" ---Generar números pseudoaleatorios
********************************************

**Código fuente:** Lib/random.py

======================================================================

Este módulo implementa generadores de números pseudoaleatorios para
varias distribuciones.

Para los enteros, existe una selección uniforme dentro de un rango.
Para las secuencias, existe una selección uniforme de un elemento
aleatorio, una función para generar una permutación aleatoria de una
lista *in-situ* y una función para el muestreo aleatorio sin
reemplazo.

Para números reales, existen funciones para calcular distribuciones
uniformes, normales (Gaussianas), log-normales, exponenciales
negativas, gamma y beta. Para generar distribuciones angulares está
disponible la distribución de von Mises.

Casi todas las funciones del módulo dependen de la función básica
"random()", la cuál genera uniformemente un número flotante aleatorio
en el rango semiabierto [0.0, 1.0). Python utiliza Mersenne Twister
como generador principal. Éste produce números de coma flotante de 53
bits de precisión y tiene un periodo de 2**19937-1. La implementación
subyacente en C es rápida y segura para subprocesos. Mersenne Twister
es uno de los generadores de números aleatorios más testados que
existen. Sin embargo, al ser completamente determinístico, no es
adecuado para todos los propósitos y es completamente inadecuado para
fines criptográficos.

Las funciones proporcionadas por este módulo en realidad son métodos
enlazados a instancias ocultas a la clase "random.Random". Puedes
instanciar tus propias instancias de "Random" para obtener generadores
que no compartan estado.

La clase "Random" puede ser también subclaseada si quieres usar un
generador básico diferente para tu propio diseño: en este caso,
invalida los métodos "random()", "seed()", "getstate()" y
"setstate()". Opcionalmente, se puede substituir un método
"getrandbits()" por un nuevo generador --- esto permite a
"randrange()" producir selecciones sobre un rango arbitrariamente
amplio.

El módulo "random" también proporciona la clase "SystemRandom", la
cuál usa la función del sistema "os.urandom()" para generar números
aleatorios a partir de fuentes proporcionadas por el sistema
operativo.

Advertencia:

  Los generadores pseudoaleatorios de este módulo no deben ser
  utilizados con fines de seguridad. Para usos de seguridad o
  criptográficos, consulta el módulo "secrets".

Ver también:

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

  *Complementary-Multiply-with-Carry recipe* para otro generador de
  números aleatorios con un período largo compatible y con operaciones
  de actualización comparativamente simples.


Funciones de contabilidad
=========================

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

   Inicializa el generador de números aleatorios.

   Si *a* es omitida o "None", se utilizará la hora actual del
   sistema. Si las fuentes de aleatoriedad vienen del sistema
   operativo, éstas se usarán en vez de la hora del sistema (ver la
   función "os.urandom()" para detalles sobre su disponibilidad).

   Si *a* es un entero, se usará directamente.

   Con la versión 2 (la versión por defecto), un objeto "str",
   "bytes", o "bytearray" se convierte en "int" y se usarán todos sus
   bits.

   Con la versión 1 (proporcionada para reproducir secuencias
   aleatorias desde versiones anteriores de Python), el algoritmo para
   "str" y "bytes" genera un rango de semillas más estrecho.

   Distinto en la versión 3.2: El esquema que usa todos los bits en
   una semilla de tipo cadena, se ha movido a la versión 2.

   Obsoleto desde la versión 3.9: En el futuro, la *semilla* debe ser
   de uno de los siguientes tipos: *NoneType*, "int", "float", "str",
   "bytes", o "bytearray".

random.getstate()

   Retorna un objeto capturando el estado interno del generador. Este
   objeto puede pasarse a "setstate()" para restaurar su estado.

random.setstate(state)

   El *state* debería haberse obtenido de una llamada previa a
   "getstate()", y "setstate()" reestablece el estado interno del
   generador al que tenia cuando se llamó a la función "getstate()".


Funciones para los bytes
========================

random.randbytes(n)

   Genera *n* bytes aleatorios.

   Este método no debe utilizarse para generar tokens de seguridad.
   Utilice "secrets.token_bytes()" en su lugar.

   Nuevo en la versión 3.9.


Funciones para enteros
======================

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

   Retorna un elemento de "range(start, stop, step)" seleccionado
   aleatoriamente. Esto es equivalente a "choice(range(start, stop,
   step))", pero en realidad no crea un objeto rango.

   El patrón de argumento posicional coincide con el de "range()". Los
   argumentos no deben usarse porque la función puede usarlos de forma
   inesperada.

   Distinto en la versión 3.2: "randrange()" es más sofisticado
   produciendo valores igualmente distribuidos. Anteriormente
   utilizaba un estilo como "int(random()*n)" el cual puede producir
   distribuciones ligeramente desiguales.

   Obsoleto desde la versión 3.10: La conversión automática de tipos
   que no son enteros a enteros equivalentes está obsoleta.
   Actualmente "randrange(10.0)" se convierte sin pérdidas a
   "randrange(10)".  En el futuro, esto lanzará un "TypeError".

   Obsoleto desde la versión 3.10: La excepción que se lanza de
   valores no enteros como "randrange(10.5)" o "randrange('10')" se
   cambiará de "ValueError" a "TypeError".

random.randint(a, b)

   Retorna un entero aleatorio *N* tal que "a <= N <= b". Alias de
   "randrange(a, b+1)".

random.getrandbits(k)

   Retorna un entero de Python no negativo con *k* bits aleatorios.
   Este método se provee con el generador MersenneTwister y algunos
   otros generadores pueden también proveerlo como una parte opcional
   de la API. Cuando está disponible, "getrandbits()" permite a
   "randrange()" manejar rangos arbitrariamente grandes.

   Distinto en la versión 3.9: Este método ahora acepta cero para *k*.


Funciones para secuencias
=========================

random.choice(seq)

   Retorna un elemento aleatorio de una secuencia *seq* no vacía. Si
   *seq* está vacía, lanza "IndexError".

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

   Retorna una lista de elementos de tamaño *k* elegidos de la
   *population* con reemplazo. Si la *population* está vacía, lanza
   "IndexError".

   Si se especifica una secuencia *weights*, las selecciones se
   realizan de acuerdo con las ponderaciones relativas.
   Alternativamente, si se da una secuencia *cum_weights*, las
   selecciones se harán según los pesos cumulativos (posiblemente se
   calculen usando "itertools.accumulate()"). Por ejemplo, los pesos
   relativos "[10, 5, 30, 5]" son equivalentes a los pesos cumulativos
   "[10, 15, 45, 50]". Internamente, los pesos relativos se convierten
   en pesos cumulativos antes de hacer selecciones, por lo cual suplir
   los pesos cumulativos ahorra trabajo.

   Si ni *weights* ni *cum_weights* están especificadas, las
   selecciones se realizan con la misma probabilidad. Si se
   proporciona una secuencia de ponderaciones, debe tener la misma
   longitud que la secuencia *population*. Es un "TypeError"
   especificar ambas *weights* y *cum_weights*.

   Los *weights* o los *cum_weights* pueden utilizar cualquier tipo
   numérico que interactúe con los valores "float" retornados por
   "random()" (eso incluye enteros, flotantes y fracciones pero
   excluye decimales). Se asume que los pesos son no negativos y
   finitos.  Se lanza un "ValueError" si todos los pesos son cero.

   Dada una semilla, la función "choices()" normalmente produce una
   secuencia diferente a las llamadas repetidas a "choice()" con la
   misma ponderación. El algoritmo usado por "choices()" emplea
   aritmética de coma flotante para la consistencia interna y
   velocidad. El algoritmo usado por "choice()" emplea por defecto
   aritmética de enteros con selecciones repetidas para evitar
   pequeños sesgos de errores de redondeo.

   Nuevo en la versión 3.6.

   Distinto en la versión 3.9: Genera un "ValueError" si todos los
   pesos son cero.

random.shuffle(x[, random])

   Mezcla la secuencia *x* in-situ.

   El argumento opcional *random* es una función de 0 argumentos que
   retorna un flotante random en [0.0, 1.0); por defecto esta es la
   función "random()".

   Para mezclar una secuencia inmutable y retornar una nueva lista
   mezclada, utilice "muestra(x, k=len(x))" en su lugar.

   Tenga en cuenta que incluso para pequeños "len(x)", el número total
   de permutaciones de *x* puede crecer rápidamente más que el periodo
   de muchos generadores de números aleatorios. Esto implica que la
   mayoría de las permutaciones de una secuencia larga nunca se pueden
   generar. Por ejemplo, una secuencia de longitud 2080 es la más
   grande que cabe dentro del período del generador de números
   aleatorios de Mersenne Twister.

   Obsoleto desde la versión 3.9, se eliminará en la versión 3.11: El
   parámetro opcional *random*.

random.sample(population, k, *, counts=None)

   Retorna una lista de longitud *k* de elementos únicos elegidos de
   la secuencia de población o conjunto. Se utiliza para el muestreo
   aleatorio sin reemplazo.

   Retorna una nueva lista que contiene elementos de la población sin
   modificar la población original. La lista resultante está en orden
   de selección de forma que todos los subsectores también son
   muestras aleatorias válidas. Esto permite que los ganadores de la
   rifa (la muestra) se dividan en primer premio y ganadores del
   segundo lugar (los subsectores).

   Los miembros de la población no tienen porqué ser *hashable* o
   únicos. Si la población incluye repeticiones, entonces cada
   ocurrencia es una posible selección en la muestra.

   Los elementos repetidos pueden especificarse de uno en uno o con el
   parámetro opcional *counts*, que es una palabra clave.  Por
   ejemplo, "sample(['red', 'blue'], counts=[4, 2], k=5)" es
   equivalente a "sample(['red', 'red', 'red', 'red', 'blue', 'blue'],
   k=5)".

   Para escoger una muestra de un rango de enteros, use un objeto
   "range()" como argumento. Esto es especialmente rápido y eficiente
   en espacio para el muestreo de poblaciones grandes:
   "sample(range(10000000), k=60)".

   Si el tamaño de la muestra es mayor que el tamaño de la población,
   se lanzará un "ValueError".

   Distinto en la versión 3.9: Se añadió el parámetro *counts*.

   Obsoleto desde la versión 3.9: En el futuro, la *población* debe
   ser una secuencia.  Las instancias de "set" ya no se admiten.  El
   conjunto debe convertirse primero en una "list" o "tuple",
   preferiblemente en un orden determinista para que la muestra sea
   reproducible.


Distribuciones para los nombres reales
======================================

Las siguientes funciones generan distribuciones específicas para
números reales. Los parámetros de la función reciben el nombre de las
variables correspondientes en la ecuación de distribución, tal y como
se utilizan en la práctica matemática común.; la mayoría de estas
ecuaciones se pueden encontrar en cualquier texto estadístico.

random.random()

   Retorna el siguiente número en coma flotante aleatorio dentro del
   rango [0.0, 1.0).

random.uniform(a, b)

   Retorna un número en coma flotante aleatorio *N* tal que "a <= N <=
   b" para "a <= b" y "b <= N <= a" para "b < a".

   El valor final "b" puede o no estar incluido en el rango,
   dependiendo del redondeo de coma flotante en la ecuación "a + (b-a)
   * random()".

random.triangular(low, high, mode)

   Retorna un número de coma flotante *N* tal que "low <= N <= high" y
   con el *mode* especificado entre esos límites. Los límites *low*
   (inferior) y *high* (superior) son por defecto cero y uno. El
   argumento *mode* tiene como valor por defecto el punto medio entre
   los límites, dando lugar a una distribución simétrica.

random.betavariate(alpha, beta)

   Distribución beta. Las condiciones de los parámetros son "alpha >
   0" y "beta > 0". Retorna valores dentro del rango entre 0 y 1.

random.expovariate(lambd)

   Distribución exponencial. *lambd* es 1.0 dividido entre la media
   deseada. Debe ser distinto a cero (El parámetro debería llamarse
   "lambda" pero esa es una palabra reservada en Python). Retorna
   valores dentro del rango de 0 a infinito positivo si *lambd* es
   positivo, y de infinito negativo a 0 si *lambd* es negativo.

random.gammavariate(alpha, beta)

   Distribución gamma. (¡*No* la función gamma!) Las condiciones en
   los parámetros son "alpha > 0" y "beta > 0".

   La función de distribución de la probabilidad es:

                x ** (alpha - 1) * math.exp(-x / beta)
      pdf(x) =  --------------------------------------
                  math.gamma(alpha) * beta ** alpha

random.gauss(mu, sigma)

   Distribución normal, también llamada la distribución Gaussiana.
   *mu* es la media y *sigma* es la desviación estándar. Es un poco
   más rápida que la función "normalvariate()" definida debajo.

   Nota sobre el multithreading: Cuando dos hilos llaman a esta
   función simultáneamente, es posible que reciban el mismo valor de
   retorno.  Esto se puede evitar de tres maneras. 1) Hacer que cada
   hilo utilice una instancia diferente del generador de números
   aleatorios. 2) Poner bloqueos alrededor de todas las llamadas. 3)
   Utilizar la función "normalvariate()", más lenta pero segura para
   los hilos, en su lugar.

random.lognormvariate(mu, sigma)

   Logaritmo de la distribución normal. Si se usa un logaritmo natural
   de esta distribución, se obtendrá una distribución normal con media
   *mu* y desviación estándar *sigma*. *mu* puede tener cualquier
   valor, y *sigma* debe ser mayor que cero.

random.normalvariate(mu, sigma)

   Distribución normal. *mu* es la media y *sigma* es la desviación
   estándar.

random.vonmisesvariate(mu, kappa)

   *mu* es el ángulo medio, expresado en radiantes entre 0 y 2**pi*, y
   *kappa* es el parámetro de concentración, que debe ser mayor o
   igual a cero. Si *kappa* es igual a cero, esta distribución se
   reduce a un ángulo aleatorio uniforme sobre el rango de 0 a 2**pi*.

random.paretovariate(alpha)

   Distribución de Pareto. *alpha* es el parámetro de forma.

random.weibullvariate(alpha, beta)

   Distribución de Weibull. *alpha* es el parámetro de escala y *beta*
   es el parámetro de forma.


Generador alternativo
=====================

class random.Random([seed])

   Esta clase implementa el generador de números pseudoaleatorios
   predeterminado que usa el módulo "random" .

   Obsoleto desde la versión 3.9: En el futuro, la *semilla* debe ser
   de uno de los siguientes tipos: "NoneType", "int", "float", "str",
   "bytes", o "bytearray".

class random.SystemRandom([seed])

   Clase que utiliza la función "os.urandom()" para generar números
   aleatorios a partir de fuentes proporcionadas por el sistema
   operativo. No está disponible en todos los sistemas. No se basa en
   el estado del software y las secuencias no son reproducibles. En
   consecuencia, el método "seed()" no tiene efecto y es ignorado. Los
   métodos "getstate()" y "setstate()" lanzan "NotImplementedError" si
   se les llama.


Notas sobre la Reproducibilidad
===============================

A veces es útil poder reproducir las secuencias dadas por un generador
de números pseudoaleatorios.  Al reutilizar un valor de semilla, la
misma secuencia debería ser reproducible de una ejecución a otra
siempre que no se estén ejecutando múltiples hilos.

Muchos de los algoritmos y de las funciones de generación de semillas
del módulo aleatorio pueden cambiar entre versiones de Python, pero se
garantiza que dos aspectos no cambien:

* Si se añade un nuevo método de generación de semilla, se ofrecerá un
  generador de semilla retrocompatible.

* El método generador "random()" continuará produciendo la misma
  secuencia cuando se le da la misma semilla al generador de semilla
  compatible.


Ejemplos
========

Ejemplos básicos:

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

Simulaciones:

   >>> # 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:  ten, jack, queen, or king.
   >>> dealt = sample(['tens', 'low cards'], counts=[16, 36], k=20)
   >>> dealt.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.
   >>> def trial():
   ...     return choices('HT', cum_weights=(0.60, 1.00), k=7).count('H') >= 5
   ...
   >>> sum(trial() for i in range(10_000)) / 10_000
   0.4169

   >>> # Probability of the median of 5 samples being in middle two quartiles
   >>> def trial():
   ...     return 2_500 <= sorted(choices(range(10_000), k=5))[2] < 7_500
   ...
   >>> sum(trial() for i in range(10_000)) / 10_000
   0.7958

Ejemplo de statistical bootstrapping utilizando el remuestreo con
reemplazo para estimar un intervalo de confianza para la media de una
muestra:

   # https://www.thoughtco.com/example-of-bootstrapping-3126155
   from statistics import fmean as mean
   from random import choices

   data = [41, 50, 29, 37, 81, 30, 73, 63, 20, 35, 68, 22, 60, 31, 95]
   means = sorted(mean(choices(data, k=len(data))) for i in range(100))
   print(f'The sample mean of {mean(data):.1f} has a 90% confidence '
         f'interval from {means[5]:.1f} to {means[94]:.1f}')

Ejemplo de un test de permutación en remuestreo (en) para determinar
la significación estadística o p-valor de una diferencia observada
entre los efectos de un fármaco y un placebo:

   # Example from "Statistics is Easy" by Dennis Shasha and Manda Wilson
   from statistics import fmean as 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 = 10_000
   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.')

Simulación de tiempos de llegada y entrega de servicios para una cola
de múltiples servidores:

   from heapq import heapify, heapreplace
   from random import expovariate, gauss
   from statistics import mean, quantiles

   average_arrival_interval = 5.6
   average_service_time = 15.0
   stdev_service_time = 3.5
   num_servers = 3

   waits = []
   arrival_time = 0.0
   servers = [0.0] * num_servers  # time when each server becomes available
   heapify(servers)
   for i in range(1_000_000):
       arrival_time += expovariate(1.0 / average_arrival_interval)
       next_server_available = servers[0]
       wait = max(0.0, next_server_available - arrival_time)
       waits.append(wait)
       service_duration = max(0.0, gauss(average_service_time, stdev_service_time))
       service_completed = arrival_time + wait + service_duration
       heapreplace(servers, service_completed)

   print(f'Mean wait: {mean(waits):.1f}   Max wait: {max(waits):.1f}')
   print('Quartiles:', [round(q, 1) for q in quantiles(waits)])

Ver también:

  *Statistics for Hackers* un video tutorial de Jake Vanderplas sobre
  análisis estadístico usando sólo algunos conceptos fundamentales
  incluyendo simulación, muestreo, baraja y validación cruzada.

  Economics Simulation a simulation of a marketplace by Peter Norvig
  that shows effective use of many of the tools and distributions
  provided by this module (gauss, uniform, sample, betavariate,
  choice, triangular, and randrange).

  A Concrete Introduction to Probability (using Python) a tutorial by
  Peter Norvig covering the basics of probability theory, how to write
  simulations, and how to perform data analysis using Python.


Recetas
=======

La función "random()" por defecto devuelve múltiplos de 2⁻⁵³ en el
rango *0.0 ≤ x < 1.0*.  Todos estos números están espaciados
uniformemente y son representables exactamente como flotantes de
Python.  Sin embargo, muchos otros flotadores representables en ese
intervalo no son selecciones posibles.  Por ejemplo,
"0.05954861408025609" no es un múltiplo entero de 2⁻⁵³.

La siguiente receta adopta un enfoque diferente.  Todos los flotantes
en el intervalo son selecciones posibles.  La mantisa proviene de una
distribución uniforme de enteros en el rango *2⁵² ≤ mantisa < 2⁵³*.
El exponente proviene de una distribución geométrica en la que los
exponentes menores de *-53* ocurren con la mitad de frecuencia que el
siguiente exponente mayor.

   from random import Random
   from math import ldexp

   class FullRandom(Random):

       def random(self):
           mantissa = 0x10_0000_0000_0000 | self.getrandbits(52)
           exponent = -53
           x = 0
           while not x:
               x = self.getrandbits(32)
               exponent += x.bit_length() - 32
           return ldexp(mantissa, exponent)

Todas las distribuciones de valor real de la clase utilizarán el nuevo
método:

   >>> fr = FullRandom()
   >>> fr.random()
   0.05954861408025609
   >>> fr.expovariate(0.25)
   8.87925541791544

La receta es conceptualmente equivalente a un algoritmo que elige
entre todos los múltiplos de 2⁻¹⁰⁷⁴ en el rango *0,0 ≤ x < 1,0*.
Todos esos números son uniformes, pero la mayoría tienen que ser
redondeados al flotante de Python representable más cercano.  (El
valor 2⁻¹⁰⁷⁴ es el menor flotante positivo no normalizado y es igual a
"math.ulp(0.0)".)

Ver también:

  Generating Pseudo-random Floating-Point Values un artículo de Allen
  B. Downey en el que se describen formas de generar flotantes más
  refinados que los generados normalmente por "random()".
