1 Introdução

A dupla com maior altura média venceu 55,0% das 59.216 partidas comparáveis. A taxa foi maior quando a diferença de altura aumentou. Já a frequência de terceiro set mudou pouco entre as faixas observadas.

A altura é uma característica fácil de observar na formação de duplas, mas ela diz pouco sozinha sobre o que acontece em quadra. Este trabalho investiga duas perguntas: a dupla mais alta vence com mais frequência? E jogos entre duplas de alturas parecidas chegam mais ao terceiro set? As respostas ajudam técnicos, atletas e analistas a avaliar o peso que vale a pena dar à altura ao descrever um confronto.

Comparamos adversários da mesma partida nos circuitos AVP e FIVB. Medimos a altura da dupla pela média dos dois atletas, calculamos a diferença entre duplas e comparamos a proporção de vitórias com a referência de 50%. Separamos os resultados por faixa de diferença, circuito e categoria. Para a segunda pergunta, usamos a presença de terceiro set como um indicador simples de equilíbrio. A investigação é descritiva: ela mede associações observadas no arquivo.

2 Pacotes requeridos

Todos os pacotes são carregados na configuração inicial. instalar_pacotes.R instala apenas os que estiverem faltando. A tabela indica a função de cada dependência usada pelo relatório.

Pacote Uso no projeto
readr Importação do CSV, interpretação de datas e números e exportação.
dplyr Seleção, filtros, variáveis, junções e resumos por grupo.
tidyr Separação dos sets e mudança entre dados largos e longos.
stringr Normalização de texto e interpretação de duração e placar.
ggplot2 Gráficos estáticos com escalas e rótulos definidos.
scales Formatação percentual dos eixos.
DT Tabela interativa de recortes com busca, ordenação e filtros.
knitr Execução dos blocos de código e produção das tabelas do relatório.
rmarkdown Geração do HTML autocontido usado no RPubs.
library(rmarkdown)
library(knitr)
library(readr)
library(dplyr)
library(tidyr)
library(stringr)
library(ggplot2)
library(scales)
library(DT)

3 Preparação dos dados

3.1 Origem e cobertura

O BigTimeStats reuniu resultados e estatísticas de torneios de vôlei de praia dos circuitos AVP e FIVB. Na publicação que apresentou o conjunto em 2017, o responsável descreve o uso do acervo para explorar desempenho de atletas e parcerias. Usamos o full_archive.csv da versão de 29/09/2022, recuperado em 07/11/2026. A data de compilação da versão é distinta das datas esportivas. Os eventos registrados vão de 16/09/2000 a 23/09/2022. A fonte não fornece a data exata de coleta de cada linha.

A origem contém 85.509 registros por partida e 65 colunas. Os campos incluem texto, datas, contagens, medidas físicas e resultados. As colunas w_ descrevem a dupla vencedora; as colunas l_, a perdedora. date indica a data do torneio, em geral o início, e country o país do torneio. As alturas estão em polegadas e os minutos precisam ser obtidos de uma duração no formato horas:minutos. Os campos de desempenho não têm a mesma cobertura em todos os circuitos e torneios.

Dados de BigTimeStats, sob CC BY-NC-SA 4.0. Os dados tratados mantêm essa licença; as transformações estão descritas neste relatório.

3.2 Importação, tipos e ausências

Lemos o arquivo em Latin-1, como indicado pela fonte, e fixamos sua versão para que a análise possa ser repetida. O CSV entra como texto; depois convertemos as datas para Date, as medidas e contagens usadas para número e as categorias para fatores. Tratamos vazios e marcadores de ausência como NA. Não houve duplicatas exatas na importação (0). A combinação de campos de torneio e número de partida da fonte tem 20.514 registros excedentes em chaves repetidas; preservamos as linhas e atribuímos um identificador sequencial próprio para evitar junções indevidas. A base não permite provar que toda ocorrência semelhante seja um jogo distinto.

versao_base <- "0fa423c5704de46a8cf0d9b8b542c694c0ae2276"
url_base <- paste0(
  "https://raw.githubusercontent.com/BigTimeStats/beach-volleyball/",
  versao_base, "/data/full_archive/full_archive.csv"
)
arquivo_base <- file.path("dados", "full_archive.csv")
md5_esperado <- "7a89bbe38e9a03ffb8b40bca8c64988d"

# A cópia incluída permite recompilar sem internet. Caso falte, baixa a mesma versão.
if (!file.exists(arquivo_base)) {
  dir.create(dirname(arquivo_base), recursive = TRUE, showWarnings = FALSE)
  utils::download.file(url_base, arquivo_base, mode = "wb", quiet = TRUE)
}
if (!identical(unname(tools::md5sum(arquivo_base)), md5_esperado)) {
  stop("O CSV difere da versão analisada. Use o arquivo original do projeto.")
}

# Ler como texto dá controle sobre o tipo e os valores ausentes de cada campo.
base_bruta <- readr::read_csv(
  arquivo_base,
  locale = readr::locale(encoding = "ISO-8859-1", decimal_mark = "."),
  col_types = readr::cols(.default = readr::col_character()),
  na = c("", "NA", "N/A"), trim_ws = FALSE, progress = FALSE
)
stopifnot(nrow(readr::problems(base_bruta)) == 0L)
base_unica <- distinct(base_bruta)
n_duplicadas <- nrow(base_bruta) - nrow(base_unica)
normalizar_texto <- function(x) na_if(str_squish(x), "")

minutos <- function(x) {
  partes <- str_match(x, "^([0-9]+):([0-5][0-9])$")
  total <- as.integer(partes[, 2]) * 60L + as.integer(partes[, 3])
  na_if(total, 0L)
}

# Mantém o número fornecido pela fonte e cria uma chave própria para as junções.
partidas <- base_unica |>
  transmute(
    id_partida = row_number(),
    circuito = factor(normalizar_texto(circuit), levels = c("AVP", "FIVB")),
    categoria = factor(normalizar_texto(gender), levels = c("W", "M"),
                       labels = c("Feminino", "Masculino")),
    torneio = normalizar_texto(tournament),
    pais_evento = normalizar_texto(country),
    data_evento = readr::parse_date(date, format = "%Y-%m-%d"),
    ano = readr::parse_integer(year),
    periodo = cut(ano, breaks = c(1999, 2007, 2014, 2022),
                  labels = c("2000–2007", "2008–2014", "2015–2022")),
    numero_fonte = normalizar_texto(match_num),
    fase = normalizar_texto(round),
    placar = normalizar_texto(score),
    duracao_min = minutos(normalizar_texto(duration)),
    w_atleta_1 = normalizar_texto(w_player1),
    w_atleta_2 = normalizar_texto(w_player2),
    l_atleta_1 = normalizar_texto(l_player1),
    l_atleta_2 = normalizar_texto(l_player2),
    w_altura_1_pol = readr::parse_double(w_p1_hgt),
    w_altura_2_pol = readr::parse_double(w_p2_hgt),
    l_altura_1_pol = readr::parse_double(l_p1_hgt),
    l_altura_2_pol = readr::parse_double(l_p2_hgt)
  )

stopifnot(
  !anyNA(partidas$circuito), !anyNA(partidas$categoria),
  !anyNA(partidas$data_evento), !anyNA(partidas$periodo),
  all(format(partidas$data_evento, "%Y") == partidas$ano)
)

# O contador da fonte se repete inclusive dentro de torneio, data e categoria.
chaves_fonte <- partidas |>
  distinct(circuito, categoria, torneio, pais_evento, data_evento, numero_fonte)
n_repeticoes_chave <- nrow(partidas) - nrow(chaves_fonte)

campos_auditoria <- c("score", "duration", "w_p1_hgt", "w_p2_hgt",
                      "l_p1_hgt", "l_p2_hgt", "w_p1_tot_attacks")
ausencias <- vapply(base_bruta[campos_auditoria], function(x) sum(is.na(x)), integer(1))
n_duracao_invalida <- sum(is.na(partidas$duracao_min)) - sum(is.na(base_unica$duration))
Tabela 1. Cobertura de campos selecionados na base original.
Campo Ausências no CSV Parcela dos registros
Placar 24 0,0%
Duração 2.752 3,2%
Altura 1, vencedora 6.036 7,1%
Altura 2, vencedora 6.073 7,1%
Altura 1, perdedora 9.435 11,0%
Altura 2, perdedora 9.439 11,0%
Ataques do atleta 1, vencedora 69.992 81,9%

Mantivemos ausências de altura sem imputação. Uma média calculada com só um atleta confundiria a altura individual com a da dupla. As estatísticas detalhadas ficaram fora do recorte principal: por exemplo, a contagem de ataques do primeiro atleta vencedor falta em 81,9% dos registros. Durações vazias seguem ausentes. O tratamento marcou outros 0 valores como ausentes por duração zero ou formato inválido. As ausências desse campo só afetam a comparação de duração.

3.3 Placar e nível de observação

O texto 21-16, 17-21, 15-10 vira três linhas, uma para cada set. Em cada linha, extraímos os pontos das duas duplas. Para aceitar um placar, exigimos dois ou três pares numéricos sem set empatado, com duas vitórias em sets para a dupla indicada como vencedora, inclusive no último set. Isso seleciona registros compatíveis com uma disputa melhor de três. O filtro exclui anotações de desistência, placares ausentes e formatos incompatíveis; ele não certifica todas as regras de pontuação de cada torneio histórico.

# Só interpreta textos com dois ou três pares de pontos separados por vírgulas.
padrao_set <- "[0-9]+\\s*-\\s*[0-9]+"
padrao_jogo <- paste0("^", padrao_set, "(?:\\s*,\\s*", padrao_set, "){1,2}$")
sets <- partidas |>
  select(id_partida, placar) |>
  filter(str_detect(placar, padrao_jogo)) |>
  tidyr::separate_longer_delim(placar, delim = ",") |>
  group_by(id_partida) |>
  mutate(set = row_number()) |>
  ungroup() |>
  tidyr::extract(
    placar, c("pontos_vencedora", "pontos_perdedora"),
    regex = "^\\s*([0-9]+)\\s*-\\s*([0-9]+)\\s*$", convert = TRUE
  )

placares <- sets |>
  group_by(id_partida) |>
  summarise(
    n_sets_lidos = n(),
    sets_da_vencedora = sum(pontos_vencedora > pontos_perdedora),
    sem_empate = all(pontos_vencedora != pontos_perdedora),
    venceu_ultimo = last(pontos_vencedora > pontos_perdedora),
    .groups = "drop"
  )
Tabela 2. Resultado da verificação dos placares.
Classificação do placar Registros
Compatível 84.116
Outro formato 1.356
Ausente 24
Inconsistente 13

3.4 Alturas, junção e amostra

O arquivo original tem uma linha por partida, com os dois lados em colunas diferentes. O pivot_longer() reorganiza esses campos em duas linhas por partida e nos permite aplicar a mesma conversão para os dois lados. Cada polegada equivale a 2,54 cm. Calculamos a média dos dois atletas e voltamos a uma linha por partida com pivot_wider(). As tabelas de alturas e resumo dos sets são unidas pelo identificador criado. Conferimos que as junções preservaram a quantidade de registros.

# w_ e l_ identificam as duas duplas. Uma linha por dupla permite a mesma
# transformação para os dois lados, sem usar o nome de atleta como identificador.
duplas <- partidas |>
  select(id_partida, matches("^[wl]_")) |>
  tidyr::pivot_longer(
    -id_partida, names_to = c("lado", ".value"), names_pattern = "^([wl])_(.+)$"
  ) |>
  mutate(
    resultado = if_else(lado == "w", "Vitória", "Derrota"),
    altura_1_cm = altura_1_pol * 2.54,
    altura_2_cm = altura_2_pol * 2.54,
    media_pol = (altura_1_pol + altura_2_pol) / 2,
    maior_pol = pmax(altura_1_pol, altura_2_pol)
  )

# Voltamos ao nível da partida para comparar os adversários na mesma observação.
alturas_partida <- duplas |>
  select(id_partida, lado, media_pol, maior_pol) |>
  tidyr::pivot_wider(names_from = lado, values_from = c(media_pol, maior_pol))

faixas <- c("0 cm", "Até 2,54 cm", ">2,54 a 5,08 cm", ">5,08 cm")
partidas_tratadas <- partidas |>
  select(-matches("^[wl]_")) |>
  left_join(placares, by = "id_partida", relationship = "one-to-one") |>
  left_join(alturas_partida, by = "id_partida", relationship = "one-to-one") |>
  mutate(
    status_placar = case_when(
      is.na(placar) ~ "Ausente",
      is.na(n_sets_lidos) ~ "Outro formato",
      !sem_empate | sets_da_vencedora != 2L | !venceu_ultimo ~ "Inconsistente",
      TRUE ~ "Compatível"
    ),
    n_sets = if_else(status_placar == "Compatível", n_sets_lidos, NA_integer_),
    terceiro_set = n_sets == 3L,
    altura_vencedora_cm = media_pol_w * 2.54,
    altura_perdedora_cm = media_pol_l * 2.54,
    altura_completa = !is.na(media_pol_w) & !is.na(media_pol_l),
    diferenca_pol = media_pol_w - media_pol_l,
    diferenca_cm = abs(diferenca_pol) * 2.54,
    faixa_diferenca = cut(abs(diferenca_pol), c(-Inf, 0, 1, 2, Inf), labels = faixas),
    mais_alta_venceu = if_else(diferenca_pol == 0, NA, diferenca_pol > 0),
    diferenca_maior_atleta_pol = maior_pol_w - maior_pol_l
  ) |>
  select(-n_sets_lidos, -sets_da_vencedora, -sem_empate, -venceu_ultimo,
         -media_pol_w, -media_pol_l, -maior_pol_w, -maior_pol_l, -diferenca_pol)

amostra <- filter(partidas_tratadas, status_placar == "Compatível", altura_completa)
amostra_vitorias <- filter(amostra, !is.na(mais_alta_venceu))

stopifnot(
  nrow(duplas) == 2L * nrow(partidas),
  nrow(partidas_tratadas) == nrow(partidas), !anyDuplicated(partidas_tratadas$id_partida),
  nrow(amostra) >= 5000L, ncol(amostra) >= 10L,
  all(amostra$n_sets %in% c(2L, 3L)),
  all(amostra$diferenca_cm >= 0),
  all(!is.na(amostra_vitorias$mais_alta_venceu))
)

Dos 85.509 registros de origem, 84.116 passaram pelo filtro de placar e 65.357 partidas, com 22 colunas, tinham também as quatro alturas. Destas, 6.141 tinham médias iguais. Elas entram na análise do terceiro set; a taxa de vitória da dupla mais alta usa as 59.216 partidas restantes.

Tabela 3. Cobertura da amostra final por circuito e categoria.
Circuito Categoria Original Placar compatível Na amostra Cobertura
AVP Feminino 12.263 12.104 9.029 73,6%
AVP Masculino 15.109 14.834 9.646 63,8%
FIVB Feminino 28.337 27.948 22.894 80,8%
FIVB Masculino 29.800 29.230 23.788 79,8%

A cobertura por grupo vai de 63,8% a 80,8%. A comparação deve ser lida como um retrato das partidas com medidas disponíveis, pois os jogos excluídos podem ter perfis diferentes.

Tabela 4. Seis registros da amostra final (uma linha por partida).
ID Circuito Categoria Ano Placar Vencedora (cm) Perdedora (cm) Diferença (cm) Sets
1 AVP Masculino 2002 21-18, 21-12 195,58 198,12 2,54 2
2 AVP Masculino 2002 21-16, 17-21, 15-10 194,31 194,31 0,00 3
3 AVP Masculino 2002 21-18, 21-18 194,31 194,31 0,00 2
4 AVP Masculino 2002 21-16, 21-15 193,04 198,12 5,08 2
5 AVP Masculino 2002 20-22, 23-21, 15-10 196,85 187,96 8,89 3
7 AVP Masculino 2002 21-15, 22-20 198,12 198,12 0,00 2
Variável de interesse Significado Resumo na amostra
circuito, categoria AVP ou FIVB; feminino ou masculino. Quatro grupos com cobertura na Tabela 3.
altura_vencedora_cm Altura média dos dois atletas vencedores, em cm. Mediana de 188,0 cm.
altura_perdedora_cm Altura média dos dois atletas perdedores, em cm. Mediana de 186,7 cm.
diferenca_cm Diferença absoluta entre as médias das duplas. Mediana de 3,81 cm.
mais_alta_venceu Verdadeiro se venceu a dupla com maior média. NA quando as médias são iguais.
n_sets, terceiro_set Número de sets do placar; indicador de três sets. 33,5% da amostra chegou a três sets.
duracao_min Duração em minutos, quando disponível. Mediana de 42 min; 504 ausências.

4 Análise exploratória

4.1 Vitória da dupla mais alta

Para cada partida com médias diferentes, contamos uma vitória da dupla mais alta se a altura média da vencedora for superior à da perdedora. A taxa usa o número de partidas, e não o de atletas ou sets. Organizamos a diferença absoluta em faixas de uma e duas polegadas convertidas para centímetros. A referência de 50% representa uma divisão igual de vitórias entre duplas mais altas e mais baixas.

cobertura <- partidas_tratadas |>
  group_by(circuito, categoria) |>
  summarise(
    registros = n(),
    placar_compativel = sum(status_placar == "Compatível"),
    analisadas = sum(status_placar == "Compatível" & altura_completa),
    proporcao = analisadas / registros,
    .groups = "drop"
  )

por_faixa <- amostra_vitorias |>
  group_by(faixa_diferenca) |>
  summarise(jogos = n(), vitorias = sum(mais_alta_venceu), taxa = vitorias / jogos,
            .groups = "drop")

por_grupo <- amostra_vitorias |>
  group_by(circuito, categoria) |>
  summarise(jogos = n(), vitorias = sum(mais_alta_venceu), taxa = vitorias / jogos,
            .groups = "drop")

por_recorte <- amostra_vitorias |>
  group_by(circuito, categoria, periodo, faixa_diferenca) |>
  summarise(jogos = n(), vitorias = sum(mais_alta_venceu), taxa = vitorias / jogos,
            .groups = "drop")

equilibrio <- amostra |>
  group_by(faixa_diferenca) |>
  summarise(jogos = n(), com_terceiro = sum(terceiro_set), taxa = com_terceiro / jogos,
            .groups = "drop")

duracao <- amostra |>
  filter(!is.na(duracao_min)) |>
  group_by(n_sets) |>
  summarise(jogos = n(), mediana = median(duracao_min),
            q1 = as.numeric(quantile(duracao_min, 0.25)),
            q3 = as.numeric(quantile(duracao_min, 0.75)), .groups = "drop")

maior_atleta <- amostra |>
  filter(diferenca_maior_atleta_pol != 0) |>
  summarise(jogos = n(), taxa = mean(diferenca_maior_atleta_pol > 0))

n_placar <- sum(partidas_tratadas$status_placar == "Compatível")
n_empates_altura <- nrow(amostra) - nrow(amostra_vitorias)
taxa_geral <- mean(amostra_vitorias$mais_alta_venceu)
ggplot(por_faixa, aes(faixa_diferenca, taxa, group = 1)) +
  geom_hline(yintercept = 0.5, linetype = "dashed", color = "#647785") +
  geom_line(color = verde, linewidth = 0.9) +
  geom_point(color = verde, size = 4) +
  geom_text(aes(label = pct(taxa)), nudge_y = 0.012, color = verde,
            fontface = "bold", size = 4.5) +
  scale_y_continuous(labels = rotulo_pct, breaks = seq(0.45, 0.65, by = 0.05),
                     limits = c(0.45, 0.65)) +
  labs(title = "A dupla mais alta venceu mais com diferenças maiores",
       subtitle = "Somente jogos com alturas médias diferentes; tracejado em 50%",
       x = "Diferença entre as alturas médias das duplas",
       y = "Vitórias da dupla mais alta",
       caption = "Fonte: BigTimeStats. Cada partida tem o mesmo peso.") +
  tema_volei

Três pontos mostram crescimento de cerca de 52% para 59% de vitórias conforme a diferença de altura aumenta.

Tabela 5. Vitórias e denominadores por diferença de altura.
Faixa Partidas Vitórias da mais alta Taxa
Até 2,54 cm 23.069 12.068 52,3%
>2,54 a 5,08 cm 17.097 9.275 54,2%
>5,08 cm 19.050 11.227 58,9%

A taxa foi de 52,3% na menor faixa e 58,9% quando a diferença passou de 5,08 cm. A distância entre as duas taxas foi de 6,6 p.p.. Mesmo na maior faixa, a dupla mais baixa venceu 41,1% das vezes. Portanto, a altura média é uma descrição útil, mas há bastante variação nos resultados.

4.2 Circuitos e categorias

Tabela 6. A associação em cada circuito e categoria.
Circuito Categoria Partidas Vitórias da mais alta Taxa
AVP Feminino 8.277 4.544 54,9%
AVP Masculino 8.635 5.114 59,2%
FIVB Feminino 20.681 11.216 54,2%
FIVB Masculino 21.623 11.696 54,1%

O grupo com a maior taxa observada foi AVP masculino, com 59,2%. A taxa variou entre os quatro grupos. A diferença pode refletir perfis dos torneios, critérios de entrada e cobertura das alturas, além de características de jogo. A tabela interativa permite verificar quantos jogos sustentam cada comparação por período e faixa. Recortes pequenos tendem a oscilar mais.

recortes <- por_recorte |>
  select(Circuito = circuito, Categoria = categoria, `Período` = periodo,
         `Diferença de altura` = faixa_diferenca, Partidas = jogos,
         `Vitórias` = vitorias, `Taxa de vitória` = taxa)

# Seletores por coluna usando a própria API da tabela no navegador.
filtros <- DT::JS(
  "function () {",
  "  var api = this.api(), area = document.createElement('div');",
  "  area.className = 'filtros-recortes';",
  "  var painel = api.table().container();",
  "  painel.insertBefore(area, painel.firstChild);",
  "  api.columns([0, 1, 2, 3]).every(function () {",
  "    var coluna = this, rotulo = document.createElement('label');",
  "    var seletor = document.createElement('select');",
  "    rotulo.textContent = coluna.header().textContent.trim();",
  "    seletor.add(new Option('Todos', ''));",
  "    coluna.data().unique().sort().each(function (v) { seletor.add(new Option(v, v)); });",
  "    seletor.addEventListener('change', function () {",
  "      var termo = $.fn.dataTable.util.escapeRegex(seletor.value);",
  "      coluna.search(termo ? '^' + termo + '$' : '', true, false).draw();",
  "    });",
  "    rotulo.appendChild(seletor); area.appendChild(rotulo);",
  "  });",
  "}"
)

DT::datatable(
  recortes, rownames = FALSE, class = "stripe compact",
  caption = "Tabela 7. Selecione os filtros para explorar os recortes de vitória.",
  options = list(
    pageLength = 8, lengthMenu = c(8, 16, 36), scrollX = TRUE, initComplete = filtros,
    language = list(search = "Buscar:", lengthMenu = "Mostrar _MENU_ recortes",
                    info = "_START_ a _END_ de _TOTAL_ recortes",
                    infoEmpty = "Nenhum recorte", zeroRecords = "Nenhum resultado",
                    paginate = list(previous = "Anterior", `next` = "Próxima"))
  )
) |>
  DT::formatPercentage("Taxa de vitória", digits = 1, dec.mark = ",", mark = ".")

Tabela 7. Filtros por circuito, categoria, período e diferença de altura.

Como verificação, substituímos a média pela altura do atleta mais alto de cada dupla. Entre as 55.494 partidas em que essa outra medida distingue as duplas, a dupla com o atleta mais alto venceu 55,9%. A direção da associação se mantém. O denominador muda porque a definição de empate de altura é outra.

4.3 Equilíbrio e duração

Terceiro set indica que cada lado venceu um dos dois primeiros sets. Usamos esse indicador para perguntar se confrontos com alturas mais próximas vão mais vezes ao set decisivo. Aqui, mantemos a faixa de 0 cm, porque empates de altura ainda são informativos para essa pergunta.

ggplot(equilibrio, aes(faixa_diferenca, taxa)) +
  geom_col(fill = azul, width = 0.62) +
  geom_text(aes(label = pct(taxa)), vjust = -0.55, color = azul,
            fontface = "bold", size = 4.2) +
  scale_y_continuous(labels = rotulo_pct, limits = c(0, 0.42),
                     breaks = seq(0, 0.4, by = 0.1), expand = c(0, 0)) +
  labs(title = "A frequência de terceiro set mudou pouco entre as faixas",
       subtitle = "Partidas com placar compatível e as quatro alturas conhecidas",
       x = "Diferença entre as alturas médias das duplas",
       y = "Partidas com terceiro set",
       caption = "Fonte: BigTimeStats. Empates de altura entram na primeira barra.") +
  tema_volei

A proporção de terceiros sets é próxima de 33% nas quatro faixas de diferença de altura.

As taxas ficaram entre 32,6% e 33,9%, uma amplitude de 1,2 p.p.. Embora a diferença de altura separe melhor as taxas de vitória, ela distingue pouco os jogos que chegam ao terceiro set nesse recorte. Esse indicador não mede a diferença de pontos dentro de um set.

ggplot(duracao, aes(mediana, factor(n_sets))) +
  geom_segment(aes(x = q1, xend = q3, yend = factor(n_sets)),
               color = "#a7cac7", linewidth = 4, lineend = "round") +
  geom_point(color = verde, size = 4) +
  geom_text(aes(label = paste0(num(mediana), " min")), nudge_y = 0.17,
            color = verde, fontface = "bold", size = 4.3) +
  scale_x_continuous(breaks = seq(20, 70, 10), limits = c(20, 70)) +
  labs(title = "Os jogos com terceiro set duraram mais",
       subtitle = "Ponto: mediana. Segmento: metade central das durações.",
       x = "Duração (minutos)", y = "Número de sets",
       caption = "Fonte: BigTimeStats. Apenas partidas com duração registrada e positiva.") +
  tema_volei + theme(panel.grid.major.x = element_line(color = "#e3e9ec"),
                     panel.grid.major.y = element_blank())

Medianas de duração de 38 minutos para dois sets e 54 minutos para três sets, acompanhadas de intervalos entre quartis.

Entre as partidas com duração disponível, a mediana passou de 38 para 54 minutos. Os denominadores foram 43.187 jogos de dois sets e 21.666 de três. A mediana descreve melhor o tempo típico quando alguns jogos são muito longos. Para organizar horários, a diferença observada é um ponto de partida; o tempo real depende do confronto.

5 Conclusões

A dupla com maior altura média venceu um pouco mais da metade das partidas em que a comparação era possível. A proporção passou de 52,3% para 58,9% entre as faixas extremas, e a associação variou por circuito e categoria. Em contraste, cerca de um terço dos jogos chegou ao terceiro set em todas as faixas. Assim, a altura média ajuda a descrever uma vantagem observada no resultado, mas oferece pouco para distinguir os confrontos que terão set decisivo.

O resultado vem da comparação de adversários do mesmo jogo, após converter alturas, interpretar placares e unir as tabelas por partida. Para técnicos e atletas, a medida pode compor uma avaliação junto com a técnica de saque, recepção e ataque. Para quem organiza torneios, a duração típica por número de sets informa o planejamento, com margem para a variabilidade.

As partidas com dados completos podem não representar todo o acervo. Os mesmos atletas e duplas aparecem em muitos jogos, e a análise dá o mesmo peso a cada partida. Não controlamos nível técnico, cabeça de chave, experiência, fase ou alterações históricas nas competições. Por isso, os percentuais são descritivos e não estimam um efeito causal da altura. Em uma extensão, seria útil vincular um identificador estável de atletas, controlar diferenças de nível e fase e testar a estabilidade do padrão por torneio e temporada. Estatísticas de ataque e bloqueio poderiam ajudar nessa investigação onde a cobertura fosse suficiente.

6 Reprodução e fontes

O código de importação, tratamento, resumos e gráficos está disponível nos blocos Code. Os resultados são calculados na geração deste HTML. A tabela analítica pode ser regenerada a partir do CSV original fixado por versão.

readr::write_csv(amostra, file.path("dados", "partidas_analisadas.csv"), na = "")
sessionInfo()
## R version 4.5.0 (2025-04-11)
## Platform: x86_64-pc-linux-gnu
## Running under: Debian GNU/Linux 13 (trixie)
## 
## Matrix products: default
## BLAS:   /tmp/volei-r-local/usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.1 
## LAPACK: /tmp/volei-r-local/usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.1;  LAPACK version 3.12.0
## 
## locale:
##  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
##  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
##  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
## [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
## 
## time zone: America/Recife
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] DT_0.33        scales_1.3.0   ggplot2_3.5.1  stringr_1.5.1  tidyr_1.3.1   
## [6] dplyr_1.1.4    readr_2.1.5    knitr_1.50     rmarkdown_2.29
## 
## loaded via a namespace (and not attached):
##  [1] bit_4.6.0         gtable_0.3.6      jsonlite_1.9.1    crayon_1.5.3     
##  [5] compiler_4.5.0    tidyselect_1.2.1  parallel_4.5.0    jquerylib_0.1.4  
##  [9] yaml_2.3.10       fastmap_1.2.0     R6_2.6.1          generics_0.1.3   
## [13] htmlwidgets_1.6.4 tibble_3.2.1      munsell_0.5.1     bslib_0.9.0      
## [17] pillar_1.10.1     tzdb_0.5.0        rlang_1.1.5       cachem_1.1.0     
## [21] stringi_1.8.4     xfun_0.51         sass_0.4.9        bit64_4.6.0-1    
## [25] cli_3.6.4         withr_3.0.2       magrittr_2.0.3    crosstalk_1.2.1  
## [29] digest_0.6.37     grid_4.5.0        vroom_1.6.5       hms_1.1.3        
## [33] lifecycle_1.0.4   vctrs_0.6.5       evaluate_1.0.3    glue_1.8.0       
## [37] farver_2.1.2      colorspace_2.1-1  purrr_1.0.4       tools_4.5.0      
## [41] pkgconfig_2.0.3   htmltools_0.5.8.1