Expectativa pitagórica na NBA

Matheus
7 min readMay 10, 2024
Os 30 times da NBA

Estava lendo alguma coisa na Wikipedia nessa semana e, por acaso, me deparei com a expectativa pitagórica no baseball, uma fórmula criada por Bill James, um estatístico desse esporte. Ela é simplesmente

A expectativa pitagórica no baseball

onde V é a taxa de vitórias, RS é o número de runs marcadas e RA, o de runs sofridas. Uma run é marcada quando um jogador avança pelas três bases e retorna em segurança para o home plate.

No dia seguinte, fui explorar um aplicativo novo sobre basquete que baixei e cheguei na parte de estatísticas da NBA, encontrei uma que nunca tinha ouvido falar: esse “pythagorean wins”.

A expectativa pitagórica entre outras estatísticas

Pelo formato do valor, pensei logo que fosse uma porcentagem, o que estaria correto. Fui procurar e achei uma versão análoga, adaptada ao esporte com as devidas proporções.

A expectativa pitagórica no basquete

PW é a expectativa pitagórica de vitórias, PM é o número de pontos convertidos por um time e PA, o de pontos sofridos. Procurei no Kaggle um banco de dados com jogos e pontos dos dois times numa partida e achei esse, em CSV e SQL. Esse dataset gigante tem jogos da primeira temporada de 1946 até 2023.

Para o tratamento dos dados, realizei uma limpeza removendo colunas e linhas desnecessárias para o estudo, alterando certos times que mudaram de nome/cidade, entre outros procedimentos. Isso resultou no arquivo abaixo.

Inicialmente importei as bibliotecas Pandas para procedimentos usuais, Seaborn para gráficos, StatsModels para estatística e Math para matemática. Depois, li o arquivo e os armazenei em variáveis como de costume. Criei uma função para a expectativa pitagórica, recebendo os pontos convertidos e sofridos:

def pyth_exp(made,allow):
return made**2/(made**2+allow**2)

Para a porcentagem de vitórias, fiz o comando abaixo. Usei o Washington Wizards, último time na ordem alfabética, como exemplo.

home_w=dados.query("home=='WAS' and wl_home==1")
away_w=dados.query("away=='WAS' and wl_away==1")
home=dados.query("home=='WAS'")
away=dados.query("away=='WAS'")
games=home.shape[0]+away.shape[0]
games_w=home_w.shape[0]+away_w.shape[0]
win_perc=games_w/games
win_perc=round(win_perc, 4)
print("Ganhou",games_w,"jogos de",games,", portanto tem um aproveitamento de",win_perc)

Como pode ser visto, fiz dois queries para separar só as vitórias em casa e fora, depois, a mesma coisa para pegar todos os jogos. Fiz os shapes para descobrir as quantidas de vitórias e jogos totais e finalmente calculei a porcentagem de vitórias como desejado, arredondando para 4 casas decimais. O output foi: “Ganhou 2052 jogos de 4516, portanto tem um aproveitamento de 0.4544”.

Para a expectativa pitagórica, fiz algo parecido.

pth_m=dados.query("home=='WAS'")["pts_home"].sum()
pth_a=dados.query("home=='WAS'")["pts_away"].sum()
pta_m=dados.query("away=='WAS'")["pts_away"].sum()
pta_a=dados.query("away=='WAS'")["pts_home"].sum()
pt_made=pth_m+pta_m
pt_allow=pth_a + pta_a
print("Os pontos marcados e permitidos são, respectivamente,",pt_made,"e",pt_allow)
print("A expectativa pitagórica é de",pyth_exp(pt_made,pt_allow).round(4))

Fiz mais alguns queries e somei os pontos convertidos e sofridos em casa e fora, juntei as duas possibilidades de cada categoria e apliquei esse dois últimos resultados na função, além de mostrá-los. O output acima foi:
“Os pontos marcados e permitidos são, respectivamente, 471509 e 477840
A expectativa pitagórica é de 0.4933”

Reuni esses resultados no arquivo abaixo, que tem as vitórias e jogos totais de cada time nas temporadas regulares, além da porcentagem de vitória histórica. Coloquei também os dois tipos de pontos e a expectativa pitagórica.

Depois, plotei o gráfico de dispersão entre a expectativa e a porcentagem. As linhas tracejadas são as médias de cada eixo. Como a porcentagem real cresce junto com a pitagórica, estima-se que a relação entre as duas variáveis seja quase perfeitamente linear positiva.

x = pyth.pyth_exp
y = pyth.win_perc

ax = sns.scatterplot(data=pyth, x="pyth_exp", y="win_perc")
ax.figure.set_size_inches(10, 6)
ax.set_title("Expectativa pitagórica X porcentagem real de vitórias")
ax.hlines(y = y.mean(), xmin = x.min(), xmax = x.max(), colors='gray', linestyles='dashed')
ax.vlines(x = x.mean(), ymin = y.min(), ymax = y.max(), colors='gray', linestyles='dashed')

Em seguida, plotei o gráfico da regressão entre as variáveis. O comportamento linear se mostra ainda mais forte com a reta de regressão.

ax=sns.lmplot(x='pyth_exp',y='win_perc',data=pyth)
ax.fig.suptitle("Expectativa pitagórica X porcentagem real de vitórias",y=1.05)

Ajustei o modelo pelo método dos mínimos quadrados ordinários com as funções ols e fit.

pyth_lm=smf.ols(formula='win_perc ~ pyth_exp', data=pyth).fit()

O principal resultado que queremos é o coeficiente de correlação R². Vamos chegar a ele de três modos diferentes.

  • O primeiro é exibir os dados estatísticos da regressão com o summary.
print(pyth_lm.summary())
Estatísticas do modelo

Como pode ser visto acima, temos 30 observações (os 30 times) e 28 graus de liberdade, isto é, 30 observações menos as 2 variáveis. Temos também os coeficientes da nossa reta de regressão, que pode ser escrita assim, arrendondando para uma casa decimal: y=-2.9+6.8x, x é a porcentagem de vitórias e y, a expectativa pitagórica.

Temos os desvios padrões de cada variável, as estatísticas de teste t de cada uma e o p-valor igual a zero, o que mostra que o modelo tem grande significância estatística. Além disso, também temos os intervalos de confiança ao nível de significância padrão de 5%. Também podemos ver o coeficiente de regressão R²=0.983, um valor quase igual a 1, comprovando o caráter linear positivo da regressão.

  • O segundo é a partir da soma dos quadrados dos erros do modelo, a soma dos quadrados dos pontos da regressão e da soma total desses dois.
Soma dos quadrados dos erros
Soma dos quadrados dos pontos da regressão
Soma dos quadrados totais

onde Yi são as porcentagens reais de vitória e Yi chapéu e Y barra, respectivamente, as expectativas e a média delas.

SQE=pyth_lm.ssr
SQR=pyth_lm.ess
SQT=SQE+SQR
print(SQE.round(3)) 0.001
print(SQR.round(3)) 0.073
print(SQT.round(3)) 0.074

Temos três valores bem pequenos, o que são ótimos sinais, principalmente o valor muito baixo da propagação dos erros do modelo.

A título de curiosidade, vamos calcular a média do SQE, que consiste na divisão desse valor pelo número de graus de liberdade, nesse caso 28.

# Erro quadrático médio
n=30 # Número de observações
EQM=SQE/(n-2)
EQM
4.3977042679913256e-05 # Aproximadamente 0.00004

Arredondando mais uma vez para 3 casas decimais, temos que ele é zero, aumentando ainda mais a confiabilidade do modelo.

Para achar o coeficiente de correlação, basta dividir SQR por SQT.

R2=SQR/SQT
print(R2.round(3)) 0.983

Achamos R² igual a 0.983, exatamente o mesmo valor obtido anteriormente.

  • O terceiro modo é simplesmente printar o valor com 3 casas decimais.
pyth_lm.rsquared.round(3)
0.983

Novamente temos o mesmo valor dos 2 métodos.

R²=0.983, bem próximo de 1, então a expectativa pitagórica explica muito bem a porcentagem de vitórias convencional. A relação linear entre elas é quase perfeita.

Quando vi a fórmula pitagórica pela primeira vez, achei bem aleatória, tirada da cartola, porém, depois desse estudo, entendi a sagacidade de quem a propôs pela primeira vez. Imagine conseguir estimar o número de vitórias de um time na temporada só com os pontos que ele fez e sofreu.

Claro que uma temporada muito positiva ou negativa pode desbalancear o modelo. Por exemplo, na situação que descobri a fórmula, estava vendo números do Minnesota Timberwolves, cuja expectativa pitagórica dessa temporada (0.57) estava muito longe do nosso modelo histórico (0.403 e 0.488 respectivamente convencional e pitagórica). Ficou distante até da taxa real de vitórias, 0.683, o time superou todas as expectativas.

Todo meu processo está no notebook abaixo, usei o Google Colab. O time que está no código é o Washington Wizards, último na ordem alfabética. Para checar os dados de um time em específico, é só substituir pela respectiva sigla, BOS para o Celtics, GSW para o Warriors etc.

Minha inspiração para esse estudo saiu desse artigo daqui do Medium em inglês.

Como o autor trabalhou só com o período de 2004 a 2021, o modelo ficou um pouquinho menos ajustado que o meu, como consequência, o R² diminuiu.

Obrigado pela atenção! 🏀

“The Last Shot”, o lendário lançamento que rendeu o sexto (e último) título a Michael Jordan

--

--