Este estudio de caso analiza el conjunto de datos FIFA 20 Complete Player Dataset (Leone, 2019), disponible en Kaggle. El objetivo es explorar las relaciones entre variables de habilidad de los jugadores de fútbol, construir modelos de regresión lineal simple para modelar la relación entre tiro (shooting) y regate (dribbling), y ajustar un modelo de regresión logística para predecir el pie dominante (preferred_foot) de los jugadores.
Referencia: Leone, S. (2019). FIFA 20 complete player dataset. Kaggle.
https://www.kaggle.com/stefanoleone992/fifa-20-complete-player-dataset
# Carga de librerías necesarias
library(tidyverse)
library(ggplot2)
library(corrplot)
library(car)
library(lmtest)
library(caret)
library(knitr)
library(kableExtra)
library(gridExtra)
library(ggfortify)
# Carga del dataset
futbol_raw <- read.csv("players_15.csv", stringsAsFactors = FALSE, sep=",")
# Selección de variables requeridas por la guía
vars_seleccionadas <- c("club", "preferred_foot", "team_position", "age",
"height_cm", "weight_kg", "overall", "potential",
"value_eur", "wage_eur",
"shooting", "passing", "dribbling",
"defending", "physic", "pace")
futbol_raw <- futbol_raw[, vars_seleccionadas]
# Vista general del dataset
cat("Dimensiones del dataset:", nrow(futbol_raw), "filas x", ncol(futbol_raw), "columnas\n")
#> Dimensiones del dataset: 15465 filas x 16 columnas
A continuación se describe cada variable del conjunto de datos:
| Variable | Tipo | Descripción |
|---|---|---|
club |
Categórica | Nombre del club al que pertenece el jugador |
preferred_foot |
Binaria | Pie dominante del jugador: Left (izquierdo) o Right (derecho) |
team_position |
Categórica | Posición táctica del jugador en su equipo (ej. CF, ST, GK) |
age |
Numérica | Edad del jugador en años cumplidos |
height_cm |
Numérica | Estatura del jugador en centímetros |
weight_kg |
Numérica | Peso del jugador en kilogramos |
overall |
Numérica | Valoración global del jugador (0–99), refleja su nivel actual |
potential |
Numérica | Potencial máximo que podría alcanzar el jugador (0–99) |
value_eur |
Numérica | Valor de mercado estimado del jugador en euros |
wage_eur |
Numérica | Salario semanal del jugador en euros |
shooting |
Numérica | Habilidad de tiro del jugador (0–99) |
passing |
Numérica | Habilidad de pase del jugador (0–99) |
dribbling |
Numérica | Habilidad de regate del jugador (0–99) |
defending |
Numérica | Habilidad defensiva del jugador (0–99) |
physic |
Numérica | Atributo físico del jugador (resistencia, fuerza) (0–99) |
pace |
Numérica | Velocidad del jugador (0–99) |
# Contar NA por variable
# Contar NA por variable y mostrar tabla
cat("Dimensiones originales:", nrow(futbol_raw), "filas x", ncol(futbol_raw), "columnas\n")
#> Dimensiones originales: 15465 filas x 16 columnas
# Eliminar filas con NAs (elimina porteros que no tienen shooting/dribbling)
futbol_150_base <- futbol_raw %>% drop_na()
cat("Filas tras eliminar NAs:", nrow(futbol_150_base), "\n")
#> Filas tras eliminar NAs: 13762
# Tomar una muestra aleatoria reproducible de 150 jugadores para el análisis
set.seed(42)
futbol_150 <- futbol_150_base %>% sample_n(150)
cat("Muestra de trabajo:", nrow(futbol_150), "jugadores\n")
#> Muestra de trabajo: 150 jugadores
Nota: Se identificaron datos faltantes principalmente en variables de habilidad (
shooting,dribbling, etc.) correspondientes a porteros. Las filas con valores NA fueron eliminadas para garantizar la integridad del análisis. Se trabaja con una muestra de 150 jugadores como indica la guía.
shooting (Tiro)p1 <- ggplot(futbol_150, aes(x = shooting)) +
geom_histogram(bins = 20, fill = "#1a5276", color = "white", alpha = 0.85) +
geom_vline(aes(xintercept = mean(shooting)), color = "#e74c3c",
linetype = "dashed", linewidth = 1) +
labs(title = "Histograma - Shooting",
x = "Puntuación de Tiro", y = "Frecuencia") +
theme_minimal(base_size = 11) +
annotate("text", x = mean(futbol_150$shooting) + 5,
y = 18, label = paste0("Media = ", round(mean(futbol_150$shooting), 1)),
color = "#e74c3c", size = 3.5)
p2 <- ggplot(futbol_150, aes(y = shooting)) +
geom_boxplot(fill = "#1a5276", alpha = 0.7, outlier.color = "#e74c3c",
outlier.shape = 16, outlier.size = 2) +
labs(title = "Boxplot - Shooting", y = "Puntuación de Tiro") +
theme_minimal(base_size = 11)
grid.arrange(p1, p2, ncol = 2)
Distribución de la variable shooting
Interpretación: La variable shooting presenta una distribución aproximadamente simétrica con media alrededor de 50.8. El boxplot revela la presencia de algunos valores atípicos en los extremos inferiores, correspondientes probablemente a porteros o jugadores defensivos con habilidades de tiro muy bajas.
dribbling (Regate)p3 <- ggplot(futbol_150, aes(x = dribbling)) +
geom_histogram(bins = 20, fill = "#117a65", color = "white", alpha = 0.85) +
geom_vline(aes(xintercept = mean(dribbling)), color = "#e74c3c",
linetype = "dashed", linewidth = 1) +
labs(title = "Histograma - Dribbling",
x = "Puntuación de Regate", y = "Frecuencia") +
theme_minimal(base_size = 11) +
annotate("text", x = mean(futbol_150$dribbling) + 5,
y = 18, label = paste0("Media = ", round(mean(futbol_150$dribbling), 1)),
color = "#e74c3c", size = 3.5)
p4 <- ggplot(futbol_150, aes(y = dribbling)) +
geom_boxplot(fill = "#117a65", alpha = 0.7, outlier.color = "#e74c3c",
outlier.shape = 16, outlier.size = 2) +
labs(title = "Boxplot - Dribbling", y = "Puntuación de Regate") +
theme_minimal(base_size = 11)
grid.arrange(p3, p4, ncol = 2)
Distribución de la variable dribbling
Interpretación: La variable dribbling presenta una media de r round(mean(futbol_150\(dribbling), 1) y una mediana de r round(median(futbol_150\)dribbling), 1). r ifelse(mean(futbol_150\(dribbling) > median(futbol_150\)dribbling), “La media es mayor que la mediana, lo que indica un ligero sesgo hacia la derecha.”, “La mediana es mayor que la media, lo que indica un ligero sesgo hacia la izquierda.”). El boxplot muestra valores atípicos en los extremos inferiores,correspondientes a jugadores defensivos con baja habilidad de regate.
Se modela la relación entre la habilidad de tiro (shooting, variable independiente \(X\)) y la habilidad de regate (dribbling, variable dependiente \(Y\)).
# Semilla para reproducibilidad
set.seed(10)
# Índices de la muestra de entrenamiento (140 datos)
indices_train <- sample(1:nrow(futbol_150), 140)
# Conjunto de entrenamiento
train <- futbol_150[indices_train, ]
# Conjunto de prueba (10 datos restantes)
test <- futbol_150[-indices_train, ]
cat("Tamaño conjunto train:", nrow(train), "\n")
#> Tamaño conjunto train: 140
cat("Tamaño conjunto test:", nrow(test), "\n")
#> Tamaño conjunto test: 10
# Correlación de Pearson
correlacion <- cor(train$shooting, train$dribbling)
cat("Correlación de Pearson (shooting vs dribbling):", round(correlacion, 4), "\n")
#> Correlación de Pearson (shooting vs dribbling): 0.71
# Gráfico de dispersión
ggplot(train, aes(x = shooting, y = dribbling)) +
geom_point(color = "#1a5276", alpha = 0.6, size = 2) +
geom_smooth(method = "lm", se = TRUE, color = "#e74c3c", fill = "#fadbd8") +
labs(title = "Dispersión: Shooting vs Dribbling",
subtitle = paste0("r de Pearson = ", round(correlacion, 4)),
x = "Shooting (Tiro)",
y = "Dribbling (Regate)") +
theme_minimal(base_size = 11)
Gráfico de dispersión Shooting vs Dribbling
Interpretación: La correlación de Pearson es r = 0.71, lo que indica una relación lineal fuerte y positiva entre las habilidades de tiro y regate. El gráfico de dispersión confirma que, en general, los jugadores con mayor capacidad de tiro también tienden a tener mejor habilidad de regate.
# Ajuste del modelo de regresión lineal simple
modelo_lineal <- lm(dribbling ~ shooting, data = train)
# Resumen del modelo
summary(modelo_lineal)
#>
#> Call:
#> lm(formula = dribbling ~ shooting, data = train)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -17.917 -4.831 0.755 4.508 15.887
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 31.07775 2.49519 12.46 <2e-16 ***
#> shooting 0.56323 0.04755 11.85 <2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 7.415 on 138 degrees of freedom
#> Multiple R-squared: 0.5041, Adjusted R-squared: 0.5005
#> F-statistic: 140.3 on 1 and 138 DF, p-value: < 2.2e-16
Interpretación de coeficientes:
| Estimado | Error Estándar | t-valor | p-valor | |
|---|---|---|---|---|
| (Intercept) | 31.0778 | 2.4952 | 12.4551 | 0 |
| shooting | 0.5632 | 0.0476 | 11.8449 | 0 |
Intercepto (\(\beta_0\) = 31.078): Cuando el valor de shooting es 0, se estima que dribbling sería 31.078. Este valor carece de interpretación práctica real, ya que un jugador con tiro igual a 0 es un escenario hipotético.
Pendiente (\(\beta_1\) = 0.563): Por cada punto adicional en la habilidad de tiro, la habilidad de regate aumenta en promedio 0.563 puntos, manteniendo todo lo demás constante.
La ecuación del modelo es:
\[\widehat{\text{dribbling}} = 31.078 + 0.563 \cdot \text{shooting}\]
summary_modelo <- summary(modelo_lineal)
# p-valores
p_intercepto <- summary_modelo$coefficients[1, 4]
p_shooting <- summary_modelo$coefficients[2, 4]
cat("Prueba H0: β0 = 0 → p-valor =", format(p_intercepto, scientific = TRUE), "\n")
#> Prueba H0: β0 = 0 → p-valor = 2.462055e-24
cat("Prueba H0: β1 = 0 → p-valor =", format(p_shooting, scientific = TRUE), "\n")
#> Prueba H0: β1 = 0 → p-valor = 9.058402e-23
Interpretación:
Para el intercepto: \(H_0: \beta_0 = 0\) vs \(H_1: \beta_0 \neq 0\). Con un p-valor de 2.462055e-24, se rechaza H₀, el intercepto es estadísticamente significativo..
Para la pendiente: \(H_0: \beta_1 = 0\) vs \(H_1: \beta_1 \neq 0\). Con un p-valor de 9.058402e-23, se rechaza H₀, lo que significa que shooting es un predictor estadísticamente significativo de dribbling. Este resultado confirma que existe una relación lineal real entre las dos variables.
r2 <- summary_modelo$r.squared
r2_adj <- summary_modelo$adj.r.squared
cat("R² =", round(r2, 4), "\n")
#> R² = 0.5041
cat("R² ajustado =", round(r2_adj, 4), "\n")
#> R² ajustado = 0.5005
# Tabla ANOVA
anova_tabla <- anova(modelo_lineal)
kable(round(anova_tabla, 4),
caption = "Tabla ANOVA del modelo de regresión lineal",
booktabs = TRUE) %>%
kable_styling(latex_options = c("hold_position"), font_size = 10)
| Df | Sum Sq | Mean Sq | F value | Pr(>F) | |
|---|---|---|---|---|---|
| shooting | 1 | 7713.344 | 7713.344 | 140.3012 | 0 |
| Residuals | 138 | 7586.828 | 54.977 | NA | NA |
Interpretación:
R² = 0.5041: El 50.41% de la variabilidad en dribbling es explicada por la variable shooting.
Tabla ANOVA: El estadístico F es significativo (p < 0.05), confirmando que el modelo en su conjunto es estadísticamente significativo, es decir, shooting contribuye de manera relevante a explicar dribbling.
#> R² = 0.5041 | R² ajustado = 0.5005
#> F-estadístico p-valor: 9.058402e-23
Conclusión sobre la calidad del modelo:
El modelo explica el 50.41% de la varianza de dribbling mediante shooting. Este es un valor moderado. Si bien la relación es estadísticamente significativa, el modelo simple deja una parte importante de la varianza sin explicar, lo que sugiere que existen otros factores (posición, overall, physic) que influyen en la habilidad de regate. En términos prácticos, el modelo es útil como primera aproximación pero se podría mejorar incorporando variables adicionales.
par(mfrow = c(2, 2))
plot(modelo_lineal, which = 1:4, col = "#1a5276", pch = 16, cex = 0.6)
Diagnóstico de residuos del modelo lineal
par(mfrow = c(1, 1))
# Test de normalidad de Shapiro-Wilk
sw_test <- shapiro.test(residuals(modelo_lineal))
cat("Shapiro-Wilk: W =", round(sw_test$statistic, 4),
"| p-valor =", round(sw_test$p.value, 4), "\n")
#> Shapiro-Wilk: W = 0.9866 | p-valor = 0.1919
# Test de homocedasticidad (Breusch-Pagan)
bp_test <- bptest(modelo_lineal)
cat("Breusch-Pagan: BP =", round(bp_test$statistic, 4),
"| p-valor =", round(bp_test$p.value, 4), "\n")
#> Breusch-Pagan: BP = 7.0713 | p-valor = 0.0078
# Test de autocorrelación (Durbin-Watson)
dw_test <- dwtest(modelo_lineal)
cat("Durbin-Watson: DW =", round(dw_test$statistic, 4),
"| p-valor =", round(dw_test$p.value, 4), "\n")
#> Durbin-Watson: DW = 1.9997 | p-valor = 0.4968
Interpretación del análisis de residuos:
Linealidad (Residuals vs Fitted, gráfico superior izquierdo): Los residuos se distribuyen alrededor de cero sin un patrón sistemático, lo que indica que el supuesto de linealidad se cumple.
Normalidad (Shapiro-Wilk, p = 0.1919): No se rechaza la hipótesis de normalidad de los residuos (p > 0.05). El supuesto de normalidad se cumple.
Homocedasticidad (Breusch-Pagan, p = 0.0078): Se detecta heterocedasticidad (p < 0.05). La varianza de los residuos no es constante a lo largo de los valores ajustados.
Independencia (Durbin-Watson, DW = 1.9997, p = 0.4968): No hay evidencia de autocorrelación en los residuos (p > 0.05). El supuesto de independencia se cumple.
# Leverage y distancia de Cook
leverage <- hatvalues(modelo_lineal)
cook_dist <- cooks.distance(modelo_lineal)
n <- nrow(train)
# Umbrales
umbral_lev <- 2 * 2 / n # 2p/n
umbral_cook <- 4 / n # 4/n
influyentes <- which(cook_dist > umbral_cook)
apalancados <- which(leverage > umbral_lev)
cat("Puntos con alto apalancamiento (>", round(umbral_lev, 4), "):",
length(apalancados), "\n")
#> Puntos con alto apalancamiento (> 0.0286 ): 8
cat("Puntos influyentes por Cook (>", round(umbral_cook, 4), "):",
length(influyentes), "\n")
#> Puntos influyentes por Cook (> 0.0286 ): 12
# Gráfico de Cook's Distance
plot(cook_dist, type = "h", col = "#e74c3c",
main = "Distancia de Cook por Observación",
xlab = "Observación", ylab = "Distancia de Cook")
abline(h = umbral_cook, col = "#1a5276", lty = 2, lwd = 2)
legend("topright", legend = paste0("Umbral = ", round(umbral_cook, 4)),
col = "#1a5276", lty = 2, bty = "n")
Detección de valores atípicos e influyentes
# Outliers por método de Bonferroni
influenceIndexPlot(modelo_lineal, vars = "Bonf", las = 1)
Detección de valores atípicos e influyentes
# Puntos influyentes por distancia de Cook
influenceIndexPlot(modelo_lineal, vars = "Cook")
Detección de valores atípicos e influyentes
Interpretación: El gráfico de Bonferroni identifica las observaciones con residuos estudentizados más extremos, que son candidatos a valores atípicos. La distancia de Cook complementa este análisis mostrando cuáles de esas observaciones tienen además una influencia real sobre los coeficientes del modelo. Se identificaron 12 observaciones con distancia de Cook superior al umbral (4/n = 0.0286). Se recomienda revisar estos casos, pues podrían corresponder a jugadores con perfiles atípicos que afectan la pendiente de la recta de regresión.
# Crear secuencia de valores para la gráfica
x_seq <- data.frame(shooting = seq(min(train$shooting), max(train$shooting), length.out = 200))
# Intervalos de confianza (IC) y predicción (IP)
ic <- predict(modelo_lineal, newdata = x_seq, interval = "confidence", level = 0.95)
ip <- predict(modelo_lineal, newdata = x_seq, interval = "prediction", level = 0.95)
# Construir dataframe para graficar
grafica_df <- data.frame(
shooting = x_seq$shooting,
fit = ic[, "fit"],
ic_lower = ic[, "lwr"],
ic_upper = ic[, "upr"],
ip_lower = ip[, "lwr"],
ip_upper = ip[, "upr"]
)
ggplot(train, aes(x = shooting, y = dribbling)) +
geom_point(color = "#1a5276", alpha = 0.5, size = 1.8) +
geom_ribbon(data = grafica_df, aes(x = shooting, ymin = ip_lower, ymax = ip_upper),
inherit.aes = FALSE, fill = "#abebc6", alpha = 0.35) +
geom_ribbon(data = grafica_df, aes(x = shooting, ymin = ic_lower, ymax = ic_upper),
inherit.aes = FALSE, fill = "#2ecc71", alpha = 0.45) +
geom_line(data = grafica_df, aes(x = shooting, y = fit),
color = "#e74c3c", linewidth = 1) +
labs(title = "Regresión Lineal con Intervalos de Confianza y Predicción",
subtitle = "Verde oscuro: IC 95% | Verde claro: IP 95% | Línea roja: recta de regresión",
x = "Shooting (Tiro)", y = "Dribbling (Regate)") +
theme_minimal(base_size = 11)
Intervalos de confianza y predicción del modelo
Interpretación:
Intervalo de Confianza (IC 95% - verde oscuro): Representa la incertidumbre sobre la media de dribbling para un valor dado de shooting. Es más estrecho en el centro de los datos.
Intervalo de Predicción (IP 95% - verde claro): Representa la incertidumbre sobre un valor individual de dribbling. Es más amplio que el IC porque incluye la variabilidad individual alrededor de la media.
Cuanto más alejado del centro de los datos se encuentra un valor de shooting, mayor es la incertidumbre en la predicción, lo que se refleja en el ensanchamiento de ambas bandas.
# Predicciones sobre el conjunto test
predicciones_test <- predict(modelo_lineal, newdata = test)
# Tabla comparativa
tabla_validacion <- data.frame(
Shooting_test = test$shooting,
Dribbling_real = test$dribbling,
Dribbling_pred = round(predicciones_test, 2),
Diferencia = round(test$dribbling - predicciones_test, 2)
)
kable(tabla_validacion,
caption = "Comparación de valores reales vs predichos en el conjunto test",
col.names = c("Shooting X_test", "Dribbling Y_test", "Dribbling Ŷ (modelo)", "Diferencia Y - Ŷ"),
booktabs = TRUE,
digits = 2) %>%
kable_styling(latex_options = c("hold_position", "scale_down"), font_size = 10)
| Shooting X_test | Dribbling Y_test | Dribbling Ŷ (modelo) | Diferencia Y - Ŷ | |
|---|---|---|---|---|
| 2 | 53 | 56 | 60.93 | -4.93 |
| 37 | 52 | 61 | 60.37 | 0.63 |
| 40 | 47 | 58 | 57.55 | 0.45 |
| 41 | 55 | 57 | 62.06 | -5.06 |
| 44 | 61 | 58 | 65.43 | -7.43 |
| 69 | 56 | 66 | 62.62 | 3.38 |
| 71 | 39 | 68 | 53.04 | 14.96 |
| 115 | 63 | 69 | 66.56 | 2.44 |
| 124 | 55 | 59 | 62.06 | -3.06 |
| 126 | 27 | 33 | 46.28 | -13.28 |
# Métricas de validación
mse_test <- mean((test$dribbling - predicciones_test)^2)
rmse_test <- sqrt(mse_test)
mae_test <- mean(abs(test$dribbling - predicciones_test))
cat("\nMetricas de error en test:\n")
#>
#> Metricas de error en test:
cat(" MSE =", round(mse_test, 4), "\n")
#> MSE = 53.2628
cat(" RMSE =", round(rmse_test, 4), "\n")
#> RMSE = 7.2981
cat(" MAE =", round(mae_test, 4), "\n")
#> MAE = 5.5621
Interpretación: El RMSE de 7.3 puntos indica que, en promedio, el modelo se equivoca en aproximadamente 7.3 puntos al predecir la habilidad de regate a partir del tiro. Dado que la escala de dribbling va de 0 a 99, este error es relativamente bajo, lo que sugiere una capacidad predictiva aceptable del modelo. Las diferencias entre valores reales y predichos muestran que el modelo no sobreajusta ni subajusta sistemáticamente en el conjunto de prueba.
# Convertir preferred_foot a variable binaria (0 = Left, 1 = Right)
futbol_logit <- futbol_150 %>%
filter(preferred_foot %in% c("Left", "Right")) %>%
mutate(foot_bin = ifelse(preferred_foot == "Right", 1, 0))
cat("Distribución de preferred_foot:\n")
#> Distribución de preferred_foot:
print(table(futbol_logit$preferred_foot))
#>
#> Left Right
#> 35 115
cat("\nProporción pie derecho:", round(mean(futbol_logit$foot_bin), 3), "\n")
#>
#> Proporción pie derecho: 0.767
Dado que preferred_foot es una variable binaria (Left = 0, Right = 1), no es apropiado usar regresión lineal, ya que la recta no garantiza predicciones entre 0 y 1. La regresión logística resuelve esto mediante la función sigmoide:
\[P(Y=1|X) = \frac{e^{\beta_0 + \beta_1 X_1 + \cdots + \beta_k X_k}} {1 + e^{\beta_0 + \beta_1 X_1 + \cdots + \beta_k X_k}}\]
que siempre produce valores entre 0 y 1, interpretables como probabilidades de pertenecer a una categoría. En este caso, el modelo estima la probabilidad de que un jugador sea diestro (preferred_foot = Right) en función de su edad, peso, habilidad de tiro, pase y regate.
# Modelo de regresión logística
# Y = preferred_foot (binaria: 1 = Right, 0 = Left)
# X = age, weight_kg, shooting, passing, dribbling
modelo_logit <- glm(
foot_bin ~ age + weight_kg + shooting + passing + dribbling,
data = futbol_logit,
family = binomial(link = "logit")
)
summary(modelo_logit)
#>
#> Call:
#> glm(formula = foot_bin ~ age + weight_kg + shooting + passing +
#> dribbling, family = binomial(link = "logit"), data = futbol_logit)
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) 0.59202 3.34744 0.177 0.860
#> age 0.02429 0.04887 0.497 0.619
#> weight_kg 0.02769 0.03537 0.783 0.434
#> shooting 0.02382 0.02251 1.058 0.290
#> passing -0.01250 0.03251 -0.385 0.701
#> dribbling -0.04230 0.04010 -1.055 0.291
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 162.98 on 149 degrees of freedom
#> Residual deviance: 157.01 on 144 degrees of freedom
#> AIC: 169.01
#>
#> Number of Fisher Scoring iterations: 4
# Validación del modelo logístico
anova(modelo_logit, test = "Chisq")
Interpretación del ANOVA logístico: El test chi-cuadrado compara el modelo con predictores contra el modelo nulo (sin variables). Si el p-valor acumulado es < 0.05, se rechaza H₀ y se concluye que al menos una variable predictora aporta significativamente al modelo. Este resultado complementa los p-valores individuales de cada coeficiente mostrados en el resumen anterior.
| Estimado | Error Estándar | z-valor | p-valor | |
|---|---|---|---|---|
| (Intercept) | 0.5920 | 3.3474 | 0.1769 | 0.8596 |
| age | 0.0243 | 0.0489 | 0.4971 | 0.6191 |
| weight_kg | 0.0277 | 0.0354 | 0.7830 | 0.4337 |
| shooting | 0.0238 | 0.0225 | 1.0581 | 0.2900 |
| passing | -0.0125 | 0.0325 | -0.3846 | 0.7005 |
| dribbling | -0.0423 | 0.0401 | -1.0550 | 0.2914 |
Interpretación de los coeficientes (en términos de odds ratio):
odds_ratios <- exp(coef(modelo_logit))
or_df <- data.frame(
Variable = names(odds_ratios),
"Odds Ratio" = round(odds_ratios, 4)
)
kable(or_df, caption = "Odds Ratios del modelo logístico",
booktabs = TRUE, row.names = FALSE) %>%
kable_styling(latex_options = c("hold_position"), font_size = 10)
| Variable | Odds.Ratio |
|---|---|
| (Intercept) | 1.8076 |
| age | 1.0246 |
| weight_kg | 1.0281 |
| shooting | 1.0241 |
| passing | 0.9876 |
| dribbling | 0.9586 |
Los coeficientes del modelo logístico se interpretan en términos de odds ratios (OR). Un OR > 1 indica que la variable aumenta la probabilidad de que el jugador sea diestro; un OR < 1 indica que la reduce. Las variables con p-valor < 0.05 son estadísticamente significativas para predecir el pie dominante.
# Probabilidades predichas
probabilidades <- predict(modelo_logit, type = "response")
# Clasificación con umbral 0.5
predicciones_clase <- ifelse(probabilidades >= 0.5, 1, 0)
# Matriz de confusión
conf_matrix <- confusionMatrix(
factor(predicciones_clase, levels = c(0, 1)),
factor(futbol_logit$foot_bin, levels = c(0, 1)),
positive = "1"
)
print(conf_matrix)
#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 0 0
#> 1 35 115
#>
#> Accuracy : 0.7667
#> 95% CI : (0.6907, 0.8318)
#> No Information Rate : 0.7667
#> P-Value [Acc > NIR] : 0.5452
#>
#> Kappa : 0
#>
#> Mcnemar's Test P-Value : 9.081e-09
#>
#> Sensitivity : 1.0000
#> Specificity : 0.0000
#> Pos Pred Value : 0.7667
#> Neg Pred Value : NaN
#> Prevalence : 0.7667
#> Detection Rate : 0.7667
#> Detection Prevalence : 1.0000
#> Balanced Accuracy : 0.5000
#>
#> 'Positive' Class : 1
#>
| Metrica | Valor |
|---|---|
| Accuracy | 0.7667 |
| Recall (Sensibilidad) | 1.0000 |
| Precisión | 0.7667 |
| F1-Score | 0.8679 |
Interpretación de las métricas:
Accuracy = 0.7667: El modelo clasifica correctamente el 76.7% de los jugadores en cuanto a su pie dominante. Este valor es relativamente alto y refleja un buen desempeño general del modelo.
Recall = 1: El modelo detecta correctamente el 100% de los jugadores diestros reales. Este es el porcentaje de verdaderos positivos sobre el total de positivos reales.
¿Qué tan efectivo es este modelo para predecir el pie dominante?
El modelo logístico con las variables age, weight_kg, shooting, passing y dribbling logra una accuracy de 76.7% y un recall de 100%. Estos resultados indican que el modelo es efectivo para predecir el pie dominante y que las variables seleccionadas capturan información relevante sobre esta característica. Cabe destacar que el dataset de FIFA está naturalmente desbalanceado hacia jugadores diestros (aproximadamente 75-80% de jugadores), lo que puede inflar el accuracy y subestimar el recall de la clase minoritaria (pie izquierdo).
Exploración de datos: El conjunto de datos FIFA contiene información rica sobre las habilidades de los jugadores. Tras eliminar valores faltantes (principalmente de porteros), se trabajó con 13762 registros completos, de los cuales se extrajo una muestra de 150. Las distribuciones de shooting y dribbling son aproximadamente simétricas con presencia de valores atípicos en los extremos inferiores.
Regresión lineal simple: Se encontró una correlación fuerte (r = 0.71) entre tiro y regate. El modelo es estadísticamente significativo (p < 0.05), aunque el R² de 0.504 sugiere que shooting explica solo una parte de la variabilidad en dribbling. Los supuestos del modelo fueron verificados mediante análisis de residuos.
Regresión logística: El modelo para predecir el pie dominante alcanzó una accuracy de 76.7% y un recall de 100%. La prueba ANOVA chi-cuadrado confirmó la significancia global del modelo. Dado el desbalance natural del dataset hacia jugadores diestros, el accuracy puede estar inflado, por lo que el recall es la métrica más informativa en este contexto. Se recomienda explorar variables adicionales para mejorar el desempeño predictivo.
Recomendaciones: Para futuros análisis se sugiere: (a) explorar regresión múltiple incorporando variables como pace, physic y overall; (b) aplicar técnicas de manejo de desbalance de clases en la regresión logística; (c) evaluar modelos más complejos como Random Forest o Gradient Boosting para mejorar la predicción.
Leone, S. (2019). FIFA 20 complete player dataset. Kaggle.
https://www.kaggle.com/stefanoleone992/fifa-20-complete-player-dataset
James, G., Witten, D., Hastie, T., & Tibshirani, R. (2021). An Introduction to Statistical Learning (2nd ed.). Springer.
R Core Team (2024). R: A language and environment for statistical computing. R Foundation for Statistical Computing. https://www.R-project.org/