# install.packages("tidyverse")
# install.packages("gt")
library(tidyverse)
library(readr)
library(gt)EDA - Dataset NHL (game.csv)
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
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.