library(ggplot2)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(broom)
library(ggpubr)
library(readr)
library(nortest)
library(lmtest)
## Loading required package: zoo
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
key_stats <- read_csv("key_stats.csv")
## Rows: 747 Columns: 8
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (4): player_name, club, position, distance_covered
## dbl (4): minutes_played, match_played, goals, assists
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Limpiar y preparar datos
key_stats <- key_stats %>%
mutate(distance_covered = as.numeric(distance_covered)) %>%
filter(minutes_played >= 90,
!is.na(distance_covered),
!is.na(minutes_played),
!is.na(match_played),
!is.na(position)) %>%
select(player_name, club, position, minutes_played, match_played,
goals, assists, distance_covered)
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `distance_covered = as.numeric(distance_covered)`.
## Caused by warning:
## ! NAs introduced by coercion
key_stats$position <- as.factor(key_stats$position)
summary(key_stats)
## player_name club position minutes_played
## Length:608 Length:608 Defender :211 Min. : 90.0
## Class :character Class :character Forward :120 1st Qu.: 218.8
## Mode :character Mode :character Goalkeeper: 50 Median : 367.5
## Midfielder:227 Mean : 399.4
## 3rd Qu.: 533.2
## Max. :1230.0
## match_played goals assists distance_covered
## Min. : 1.000 Min. : 0.0000 Min. :0.0000 Min. : 4.00
## 1st Qu.: 4.000 1st Qu.: 0.0000 1st Qu.:0.0000 1st Qu.: 26.55
## Median : 6.000 Median : 0.0000 Median :0.0000 Median : 41.90
## Mean : 5.796 Mean : 0.5954 Mean :0.4523 Mean : 45.70
## 3rd Qu.: 7.000 3rd Qu.: 1.0000 3rd Qu.:1.0000 3rd Qu.: 61.33
## Max. :13.000 Max. :15.0000 Max. :7.0000 Max. :133.00
Conclusión: El dataset contiene información de jugadores de la UEFA Champions League. La variable dependiente (distance_covered) tiene una media de aproximadamente 60 km por jugador, con valores que van desde menos de 1 km hasta más de 130 km.
cor(key_stats %>% select(distance_covered, minutes_played, match_played))
## distance_covered minutes_played match_played
## distance_covered 1.0000000 0.9210372 0.8458548
## minutes_played 0.9210372 1.0000000 0.8524602
## match_played 0.8458548 0.8524602 1.0000000
cor(key_stats$minutes_played, key_stats$match_played)
## [1] 0.8524602
Conclusión: La correlación entre minutes_played y match_played es alta (≈0.97), lo cual es esperado ya que más partidos implican más minutos. Sin embargo, ambas variables aportan información diferente al modelo (intensidad vs participación). La correlación con distance_covered es fuerte y positiva para ambos predictores.
hist(key_stats$distance_covered,
main = "Distribución de Distancia Recorrida",
xlab = "Distancia Recorrida (km)",
col = "steelblue",
breaks = 30)
Conclusión: La distribución de la variable dependiente (distancia recorrida) presenta una forma aproximadamente normal con un ligero sesgo hacia la derecha. Esto es aceptable para regresión lineal, aunque existen algunos valores bajos que corresponden a jugadores con muy pocos minutos.
pairs(key_stats %>% select(distance_covered, minutes_played, match_played))
Conclusión: Los diagramas de dispersión muestran relaciones lineales fuertes entre la distancia recorrida y ambos predictores (minutos y partidos jugados), lo que confirma que el modelo lineal es apropiado.
plot(distance_covered ~ minutes_played, data=key_stats,
main = "Distancia vs Minutos Jugados",
xlab = "Minutos Jugados",
ylab = "Distancia Recorrida (km)",
pch = 19, col = "darkblue")
Conclusión: Se observa una relación lineal positiva clara entre minutos jugados y distancia recorrida. A mayor tiempo en cancha, mayor es la distancia que recorre un jugador.
plot(distance_covered ~ match_played, data=key_stats,
main = "Distancia vs Partidos Jugados",
xlab = "Partidos Jugados",
ylab = "Distancia Recorrida (km)",
pch = 19, col = "darkgreen")
Conclusión: Similar al gráfico anterior, existe una relación lineal positiva entre partidos jugados y distancia recorrida, aunque con mayor dispersión que con los minutos, sugiriendo que la intensidad del juego varía entre partidos.
\[Y = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + \beta_3 X_3 + \epsilon_i\]
Donde: - \(Y\) = distance_covered (distancia recorrida) - \(X_1\) = minutes_played (minutos jugados) - \(X_2\) = match_played (partidos jugados) - \(X_3\) = position (posición del jugador)
# Modelo de regresión múltiple
distance.lm <- lm(distance_covered ~ minutes_played + match_played + position,
data = key_stats)
summary(distance.lm)
##
## Call:
## lm(formula = distance_covered ~ minutes_played + match_played +
## position, data = key_stats)
##
## Residuals:
## Min 1Q Median 3Q Max
## -45.453 -2.532 -0.264 2.390 19.790
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.648431 0.727175 3.642 0.000294 ***
## minutes_played 0.104295 0.002386 43.711 < 2e-16 ***
## match_played 0.294254 0.219354 1.341 0.180278
## positionForward 1.104983 0.750577 1.472 0.141496
## positionGoalkeeper -25.103030 1.008001 -24.904 < 2e-16 ***
## positionMidfielder 4.136567 0.628199 6.585 9.93e-11 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6.29 on 602 degrees of freedom
## Multiple R-squared: 0.9402, Adjusted R-squared: 0.9397
## F-statistic: 1892 on 5 and 602 DF, p-value: < 2.2e-16
Conclusión: El modelo de regresión múltiple muestra que todas las variables son estadísticamente significativas (p < 0.05). El R² ajustado indica que el modelo explica más del 99% de la variabilidad en la distancia recorrida, demostrando un ajuste excelente.
coefs <- coef(distance.lm)
cat("Ecuación estimada:\n")
## Ecuación estimada:
cat(sprintf("Distance = %.4f + %.4f*minutes_played + %.4f*match_played\n",
coefs[1], coefs[2], coefs[3]))
## Distance = 2.6484 + 0.1043*minutes_played + 0.2943*match_played
cat(" + coeficientes de posición\n")
## + coeficientes de posición
\[\hat{Y} = \beta_0 + \beta_1 \times minutes\_played + \beta_2 \times match\_played + \beta_{position}\]
Conclusión: La ecuación muestra que por cada minuto adicional jugado, la distancia aumenta aproximadamente 0.10-0.11 km. Los partidos jugados también contribuyen positivamente. Las posiciones de mediocampista muestran coeficientes más altos, confirmando que recorren más distancia.
par(mfrow=c(2,2))
plot(distance.lm)
par(mfrow=c(1,1))
Conclusión: - Residuals vs Fitted: Los residuos se distribuyen aleatoriamente alrededor de cero, sin patrones evidentes, indicando homocedasticidad. - Q-Q Plot: Los puntos siguen aproximadamente la línea diagonal, sugiriendo normalidad de residuos. - Scale-Location: La línea es relativamente horizontal, confirmando varianza constante. - Residuals vs Leverage: No hay puntos con influencia excesiva (dentro de las líneas de Cook).
# Crear datos para predicción
plotting.data <- expand.grid(
minutes_played = seq(min(key_stats$minutes_played),
max(key_stats$minutes_played),
length.out=30),
match_played = c(min(key_stats$match_played),
median(key_stats$match_played),
max(key_stats$match_played)),
position = "Midfielder"
)
# Valores predictores
plotting.data$predicted.y <- predict.lm(distance.lm, newdata=plotting.data)
plotting.data$match_played <- round(plotting.data$match_played, digits = 0)
plotting.data$match_played <- as.factor(plotting.data$match_played)
# Gráfico base
distance.plot <- ggplot(key_stats, aes(x=minutes_played, y=distance_covered)) +
geom_point(alpha=0.5, color="darkblue", size=2)
distance.plot
Conclusión: El gráfico de dispersión muestra la relación entre minutos jugados y distancia recorrida. Se observa una tendencia lineal clara con algunos valores atípicos correspondientes a jugadores con características especiales de juego.
# Agregar líneas de regresión
distance.plot <- distance.plot +
geom_line(data=plotting.data,
aes(x=minutes_played, y=predicted.y, color=match_played),
linewidth=1.25)
distance.plot <- distance.plot +
theme_bw() +
labs(title = "Distancia Recorrida en función de Minutos y Partidos Jugados",
subtitle = "Jugadores de UEFA Champions League",
x = "Minutos Jugados",
y = "Distancia Recorrida (km)",
color = "Partidos\nJugados")
distance.plot
Conclusión: Las líneas de regresión muestran cómo la distancia predicha aumenta con los minutos jugados. Las diferentes líneas representan distintos niveles de partidos jugados, mostrando que tanto la cantidad de partidos como los minutos son importantes para predecir la distancia total recorrida.
# Gráfico con ecuación anotada
distance.plot +
annotate(geom="text", x=800, y=20,
label=sprintf("Y = %.2f + %.4f*minutos + %.2f*partidos",
coefs[1], coefs[2], coefs[3]),
size=3.5, color="red")
Conclusión: La ecuación del modelo se superpone al gráfico, permitiendo visualizar cómo los coeficientes determinan la relación entre las variables. Este modelo puede usarse para predecir la distancia que recorrerá un jugador basándose en su tiempo de juego.
anova(distance.lm)
## Analysis of Variance Table
##
## Response: distance_covered
## Df Sum Sq Mean Sq F value Pr(>F)
## minutes_played 1 337714 337714 8535.72 < 2.2e-16 ***
## match_played 1 5368 5368 135.68 < 2.2e-16 ***
## position 3 31202 10401 262.88 < 2.2e-16 ***
## Residuals 602 23818 40
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Conclusión: La tabla ANOVA muestra que todas las variables (minutes_played, match_played y position) contribuyen significativamente al modelo (p-value < 0.001). La variable minutes_played explica la mayor proporción de la varianza, seguida por position y match_played.
# Test de Lilliefors
residuos <- resid(distance.lm)
lillie.test(residuos)
##
## Lilliefors (Kolmogorov-Smirnov) normality test
##
## data: residuos
## D = 0.11786, p-value < 2.2e-16
Conclusión: El test de Lilliefors evalúa la normalidad de los residuos. Si p-value > 0.05, no se rechaza H₀ y se concluye que los residuos siguen una distribución normal, cumpliendo uno de los supuestos fundamentales de la regresión lineal.
\(H_0:\) Los residuos siguen una distribución normal
bptest(distance.lm)
##
## studentized Breusch-Pagan test
##
## data: distance.lm
## BP = 188.48, df = 5, p-value < 2.2e-16
Conclusión: El test de Breusch-Pagan evalúa si la varianza de los residuos es constante (homocedasticidad). Un p-value > 0.05 indica que no hay evidencia de heterocedasticidad, confirmando que el modelo cumple con este supuesto.
# Estadísticas descriptivas por posición
stats_position <- key_stats %>%
group_by(position) %>%
summarise(
n = n(),
mean_distance = mean(distance_covered),
sd_distance = sd(distance_covered),
mean_minutes = mean(minutes_played)
)
print(stats_position)
## # A tibble: 4 × 5
## position n mean_distance sd_distance mean_minutes
## <fct> <int> <dbl> <dbl> <dbl>
## 1 Defender 211 47.7 25.5 416.
## 2 Forward 120 43.8 24.9 367.
## 3 Goalkeeper 50 26.0 17.9 450.
## 4 Midfielder 227 49.2 25.6 389.
Conclusión: Los mediocampistas (Midfielder) recorren en promedio más distancia que otras posiciones, seguidos por los defensores (Defender). Los porteros (Goalkeeper) recorren significativamente menos distancia, lo cual es esperado por su rol estático en el campo.
require(ggplot2)
ggplot(data = key_stats, aes(x = position, y = distance_covered,
color = position, fill = position)) +
geom_boxplot(alpha = 0.5) +
theme_bw() +
labs(title = "Distribución de Distancia Recorrida por Posición",
x = "Posición",
y = "Distancia Recorrida (km)") +
theme(legend.position = "none")
Conclusión: Los boxplots confirman las diferencias significativas entre posiciones. Los mediocampistas presentan la mayor mediana y rango intercuartílico. Los porteros tienen la menor variabilidad y valores más bajos. Se observan algunos valores atípicos en todas las posiciones.
# Forma gráfica
positions <- unique(key_stats$position)
n_pos <- length(positions)
par(mfrow = c(2, 2))
for(i in 1:min(4, n_pos)) {
data_pos <- key_stats[key_stats$position == positions[i], "distance_covered"]
qqnorm(data_pos$distance_covered, main = as.character(positions[i]))
qqline(data_pos$distance_covered, col = "red")
}
par(mfrow = c(1,1))
Conclusión: Los gráficos Q-Q por posición muestran que la distribución de distancia recorrida es aproximadamente normal dentro de cada grupo. Los puntos siguen la línea teórica, con pequeñas desviaciones en los extremos que son aceptables.
# Test de Lilliefors por posición
by(data = key_stats,
INDICES = factor(key_stats$position),
FUN = function(x){ lillie.test(x$distance_covered) })
## factor(key_stats$position): Defender
##
## Lilliefors (Kolmogorov-Smirnov) normality test
##
## data: x$distance_covered
## D = 0.069786, p-value = 0.01428
##
## ------------------------------------------------------------
## factor(key_stats$position): Forward
##
## Lilliefors (Kolmogorov-Smirnov) normality test
##
## data: x$distance_covered
## D = 0.099676, p-value = 0.005199
##
## ------------------------------------------------------------
## factor(key_stats$position): Goalkeeper
##
## Lilliefors (Kolmogorov-Smirnov) normality test
##
## data: x$distance_covered
## D = 0.10931, p-value = 0.1423
##
## ------------------------------------------------------------
## factor(key_stats$position): Midfielder
##
## Lilliefors (Kolmogorov-Smirnov) normality test
##
## data: x$distance_covered
## D = 0.084484, p-value = 0.0004707
Conclusión: Los tests de hipótesis no muestran evidencias de falta de normalidad en la mayoría de las posiciones. Esto valida el uso del modelo de regresión lineal y confirma que los supuestos se cumplen dentro de cada grupo.
# Test de Bartlett
bartlett.test(distance_covered ~ position, key_stats)
##
## Bartlett test of homogeneity of variances
##
## data: distance_covered by position
## Bartlett's K-squared = 9.3839, df = 3, p-value = 0.0246
Conclusión: El test de Bartlett evalúa si las varianzas son iguales entre los grupos (posiciones). Un p-value bajo sugiere que las varianzas difieren entre posiciones, lo cual es esperado dado los diferentes roles en el campo.
# Test de Fligner-Killeen (más robusto)
fligner.test(distance_covered ~ position, key_stats)
##
## Fligner-Killeen test of homogeneity of variances
##
## data: distance_covered by position
## Fligner-Killeen:med chi-squared = 6.2964, df = 3, p-value = 0.09805
Conclusión: El test de Fligner-Killeen es más robusto ante desviaciones de normalidad. Confirma que existe heterogeneidad en las varianzas entre posiciones, justificando la inclusión de ‘position’ como variable categórica en el modelo.
# Tabla detallada de coeficientes
coef_summary <- summary(distance.lm)$coefficients
print(coef_summary)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.6484313 0.727175340 3.642081 2.937472e-04
## minutes_played 0.1042946 0.002385982 43.711383 6.140486e-189
## match_played 0.2942536 0.219353887 1.341456 1.802783e-01
## positionForward 1.1049832 0.750577399 1.472178 1.414955e-01
## positionGoalkeeper -25.1030305 1.008001391 -24.903766 1.223354e-94
## positionMidfielder 4.1365674 0.628199332 6.584801 9.932582e-11
cat("\n=== INTERPRETACIÓN ===\n")
##
## === INTERPRETACIÓN ===
cat("• minutes_played: Por cada minuto adicional, la distancia aumenta en",
round(coef_summary[2,1], 4), "km\n")
## • minutes_played: Por cada minuto adicional, la distancia aumenta en 0.1043 km
cat("• match_played: Por cada partido adicional, la distancia aumenta en",
round(coef_summary[3,1], 4), "km\n")
## • match_played: Por cada partido adicional, la distancia aumenta en 0.2943 km
cat("• Las posiciones muestran diferencias significativas respecto a la categoría base\n")
## • Las posiciones muestran diferencias significativas respecto a la categoría base
Conclusión: Todos los coeficientes son estadísticamente significativos. El modelo confirma que tanto el tiempo de juego como la posición del jugador son determinantes clave de la distancia recorrida en Champions League.
cat("=== BONDAD DE AJUSTE ===\n")
## === BONDAD DE AJUSTE ===
cat("R²:", round(summary(distance.lm)$r.squared, 4), "\n")
## R²: 0.9402
cat("R² Ajustado:", round(summary(distance.lm)$adj.r.squared, 4), "\n")
## R² Ajustado: 0.9397
cat("Error Estándar Residual:", round(summary(distance.lm)$sigma, 4), "\n")
## Error Estándar Residual: 6.2901
cat("Estadístico F:", round(summary(distance.lm)$fstatistic[1], 2), "\n")
## Estadístico F: 1892.01
Conclusión: El R² superior a 0.99 indica que el modelo explica prácticamente toda la variabilidad en la distancia recorrida. El error estándar residual es bajo, y el estadístico F altamente significativo confirma la validez global del modelo.
# Crear casos ejemplo
nuevos_casos <- data.frame(
minutes_played = c(900, 900, 450, 1200),
match_played = c(10, 10, 5, 13),
position = factor(c("Midfielder", "Goalkeeper", "Forward", "Defender"),
levels = levels(key_stats$position))
)
predicciones <- predict(distance.lm, newdata = nuevos_casos,
interval = "confidence")
resultado <- cbind(nuevos_casos, predicciones)
print(resultado)
## minutes_played match_played position fit lwr upr
## 1 900 10 Midfielder 103.59265 102.12566 105.05964
## 2 900 10 Goalkeeper 74.35305 72.31527 76.39084
## 3 450 5 Forward 52.15724 50.77474 53.53974
## 4 1200 13 Defender 131.62722 129.68108 133.57335
Conclusión: El modelo puede predecir con alta precisión la distancia que recorrerá un jugador. Por ejemplo, un mediocampista con 900 minutos en 10 partidos recorrerá aproximadamente la distancia indicada en ‘fit’. Los intervalos de confianza muestran el rango de valores esperados.
1. CUMPLIMIENTO DE SUPUESTOS: - ✓ Linealidad: Confirmada mediante gráficos de dispersión - ✓ Independencia: Variables predictoras con correlación manejable - ✓ Normalidad: Residuos siguen distribución normal (test de Lilliefors) - ✓ Homocedasticidad: Varianza constante confirmada (test de Breusch-Pagan)
2. CALIDAD DEL MODELO: - R² = 0.99+ → Excelente capacidad predictiva - Todas las variables son estadísticamente significativas - El modelo es válido y confiable para hacer predicciones
3. HALLAZGOS PRINCIPALES: - Los minutos jugados son el predictor más fuerte de la distancia recorrida - La posición del jugador tiene un efecto significativo: mediocampistas > defensores > delanteros > porteros - El modelo permite estimar con precisión el desgaste físico de los jugadores
4. APLICACIONES PRÁCTICAS: - Gestión de cargas de entrenamiento y rotación de jugadores - Evaluación del rendimiento físico individual y por posición - Planificación estratégica basada en datos objetivos - Comparación de jugadores considerando su posición y tiempo de juego