EDA - Dataset NHL (game.csv)

Author

Andrés Correa

Published

October 1, 2026

Descripción de los datos

El dataset game.csv contiene el registro de partidos de la NHL (liga profesional de hockey sobre hielo de Norteamérica), correspondientes a las temporadas 2000-2001 a 2019-2020. Cada fila representa un partido, con información sobre los goles anotados por el equipo local y visitante (home_goals, away_goals), el tipo de partido (type: temporada regular, playoffs o all-star) y el resultado del encuentro (outcome).

Librerías

# install.packages("tidyverse")
# install.packages("gt")
library(tidyverse)
library(readr)
library(gt)

Carga de datos

game <- read_csv("game.csv")
head(game)
# A tibble: 6 × 15
   game_id season type  date_time_GMT       away_team_id home_team_id away_goals
     <dbl>  <dbl> <chr> <dttm>                     <dbl>        <dbl>      <dbl>
1   2.02e9 2.02e7 R     2016-10-19 00:30:00            4           16          4
2   2.02e9 2.02e7 R     2018-02-07 00:00:00           24            7          4
3   2.02e9 2.02e7 R     2015-11-24 01:00:00           21           52          4
4   2.02e9 2.02e7 R     2016-02-17 00:00:00           52           12          1
5   2.02e9 2.02e7 R     2017-12-30 03:00:00           20           24          1
6   2.02e9 2.02e7 R     2017-01-10 00:30:00           15            8          4
# ℹ 8 more variables: home_goals <dbl>, outcome <chr>,
#   home_rink_side_start <chr>, venue <chr>, venue_link <chr>,
#   venue_time_zone_id <chr>, venue_time_zone_offset <dbl>,
#   venue_time_zone_tz <chr>

Dimensiones de la base de datos y sus variables

dimensiones <- tibble(
  Indicador = c(
    "Número de observaciones",
    "Número de variables",
    "Número de temporadas",
    "Número de sedes distintas"
  ),
  Valor = c(
    nrow(game),
    ncol(game),
    n_distinct(game$season),
    n_distinct(game$venue)
  )
)

dimensiones %>%
  gt() %>%
  tab_header(title = "Dimensiones de la base de datos")
Dimensiones de la base de datos
Indicador Valor
Número de observaciones 26305
Número de variables 15
Número de temporadas 19
Número de sedes distintas 112

El dataset en su totalidad contiene 26.305 observaciones y 15 variables, de las cuales dos son únicamente numéricas: away_goals y home_goals. Todos los datos se cargaron de manera correcta y se comprobaron varias veces.

Los nombres de las variables son los siguientes:

game_id (cat), season (cat), type (cat), date_time_GMT (cat), away_team_id (cat), home_team_id (cat), away_goals (num), home_goals (num), outcome (cat), home_rink_side_start (cat), venue (cat), venue_link (cat), venue_time_zone_id (cat), venue_time_zone_offset (cat), venue_time_zone_tz (cat).

Datos faltantes

faltantes <- game %>%
  summarise(across(everything(), ~ sum(is.na(.)))) %>%
  pivot_longer(everything(), names_to = "Variable", values_to = "Datos_faltantes") %>%
  mutate(Porcentaje = round(Datos_faltantes / nrow(game), 4))

faltantes %>%
  gt() %>%
  tab_header(title = "Datos faltantes por variable")
Datos faltantes por variable
Variable Datos_faltantes Porcentaje
game_id 0 0.0000
season 0 0.0000
type 0 0.0000
date_time_GMT 0 0.0000
away_team_id 0 0.0000
home_team_id 0 0.0000
away_goals 0 0.0000
home_goals 0 0.0000
outcome 0 0.0000
home_rink_side_start 1196 0.0455
venue 0 0.0000
venue_link 0 0.0000
venue_time_zone_id 0 0.0000
venue_time_zone_offset 0 0.0000
venue_time_zone_tz 0 0.0000

Aquí la tabla indica una falta de 1196 datos pertenecientes a la décima variable del dataset: home_rink_side_start. Esta ausencia de datos no tiene ningún efecto negativo en el análisis, ya que solo se están teniendo en cuenta las variables home_goals, away_goals, type y outcome.

Resumen estadístico

1. Variables numéricas

resumen_num <- game %>%
  select(home_goals, away_goals) %>%
  pivot_longer(everything(), names_to = "Variable", values_to = "valor") %>%
  group_by(Variable) %>%
  summarise(
    Minimo   = min(valor, na.rm = TRUE),
    Q1       = quantile(valor, 0.25, na.rm = TRUE),
    Mediana  = median(valor, na.rm = TRUE),
    Media    = mean(valor, na.rm = TRUE),
    Q3       = quantile(valor, 0.75, na.rm = TRUE),
    Maximo   = max(valor, na.rm = TRUE),
    Desv_Est = sd(valor, na.rm = TRUE)
  )

resumen_num %>%
  gt() %>%
  fmt_number(columns = c(Media, Desv_Est), decimals = 6) %>%
  tab_header(title = "Resumen estadístico - Variables numéricas")
Resumen estadístico - Variables numéricas
Variable Minimo Q1 Mediana Media Q3 Maximo Desv_Est
away_goals 0 1 3 2.687968 4 11 1.619068
home_goals 0 2 3 2.958981 4 12 1.689031

La tabla resume la distribución de goles anotados por el equipo local y el visitante a lo largo de los 26.305 partidos:

  • Mínimo = 0 en ambos: hubo partidos donde el local no anotó ningún gol, y partidos donde el visitante tampoco anotó ninguno (no necesariamente el mismo partido).
  • Q1: El 25% de los partidos tuvo como máximo 2 goles del local, mientras que para el visitante ese 25% inferior está en apenas 1 gol. Ya acá se empieza a ver que el visitante tiende a anotar un poco menos.
  • Mediana = 3 en ambos: la mitad de los partidos el local anotó 3 goles o menos, y lo mismo para el visitante. Que la mediana sea igual, pero el Q1 distinto, indica que la diferencia entre local y visitante se concentra más en la “cola baja” (partidos de pocos goles).
  • Media: 2.96 para el local vs. 2.69 para el visitante, con una diferencia de ~0.27 goles por partido a favor del local. Esta es la evidencia numérica de la ventaja de jugar de local, algo bien documentado en el hockey.
  • Q3 = 4 en ambos: el 75% de los partidos tuvo 4 goles o menos, para ambos equipos.
  • Máximo: El local llegó hasta 12 goles en un partido, el visitante hasta 11 — valores atípicos, partidos puntuales de goleada.
  • Desv. Est.: 1.69 (local) vs. 1.62 (visitante) — dispersión similar; el local no solo anota más en promedio, sino que tiene un poco más de variabilidad.

En conjunto: la distribución de goles es parecida en forma para ambos equipos (mínimo 0, mediana 3, Q3 4), pero está desplazada levemente hacia arriba para el local en cada medida de tendencia central (Q1, media). Es un patrón consistente y no una casualidad de la muestra: confirma cuantitativamente que jugar en casa da una ventaja ofensiva medible, aunque modesta (menos de un gol de diferencia en promedio).

2. Variables categóricas

resumen_outcome <- game %>%
  count(outcome, name = "Frecuencia") %>%
  mutate(Porcentaje = round(100 * Frecuencia / sum(Frecuencia), 2)) %>%
  arrange(desc(Frecuencia))

resumen_outcome %>%
  gt() %>%
  tab_header(title = "Distribución de la variable outcome")
Distribución de la variable outcome
outcome Frecuencia Porcentaje
home win REG 11256 42.79
away win REG 8897 33.82
home win OT 2784 10.58
away win OT 2716 10.33
tbc win OT 628 2.39
tbc win tbc 14 0.05
away win tbc 6 0.02
home win tbc 4 0.02

Las dos categorías más frecuentes son victorias en tiempo regular (REG): el local gana en REG el 42.8% de las veces, el visitante el 33.8%, una diferencia de casi 9 puntos porcentuales a favor del local. Cuando el partido se define en tiempo extra/shootout (OT), la diferencia prácticamente desaparece: 10.58% local vs. 10.33% visitante, casi 50/50. Esto es coherente con la lógica del deporte: la ventaja de localía pesa más cuando el partido se resuelve normalmente; una vez que llega a tiempo extra, el resultado se vuelve más parejo.

resumen_type <- game %>%
  count(type, name = "Frecuencia") %>%
  mutate(Porcentaje = round(100 * Frecuencia / sum(Frecuencia), 2)) %>%
  arrange(desc(Frecuencia))

resumen_type %>%
  gt() %>%
  tab_header(title = "Distribución de la variable type")
Distribución de la variable type
type Frecuencia Porcentaje
R 25171 95.69
P 1124 4.27
A 10 0.04

Casi todo el dataset (95.7%) son partidos de temporada regular. Esto tiene sentido con el calendario real de la NHL: cada equipo juega 82 partidos regulares por temporada, pero solo los que clasifican juegan playoffs, y de esos, muchos menos llegan a instancias finales; por eso los playoffs son apenas el 4.3%. Los partidos de All-Star son prácticamente anecdóticos (10 partidos en 19 temporadas, 0.04%), porque es un solo evento de exhibición por año y no siempre se registró en este dataset.

Histogramas

1. Distribución de goles de los equipos locales

ggplot(game, aes(x = home_goals)) +
  geom_histogram(binwidth = 1, fill = "steelblue", color = "white") +
  labs(
    title = "Distribución de goles del equipo local",
    x = "Goles del local",
    y = "Frecuencia"
  ) +
  theme_minimal()

La forma es asimétrica hacia la izquierda (cola larga a la izquierda, no simétrica). La mayoría de los partidos se concentra entre 2 y 4 goles, con el pico en 3 (~5.900 partidos). Después de 4 goles, la frecuencia cae rápido, y partidos con 8, 10 o 12 goles del local son cada vez más raros; son los valores atípicos ya identificados en el boxplot. Esta forma es típica de un conteo de eventos (goles) que tiene un piso en 0, pero no un techo definido.

2. Distribución de goles de los equipos visitantes

ggplot(game, aes(x = away_goals)) +
  geom_histogram(binwidth = 1, fill = "darkred", color = "white") +
  labs(
    title = "Distribución de goles del equipo visitante",
    x = "Goles del visitante",
    y = "Frecuencia"
  ) +
  theme_minimal()

Misma forma general (asimétrica a la izquierda), pero desplazada levemente hacia la derecha respecto al local: el pico está en 2 goles (no en 3), y la barra de “0 goles” es más alta que la del local (~1.900 vs. ~1.500 partidos). Esto es la misma ventaja de localía que vimos en los estadísticos descriptivos, ahora visible gráficamente: el visitante tiende a anotar un gol menos que el local en el partido más “típico”.

Gráficos de barras

1. Distribución por tipo de partido

ggplot(game, aes(x = type)) +
  geom_bar(fill = "darkgreen") +
  labs(
    title = "Distribución por tipo de partido",
    x = "Tipo de partido",
    y = "Frecuencia"
  ) +
  theme_minimal()

La barra de R (regular) es significativamente más alta (~25.000) que P (playoffs) (~1.100), y A (all-star) es casi invisible en esta escala (10 partidos). El gráfico deja claro visualmente por qué el dataset está dominado por temporada regular. Cualquier patrón general que se observe en el resto del EDA es, en la práctica, un patrón de temporada regular.

2. Distribución del resultado por partido

ggplot(game, aes(x = outcome)) +
  geom_bar(fill = "darkorchid") +
  labs(
    title = "Distribución del resultado del partido",
    x = "Resultado",
    y = "Frecuencia"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Las dos barras más altas son “home win REG” (~11.250) y “away win REG” (~8.900); la diferencia visual entre ambas es la evidencia más directa de la ventaja de localía en el dataset completo. Las barras de tiempo extra (“home win OT” y “away win OT”) son mucho más bajas (~2.700-2.800 cada una) y prácticamente de la misma altura entre sí, lo que confirma que en OT el resultado es mucho más parejo que en tiempo regular. Las categorías “tbc” son casi invisibles (barras que apenas se despegan del eje), consistentes con el ~2.5% residual que vimos en la tabla de frecuencias.

Conclusiones

El dataset game.csv contiene 26.305 partidos de la NHL jugados entre las temporadas 2000-2001 y 2019-2020, con 15 variables que registran el marcador, el tipo de partido, el resultado y datos de la sede. De estas variables, identificamos dos cuantitativas (home_goals, away_goals) y dos categóricas (type, outcome) como las más relevantes para el análisis; el resto de variables “numéricas” (game_id, season, los team_id) son simples identificadores, no mediciones.

En cuanto a calidad de los datos, el dataset está prácticamente completo: solo home_rink_side_start tiene valores faltantes (4,5%), y ninguna de las cuatro variables centrales del análisis tiene datos ausentes.

El hallazgo central del análisis es una ventaja de localía consistente y medible en todas las variables analizadas:

  • Los equipos locales anotan en promedio 2,96 goles por partido frente a 2,69 de los visitantes (diferencia de ~0,27 goles).
  • El histograma del local está levemente desplazado hacia la derecha respecto al del visitante, y la barra de “0 goles” es más frecuente en el visitante.
  • El local gana ~53,4% de los partidos frente a ~44,2% del visitante.

Esta ventaja es clara en tiempo regular (42,8% vs. 33,8%), pero casi desaparece en tiempo extra/shootout (10,6% vs. 10,3%), lo que sugiere que el factor localía pesa más cuando el partido se resuelve dentro del tiempo reglamentario y se diluye cuando entra el azar del OT.

Respecto al tipo de partido, el 95,7% del dataset son partidos de temporada regular, por lo que todos estos patrones reflejan principalmente el comportamiento de la temporada regular; los playoffs (4,3%) y los partidos All-Star (0,04%) tienen muestra insuficiente para sacar conclusiones propias sin filtrarlos y analizarlos aparte.

En síntesis: los datos confirman de forma cuantitativa un fenómeno bien conocido en el hockey —la ventaja de jugar de local—, tanto en el marcador promedio como en la probabilidad de ganar. Estas conclusiones describen la muestra registrada en este dataset; para generalizarlas a “la NHL” como población completa haría falta complementar el EDA con herramientas de inferencia estadística (por ejemplo, un test de proporciones para comparar formalmente el 53,4% vs 44,2%).

Intervalos de Confianza

En esta sección se construyen cuatro intervalos de confianza del 95% a partir de las mismas variables analizadas en el EDA (home_goals, away_goals, type, outcome), además de venue_time_zone_tz para el último intervalo. El objetivo es pasar de describir la muestra de 26.305 partidos a hacer afirmaciones, con un nivel de confianza conocido, sobre la población de partidos de la NHL que ese conjunto de datos representa.

1. Intervalo de confianza para la media poblacional

Se estima el promedio real de goles que anota el equipo local por partido.

x_barra = mean(game$home_goals)
desv    = sd(game$home_goals)
n       = length(game$home_goals)
alfa    = 0.05
z       = qnorm(1 - alfa/2)

limite_inferior = x_barra - z*desv/sqrt(n)
limite_superior = x_barra + z*desv/sqrt(n)

tibble(
  Estadístico = c("Media muestral", "Desv. estándar", "n", "Límite inferior", "Límite superior"),
  Valor = c(x_barra, desv, n, limite_inferior, limite_superior)
) %>%
  gt() %>%
  fmt_number(columns = Valor, decimals = 4) %>%
  tab_header(title = "IC 95% para la media de home_goals")
IC 95% para la media de home_goals
Estadístico Valor
Media muestral 2.9590
Desv. estándar 1.6890
n 26,305.0000
Límite inferior 2.9386
Límite superior 2.9794

Interpretación: con un 95% de confianza, el promedio real de goles que anota el equipo local por partido, considerando toda la población de partidos de la NHL (no solo esta muestra), está entre 2,94 y 2,98 goles. El intervalo es muy angosto (apenas 0,04 goles de ancho) gracias al tamaño grande de la muestra (26.305 partidos), lo que da mucha precisión a la estimación. En términos prácticos, esto confirma que el “casi 3 goles por partido” que anota el local en promedio no es una particularidad de esta muestra, sino un valor estable que se puede esperar también en partidos fuera del dataset.

2. Intervalo de confianza para la diferencia de dos medias poblacionales

Se compara el promedio de goles del equipo local entre partidos de Playoffs y de Temporada regular, para ver si el estilo de juego (más defensivo en playoffs, según la literatura deportiva) se refleja en el marcador.

izquierda = game[game$type=="P",]
derecha   = game[game$type=="R",]

x_barra_izq = mean(izquierda$home_goals)
x_barra_der = mean(derecha$home_goals)

desv_izq = sd(izquierda$home_goals)
desv_der = sd(derecha$home_goals)

n_izq = length(izquierda$home_goals)
n_der = length(derecha$home_goals)

alfa = 0.05
z    = qnorm(1 - alfa/2)

dif_medias = x_barra_izq - x_barra_der

lim_inf_dif = dif_medias - z*sqrt(desv_izq^2/n_izq + desv_der^2/n_der)
lim_sup_dif = dif_medias + z*sqrt(desv_izq^2/n_izq + desv_der^2/n_der)

tibble(
  Grupo = c("Playoffs", "Temporada regular", "Diferencia (Playoffs - Regular)",
            "Límite inferior", "Límite superior"),
  Valor = c(x_barra_izq, x_barra_der, dif_medias, lim_inf_dif, lim_sup_dif)
) %>%
  gt() %>%
  fmt_number(columns = Valor, decimals = 4) %>%
  tab_header(title = "IC 95% para la diferencia de medias de home_goals (Playoffs vs. Regular)")
IC 95% para la diferencia de medias de home_goals (Playoffs vs. Regular)
Grupo Valor
Playoffs 2.7198
Temporada regular 2.9686
Diferencia (Playoffs - Regular) −0.2489
Límite inferior −0.3481
Límite superior −0.1496

Interpretación: con un 95% de confianza, el promedio de goles del equipo local en Playoffs es entre 0,15 y 0,35 goles menor que en temporada regular (el intervalo completo es negativo: no incluye el 0). Esto es evidencia estadística, y no solo una diferencia muestral casual, de que los partidos de playoffs son más cerrados y defensivos que los de temporada regular — un patrón consistente con la estrategia habitual de los equipos cuando cada partido puede eliminarlos de la competencia.

3. Intervalo de confianza para una proporción poblacional

Se estima qué proporción de todos los partidos de la NHL (población) corresponden a temporada regular (type == "R").

tabla = prop.table(table(game$type))
tabla

           A            P            R 
0.0003801559 0.0427295191 0.9568903250 
p = tabla["R"]
n = nrow(game)

alfa = 0.05
z    = qnorm(1 - alfa/2)

lim_inf = p - z*sqrt(p*(1-p)/n)
lim_sup = p + z*sqrt(p*(1-p)/n)

tibble(
  Estadístico = c("Proporción muestral (p)", "n", "Límite inferior", "Límite superior"),
  Valor = c(as.numeric(p), n, lim_inf, lim_sup)
) %>%
  gt() %>%
  fmt_number(columns = Valor, decimals = 4) %>%
  tab_header(title = "IC 95% para la proporción de partidos de temporada regular")
IC 95% para la proporción de partidos de temporada regular
Estadístico Valor
Proporción muestral (p) 0.9569
n 26,305.0000
Límite inferior 0.9544
Límite superior 0.9593

Interpretación: con un 95% de confianza, la proporción real de partidos de la NHL que corresponden a temporada regular está entre 95,4% y 95,9%. El intervalo es muy angosto porque la proporción muestral (95,7%) está cerca de los extremos (0 o 1), lo que reduce la varianza de la estimación, y porque n es grande. Esto confirma numéricamente algo esperable por el formato de la competencia: la gran mayoría de los partidos que se juegan en una temporada de NHL son de temporada regular, mientras que playoffs y all-star representan, en conjunto, menos del 5% del calendario.

4. Intervalo de confianza para la diferencia de dos proporciones poblacionales

Se compara la proporción de partidos de Playoffs entre las sedes ubicadas en zona horaria PDT (costa oeste de EE. UU./Canadá) frente a las sedes en zona horaria EDT (costa este), para explorar si hay diferencias regionales en qué tan seguido los equipos de cada huso horario llegan a instancias de playoffs.

tabla_proporciones = prop.table(table(game$type, game$venue_time_zone_tz), margin = 2)

P1 = tabla_proporciones["P", "PDT"]
P2 = tabla_proporciones["P", "EDT"]

n_pdt = sum(game$venue_time_zone_tz == "PDT")
n_edt = sum(game$venue_time_zone_tz == "EDT")

alfa = 0.05
z    = qnorm(1 - alfa/2)

dif_prop = P1 - P2

lim_inf_prop = dif_prop - z*sqrt(P1*(1-P1)/n_pdt + P2*(1-P2)/n_edt)
lim_sup_prop = dif_prop + z*sqrt(P1*(1-P1)/n_pdt + P2*(1-P2)/n_edt)

tibble(
  Grupo = c("Proporción Playoffs en PDT", "Proporción Playoffs en EDT",
            "Diferencia (PDT - EDT)", "Límite inferior", "Límite superior"),
  Valor = c(as.numeric(P1), as.numeric(P2), dif_prop, lim_inf_prop, lim_sup_prop)
) %>%
  gt() %>%
  fmt_number(columns = Valor, decimals = 4) %>%
  tab_header(title = "IC 95% para la diferencia de proporciones de Playoffs (PDT vs. EDT)")
IC 95% para la diferencia de proporciones de Playoffs (PDT vs. EDT)
Grupo Valor
Proporción Playoffs en PDT 0.0571
Proporción Playoffs en EDT 0.0341
Diferencia (PDT - EDT) 0.0230
Límite inferior 0.0141
Límite superior 0.0319

Interpretación: con un 95% de confianza, la proporción de partidos de Playoffs es entre 1,4 y 3,2 puntos porcentuales mayor en sedes de zona horaria PDT que en sedes EDT (el intervalo completo es positivo: no incluye el 0). Es decir, durante el período 2000-2020, los equipos ubicados en la costa oeste jugaron proporcionalmente más partidos de playoffs que los de la costa este. Esto probablemente refleja qué tan seguido los equipos de cada región clasificaron a playoffs en esas temporadas específicas, más que un efecto real del huso horario sobre el rendimiento deportivo — es un hallazgo a interpretar con cautela, ya que la cantidad de sedes en cada zona horaria no es la misma (3.031 partidos en PDT frente a 11.997 en EDT).

Conclusión final

El análisis mostró que, en la muestra de 26.305 partidos, el equipo local anota en promedio más goles y gana con más frecuencia que el visitante. Los cuatro intervalos de confianza construidos permiten dar un paso más: pasar de describir esa muestra a hacer afirmaciones, con un 95% de confianza, sobre la población completa de partidos de la NHL que ese dataset representa.

Los resultados son consistentes entre sí y refuerzan la misma historia desde distintos ángulos. El intervalo para la media confirma que el promedio poblacional de goles del local (entre 2,94 y 2,98) es un valor estable, no un artefacto de la muestra. El intervalo para la diferencia de medias muestra que esa ventaja ofensiva del local se reduce en Playoffs, con una diferencia poblacional de entre -0,35 y -0,15 goles respecto a temporada regular — evidencia de que el estilo de juego cambia en instancias eliminatorias. El intervalo para la proporción confirma que la estructura del calendario de la NHL (predominio casi total de temporada regular, entre 95,4% y 95,9%) es estable en el tiempo. Finalmente, el intervalo para la diferencia de proporciones aporta un hallazgo más exploratorio: una diferencia real, aunque probablemente no muy informativa sobre el deporte en sí, en qué tan seguido clasificaron a playoffs los equipos de cada zona horaria durante el período analizado.