<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Escalando Cartola com Cadeias de Markov e Programação Linear
Autor
Step1:  Quantidade única de jogadores é do mesmo tamanho do Dataframe.
Step2:  Para o contexto desse estudo, vamos analisar cada posição utilizada no Cartola separadamente. Sendo assim criamos uma lista com todas as posições existentes no arquivo médias.
Step3:  Para facilitar a localização de cada jogador nas matrizes que construirmos, vamos criar um índice baseado no rankeamento do "player_id". Como teremos matrizes para cada posição, criamos um ranking para cada posição.
Step4:  Partidas
Os resultados das partidas irão gerar informações para a matriz de transição, sendo assim utilizamos o arquivo com todas as partidas de 2019 e selecionamos treino e teste mais adiante.
Step5:  Uma das hipóteses testadas nesse estudo, é o impacto da quantidade de gols do time na performance do jogador. Para facilitar a utilização desses dados, vamos normalizar as colunas de quantidade de gols dos time de casa e visitante.
Step6:  Dados das rodadas
Agora, vamos importar os dados de performance de todos os jogadores em todas as rodadas de 2019, deixando uma coluna de identificação da rodada para podermos iterar nela.
Step7:  Para o contexto desse estudo não vamos analisar a performance de técnicos.
Step8:  Para colocar cada jogador em uma posição específica na matriz, vamos trazer a informação de ranking que criamos para o dataframe de partidas.
Step9:  Removendo jogadores não cadastrados
Como a base para construção da nossa matriz é a tabela de atletas que criamos, caso algum jogador apareça na rodada e não esteja na tabela, desconsideramos esse jogador.
Step10:  Matriz M de estados
Para cada posição(atacante, zagueiro, etc.), iniciamos uma matriz de zeros com tamanho d x d, sendo d=quantidade de jogadores únicos.
Exemplo com atacantes
Para o restante do estudo vamos analisar os resultados para os atacantes.
Step11:  Atualizando a matriz
Selecionar partida. 
Selecionar os jogadores que atuaram pelo time da casa.
Selecionar os jogadores que atuaram pelo time visitante.
Avaliar cada jogador do time da casa contra cada jogador do time visitante.
Atualizar a matriz de transições de acordo com a regra*
Step12:  Depois de processar todas as partidas de todos os jogadores, vamos normalizar M para que todas as colunas somem 1.
Step13:  Distribuição estacionária
Agora que temos a nossa matriz de transição pronta, podemos calcular a distribuição estacionária.
Step14:  Por final geramos uma array de tamanho d, lembrando que uma posição i aqui está relacionada a posição i no ranking de ids que criamos no começo do estudo.
Podemos notar que as probabilidades são muito baixas, sendo muito difícil selecionar um jogador apenas pela probabilidades aqui geradas.
Step15:  Podemos verificar por exemplo quem teve probabilidade maior que 1.5%.
O fato dos jogadores como Gabriel e Bruno Henrique aparecem entre os 5 maiores, pode ser um indicador que a regra criada para uma comparação de pontos + quantidade de gols marcada pelo time, pode estar dando maior peso para esses jogadores, o que é uma coisa boa. 
Step16:  Calculando a distribuição para todas posições
Para as posições de defesa, vamos substituir a pontuação referente aos gols marcado pelo time, por uma variável binária, referente a ter sofrido gol no jogo. Caso o time não tenha levado gol no jogo, damos pontuação de 1, se a defesa foi vazada o valor é 0.
No meio-campo fazemos uma combinação das regras de defesa e ataque.
Step17:  Escalando para a rodada
No cálculo da distribuição, deixamos a última rodada de 2019 de fora, agora podemos utilizá-la para testar o nosso modelo.
Step18:  Primeiro vamos criar um DataFrame somente com as informações da rodada e colocar as probabilidades que encontramos referente a cada jogador.
Step19:  Vamos utilizar também do recurso de status e só trabalhar com jogadores em status Provável.
Step20:  Otimizando a escalação
Agora que temos as probabilidades de cada jogador, precisamos gerar a melhor escalação possível de acordo com as restrições de quantidade de "cartoletas" e quantidade de jogadores por posição.
Para isso vamos usar Programação Linear para maximizar a soma das probabilidades, restringindo a escalação escolhida e a quantidade de cartoletas.
Para os exemplos abaixo, vou usar a formação 4-3-3 e um total de 140 cartoletas.
Step21:  Programação Linear
Podemos representar esse problema com a seguinte notação matemática.
zi, probabilidade de cada jogador i
ci, custo de cada jogador i
yi, valor binário indicando se o jogador i foi escalado ou não
n, total de jogadores
ai, valor binário indicando se o jogador i é atacante
mi, valor binário indicando se o jogador i é meio-campista
li, valor binário indicando se o jogador i é laterai
zi, valor binário indicando se o jogador i é zagueiro
gi, valor binário indicando se o jogador i é goleiro
$$ Max. \sum^n_{i=1}{z}{i} * {y}{i}$$
Restrições
Step22:  Primeiro, iniciamos o problema de otimização e definimos uma função objetivo.
Step23:  Agora adicionamos todas as restrições e calculamos.
Step24:  Os jogadores escalados que maximizam as probabilidades dentro das restrições, ficam com o valor 1 para a variável de atletas.
Step25:  Podemos verificar qual foi o total de pontos que essa escalação somaria na última rodada.
Step26:  Também o custo total.
Step27:  Incluindo Palpites
Agora a cereja do bolo...
Até aqui, o nosso modelo compara posição por posição, os jogadores contra seus adversários. Simulando, de certa forma, qual jogador que deveríamos escolher, para cada posição, considerando a sequência de jogos que esse jogador teve.
No cálculo da distribuição estacionária, podemos notar que as probabilidades são muito semelhantes, ficando difícil escolher jogadores com muita certeza. Faz todo sentido, o futebol é rodeado de incertezas. No entanto essa estatística pode nos ajudar a escalar o time automaticamente.
Podemos então utilizar a probabilidade gerada pela cadeia de Markov para selecionar um time baseado em alguns palpites que temos para os jogos. Vamos fazer um sistema simples, onde distribuimos 10 pontos, para a importância de alguns fatores. Por exemplo, eu considero que jogar em casa é um fator importante, e também gosto de apostar em times que além de jogar em casa, vão pegar adversários que nas últimas posições no campeonato. Então, dei as seguintes notas para a última rodada
Step28:  Agora, aumentamos as probabilidades dos jogadores que se enquadram nessa regra, multiplicando o seu valor atual, pela porcentagem de pontos que demos a ele, por exemplo
Step29:  Programação Linear
Podemos otimizar a equação novamente.
Step30:  Por fim geramos uma nova escalação, que levou em consideração os pesos que colocamos acima.
Step31:  Ao avaliar a escalação abaixo, geramos dessa vez uma pontuação de 81 pontos, 60% a mais que o resultado anterior.
Outro ponto interessante é que usamos menos cartoletas do que na escalação anterior. Uma oportunidade é usar esse modelo para fazer escalações mais baratas, quando o objetivo for valorização. Para isso, basta colocar o limite que deseja no total de cartoletas.
<ASSISTANT_TASK:>
Python Code:
import pandas as pd
url = 'https://raw.githubusercontent.com/henriquepgomide/caRtola/master/data/2019/2019-medias-jogadores.csv'
medias = pd.read_csv(url)
medias.head()
medias.shape
medias.columns
Explanation: Escalando Cartola com Cadeias de Markov e Programação Linear
Autor: Júlio Oliveira
Motivação
Meu time foi rebaixado, provavelmente não teremos Cartola FC para Série B em 2020.
Resumo
O time está em uma má fase!
Times de futebol passam por diferentes fases, e a impressão é sempre que os resultados resultados anteriores influenciam nos jogos seguintes. A maioria dos modelos que utilizamos para modelagem, assumem que os dados são i.i.d(independent and identically distributed), caso o jogo anterior realmente influencie no resultado, essa premissa não é verdadeira, e talvez um modelo para trabalhar com dados sequenciais funcione melhor.
Técnica
Cadeias de Markov
Créditos:
Todas as bases utilizadas nesse estudo, são parte do trabalho conduzido pelo Henrique Gomide, e estão disponíveis nesse repositório.
Vários dos conceitos aplicados nesse estudo, tem como referência o trabalho do professor John Paisley da universidade de Columbia, para montar um ranking de clubes de basquete universitário com Cadeias de Markov. Todo o conceito aplicado está disponível aqui.
A otimização linear teve como referência o artigo feito por Gupta, Akhil na International Conference on Sports Engineering ICSE-2017, disponível no link.
Gerando lista com todos jogadores
Para gerar a nossa matriz de transições, precisamos manter a quantidade de jogadores única. Para isso, vamos usar o arquivo de média dos jogadores, apenas para conseguir todos os jogadores que atuaram no ano e a sua posição.
End of explanation
qtd_atletas = len(medias['player_id'].unique())
print(qtd_atletas)
Explanation: Quantidade única de jogadores é do mesmo tamanho do Dataframe.
End of explanation
posicoes = medias['player_position'].unique()
Explanation: Para o contexto desse estudo, vamos analisar cada posição utilizada no Cartola separadamente. Sendo assim criamos uma lista com todas as posições existentes no arquivo médias.
End of explanation
medias['Rank'] = None
for posicao in posicoes:
    rank = medias[medias['player_position'] == posicao].player_id.rank(method='min')
    rank = rank - 1
    medias.iloc[rank.index,-1] = rank
colunas_unicos = ['Rank','player_id','player_position']
atletas = medias[colunas_unicos].drop_duplicates()
atletas.head()
atletas.shape
Explanation: Para facilitar a localização de cada jogador nas matrizes que construirmos, vamos criar um índice baseado no rankeamento do "player_id". Como teremos matrizes para cada posição, criamos um ranking para cada posição.
End of explanation
partidas = pd.read_csv(r'https://raw.githubusercontent.com/henriquepgomide/caRtola/master/data/2019/2019_partidas.csv')
Explanation: Partidas
Os resultados das partidas irão gerar informações para a matriz de transição, sendo assim utilizamos o arquivo com todas as partidas de 2019 e selecionamos treino e teste mais adiante.
End of explanation
partidas['home_score_norm'] = partidas['home_score'] / max(partidas['home_score'])
partidas['away_score_norm'] = partidas['away_score'] / max(partidas['away_score'])
partidas.head()
partidas.shape
Explanation: Uma das hipóteses testadas nesse estudo, é o impacto da quantidade de gols do time na performance do jogador. Para facilitar a utilização desses dados, vamos normalizar as colunas de quantidade de gols dos time de casa e visitante.
End of explanation
df_partidas = pd.DataFrame()
for rodada in range(1,39):
    df_rodada = pd.read_csv(f'https://raw.githubusercontent.com/henriquepgomide/caRtola/master/data/2019/rodada-{rodada}.csv')
    df_rodada['round'] = rodada
    df_partidas =df_partidas.append(df_rodada,sort=False)
df_partidas.shape
Explanation: Dados das rodadas
Agora, vamos importar os dados de performance de todos os jogadores em todas as rodadas de 2019, deixando uma coluna de identificação da rodada para podermos iterar nela.
End of explanation
df_partidas = df_partidas[df_partidas['atletas.posicao_id'] != 'tec']
Explanation: Para o contexto desse estudo não vamos analisar a performance de técnicos.
End of explanation
df_partidas = df_partidas.set_index('atletas.atleta_id').join(atletas.set_index('player_id'))
df_partidas.head()
df_partidas['Rank']
Explanation: Para colocar cada jogador em uma posição específica na matriz, vamos trazer a informação de ranking que criamos para o dataframe de partidas.
End of explanation
df_partidas.drop(df_partidas[df_partidas['Rank'].isnull()].index, inplace=True)
df_partidas['Rank'] = df_partidas['Rank'].astype(int)
Explanation: Removendo jogadores não cadastrados
Como a base para construção da nossa matriz é a tabela de atletas que criamos, caso algum jogador apareça na rodada e não esteja na tabela, desconsideramos esse jogador.
End of explanation
import numpy as np
posicao = 'ata'
qtd_atletas = len(atletas[atletas.player_position == posicao])
M = np.zeros((qtd_atletas,qtd_atletas))
M
M.shape
Explanation: Matriz M de estados
Para cada posição(atacante, zagueiro, etc.), iniciamos uma matriz de zeros com tamanho d x d, sendo d=quantidade de jogadores únicos.
Exemplo com atacantes
Para o restante do estudo vamos analisar os resultados para os atacantes.
End of explanation
df_partidas_posicao = df_partidas[df_partidas['atletas.posicao_id'] == posicao].copy()
for partida in range(len(partidas)-1): #Vamos deixar a última partida de fora para testes
    df_rodada = df_partidas_posicao[df_partidas_posicao['round'] == partidas['round'][partida]]
    jogadores_casa = df_rodada[df_rodada['atletas.clube_id'] == partidas['home_team'][partida]]
    jogadores_visitantes = df_rodada[df_rodada['atletas.clube_id'] == partidas['away_team'][partida]]
    
    for j_casa in range(len(jogadores_casa)):
        for j_visitante in range(len(jogadores_visitantes)):
            score_casa = 0
            score_visitante = 0
            
            pontos_j_casa = jogadores_casa['atletas.pontos_num'].iloc[j_casa]
            pontos_j_visitante = jogadores_visitantes['atletas.pontos_num'].iloc[j_visitante]
            
            soma =  pontos_j_casa + pontos_j_visitante 
            if soma != 0:
                score_casa = pontos_j_casa / soma
                score_visitante = pontos_j_visitante / soma
            
            j1 = jogadores_casa['Rank'].iloc[j_casa]
            j2 = jogadores_visitantes['Rank'].iloc[j_visitante]
                
            M[j1,j1] = M[j1,j1] + partidas['home_score_norm'][partida] + score_casa
            M[j1,j2] = M[j1,j2] + partidas['away_score_norm'][partida] + score_visitante
            M[j2,j1] = M[j2,j1] + partidas['home_score_norm'][partida] + score_casa
            M[j2,j2] = M[j2,j2] + partidas['away_score_norm'][partida] + score_visitante
M
Explanation: Atualizando a matriz
Selecionar partida. 
Selecionar os jogadores que atuaram pelo time da casa.
Selecionar os jogadores que atuaram pelo time visitante.
Avaliar cada jogador do time da casa contra cada jogador do time visitante.
Atualizar a matriz de transições de acordo com a regra*:
Para um jogo específico, j1 é o índice do jogador avaliado do time da casa e j2 o índice do jogador avaliado do time visitante.
$$\hat{M}{j1j1} \ \leftarrow \ \hat{M}{j1j1} \ +\ gols_time_{j1} \ +\ \frac{pontos_{j1}}{pontos_{j1} +pontos_{j2}}$$
$$\hat{M}{j2j2} \ \leftarrow \ \hat{M}{j2j2} \ +\ gols_time_{j2} \ +\ \frac{pontos_{j2}}{pontos_{j1} +pontos_{j2}}$$
$$\hat{M}{j1j2} \ \leftarrow \ \hat{M}{j1j2} \ +\ gols_time_{j2} \ +\ \frac{pontos_{j2}}{pontos_{j1} +pontos_{j2}}$$
$$\hat{M}{j2j1} \ \leftarrow \ \hat{M}{j2j1} \ +\ gols_time_{j1} \ +\ \frac{pontos_{j1}}{pontos_{j1} +pontos_{j2}}$$
*Regras para atacantes
End of explanation
M = M / np.sum(M,axis=1)
Explanation: Depois de processar todas as partidas de todos os jogadores, vamos normalizar M para que todas as colunas somem 1.
End of explanation
evals, evecs = np.linalg.eig(M.T)
evec1 = evecs[:,np.isclose(evals, 1)]
evec1 = evec1[:,0]
stationary = evec1 / evec1.sum()
stationary = stationary.real
Explanation: Distribuição estacionária
Agora que temos a nossa matriz de transição pronta, podemos calcular a distribuição estacionária.
End of explanation
stationary
Explanation: Por final geramos uma array de tamanho d, lembrando que uma posição i aqui está relacionada a posição i no ranking de ids que criamos no começo do estudo.
Podemos notar que as probabilidades são muito baixas, sendo muito difícil selecionar um jogador apenas pela probabilidades aqui geradas.
End of explanation
medias[medias.player_position == posicao][list(stationary > 0.015)]
Explanation: Podemos verificar por exemplo quem teve probabilidade maior que 1.5%.
O fato dos jogadores como Gabriel e Bruno Henrique aparecem entre os 5 maiores, pode ser um indicador que a regra criada para uma comparação de pontos + quantidade de gols marcada pelo time, pode estar dando maior peso para esses jogadores, o que é uma coisa boa. :)
End of explanation
stationaries = {}
for posicao in posicoes:
    qtd_atletas = len(atletas[atletas.player_position == posicao])
    M = np.zeros((qtd_atletas,qtd_atletas))
    df_partidas_posicao = df_partidas[df_partidas['atletas.posicao_id'] == posicao].copy()
    for partida in range(len(partidas)-1): #Vamos deixar a última partida de fora para testes
        df_rodada = df_partidas_posicao[df_partidas_posicao['round'] == partidas['round'][partida]]
        jogadores_casa = df_rodada[df_rodada['atletas.clube_id'] == partidas['home_team'][partida]]
        jogadores_visitantes = df_rodada[df_rodada['atletas.clube_id'] == partidas['away_team'][partida]]
        for j_casa in range(len(jogadores_casa)):
            for j_visitante in range(len(jogadores_visitantes)):
                score_casa = 0
                score_visitante = 0
                pontos_j_casa = jogadores_casa['atletas.pontos_num'].iloc[j_casa]
                pontos_j_visitante = jogadores_visitantes['atletas.pontos_num'].iloc[j_visitante]
                soma =  pontos_j_casa + pontos_j_visitante 
                if soma != 0:
                    score_casa = pontos_j_casa / soma
                    score_visitante = pontos_j_visitante / soma
                def_n_vazada_casa = 0 if partidas['away_score_norm'][partida] > 0 else 1
                def_n_vazada_visitante = 0 if partidas['home_score_norm'][partida] > 0 else 1
                
                if posicao == 'ata':
                    pontos_casa = partidas['home_score_norm'][partida] + score_casa
                    pontos_visitante = partidas['away_score_norm'][partida] + score_visitante
                elif posicao == 'mei':
                    pontos_casa = partidas['home_score_norm'][partida] + def_n_vazada_casa + score_casa
                    pontos_visitante = partidas['away_score_norm'][partida] + def_n_vazada_visitante + score_visitante
                else:
                    pontos_casa = def_n_vazada_casa + score_casa
                    pontos_visitante = def_n_vazada_visitante + score_visitante                  
                    
                j1 = jogadores_casa['Rank'].iloc[j_casa]
                j2 = jogadores_visitantes['Rank'].iloc[j_visitante]               
                M[j1,j1] = M[j1,j1] + pontos_casa
                M[j1,j2] = M[j1,j2] + pontos_visitante
                M[j2,j1] = M[j2,j1] + pontos_casa
                M[j2,j2] = M[j2,j2] + pontos_visitante
    M = M / np.sum(M,axis=1)
    evals, evecs = np.linalg.eig(M.T)
    evec1 = evecs[:,np.isclose(evals, 1)]
    evec1 = evec1[:,0]
    stationary = evec1 / evec1.sum()
    stationary = stationary.real
    stationaries[posicao] = stationary
Explanation: Calculando a distribuição para todas posições
Para as posições de defesa, vamos substituir a pontuação referente aos gols marcado pelo time, por uma variável binária, referente a ter sofrido gol no jogo. Caso o time não tenha levado gol no jogo, damos pontuação de 1, se a defesa foi vazada o valor é 0.
No meio-campo fazemos uma combinação das regras de defesa e ataque.
End of explanation
rodada = 38
Explanation: Escalando para a rodada
No cálculo da distribuição, deixamos a última rodada de 2019 de fora, agora podemos utilizá-la para testar o nosso modelo.
End of explanation
df_rodada = df_partidas[df_partidas['round'] == rodada].copy()
df_rodada['Rank'] = df_rodada['Rank'].astype(int)
df_rodada['probs'] = 0
for jogador in range(len(df_rodada)):
    posicao = df_rodada.iloc[jogador]['player_position']
    rank = df_rodada.iloc[jogador]['Rank']
    if rank:
        df_rodada.iloc[jogador,-1] = stationaries[posicao][rank]
Explanation: Primeiro vamos criar um DataFrame somente com as informações da rodada e colocar as probabilidades que encontramos referente a cada jogador.
End of explanation
df_rodada = df_rodada[df_rodada['atletas.status_id'] == 'Provável'].copy()
df_rodada.head()
Explanation: Vamos utilizar também do recurso de status e só trabalhar com jogadores em status Provável.
End of explanation
formacao = {
    'ata': 3,
    'mei': 3,
    'lat': 2,
    'zag': 2,
    'gol':1
}
cartoletas = 140
Explanation: Otimizando a escalação
Agora que temos as probabilidades de cada jogador, precisamos gerar a melhor escalação possível de acordo com as restrições de quantidade de "cartoletas" e quantidade de jogadores por posição.
Para isso vamos usar Programação Linear para maximizar a soma das probabilidades, restringindo a escalação escolhida e a quantidade de cartoletas.
Para os exemplos abaixo, vou usar a formação 4-3-3 e um total de 140 cartoletas.
End of explanation
df_rodada.set_index('atletas.slug',inplace=True)
z = df_rodada['probs'].to_dict()
c = df_rodada['atletas.preco_num'].to_dict()
dummies_posicao = pd.get_dummies(df_rodada['atletas.posicao_id'])
dummies_posicao = dummies_posicao.to_dict()
Explanation: Programação Linear
Podemos representar esse problema com a seguinte notação matemática.
zi, probabilidade de cada jogador i
ci, custo de cada jogador i
yi, valor binário indicando se o jogador i foi escalado ou não
n, total de jogadores
ai, valor binário indicando se o jogador i é atacante
mi, valor binário indicando se o jogador i é meio-campista
li, valor binário indicando se o jogador i é laterai
zi, valor binário indicando se o jogador i é zagueiro
gi, valor binário indicando se o jogador i é goleiro
$$ Max. \sum^n_{i=1}{z}{i} * {y}{i}$$
Restrições:
$$ \sum^n_{i=1}{c}{i} * {y}{i} <= 140 $$
$$ \sum^n_{i=1}{a}{i} * {y}{i} = 3 $$
$$ \sum^n_{i=1}{m}{i} * {y}{i} = 3 $$
$$ \sum^n_{i=1}{l}{i} * {y}{i} = 2 $$
$$ \sum^n_{i=1}{z}{i} * {y}{i} = 2 $$
$$ \sum^n_{i=1}{g}{i} * {y}{i} = 1 $$
As variáveis que entraram na equação são relacionadas as posições, custo e probabilidade. Sendo assim, vamos criar dicionários com cada uma dessas informações relacionadas ao nome do jogador para facilitar a montagem do problema.
End of explanation
!pip install pulp
from pulp import LpMaximize, LpProblem, lpSum, LpVariable
prob = LpProblem("Melhor_Escalacao", LpMaximize)
y = LpVariable.dicts("Atl",df_rodada.index,0,1,cat='Binary')
prob += lpSum([z[i] * y[i] for i in y])
Explanation: Primeiro, iniciamos o problema de otimização e definimos uma função objetivo.
End of explanation
prob += lpSum([c[i] * y[i] for i in y]) <= cartoletas, "Limite de Cartoletas"   
prob += lpSum([dummies_posicao['ata'][i] * y[i] for i in y]) == formacao['ata'], "Quantidade Atacantes"
prob += lpSum([dummies_posicao['lat'][i] * y[i] for i in y]) == formacao['lat'], "Quantidade Laterais"
prob += lpSum([dummies_posicao['mei'][i] * y[i] for i in y]) == formacao['mei'], "Quantidade Meio"
prob += lpSum([dummies_posicao['zag'][i] * y[i] for i in y]) == formacao['zag'], "Quantidade Zagueiros"
prob += lpSum([dummies_posicao['gol'][i] * y[i] for i in y]) == formacao['gol'], "Quantidade Goleiro"
prob.solve()
Explanation: Agora adicionamos todas as restrições e calculamos.
End of explanation
escalados = []
for v in prob.variables():
    if v.varValue == 1:
        atleta = v.name.replace('Atl_','').replace('_','-')
        escalados.append(atleta)
        print(atleta, "=", v.varValue)
colunas = ['atletas.posicao_id','atletas.clube.id.full.name','atletas.pontos_num','atletas.preco_num']
df_rodada.loc[escalados][colunas]
Explanation: Os jogadores escalados que maximizam as probabilidades dentro das restrições, ficam com o valor 1 para a variável de atletas.
End of explanation
df_rodada.loc[escalados]['atletas.pontos_num'].sum()
Explanation: Podemos verificar qual foi o total de pontos que essa escalação somaria na última rodada.
End of explanation
df_rodada.loc[escalados]['atletas.preco_num'].sum()
Explanation: Também o custo total.
End of explanation
jogar_em_casa = 5
times = {
    'Internacional':3,
    'Fortaleza':2
}
Explanation: Incluindo Palpites
Agora a cereja do bolo...
Até aqui, o nosso modelo compara posição por posição, os jogadores contra seus adversários. Simulando, de certa forma, qual jogador que deveríamos escolher, para cada posição, considerando a sequência de jogos que esse jogador teve.
No cálculo da distribuição estacionária, podemos notar que as probabilidades são muito semelhantes, ficando difícil escolher jogadores com muita certeza. Faz todo sentido, o futebol é rodeado de incertezas. No entanto essa estatística pode nos ajudar a escalar o time automaticamente.
Podemos então utilizar a probabilidade gerada pela cadeia de Markov para selecionar um time baseado em alguns palpites que temos para os jogos. Vamos fazer um sistema simples, onde distribuimos 10 pontos, para a importância de alguns fatores. Por exemplo, eu considero que jogar em casa é um fator importante, e também gosto de apostar em times que além de jogar em casa, vão pegar adversários que nas últimas posições no campeonato. Então, dei as seguintes notas para a última rodada:
5 pontos - jogar em casa
3 pontos - Internacional
2 pontos - Fortaleza
End of explanation
times_casa = partidas[partidas['round'] == rodada]['home_team']
df_rodada.loc[df_rodada['atletas.clube_id'].isin(times_casa),'probs'] = df_rodada.loc[
    df_rodada['atletas.clube_id'].isin(times_casa),'probs'] * (jogar_em_casa / 10 + 1)
for time in times:
    df_rodada.loc[df_rodada['atletas.clube.id.full.name'] == time,'probs'] = df_rodada.loc[
        df_rodada['atletas.clube.id.full.name'] == time,'probs'] * (times[time] / 10 + 1)
z = df_rodada['probs'].to_dict()
Explanation: Agora, aumentamos as probabilidades dos jogadores que se enquadram nessa regra, multiplicando o seu valor atual, pela porcentagem de pontos que demos a ele, por exemplo:
Jogadores que jogam em casa = Probabilidade * 150%
Jogadores Internacional = Probabilidade * 130%
Jogadores Fortaleza = Probabilidade * 120%
End of explanation
prob = LpProblem("Melhor_Escalacao", LpMaximize)
y = LpVariable.dicts("Atl",df_rodada.index,0,1,cat='Binary')
prob += lpSum([z[i] * y[i] for i in y])
prob += lpSum([c[i] * y[i] for i in y]) <= cartoletas, "Limite de Cartoletas"   
prob += lpSum([dummies_posicao['ata'][i] * y[i] for i in y]) == formacao['ata'], "Quantidade Atacantes"
prob += lpSum([dummies_posicao['lat'][i] * y[i] for i in y]) == formacao['lat'], "Quantidade Laterais"
prob += lpSum([dummies_posicao['mei'][i] * y[i] for i in y]) == formacao['mei'], "Quantidade Meio"
prob += lpSum([dummies_posicao['zag'][i] * y[i] for i in y]) == formacao['zag'], "Quantidade Zagueiros"
prob += lpSum([dummies_posicao['gol'][i] * y[i] for i in y]) == formacao['gol'], "Quantidade Goleiro"
prob.solve()
Explanation: Programação Linear
Podemos otimizar a equação novamente.
End of explanation
escalados = []
for v in prob.variables():
    if v.varValue == 1:
        atleta = v.name.replace('Atl_','').replace('_','-')
        escalados.append(atleta)
        print(atleta, "=", v.varValue)
Explanation: Por fim geramos uma nova escalação, que levou em consideração os pesos que colocamos acima.
End of explanation
colunas = ['atletas.posicao_id','atletas.clube.id.full.name','atletas.pontos_num','atletas.preco_num']
df_rodada.loc[escalados][colunas]
df_rodada.loc[escalados]['atletas.pontos_num'].sum()
df_rodada.loc[escalados]['atletas.preco_num'].sum()
Explanation: Ao avaliar a escalação abaixo, geramos dessa vez uma pontuação de 81 pontos, 60% a mais que o resultado anterior.
Outro ponto interessante é que usamos menos cartoletas do que na escalação anterior. Uma oportunidade é usar esse modelo para fazer escalações mais baratas, quando o objetivo for valorização. Para isso, basta colocar o limite que deseja no total de cartoletas.
End of explanation
<END_TASK>