Projeto adaptado para trabalhar com as versões mais recentes do sorteio.
site: http://loterias.caixa.gov.br/wps/portal/loterias/landing/megasena/
Vamos focar a análise nos números sorteados, desconsiderando as informações de arrecadação e premiações. A ideia é brincar um pouco com os sorteios e encontrar uma lógica para gerar um jogo vencedor!
Será que é possível? Vamos ver!
# Importação dos pacotes
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# Importando funções criadas para o projeto
from analysis.functions import download_raffle_file
from analysis.functions import transform_html_to_csv
from analysis.functions import pre_process_dataframe
from analysis.functions import get_data_dir
from analysis.functions import print_frequency_report
# Definindo o caminho do download
DOWNLOAD_PATH = 'http://www1.caixa.gov.br/loterias/_arquivos/loterias/D_megase.zip'
FILENAME = 'raffle.html'
# Acessando o diretório de trabalho
SAVE_PATH = get_data_dir()
# Fazendo download do arquivo atualizado
download_raffle_file(url=DOWNLOAD_PATH, path=SAVE_PATH, filename=FILENAME)
# Transformando a tabela html que vem no arquivo zip para csv
transform_html_to_csv(path=SAVE_PATH, filename=FILENAME)
# Criando e pré-processando o dataframe com os números.
megasena = pre_process_dataframe(filename=FILENAME, drop=['Cidade',
'UF',
'Arrecadacao_Total',
'Ganhadores_Sena',
'Rateio_Sena',
'Ganhadores_Quina',
'Rateio_Quina',
'Ganhadores_Quadra',
'Rateio_Quadra',
'Acumulado',
'Valor_Acumulado',
'Estimativa_Prêmio',
'Acumulado_Mega_da_Virada'])
# Definindo nome das colunas
names = ['Data', '1_n', '2_n','3_n', '4_n', '5_n', '6_n']
# Alterando os nomes para facilitar a manipulação
megasena.columns = names
# Vizualizando as primeiras linhas
megasena.head()
Será que conseguimos acertar os números de algum sorteio dessa forma?
# Vamos extrair os sorteios para verificar se algum dos jogos que foram gerados já foram sorteados em alguma oportunidade.
sorteios = []
for i in range(1, len(megasena) + 1):
sorteios.append(list(megasena.loc[i, '1_n':'6_n'].values))
# Colocando os números em ordem para facilitar a avaliação
for sorteio in sorteios:
sorteio.sort()
# Avaliando a distribuição de frêquencia entre os números sorteados em cada dezena
mega_dezenas = megasena.loc[:, '1_n':'6_n']
plt.figure(figsize=(18,9))
for i, n in enumerate(mega_dezenas.columns):
plt.subplot(231+i)
plt.hist(mega_dezenas[n], bins=60)
plt.xticks(range(0, 61, 5))
plt.yticks(range(20, mega_dezenas[n].value_counts().max()+5, 5))
plt.ylim(20, mega_dezenas[n].value_counts().max()+5)
plt.title(f'{i+1}ª Dezena')
plt.tight_layout()
# Agora, vamos criar alguns jogos baseados na frequencia de sorteio de cada número.
# Jogos com os números mais frequentes:
jogos_mais_frequentes = []
print()
for i, col in enumerate(mega_dezenas.columns):
jogos_mais_frequentes.append(mega_dezenas[col].value_counts().head(6).index.to_list())
print(f'{i+1}ª Dezena: {mega_dezenas[col].value_counts().head(6).index.to_list()}', end='\n\n')
print('-=' * 20, end='\n\n')
# Colocando os números em ordem para facilitar a avaliação
for jogo in jogos_mais_frequentes:
jogo.sort()
# Jogos com os números mais frequentes por dezena:
jogos_menos_frequentes = []
print()
for i, col in enumerate(mega_dezenas.columns):
jogos_menos_frequentes.append(mega_dezenas[col].value_counts(ascending=True).head(6).index.to_list())
print(f'{i+1}ª Dezena: {mega_dezenas[col].value_counts(ascending=True).head(6).index.to_list()}', end='\n\n')
print('-=' * 20, end='\n\n')
# Colocando os números em ordem para facilitar a avaliação
for jogo in jogos_menos_frequentes:
jogo.sort()
# Avaliando a distribuição das médias entre os 6 números sorteados em cada sorteio.
plt.figure(figsize=(16,8))
plt.hist(mega_dezenas.agg(func=['mean'], axis=1)['mean'], bins=30, color='gray')
plt.xticks(range(10, 58, 4))
plt.title('Distribuição das Médias de cada Sorteio')
plt.xlabel('Médias')
plt.ylabel('Frequência')
plt.tight_layout()
plt.show()
# Preparando o dataset para avaliação geral dos números
dezenas = mega_dezenas.reset_index(level=0)
dezenas = dezenas.melt(id_vars='Concurso',
value_vars=['1_n', '2_n', '3_n', '4_n', '5_n', '6_n'],
var_name='Dezena',
value_name='Numero')
dezenas.sort_values(['Concurso', 'Dezena']).reset_index(drop=True).head(12)
# Avaliando a distribuição de frequência geral dos números, descartando a dezena em que foi sorteado.
y_lim = dezenas.Numero.value_counts().max() + 5
plt.figure(figsize=(18,9))
plt.hist(dezenas['Numero'], bins=60, color='green')
plt.xticks(range(0, 61, 5))
plt.ylim(170, y_lim)
plt.title('Frequência Geral dos Números')
plt.xlabel('Números')
plt.ylabel('Frequência')
plt.tight_layout()
plt.show()
# Organizando o gráfico em ordem crescente de frequência
freq = dezenas.groupby('Numero').count().iloc[:,0]
plt.figure(figsize=(16,8))
freq.sort_values().plot(kind='bar')
plt.title('Frequência dos números em ordem crescente')
plt.ylim(170, y_lim)
plt.tight_layout()
plt.show()
print_frequency_report(frequency_dataframe=freq)
# Finalizamos então com 14 jogos,
# 12 para a maior e menor frequencia dos números em cada dezena
# Além do maior e menor descartando a ordem que apareceu.
geral = [[4, 5, 10, 23, 33, 53], [3, 15, 21, 22, 26, 55]]
# Vamos juntar todos os jogos!
# Será que algum deles já foi sorteado?
todos_os_jogos = [jogos_mais_frequentes, jogos_menos_frequentes, geral]
# Checando todos os jogos
for jogos in todos_os_jogos:
for jogo in jogos:
if jogo in sorteios:
print('Temos um vencedor!!!', jogo)
break
else:
print('Esse jogo nunca foi sorteado:', jogo)
Nem com toda essa lógica conseguimos acertar um jogo.
E ainda, tentamos em mais de 2300 sorteios.
É tão díficil assim ganhar na Megasena? Vamos fazer alguns testes.
# Importando a classe "Gambler" criada para esse projeto
# Essa classe simula um apostador.
from statistics.gambler import Gambler
Dessa forma, podemos validar outros tipos de jogos e sorteios.
Nesse caso, vamos focar apenas na megasena.
# Definindo as regras do sorteio
intervalo_numeros = 60
quantidade_numeros_sorteados = 6
# Definindo como serão nossos jogos
quantidade_numeros_jogados = 6
jogos_por_jogador = 1
# Instanciando a classe com os parametros definidos anteriormente
gambler = Gambler(numbers_range=intervalo_numeros,
numbers_amount=quantidade_numeros_sorteados,
numbers_played=quantidade_numeros_jogados,
trials=jogos_por_jogador)
# Com a classe instanciada, conseguimos acessar algumas informações, como o número sorteado.
# Prodemos sobrescrever esse número se for necessário.
# Nesse exemplo, temos o sorteio, os jogos feitos pelo apostador e a quantidade de acertos por jogo.
# Nesse caso, temos apenas um jogo, mas podemos aumentar para quantos quisermos.
print(f'Sorteio: {gambler.raffle_numbers}')
print(f'Aposta: {gambler.gamble()[0]}')
print(f'Acertos: {gambler.check_hits()}')
# Agora, vamos tentar 100 vezes.
jogos_por_jogador = 100
# Vamos instanciar a classe novamente, agora usando 100 jogos por jogador.
gambler = Gambler(numbers_range=intervalo_numeros,
numbers_amount=quantidade_numeros_sorteados,
numbers_played=quantidade_numeros_jogados,
trials=jogos_por_jogador)
# Como o relatório ficaria muito grande, criamos uma função que avalia todos os jogos e retorna nosso resultado.
gambler.play()
Não conseguimos acertar nem com 100 tentativas.
Será que conseguimos com 1 milhão?
# Vamos tentar novamente, agora com 1.000.000 de tentativas.
jogos_por_jogador = 1_000_000
gambler = Gambler(numbers_range=intervalo_numeros,
numbers_amount=quantidade_numeros_sorteados,
numbers_played=quantidade_numeros_jogados,
trials=jogos_por_jogador)
gambler.play()
Já chegamos mais perto, mas mesmo assim não foi o suficiente.
Vamos pedir ajuda ao reporter especializado em jogos e a um clube de apostadores para ver qual a real dificuldade de se vencer nesse jogo
A ideia aqui foi criar uma classe espefícia para gerar os relatórios e outras, para criar uma amostragem significativa o suficiente para validar os resultados que tivermos.
Na "GamblersClub" (Clube de Apostadores) foi adicionado um argumento para definir o número de jogadores (número de amostras) mas a feature mais interessante é que a classe foi otimizada para realizar o processamento das amostras em pararelo, com um ganho bem expressivo de performance:
Esses foram os tempos de processamento para gerar 30 amostras com 100.000.000 de tentativas cada.
# Importando a classe Reporter criada para esse projeto
from magazine.reporter import Reporter
# Instanciando a classe
reporter = Reporter()
# Checando a tabela de probabilidades divulgado pela CEF
reporter.show_probabilities_image()
Para cada quantidade de números jogados, nós geramos 30 amostras com uma quantidade de tentativas alinhada com as chances de acerto. A ideia era ter um número grande o suficiente para ter ao menos 2 acertos na sena.
Mesmo com o processo otimizado para processar os jogos paralelamente, o tempo de processamento é relativamente alto, por isso, salvei as listas para facilitar o acesso.
todos as 15 amostras estão disponíveis através do link: SAMPLES
Um ponto interessante que surgiu no momento de armazenar os dados, foi que como uma matriz numpy o tamanho das amostras excedeu bastante as minhas expectativas. Como solução, usei a biblioteca joblib
para salvar os dados no formato pickle e tivemos uma redução bastante significativa.
import matplotlib.pyplot as plt
numpy = plt.imread('data/numpy.png')
pickle = plt.imread('data/pickle.png')
plt.figure(figsize=(15,10))
plt.subplot(121)
plt.axis('off')
plt.title('Armazenamento no formato Numpy')
plt.imshow(numpy)
plt.subplot(122)
plt.axis('off')
plt.title('Armazenamento no formato Pickle')
plt.imshow(pickle)
plt.tight_layout()
plt.show()
Para exemplificar qual seria o processo para gerar as amostras, vamos exemplicar a utilização da classe GamblersClub
.
Vamos gerar uma amostra de tamanho médio e medir seu tempo.
from statistics.club import GamblersClub
intervalo_numeros = 60 #Intevalo de números aleatórios(Ex: MegaSena = 60)
quantidade_numeros_sorteados = 6 #Quantidade de números a serem sorteados (Ex: MegaSena = 6)
quantidade_numeros_jogados = 6 #Quantidade de números escolhidos para jogar (Ex: MegaSena de 6 até 15)
jogos_por_jogador = 1_000_000 #Quantidade de jogos aleatórios para tentar acertar o sorteio
jogadores = 10 #Quantidade de repetições para gerar um amostra estatística
gamblers = GamblersClub(numbers_range=intervalo_numeros,
numbers_amount=quantidade_numeros_sorteados,
numbers_played=quantidade_numeros_jogados,
trials=jogos_por_jogador,
players=jogadores)
%time gamblers.play()
Para finalizar, vamos solicitar ao reporter que verifique as amostras e nos gere um relatório sobre nossos resultados comparados com a tabela de probabilidades.
Também vamos solicitar um intervalo de confiança para avaliar esses números.
path = 'data/trials-15_000_000-samples-30-numbers-7.pkl'
%time reporter.load_hits(path)
%time reporter.hit_report(number=4)
%time reporter.confidence_report(number=4, confidence=0.99)
%time reporter.hit_report(number=5)
%time reporter.confidence_report(number=5, confidence=95)
%time reporter.hit_report(number=6)
%time reporter.confidence_report(number=6, confidence=0.90)
As quantidades ficaram bem próximas da tabela, com exceção do número 6 que exigiria que mais tentativas fossem realizadas para aferir corretamente a média de jogos para conseguir uma sena.
Outro ponto interessante é que a técnica de bootstrap com uma quantidade grande de dados apresentou um gargalo no processo, onde cada relatório levou quase dois minutos para ser produzido. Essa é uma ótima oportunidade para otimização via processamento paralelo. Na próxima atualização do projeto, será o foco principal.
Agradeço de você acompanhou todo o processo. Qualquer dúvida, estarei a disposição.
Meus contatos estão no site: Samuel Baptista
MUITO OBRIGADO!