9.6. "random" --- Gera números pseudoaleatórios
***********************************************

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

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

Este módulo implementa geradores de números pseudoaleatórios para
várias distribuições.

Para números inteiros, há uma seleção uniforme de um intervalo. Para
sequências, há uma seleção uniforme de um elemento aleatório, uma
função para gerar uma permutação aleatória de uma lista no local e uma
função para amostragem aleatória sem substituição.

Na linha real, existem funções para calcular distribuições uniforme,
normal (gaussiana), lognormal, exponencial negativa, gama e beta. Para
gerar distribuições de ângulos, a distribuição de von Mises está
disponível.

Quase todas as funções do módulo dependem da função básica "random()",
que gera um ponto flutuante aleatório uniformemente no intervalo
semiaberto [0.0, 1.0). O Python usa o Mersenne Twister como gerador de
núcleo. Produz pontos flutuantes de precisão de 53 bits e possui um
período de 2**19937-1. A implementação subjacente em C é rápida e
segura para threads. O Mersenne Twister é um dos geradores de números
aleatórios mais amplamente testados existentes. No entanto, sendo
completamente determinístico, não é adequado para todos os fins e é
totalmente inadequado para fins criptográficos.

As funções fornecidas por este módulo são, na verdade, métodos
vinculados de uma instância oculta da classe "random.Random". Você
pode instanciar suas próprias instâncias de "Random" para obter
geradores que não compartilham estado.

A classe "Random" também pode ser usado como subclasse se você quiser
usar um gerador básico diferente de sua preferência: nesse caso,
substitua os métodos "random()", "seed()", "getstate()" e
"setstate()". Opcionalmente, um novo gerador pode fornecer um método
"getrandbits()" --- isso permite que "randrange()" produza seleções a
um intervalo grande arbitrário.

O módulo "random" também fornece a classe "SystemRandom" que usa a
função do sistema "os.urandom()" para gerar números aleatórios a
partir de fontes fornecidas pelo sistema operacional.

Aviso:

  Os geradores pseudoaleatórios deste módulo não devem ser usados para
  fins de segurança. Para segurança ou uso criptográfico, veja o
  módulo "secrets".

Ver também:

  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, January pp.3--30 1998.

  Receita de Complementary-Multiply-with-Carry para um gerador de
  números aleatórios alternativo compatível com um longo período e
  operações de atualização comparativamente simples.


9.6.1. Funções de contabilidade
===============================

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

   Inicializa o gerador de números aleatórios.

   Se *a* for omitido ou "None", a hora atual do sistema será usada.
   Se fontes de aleatoriedade são fornecidas pelo sistema operacional,
   elas são usadas no lugar da hora do sistema (consulte a função
   "os.urandom()" para detalhes sobre disponibilidade).

   Se *a* é um int, ele é usado diretamente.

   Com a versão 2 (o padrão), o objeto a "str", "bytes" ou "bytearray"
   é convertido em um objeto "int" e todos os seus bits são usados.

   Com a versão 1 (fornecida para reproduzir sequências aleatórias de
   versões mais antigas do Python), o algoritmo para "str" e "bytes"
   gera um intervalo mais restrito de sementes.

   Alterado na versão 3.2: Movido para o esquema da versão 2, que usa
   todos os bits em uma semente de strings.

random.getstate()

   Retorna um objeto capturando o estado interno atual do gerador.
   Este objeto pode ser passado para "setstate()" para restaurar o
   estado.

random.setstate(state)

   *state* deveria ter sido obtido de uma chamada anterior para
   "getstate()", e "setstate()" restaura o estado interno do gerador
   para o que era no momento "getstate()" foi chamado.

random.getrandbits(k)

   Retorna um número inteiro Python com bits aleatórios *k*. Este
   método é fornecido com o gerador MersenneTwister e alguns outros
   geradores também podem fornecê-lo como uma parte opcional da API.
   Quando disponível, "getrandbits()" permite que "randrange()"
   manipule intervalos arbitrariamente grandes.


9.6.2. Funções para inteiros
============================

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

   Retorna um elemento selecionado aleatoriamente em "range(start,
   stop, step)". Isso é equivalente a "choice(range(start, stop,
   step))", mas na verdade não cria um objeto range.

   O padrão de argumento posicional corresponde ao de "range()". Os
   argumentos nomeados não devem ser usados porque a função pode
   usá-los de maneiras inesperadas.

   Alterado na versão 3.2: "randrange()" é mais sofisticado em
   produzir valores igualmente distribuídos. Anteriormente, usava um
   estilo como "int(random()*n)", que poderia produzir distribuições
   ligeiramente desiguais.

random.randint(a, b)

   Retorna um inteiro aleatório *N* de forma que "a <= N <= b".
   Apelido para "randrange(a, b+1)".


9.6.3. Funções para sequências
==============================

random.choice(seq)

   Retorna um elemento aleatório da sequência não vazia *seq*. Se
   *seq* estiver vazio, levanta "IndexError".

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

   Retorna uma lista do tamanho de *k* de elementos escolhidos da
   *population* com substituição. Se a *population* estiver vazio,
   levanta "IndexError".

   Se uma sequência *weights* for especificada, as seleções serão
   feitas de acordo com os pesos relativos. Alternativamente, se uma
   sequência *cum_weights* for fornecida, as seleções serão feitas de
   acordo com os pesos cumulativos (talvez calculados usando
   "itertools.accumulate()"). Por exemplo, os pesos relativos "[10, 5,
   30, 5]" são equivalentes aos pesos acumulados "[10, 15, 45, 50]".
   Internamente, os pesos relativos são convertidos em pesos
   acumulados antes de fazer seleções, portanto, fornecer pesos
   cumulativos economiza trabalho.

   Se nem *weights* nem *cum_weights* forem especificados, as seleções
   serão feitas com igual probabilidade. Se uma sequência de pesos for
   fornecida, ela deverá ter o mesmo comprimento que a sequência
   *population*. É um "TypeError" para especificar ambos os *weights*
   e *cum_weights*.

   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).

   Novo na versão 3.6.

random.shuffle(x[, random])

   Embaralha a sequência *x* no lugar.

   O argumento opcional *random* é uma função de 0 argumentos
   retornando um ponto flutuante aleatório em [0.0, 1.0); por padrão,
   esta é a função "random()".

   Para embaralhar uma sequência imutável e retornar uma nova lista
   embaralhada, use "sample(x, k=len(x))".

   Observe que, mesmo para pequenos "len(x)", o número total de
   permutações de *x* pode crescer rapidamente maior que o período da
   maioria dos geradores de números aleatórios. Isso implica que a
   maioria das permutações de uma longa sequência nunca pode ser
   gerada. Por exemplo, uma sequência de comprimento 2080 é a maior
   que pode caber no período do gerador de números aleatórios Mersenne
   Twister.

random.sample(population, k)

   Retorna uma lista de comprimento *k* de elementos exclusivos
   escolhidos a partir da sequência ou conjunto da população. Usado
   para amostragem aleatória sem substituição.

   Retorna uma nova lista contendo elementos da população, mantendo a
   população original inalterada. A lista resultante está na ordem de
   seleção, para que todas as sub-fatias também sejam amostras
   aleatórias válidas. Isso permite que os vencedores do sorteio (a
   amostra) sejam divididos em grandes prêmios e vencedores de segundo
   lugar (as sub-fatias).

   Os membros da população não precisam ser *hasheáveis* ou único. Se
   a população contiver repetições, cada ocorrência é uma seleção
   possível na amostra.

   Para escolher uma amostra de um intervalo de números inteiros, use
   um objeto "range()" como argumento. Isso é especialmente rápido e
   com eficiência de espaço para amostragem de uma grande população:
   "sample(range(10000000), k=60)".

   Se o tamanho da amostra for maior que o tamanho da população, uma
   "ValueError" é levantada.


9.6.4. Distribuições com valor real
===================================

As funções a seguir geram distribuições específicas com valor real. Os
parâmetros de função são nomeados após as variáveis correspondentes na
equação da distribuição, conforme usadas na prática matemática comum;
a maioria dessas equações pode ser encontrada em qualquer texto
estatístico.

random.random()

   Retorna o próximo número de ponto flutuante aleatório no intervalo
   [0.0, 1.0).

random.uniform(a, b)

   Retorna um número de ponto flutuante aleatório *N* de forma que "a
   <= N <= b" para "a <= b" e "b <= N <= a" para "b < a".

   O valor do ponto final "b" pode ou não ser incluído no intervalo,
   dependendo do arredondamento do ponto flutuante na equação "a +
   (b-a) * random()".

random.triangular(low, high, mode)

   Retorna um número de ponto flutuante aleatório *N* de forma que
   "low <= N <= high" e com o modo *mode* especificado entre esses
   limites. Os limites *low* e *high* são padronizados como zero e um.
   O argumento *mode* assume como padrão o ponto médio entre os
   limites, fornecendo uma distribuição simétrica.

random.betavariate(alpha, beta)

   Distribuição beta. As condições nos parâmetros são "alpha > 0" e
   "beta > 0". Os valores retornados variam entre 0 e 1.

random.expovariate(lambd)

   Distribuição exponencial. *lambd* é 1.0 dividido pela média
   desejada. Deve ser diferente de zero. (O parâmetro seria chamado
   "lambda", mas é uma palavra reservada em Python.) Os valores
   retornados variam de 0 a infinito positivo se *lambd* for positivo
   e de infinito negativo a 0 se *lambd* for negativo.

random.gammavariate(alpha, beta)

   Distribuição gama. (*Não* a função gama!) As condições nos
   parâmetros são "alpha > 0" e "beta > 0".

   A função de distribuição de probabilidade é:

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

random.gauss(mu, sigma)

   Distribuição gaussiana. *mu* é a média e *sigma* é o desvio padrão.
   Isso é um pouco mais rápido que a função "normalvariate()" definida
   abaixo.

random.lognormvariate(mu, sigma)

   Distribuição log normal. Se você usar o logaritmo natural dessa
   distribuição, obterá uma distribuição normal com média *mu* e
   desvio padrão *sigma*. *mu* pode ter qualquer valor e *sigma* deve
   ser maior que zero.

random.normalvariate(mu, sigma)

   Distribuição normal. *mu* é a média e *sigma* é o desvio padrão.

random.vonmisesvariate(mu, kappa)

   *mu* é o ângulo médio, expresso em radianos entre 0 e 2**pi*, e
   *kappa* é o parâmetro de concentração, que deve ser maior ou igual
   a zero. Se *kappa* for igual a zero, essa distribuição será
   reduzida para um ângulo aleatório uniforme no intervalo de 0 a
   2**pi*.

random.paretovariate(alpha)

   Distribuição de Pareto. *alpha* é o parâmetro de forma.

random.weibullvariate(alpha, beta)

   Distribuição Weibull. *alpha* é o parâmetro de escala e *beta* é o
   parâmetro de forma.


9.6.5. Gerador alternativo
==========================

class random.SystemRandom([seed])

   Classe que usa a função "os.urandom()" para gerar números
   aleatórios a partir de fontes fornecidas pelo sistema operacional.
   Não disponível em todos os sistemas. Não depende do estado do
   software e as sequências não são reproduzíveis. Assim, o método
   "seed()" não tem efeito e é ignorado. Os métodos "getstate()" e
   "setstate()" levantam "NotImplementedError" se chamados.


9.6.6. Notas sobre reprodutibilidade
====================================

Sometimes it is useful to be able to reproduce the sequences given by
a pseudo random number generator.  By re-using a seed value, the same
sequence should be reproducible from run to run as long as multiple
threads are not running.

A maioria dos algoritmos e funções de propagação do módulo aleatório
está sujeita a alterações nas versões do Python, mas dois aspectos são
garantidos para não serem alterados:

* Se um novo método de semeadura for adicionado, será oferecida uma
  semeadora compatível com versões anteriores.

* O método do gerador "random()" continuará produzindo a mesma
  sequência quando o semeador compatível receber a mesma semente.


9.6.7. Exemplos e receitas
==========================

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

Simulações:

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

Example of statistical bootstrapping using resampling with replacement
to estimate a confidence interval for the mean of a sample of size
five:

   # 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}')

Exemplo de um teste de permutação de reamostragem pára determinar a
significância estatística ou valor-p de uma diferença observada entre
os efeitos de uma droga em comparação com um 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 of arrival times and service deliveries in a single server
queue:

   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}.')

Ver também:

  Statistics for Hackers um tutorial em vídeo por Jake Vanderplas
  sobre análise estatística usando apenas alguns conceitos
  fundamentais, incluindo simulação, amostragem, embaralhamento e
  validação cruzada.

  Economics Simulation uma simulação de um mercado por Peter Norvig
  isso mostra o uso eficaz de muitas das ferramentas e distribuições
  fornecidas por este módulo (gauss, uniforme, amostra, betavariado,
  escolha, triangular e intervalo).

  A Concrete Introduction to Probability (using Python) um tutoria por
  Peter Norvig cobrindo o básico da teoria das probabilidades, como
  escrever simulações e como realizar análise de dados usando Python.
