Você já viu um daqueles gráficos que corre por um tempo mostrando um histórico? Tinha vários correndo a internet mostrando as maiores economias do mundo nos últimos cinquenta anos. Isso é chamado um gráfico race bar. Nesse post vamos fazer um também, mostrando o desenvolvimento do campeonato Brasileiro de 2024.

Muitas vezes, quando trabalhando com um projeto mais complicado, é necessário começar de trás pra frente. Isso é o que fiz pra chegar nesse resultado. Uma coisa era certa, eu iria precisar da posição de cada time por rodada. Esse seria o maior desafio desse trabalho.

Mas eu precisaria de mais coisas também. Vamos falar sobre esse raciocinio por trás de tudo isso.

Raciocinando o Problema

Para que o gráfico funcionasse sem problemas, a coisa principal seria dados. Onde poderia encontrar os dados?

Comecei dando uma olhada na documentação do bar-chart-race, que se encontra aqui. Isso me ajudou a determinar o que eu precisaria, e como o pacote funciona. Mas e os dados?

A princípio, procurei os dados em várias websites. O que eu precisava não era a tabela final. Eu tinha que ter a tabela de cada rodada. Infelizmente, não achei nenhuma website com essa informação. A única maneira de chegar a isso, seria eu criar os meu próprios dados.

Espera ai, não estou inventando nada.

O que eu quero dizer, é que tive que organizar os dados de acordo, para satisfazer aquilo que faltava. Às vezes, como cientista de dados, precisamos fazer exatamente isso pra chegar a solução desejável. Por exemplo, se você está trabalhando com dados de vendas, e precisa saber quanto foi vendido no primeiro trimestre, vai ter que resolver isso com algumas funções. Foi isso que fiz.

Ao invés de procurar por tabelas, mudei a minha estratégia. Eu peguei os resultados dos 380 jogos do campeonato, e gerei os dados que precisava. Vou explicar como fazer isso daqui a pouco.

Com os dados em mão, vamos começar o processo.

Preparando os Dados

Para pegar os dados, eu usei requests e BeautifulSoup. Eu não vou entrar no processo de extração nesse post. Pra conseguir os dados de maneira correta, é um procedimento tedioso, e a explicação é bem longa. Vou reservar isso para outro post.

Pra esse projeto usei o Jupyter Notebook. Se preferir pode usar qualquer IDE da sua escolha. Mas nesse tutorial, está feito com Jupyter Notebook.

Vamos começar com os dados que já limpei e pronto para usar. Vamos importar os dados no nosso workbook. Lembre-se onde salvou o documento antes de abrir.

import pandas as pd

jogos_completos = pd.read_csv("jogos_2024.csv", index_col=0)

Se o seu arquivo não está no mesmo diretório que o seu caderno (Jupyter Notebook), vai precisar direcionar aonde está. Por exemplo, se está no seu desktop, vai ter que fazer assim: ~/Desktop/jogos_2024.csv.

Esse index_col fala para o pandas usar a primeira coluna como index. Isso depende de como o arquivo csv foi salvo.

Olhando o documento, vamos ver isso aqui:

Você pode observar que o vencedor não está claro. Os gols estão resumidos dentro do Score. Vamos ter que resolver isso.

Determinando os Vencedores

Para determinarmos os vencedores do jogo, vamos ter que separar o Score. Pode ver que os gols na esquerda são os gols da casa. E o da direita os gols dos visitantes. Vamos dividir dessa maneira.

Durante o processo, descobri que o tracinho não é um tracinho comum. Então tive que copiar e colar o que eles tem ai e trocar pelo comum.

jogos_completos['gols'] = jogos_completos['Score'].str.replace('–', '-')

Agora que já temos o correto, vamos criar duas colunas novas: Casa e Visitantes.

jogos_completos[['Casa', 'Visitantes']] = jogos_completos['Score'].str.split('–', expand=True)

Temos o resultado assim:

Dessa maneira fica mais fácil trabalhar os dados.

Atribuindo os Pontos pra Cada Time

O nosso gráfico precisa da informação dos pontos. Agora que já separamos o Score, fica mais fácil determinar quem ganhou, e os pontos que receberam naquela rodada.

Para isso, escrevi uma função que atribue os pontos pra quem ganhou ou empatou:

def atribuir_pontos(row):
    if row.Casa > row.Visitantes:
        return pd.Series({'casa_pontos': 3, 'visitante_pontos': 0})
    elif row.Casa < row.Visitantes:
        return pd.Series({'casa_pontos': 0, 'visitante_pontos': 3})
    else:
        return pd.Series({'casa_pontos': 1, 'visitante_pontos': 1})

# colocando contra o dataframe
pts = jogos_completos.apply(atribuir_pontos, axis=1)

Observe que criei uma dataframe nova. O resultado é somente as duas colunas casa_pontos e visitante_pontos.

Finalmente, vamos juntar as duas dataframes e criar somente uma.

jogos_completos = pd.concat([jogos_completos, pts], axis=1)

Preparando o Dataframe

Estamos quase lá. Vamos dar uma limpada e organizada para o momento mais esperado.

O cabeçalho é o mesmo de quando eu puxei da internet. Vamos dar uma limpada nele pra ficar mais fácil na hora de criar o gráfico.

# criando uma copia do dataframe
df = jogos_completos.copy()

# normalizando o nome das colunas
df.rename(columns={
    'Wk':'rodada',
    'Home':'time_casa',
    'Away':'time_vis',
    'casa_pontos': 'casa_pts',
    'visitante_pontos':'vis_pts',
    'Casa':'gols_casa',
    'Visitantes':'gols_vis',
}, inplace=True)

Eu quero ter certeza que a rodada, casa_pts, e vis_pts são todos números. Então vamos passar o código para isso.

df['rodada']      = df['rodada'].astype(int)
df['casa_pts']   = df['casa_pts'].astype(int)
df['vis_pts']   = df['vis_pts'].astype(int)

Se você observar o dataframe, vai ver que os pontos estão divididos entre casa e visitante. Por exemplo, se o Internacional ganhou em casa, vai ter 3 pontos na coluna casa_pts. E se na próxima rodada ganhou fora de casa, vai ter 3 pontos na coluna vis_pts. Queremos que tudo isso fique em uma coluna.

Para isso, vou dividir o dataframe em dois e depois juntá-los novamente. Vamos lá:

# dataframe de pontos ganho em casa
casa = df[['rodada','time_casa','casa_pts']].rename(columns={'time_casa':'time','casa_pts':'pts'})

# dataframe de pontos ganho fora de casa
visitante = df[['rodada','time_vis','vis_pts']].rename(columns={'time_vis':'time','vis_pts':'pts'})

Agora vamos juntar esses dois:

longo = pd.concat([casa, visitante], ignore_index=True)

Esse dataframe tem os pontos ganhos em cada rodada. Vamos criar a acumulação de pontos. Para isso vamos usar o cumsum. Essa função acumula a conta.

Por exemplo, o Internacional tem 3 pontos na primeira rodada, e se ganhou na segunda vai ter 6. E se empatou na terceira rodada, vai ter 7 pontos. E assim por diante.

# ordenamento das rodadas
longo = longo.sort_values(['rodada'])

# contando pontos acumulativos
longo['cum_pts'] = longo.groupby('time')['pts'].cumsum()

Pivotando a Tabela

O último passo antes de criar o gráfico animado é pivotar a tabela. Pivotando a tabela, vai nos dar os nomes dos times como nomes de colunas e o resto como os pontos acumulados. Desta maneira, é fácil criar o gráfico.

Outra coisa, você observa que a tabela começa na primeira rodada, mas eu quero começa-la do zero. Então vou criar uma linha especial que assim todos os times ficam com zero.

# pivotando a tabela
pts_wide = (
    longo
    .pivot(index='rodada', columns='time', values='cum_pts')
    .fillna(method='ffill')  
    .fillna(0)               
)

# criando uma linha extra, cuja vai ser a primeira linha
todas_rodadas = range(0, 39)  

# adicionado 
pts_wide = pts_wide.reindex(todas_rodadas, fill_value=0)

Então ai, estamos pronto pra criar o vídeo com o bar_chart_race.

Criando Gráfico Race Bar

Bem amigos, se vocês chegaram até aqui, estão de parabéns. Esse é o último passo.

# importando o pacote necessário
import bar_chart_race as bcr

# iniciando o gráfico
bcr.bar_chart_race(
    df=pts_wide, # essa é o seu dataframe com os dados usados
    filename='brasileirao_points_race.mp4',  # o output final.
    orientation='h', # horizontal
    sort='desc', # do maior para o menor
    n_bars=20, # número de barras
    fixed_order=False, # não é fixo
    steps_per_period=30, # quantos quadros por segundos       
    period_length=2000,  # tempo em segundos         
    period_fmt='Rodada {x:.0f}',  # rotulo no gráfico
    title='Campeonato Brasileiro Série A (2024)' # titúlo do gráfico
)

Conclusão

Nesse projeto vocês podem sentir como é o dia a dia de um cientista de dados. Você observa que gastamos quase 95% do tempo preparando os dados para fazer o gráfico. Se contar o tempo que demorou para pegar os dados e tudo mais, seria 95%. Tranquilo.

Se você está querendo aprender python e entrar no ramo da ciência de dados, isso é o que você vai se deparar. Bastante manipulação de dados.