library(tidyverse)
library(lubridate)
library(gt)
library(DT)Tabela, Trave e Tendências: O Brasileirão sob a Lupa Estatística
1. Introdução
O Campeonato Brasileiro de Futebol, popularmente conhecido como Brasileirão, é a principal competição nacional de clubes no Brasil. Disputado desde 1970 em diferentes formatos, ele se consolidou em 2003 no modelo de pontos corridos, onde os 20 clubes participantes se enfrentam em turno e returno, totalizando 38 rodadas. A cada temporada, os dados gerados por centenas de partidas oferecem uma rica oportunidade para a análise estatística do desempenho dos clubes, jogadores e aspectos táticos do jogo. Este trabalho tem como objetivo explorar estatisticamente as informações do campeonato. As bases de dados utilizadas contêm informações detalhadas sobre partidas, gols, cartões e estatísticas táticas.
As análises incluem:
- Média de público e lotação dos estádios por time.
- Confrontos com maior público e número de gols.
- Desempenho dos times em casa e fora.
- Distribuição de gols por período do jogo e principais artilheiros.
- Analise de cartões
- relação de posse de bola com a taxa de vitórias
Pacotes Utilizados
2. Carregamento das Bases
dados_cartao <- read_csv("campeonato-brasileiro-cartoes.csv")
dados_corner <- read_csv("campeonato-brasileiro-estatisticas-full.csv")
dados_full <- read_csv("campeonato-brasileiro-full.csv")
dados_gol <- read_csv("campeonato-brasileiro-gols.csv")
dados_tratados <- read_csv("dados_tratados.csv")3. Tratamento de Times
Padronização dos Times
dados_tratados$data <- ymd(dados_tratados$data)
dados_tratados <- dados_tratados |>
arrange(data) |>
filter(ano_campeonato >= 2015) |>
mutate(time_mandante = case_when(
time_mandante == "Goiás EC" ~ "Goiás",
time_mandante == "Santos FC" ~ "Santos",
time_mandante == "Atlético-PR" ~ "Athletico-PR",
T ~ time_mandante
)) |>
mutate(time_visitante = case_when(
time_visitante == "Goiás EC" ~ "Goiás",
time_visitante == "Santos FC" ~ "Santos",
time_visitante == "Atlético-PR" ~ "Athletico-PR",
T ~ time_visitante
))Média/Média Relativa de Público dos Times Mandantes por Estádio
publico <- dados_tratados |>
mutate(media_relativa = publico / publico_max) |>
group_by(time_mandante) |>
summarise(media = mean(publico, na.rm = T),
media_relativa = mean(media_relativa, na.rm = T)) |>
arrange(desc(media))
tabela_publico <- datatable(publico,
rownames = F,
colnames = c("Time", "Média", "Média Relativa")
) |>
formatPercentage("media_relativa", 2) |>
formatRound("media", 0)
tabela_publico4. Análise de Público
publico_ano <- dados_tratados |>
mutate(media_relativa = publico / publico_max) |>
group_by(time_mandante, ano_campeonato) |>
summarise(media = mean(publico, na.rm = T),
media_relativa = mean(media_relativa, na.rm = T))
clubes_grandes <- c("Flamengo", "Corinthians", "Palmeiras", "Vasco da Gama", "Fluminense",
"São Paulo", "Botafogo", "Atlético-MG",
"Grêmio", "Internacional", "EC Bahia", "Fortaleza")Evolução da Média de Publico por Time
plot_1 <- publico_ano |>
filter(time_mandante %in% clubes_grandes) |>
ggplot(aes(x = ano_campeonato, y = media, color = time_mandante)) +
geom_line(size = 1.2, alpha = 0.8) +
geom_point(size = 2) +
labs(
title = "Evolução da Média de Público por Time (2015–2024)",
x = "Ano",
y = "Média de Público",
color = "Clubes"
) +
scale_color_manual(values = c(
"Flamengo" = "red3",
"Corinthians" = "black",
"Palmeiras" = "green4",
"Vasco da Gama" = "gray20",
"Fluminense" = "deeppink",
"São Paulo" = "firebrick",
"Botafogo" = "gold",
"Atlético-MG" = "dimgray",
"Grêmio" = "dodgerblue2",
"Internacional" = "orange",
"EC Bahia" = "blue4",
"Fortaleza" = "steelblue"
),
labels = c("Flamengo" = "Mengão")) +
theme_minimal()
plot_1Evolução da lotação do estádio por Time
plot_2 <- publico_ano |>
filter(time_mandante %in% clubes_grandes) |>
ggplot(aes(x = ano_campeonato, y = media_relativa, color = time_mandante)) +
geom_line(size = 1.5) +
geom_point(size = 3) +
labs(title = "Evolução da lotação do estádio por Time",
x = "Ano",
y = "Lotação (%)",
color = "Clubes") +
scale_color_manual(values = c(
"Flamengo" = "red3",
"Corinthians" = "black",
"Palmeiras" = "green4",
"Vasco da Gama" = "gray20",
"Fluminense" = "deeppink",
"São Paulo" = "firebrick",
"Botafogo" = "gold",
"Atlético-MG" = "dimgray",
"Grêmio" = "dodgerblue2",
"Internacional" = "orange",
"EC Bahia" = "blue4",
"Fortaleza" = "steelblue"
),
labels = c("Flamengo" = "Mengão")) +
theme_minimal()
plot_25. Análise de Confrontos
dados_tratados <- dados_tratados |>
mutate(
confronto = map2_chr(
time_mandante,
time_visitante,
~ paste(sort(c(.x, .y)), collapse = " x ")
)
)
confronto_stats <- dados_tratados |>
group_by(confronto) |>
summarise(n_partidas = n(),
media_publico = mean(publico, na.rm = T),
max_publico = max(publico, na.rm = T)) |>
filter(n_partidas >= 12) |>
arrange(desc(media_publico))Top 10 Confrontos com Maior Média de Público
plot_3 <- confronto_stats |>
slice_max(order_by = media_publico, n = 10) |>
ggplot(aes(x = reorder(confronto, media_publico), y = media_publico)) +
geom_col(fill = "steelblue") +
coord_flip() +
labs(title = "Top 10 Confrontos com Maior Média de Público",
x = "Confronto", y = "Média de Público")
plot_36. Análise de Gols e Índices
dados_tratados <- dados_tratados |>
mutate(vencedor = case_when(
gols_mandante > gols_visitante ~ time_mandante,
gols_mandante < gols_visitante ~ time_visitante,
T ~ "Empate"),
gols_total = gols_mandante + gols_visitante
)Times Com Mais Gols
gols_times <- bind_rows(
dados_tratados |>
mutate(
time = time_mandante,
gols_marcados = gols_mandante,
gols_sofridos = gols_visitante
) |>
select(time, gols_marcados, gols_sofridos),
dados_tratados |>
mutate(
time = time_visitante,
gols_marcados = gols_visitante,
gols_sofridos = gols_mandante
) |>
select(time, gols_marcados, gols_sofridos)
) |>
group_by(time) |>
summarise(
gols_marcados = sum(gols_marcados, na.rm = T),
gols_sofridos = sum(gols_sofridos, na.rm = T)
) |>
mutate(
saldo = gols_marcados - gols_sofridos,
indice = gols_marcados / gols_sofridos
) |>
arrange(desc(gols_marcados))
datatable(gols_times,
rownames = FALSE,
options = list(pageLength = 15),
colnames = c("Clube", "Gols Feitos", "Gols Sofridos", "Saldo de Gols", "Índice Ofensivo")) |>
formatRound("indice", 2)gols_times |>
slice_head(n = 20) |>
ggplot(aes(x = reorder(time, indice), y = indice)) +
geom_col(fill = "royalblue") +
coord_flip() +
labs(title = "Índice Ofensivo (Gols Feitos / Gols Sofridos)",
x = "Clube", y = "Índice") +
theme_minimal()7. Análise de Gols por Tempo
dados_gol <- dados_gol |>
mutate(atleta = recode(atleta, "Gabriel Barbosa Almeida" = "Gabriel Barbosa")) |>
separate(minuto, into = c("minuto", "min_extra"), sep = "\\+", fill = "right") |>
mutate(minuto = as.numeric(minuto),
tempo_gol = case_when(
minuto <= 15 ~ 1,
minuto %in% 16:30 ~ 2,
minuto %in% 31:45 ~ 3, #criando estratos para o tempo que saiu o gol
minuto %in% 46:60 ~ 4,
minuto %in% 61:75 ~ 5,
minuto %in% 76:90 ~ 6)
)
gols_por_periodo <- dados_gol |>
count(tempo_gol, name = "quantidade_gols") |>
mutate(proporcao = quantidade_gols / sum(quantidade_gols) * 100)
ggplot(gols_por_periodo, aes(x = tempo_gol, y = proporcao)) +
geom_col(fill = "steelblue") +
labs(title = "Distribuição de gols por intervalo de 15 minutos",
x = "Intervalo de tempo", y = "Porcentagem de gols") +
theme_minimal()dados_gol |>
count(atleta, name = "quantidade_gols") |>
slice_max(order_by = quantidade_gols, n = 10) |>
ggplot(aes(x = reorder(atleta, quantidade_gols), y = quantidade_gols)) +
geom_col(fill = "steelblue") +
geom_text(aes(label = quantidade_gols), hjust = -0.1, size = 3) +
coord_flip() +
labs(title = "Top 10 Artilheiros do Campeonato Brasileiro (2015–2024)",
x = "Atleta",
y = "Total de Gols") +
theme_minimal()8. Análise de Cartões
Total de cartões por time
cartoes_por_time <- dados_filtrados |>
group_by(clube) |>
summarise(
total_amarelos = sum(amarelos, na.rm = TRUE),
total_vermelhos = sum(vermelhos, na.rm = TRUE)
)Média de cartões amarelos e vermelhos
medias_por_time <- total_jogos |>
left_join(cartoes_por_time, by = "clube") |>
mutate(
media_amarelos = total_amarelos / total_jogos,
media_vermelhos = total_vermelhos / total_jogos
) |>
arrange(desc(media_amarelos))Gráfico: Média de cartões amarelos e vermelhos por time
medias_long <- medias_por_time |>
pivot_longer(
cols = c(media_amarelos, media_vermelhos),
names_to = "tipo_cartao",
values_to = "media"
)
ggplot(medias_long,
aes(x = reorder(clube, -media),
y = media,
fill = tipo_cartao)) +
geom_bar(stat = "identity",
position = "dodge") +
geom_text(aes(label = round(media, 2)),
position = position_dodge(width = 0.9),
vjust = -0.5,
size = 3) +
scale_fill_manual(
values = c("media_amarelos" = "gold2", "media_vermelhos" = "firebrick3"),
labels = c("Amarelos", "Vermelhos")
) +
labs(
title = "Média de Cartões por Jogo (2015–2023)",
x = "Clube",
y = "Cartões/jogo",
fill = " "
) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top"
)Gráfico: Média de cartões por mês
dados_filtrados |>
mutate(mes = month(data, label = TRUE, abbr = TRUE),
mes_num = month(data)) |>
filter(mes_num >= 4, mes_num <= 12) |>
group_by(mes) |>
summarise(media_cartoes = mean(total_cartoes)) |>
ggplot(aes(x = mes, y = media_cartoes, group = 1)) +
geom_line(color = "steelblue", linewidth = 1) +
geom_point(size = 3, color = "firebrick") +
labs(title = "Média de Cartões por Mês (2015-2023)",
x = "Mês",
y = "Média de Cartões")Gráfico: Cartões por dia da semana e hora
dados_filtrados |>
mutate(
dia_semana = wday(data, label = TRUE, abbr = FALSE),
hora = as.factor(hour(hora))
) |>
group_by(dia_semana, hora) |>
summarise(
total_cartoes = sum(total_cartoes),
.groups = "drop"
) |>
ggplot(aes(x = hora, y = dia_semana, fill = total_cartoes)) +
geom_tile() +
scale_fill_gradient(low = "white", high = "red") +
labs(title = "Cartões por Dia da Semana e Hora (2015–2023)") +
theme_minimal()9. Análise Tática
Correlação entre posse de bola e vitórias
vitorias_casa <- dados_tratados |>
filter(gols_mandante > gols_visitante) |>
count(time_mandante, name = "vitorias_casa") |>
rename(time = time_mandante)
vitorias_fora <- dados_tratados |>
filter(gols_visitante > gols_mandante) |>
count(time_visitante, name = "vitorias_fora") |>
rename(time = time_visitante)
jogos_casa <- dados_tratados |>
count(time_mandante, name = "jogos_casa") |>
rename(time = time_mandante)
jogos_fora <- dados_tratados |>
count(time_visitante, name = "jogos_fora") |>
rename(time = time_visitante)
desempenho_vitorias <- jogos_casa |>
full_join(jogos_fora, by = "time") |>
full_join(vitorias_casa, by = "time") |>
full_join(vitorias_fora, by = "time") |>
mutate(
taxa_de_vitoria_casa = vitorias_casa / jogos_casa,
taxa_de_vitoria_fora = vitorias_fora / jogos_fora
) |>
arrange(desc(taxa_de_vitoria_casa))
resultado_mandante <- desempenho_vitorias |>
mutate(derrotas_casa = jogos_casa - vitorias_casa,
derrotas_fora = jogos_fora - vitorias_fora) |>
select(-taxa_de_vitoria_casa, -taxa_de_vitoria_fora)
tabela_resultado_mandante <- datatable(resultado_mandante,
rownames = F)
tabela_resultado_mandantedados_corner <- dados_corner |>
filter(partida_id >= 4987) |>
select(-rodata)
base <- dados_full |>
filter(ID >= 4987) |>
select(ID, mandante, visitante) |>
rename("partida_id" = ID)
dados_partidas <- dados_corner |>
left_join(base, by = "partida_id") |>
# Definir um identificador para mandante/visitante
mutate(tipo_time = case_when(
clube == mandante ~ "mandante",
clube == visitante ~ "visitante",
TRUE ~ NA_character_
)) |>
# Pivotar os dados para ter dados de mandante e visitante na mesma linha
pivot_wider(
id_cols = c(partida_id, mandante, visitante),
names_from = tipo_time,
values_from = c(clube, chutes, chutes_no_alvo, posse_de_bola, passes, faltas,
cartao_amarelo, cartao_vermelho, impedimentos, escanteios),
names_sep = "_"
) |>
select(-mandante, -visitante)
# Verificando o resultado
print(dados_partidas)# A tibble: 3,419 × 21
partida_id clube_visitante clube_mandante chutes_visitante chutes_mandante
<dbl> <chr> <chr> <dbl> <dbl>
1 4987 Coritiba Chapecoense 16 13
2 4988 Atletico-MG Palmeiras 10 17
3 4989 Joinville Fluminense 3 26
4 4990 Ponte Preta Gremio 17 11
5 4993 Corinthians Cruzeiro 11 13
6 4994 Figueirense Sport 7 17
7 4991 Internacional Athletico-PR 17 10
8 4992 Flamengo Sao Paulo 12 19
9 4996 Goias Vasco 5 11
10 4995 Santos Avai 8 16
# ℹ 3,409 more rows
# ℹ 16 more variables: chutes_no_alvo_visitante <dbl>,
# chutes_no_alvo_mandante <dbl>, posse_de_bola_visitante <chr>,
# posse_de_bola_mandante <chr>, passes_visitante <dbl>,
# passes_mandante <dbl>, faltas_visitante <dbl>, faltas_mandante <dbl>,
# cartao_amarelo_visitante <dbl>, cartao_amarelo_mandante <dbl>,
# cartao_vermelho_visitante <dbl>, cartao_vermelho_mandante <dbl>, …
dados_juntos <- dados_partidas |>
left_join(dados_full, by =c("partida_id" = "ID"))
dados_juntos <- dados_juntos |>
mutate(vencedor = case_when(
mandante_Placar > visitante_Placar ~ mandante,
mandante_Placar < visitante_Placar ~ visitante,
TRUE ~ "Empate")
) |>
mutate(resultado_mandante = case_when(
vencedor == mandante ~ "Vitória",
vencedor == visitante ~ "Derrota",
vencedor == "Empate" ~ "Empate")
)
dados_limpos <- dados_juntos |>
mutate(
posse_de_bola_mandante = as.numeric(str_remove(posse_de_bola_mandante, "%")),
posse_de_bola_visitante = as.numeric(str_remove(posse_de_bola_visitante, "%"))
) |>
filter(!is.na(posse_de_bola_mandante) & !is.na(posse_de_bola_visitante))
# Agora você pode usar estas colunas convertidas para analisar a relação com vitórias
posse_por_resultado <- dados_limpos |>
group_by(resultado_mandante) |>
summarise(
media_posse_mandante = mean(posse_de_bola_mandante, na.rm = TRUE),
media_posse_visitante = mean(posse_de_bola_visitante, na.rm = TRUE),
contagem = n()
)
# 1. Visualização da relação entre posse de bola do mandante e resultado
ggplot(dados_limpos, aes(x = resultado_mandante,
y = posse_de_bola_mandante)) +
geom_boxplot(aes(fill = resultado_mandante)) +
labs(title = "Relação entre Posse de Bola do Mandante e Resultado",
x = "Resultado",
y = "Posse de Bola do Mandante (%)") +
theme_minimal()# 2. Visualização da posse de bola média do mandante por resultado
posse_por_resultado <- dados_limpos |>
group_by(resultado_mandante) |>
summarise(
media_posse_mandante = mean(posse_de_bola_mandante, na.rm = TRUE)
)
ggplot(posse_por_resultado, aes(x = resultado_mandante,
y = media_posse_mandante,
fill = resultado_mandante)) +
geom_col() +
labs(title = "Posse de Bola Média do Mandante por Resultado",
x = "Resultado",
y = "Posse de Bola (%)") +
scale_fill_manual(values = c("Vitória" = "forestgreen",
"Empate" = "goldenrod",
"Derrota" = "firebrick")) +
theme_minimal()# 3. Analisando as chances de vitória por diferentes faixas de posse de bola
dados_limpos <- dados_limpos |>
mutate(
# Criando faixas de posse de bola
faixa_posse = cut(posse_de_bola_mandante,
breaks = c(0, 40, 50, 60, 100),
labels = c("Baixa (<40%)", "Média-Baixa (40-50%)",
"Média-Alta (50-60%)", "Alta (>60%)"))
)
# Taxa de vitória por faixa de posse de bola
taxa_vitoria_por_posse <- dados_limpos |>
group_by(faixa_posse) |>
summarise(
total_jogos = n(),
vitorias = sum(resultado_mandante == "Vitória", na.rm = TRUE),
empates = sum(resultado_mandante == "Empate", na.rm = TRUE),
derrotas = sum(resultado_mandante == "Derrota", na.rm = TRUE),
taxa_vitoria = vitorias / total_jogos * 100
)
ggplot(taxa_vitoria_por_posse, aes(x = faixa_posse,
y = taxa_vitoria,
fill = faixa_posse)) +
geom_bar(stat = "identity") +
labs(title = "Taxa de Vitória por Faixa de Posse de Bola",
x = "Faixa de Posse de Bola",
y = "Taxa de Vitória (%)") +
theme_minimal()