El objetivo de este análisis es realizar una predicción usando modelos de regresión logística sobre datos existentes de futbolistas, alojados en la web de TransferMarkt y obtenidos el 02/06/24, es por lo que los modelos serán supervisados.
A. Carga de Datos
Conexión a la base de datos y obtención de datos; dentro del script SQL se detalla el proceso de limpieza de valores faltantes:
# Cargamos la libreria necesaria
library(odbc)
# Ejecutamos una funcion para conectar a la base de datos
connect_to_db <- function(driver, server, database) {
connection_string <- paste0("Driver={", driver, "};Server=", server, ";Database=", database, ";Trusted_Connection=yes;")
dbConnect(odbc::odbc(), .connection_string = connection_string)
}
# Conectamos a la base de datos
conn <- connect_to_db("ODBC Driver 17 for SQL Server", "LAPTOP-P7645H6F", "TransferMarkt")
# Ejecutamos la consulta SQL
query <- "
WITH inicial AS
(
SELECT
player_id,
game_id,
yellow_cards,
red_cards,
goals,
assists,
minutes_played
FROM
dbo.appearances_fct
WHERE
date BETWEEN '20230107' AND '20240630' -- FILTRAMOS DATOS POR LA ULTIMA TEMPORADA
),
tabla1 AS
(
SELECT
DISTINCT (player_id),
COUNT(game_id) OVER (PARTITION BY player_id) AS partidos_jugados,
SUM (yellow_cards) OVER (PARTITION BY player_id) AS tarj_amarillas,
SUM (red_cards) OVER (PARTITION BY player_id) AS tarj_rojas,
SUM (goals) OVER (PARTITION BY player_id) AS goles,
SUM (assists) OVER (PARTITION BY player_id) AS asistencias,
SUM (minutes_played) OVER (PARTITION BY player_id) AS minutos_jugados
FROM
inicial
)
SELECT
T1.player_id,
-- PARA LA MANIPULACION DE DATOS FALTANTES, SE OPTA POR PONER LA PALABRA 'desconocido'
-- A LAS VARIABLES CATEGORICAS pais_nacimiento y pie_bueno Y SE OPTA POR SUSTITUIR
-- LOS VALORES FALTANTES DE LA VARIABLE NUMERICA Altura POR LA MEDIA OBTENIDA SIN
-- LOS VALORES FALTANTES
CASE WHEN T2.country_of_birth IS NULL THEN 'desconocido' ELSE T2.country_of_birth END AS pais_nacimiento,
T2.position,
CASE WHEN T2.foot IS NULL THEN 'desconocido' ELSE T2.foot END AS pie_bueno,
CASE WHEN T2.height_in_cm IS NULL THEN 182 ELSE T2.height_in_cm END AS Altura,
T2.edad_actual,
T1.minutos_jugados,
T1.partidos_jugados,
T1.asistencias,
T1.goles,
T1.tarj_amarillas,
T1.tarj_rojas,
T2.market_value_in_eur
FROM
tabla1 T1
INNER JOIN jugadores T2
ON T1.player_id = T2.player_id
"
# Obtenemos los resultados de la consulta y lo guardamos en una variable llamada 'df'
df <- dbGetQuery(conn, query)
B.Exploración inicial
Mostramos las primeras filas de los resultados de la consulta:
## player_id pais_nacimiento position pie_bueno Altura edad_actual
## 1 58088 Argentina Goalkeeper right 190 38
## 2 58843 UdSSR Defender right 182 35
## 3 96533 France Attack right 183 37
## 4 96718 Spain Defender right 182 35
## 5 130365 Italy Defender right 173 33
## minutos_jugados partidos_jugados asistencias goles tarj_amarillas tarj_rojas
## 1 6175 69 0 0 5 0
## 2 586 7 0 0 6 0
## 3 798 28 3 7 1 0
## 4 4694 59 8 3 11 0
## 5 2244 44 7 1 9 0
## market_value_in_eur
## 1 1400000
## 2 125000
## 3 300000
## 4 1800000
## 5 2000000
Mostramos la estadística descriptiva de los datos:
Name | df |
Number of rows | 8193 |
Number of columns | 13 |
_______________________ | |
Column type frequency: | |
character | 3 |
numeric | 10 |
________________________ | |
Group variables | None |
Variable type: character
skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
---|---|---|---|---|---|---|---|
pais_nacimiento | 0 | 1 | 4 | 21 | 0 | 148 | 0 |
position | 0 | 1 | 6 | 10 | 0 | 5 | 0 |
pie_bueno | 0 | 1 | 4 | 11 | 0 | 4 | 0 |
Variable type: numeric
skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
---|---|---|---|---|---|---|---|---|---|---|
player_id | 0 | 1.00 | 418361.92 | 256183.80 | 3333 | 212847 | 381833 | 591796 | 1240467 | ▇▇▆▂▁ |
Altura | 0 | 1.00 | 182.40 | 6.76 | 160 | 178 | 182 | 187 | 206 | ▁▅▇▃▁ |
edad_actual | 0 | 1.00 | 26.83 | 4.73 | 16 | 23 | 26 | 30 | 43 | ▃▇▇▂▁ |
minutos_jugados | 0 | 1.00 | 1652.58 | 1454.80 | 1 | 407 | 1270 | 2618 | 7110 | ▇▃▂▁▁ |
partidos_jugados | 0 | 1.00 | 25.41 | 18.60 | 1 | 9 | 22 | 39 | 88 | ▇▅▃▂▁ |
asistencias | 0 | 1.00 | 1.80 | 2.99 | 0 | 0 | 1 | 2 | 34 | ▇▁▁▁▁ |
goles | 0 | 1.00 | 2.34 | 4.50 | 0 | 0 | 1 | 3 | 63 | ▇▁▁▁▁ |
tarj_amarillas | 0 | 1.00 | 3.45 | 3.72 | 0 | 1 | 2 | 5 | 27 | ▇▂▁▁▁ |
tarj_rojas | 0 | 1.00 | 0.09 | 0.31 | 0 | 0 | 0 | 0 | 3 | ▇▁▁▁▁ |
market_value_in_eur | 60 | 0.99 | 4870622.77 | 11187481.60 | 10000 | 400000 | 1000000 | 4000000 | 180000000 | ▇▁▁▁▁ |
Podemos observar que, gracias al preprocesamiento de datos en SQL sólo aparecen valores faltantes en la variable ‘market_value_in_eur’
A.Tratamiento de valores faltantes en la variable ‘market_value_in_eur’
Dado que tenemos 60 valores faltantes sobre un total de 8193 observaciones, se estima eliminar los valores faltantes, ya que sólo representan el 0,73 % aproximadamente del total de observaciones del dataset:
Volvemos a revisar la estadística descriptiva de los datos:
Name | df_limpio |
Number of rows | 8133 |
Number of columns | 13 |
_______________________ | |
Column type frequency: | |
character | 3 |
numeric | 10 |
________________________ | |
Group variables | None |
Variable type: character
skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
---|---|---|---|---|---|---|---|
pais_nacimiento | 0 | 1 | 4 | 21 | 0 | 148 | 0 |
position | 0 | 1 | 6 | 10 | 0 | 5 | 0 |
pie_bueno | 0 | 1 | 4 | 11 | 0 | 4 | 0 |
Variable type: numeric
skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
---|---|---|---|---|---|---|---|---|---|---|
player_id | 0 | 1 | 414626.06 | 252858.26 | 3333 | 211637 | 378972 | 586659 | 1229924 | ▇▇▆▂▁ |
Altura | 0 | 1 | 182.41 | 6.76 | 160 | 178 | 182 | 187 | 206 | ▁▅▇▃▁ |
edad_actual | 0 | 1 | 26.88 | 4.71 | 16 | 23 | 26 | 30 | 43 | ▃▇▇▂▁ |
minutos_jugados | 0 | 1 | 1664.34 | 1453.66 | 1 | 421 | 1287 | 2632 | 7110 | ▇▃▂▁▁ |
partidos_jugados | 0 | 1 | 25.58 | 18.56 | 1 | 10 | 23 | 39 | 88 | ▇▅▅▂▁ |
asistencias | 0 | 1 | 1.82 | 3.00 | 0 | 0 | 1 | 2 | 34 | ▇▁▁▁▁ |
goles | 0 | 1 | 2.36 | 4.52 | 0 | 0 | 1 | 3 | 63 | ▇▁▁▁▁ |
tarj_amarillas | 0 | 1 | 3.47 | 3.72 | 0 | 1 | 2 | 5 | 27 | ▇▂▁▁▁ |
tarj_rojas | 0 | 1 | 0.09 | 0.31 | 0 | 0 | 0 | 0 | 3 | ▇▁▁▁▁ |
market_value_in_eur | 0 | 1 | 4870622.77 | 11187481.60 | 10000 | 400000 | 1000000 | 4000000 | 180000000 | ▇▁▁▁▁ |
Podemos observar que se han eliminado 60 observaciones del total del dataset.
B.Identificación, visualización y ajuste de valores atípicos
Procedemos a analizar la variable ‘goles’ en función de la posición del jugador; para ello usamos un diagrama de cajas para visualizar las variables y observar posibles valores atipicos:
library(ggplot2)
library(gridExtra)
plot1 <- ggplot(df_limpio, aes(x = position, y = goles)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightblue") +
coord_flip() +
labs(title = "Boxplot de Goles por Posición", y = "Número de Goles", x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
print(plot1)
Vemos que existe una posición ‘Missing’, la cual después de verificar
los datos, procedemos a sustituir por la posición ‘Attack’:
library(dplyr)
df_limpio <- df_limpio %>%
mutate(position = ifelse(position == "Missing", "Attack", position))
Realizamos un nuevo boxplot con los datos actualizados:
box_goles_x_posicion <- ggplot(df_limpio, aes(x = position, y = goles)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightgreen") +
coord_flip() +
labs(title = "Boxplot de Goles por Posición", y = "Número de Goles", x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
Realizamos una comparativa antes y después del cambio:
Una vez realizada la transformación, elaboramos varios diagramas de cajas sin discriminar por posición para todas las variables numéricas y así obtener una visión clara de todos los valores atípicos que tenemos en el dataset:
box_Altura <- ggplot(df_limpio, aes(y = Altura)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightblue") +
coord_flip() +
labs(title = "Boxplot de Altura", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_edad_actual <- ggplot(df_limpio, aes(y = edad_actual)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightblue") +
coord_flip() +
labs(title = "Boxplot de Edad_actual", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_minutos_jugados <- ggplot(df_limpio, aes(y = minutos_jugados)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightblue") +
coord_flip() +
labs(title = "Boxplot de minutos_jugados", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_partidos_jugados <- ggplot(df_limpio, aes(y = partidos_jugados)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightblue") +
coord_flip() +
labs(title = "Boxplot de partidos_jugados", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_asistencias <- ggplot(df_limpio, aes(y = asistencias)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightblue") +
coord_flip() +
labs(title = "Boxplot de asistencias", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_goles <- ggplot(df_limpio, aes(y = goles)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightblue") +
coord_flip() +
labs(title = "Boxplot de goles", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_tarj_amarillas <- ggplot(df_limpio, aes(y = tarj_amarillas)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightblue") +
coord_flip() +
labs(title = "Boxplot de tarj_amarillas", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_tarj_rojas <- ggplot(df_limpio, aes(y = tarj_rojas)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightblue") +
coord_flip() +
labs(title = "Boxplot de tarj_rojas", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_market_value_in_eur <- ggplot(df_limpio, aes(y = market_value_in_eur )) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightblue") +
coord_flip() +
labs(title = "Boxplot de market_value_in_eur", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
grid.arrange(box_Altura, box_edad_actual, box_minutos_jugados, box_partidos_jugados, box_asistencias, box_goles, box_tarj_amarillas, box_tarj_rojas, box_market_value_in_eur, ncol = 2)
C. Transformación de los datos
Después de verificar los valores atípicos de cada una de las variables numéricas, vamos a intentar reducir esos valores mediante una transformación logarítmica de las variables que consideramos que pueden interferir en la robustez de modelo de regresión logística que vamos a realizar posteriormente y comparamos el resultado obtenido mediante diagramas de cajas:
library(dplyr)
# Creamos las nuevas variables log-transformadas y las incluimos en el dataset:
df_limpio <- df_limpio %>%
mutate(
minutos_jugados_log = log(minutos_jugados + 1),
asistencias_log = log(asistencias + 1),
goles_log = log(goles + 1),
tarj_amarillas_log = log(tarj_amarillas + 1),
market_value_in_eur_log = log(market_value_in_eur + 1)
)
# Creamos los gráficos de las variables log-transformadas:
box_minutos_jugados_log <- ggplot(df_limpio, aes(y = minutos_jugados_log)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightgreen") +
coord_flip() +
labs(title = "Boxplot de Minutos Jugados (Log)", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_asistencias_log <- ggplot(df_limpio, aes(y = asistencias_log)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightgreen") +
coord_flip() +
labs(title = "Boxplot de Asistencias (Log)", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_goles_log <- ggplot(df_limpio, aes(y = goles_log)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightgreen") +
coord_flip() +
labs(title = "Boxplot de Goles (Log)", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_tarj_amarillas_log <- ggplot(df_limpio, aes(y = tarj_amarillas_log)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightgreen") +
coord_flip() +
labs(title = "Boxplot de Tarjetas Amarillas (Log)", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
box_market_value_in_eur_log <- ggplot(df_limpio, aes(y = market_value_in_eur_log)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 16, outlier.size = 1, fill = "lightgreen") +
coord_flip() +
labs(title = "Boxplot de Valor de Mercado (Log)", y = NULL, x = NULL) +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
)
grid.arrange(box_minutos_jugados,
box_minutos_jugados_log, box_asistencias, box_asistencias_log,
box_goles, box_goles_log, box_tarj_amarillas, box_tarj_amarillas_log, box_market_value_in_eur,
box_market_value_in_eur_log,
ncol = 2
)
Podemos observar que las variables logarítmicas reducen
la cantidad de valores atípicos.
A. Preparación de la variable de respuesta
Vamos a comprobar como se comporta el modelo de regresión logística primero sin las variables log y luego con ellas. Para ello lo primero que haremos será transformar la variable ‘goles’ a código binario, donde 0 significa no hay goles y 1 hay goles:
B. Ajuste de modelos de regresión logística
Ahora realizaremos todas las fases de la creación de los modelos de regresión logística; para ello, vamos a realizar dos modelos, en el primer modelo usaremos las variables originales (sin transformación logarítmica) y en el segundo modelo sustituiremos las variables originales por la logarítmicas, con el próposito de observar el comportamiento de ambos modelos y escoger el mejor de ellos
MODELO CON VARIABLES ORIGINALES
# Creamos el modelo de regresión logística
modelo <- glm(goles_bin ~ edad_actual + position + minutos_jugados + partidos_jugados+ market_value_in_eur + asistencias,
data = df_limpio, family = "binomial")
# Calculamos las probabilidades predictivas usando el modelo ajustado
prob_sin_log <- predict(modelo, type = "response")
# Añadimos las probabilidades al dataset
df_limpio$prob_goles <- prob_sin_log
MODELO CON VARIABLES LOGARITMICAS
# Creamos el modelo de regresión logística
modelo_log <- glm(goles_bin ~ edad_actual + position + minutos_jugados_log + partidos_jugados + market_value_in_eur_log + asistencias_log,
data = df_limpio, family = "binomial")
# Calculamos las probabilidades predictivas usando el modelo ajustado
prob_con_log <- predict(modelo_log, type = "response")
# Añadimos las probabilidades al dataset
df_limpio$prob_goles_log <- prob_con_log
C. Evaluación de los modelos
Vamor a comparar los dos modelos obtenidos, para ello, vamos a usar la curva ROC y los indicadores de AIC y AUC:
# Cargamos la librería necesaria
library(pROC)
# Calculamos el AIC de los modelos para valorar su calidad
aic_modelo <- AIC(modelo)
aic_modelo_log <- AIC(modelo_log)
# Obtenemos las predicciones
predicciones_modelo <- predict(modelo, df_limpio, type = "response")
predicciones_modelo_log <- predict(modelo_log, df_limpio, type = "response")
# Creamos los objetos ROC
roc_obj <- roc(df_limpio$goles_bin, predicciones_modelo)
roc_obj_log <- roc(df_limpio$goles_bin, predicciones_modelo_log)
# Convertimos los objetos ROC a data frames para usarlos en ggplot2
roc_df <- data.frame(
fpr = c(roc_obj$specificities, roc_obj_log$specificities),
tpr = c(roc_obj$sensitivities, roc_obj_log$sensitivities),
model = factor(rep(c("Modelo Original", "Modelo Logarítmico"), each = length(roc_obj$specificities)))
)
# Creamos el gráfico con ggplot2
ggplot(roc_df, aes(x = 1 - fpr, y = tpr, color = model)) +
geom_line() +
geom_abline(linetype = "dashed") +
labs(title = "Curvas ROC Comparativas",
x = "Tasa de Falsos Positivos",
y = "Tasa de Verdaderos Positivos") +
scale_color_manual(values = c("blue", "red")) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5, size = 14, face = "bold")
) +
theme(legend.position = "none") +
annotate("text", x = 0.7, y = 0.3,
label = paste("Modelo Original\n(AUC =", round(auc(roc_obj), 2),
", AIC =", round(aic_modelo, 2), ")"),
color = "blue", size = 3.5) +
annotate("text", x = 0.7, y = 0.2,
label = paste("Modelo Logarítmico\n(AUC =", round(auc(roc_obj_log), 2),
", AIC =", round(aic_modelo_log, 2), ")"),
color = "red", size = 3.5)
Podemos concluir que el modelo logarítmico mejora al modelo original, ya que arroja un AUC superior (0.91), lo cual indica que tiene una capacidad predictiva muy buena y también tiene un mejor AIC (6255.52), lo que indica un mejor ajuste del modelo.
A.Validación Cruzada
Vamos a evaluar el rendimiento del modelo logarítmico mediante validación cruzada para garantizar que generaliza bien con nuevos datos.
# Creamos una función para dividir los datos en K pliegues (folds)
crear_pliegues <- function(data, K) {
pliegues <- sample(rep(1:K, length.out = nrow(data)))
return(pliegues)
}
# Creamos un función para realizar la validación cruzada manual en el modelo
validacion_cruzada_logistica <- function(data, formula, K = 5) {
# Creamos los pliegues
pliegues <- crear_pliegues(data, K)
# Creamos una variable para almacenar la precisión en cada iteración
precision <- numeric(K)
# Realizamos un bucle sobre cada pliegue, donde dividimos los datos en entrenamiento y prueba
for (i in 1:K) {
datos_entrenamiento <- data[pliegues != i, ]
datos_prueba <- data[pliegues == i, ]
# Ajustarmos el modelo en el conjunto de entrenamiento
modelo_log <- glm(formula, data = datos_entrenamiento, family = binomial)
# Predecimos las probabilidades en el conjunto de prueba
predicciones <- predict(modelo_log, newdata = datos_prueba, type = "response")
# Convertimos las probabilidades en clases (0 o 1)
clases_predichas <- ifelse(predicciones > 0.5, 1, 0)
# Evaluamos la precisión
clases_reales <- datos_prueba[, as.character(formula[[2]])]
precision[i] <- mean(clases_predichas == clases_reales)
}
# Retornarmos el promedio de la precisión de todos los pliegues
return(mean(precision))
}
# Aplicamos la función de validación cruzada al dataset
# El modelo predice 'goles_bin' basado en las variables independientes
precision_promedio <- validacion_cruzada_logistica(
df_limpio,
goles_bin ~ edad_actual + position + minutos_jugados_log + partidos_jugados + market_value_in_eur_log + asistencias_log,
K = 5
)
# Mostramos el resultado de la validación cruzada
print(paste("Precisión promedio en la validación cruzada: ", round(precision_promedio, 4)))
## [1] "Precisión promedio en la validación cruzada: 0.8184"
El resultado indica que el modelo, en promedio, clasifica correctamente por encima del 80 % de los casos, lo que indica que el modelo tiene una efectividad sólida en su rendimiento.
B. Multicolinealidad
Verificamos si existe multicolinealidad usando el VIF (Variance Inflation Factor).
# Cargamos la librería necesaria
library(car)
# Ajustamos el modelo de regresión logística en los datos completos
modelo_log <- glm(goles_bin ~ edad_actual + position + minutos_jugados_log + partidos_jugados + market_value_in_eur_log + asistencias_log,
data = df_limpio,
family = binomial)
# Calculamos el VIF para las variables independientes del modelo
vif_resultados <- vif(modelo_log)
# Mostramos los resultados del VIF
print(vif_resultados)
## GVIF Df GVIF^(1/(2*Df))
## edad_actual 1.121123 1 1.058831
## position 1.487911 3 1.068471
## minutos_jugados_log 4.309226 1 2.075868
## partidos_jugados 3.945419 1 1.986308
## market_value_in_eur_log 1.365263 1 1.168445
## asistencias_log 1.387616 1 1.177971
Los resultados indican que, aunque existen variables con un GVIF DE 2.08 Y 1.99, no provocan una multicolinealidad seria y al no haber ninguna variable cercana al GVIF == 5, se concluye que no existe multicolinealidad.
C. Análisis de residuos
Para realizar un diagnóstico de la calidad del modelo, procedemos a realizar un análisis de residuos.
Se extraen los valores predichos del modelo, los residuos y los valores de leverage para cada observación:
library(broom)
df_limpio <- df_limpio %>%
mutate(
fitted = predict(modelo_log, type = "response"),
residuos = residuals(modelo_log, type = "deviance"),
leverage = hatvalues(modelo_log)
)
Los residuos se estandarizan usando el leverage para obtener residuos estándar, lo que permite una mejor interpretación y comparación:
Se generan varios gráficos para diagnosticar el ajuste del modelo; el primer gráfico es de utilidad para detectar puntos influyentes (leverage):
ggplot(df_limpio, aes(x = leverage, y = residuos_stand)) +
geom_point(alpha = 0.5, color = "gray") +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
labs(title = "Leverage vs Residuos Estándarizados",
x = "Leverage",
y = "Residuos Estándarizados") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
Podemos observar una concentración de residuos en los mismos sectores
arriba y debajo de la linea discontinua, pero estos no los podemos
considerar extremos, además de una linea de puntos alrededor del valor 0
a lo largo del eje de influencia (leverage), lo que indica que hay un
buen ajuste en general con una leve tendencia a la
sobreestimación.
El histograma de residuos estandarizados nos permite evaluar visualmente su distribucion:
ggplot(df_limpio, aes(x = residuos_stand)) +
geom_histogram(binwidth = 0.5, fill = "lightblue", color = "black", alpha = 0.7) +
labs(title = "Histograma de Residuos Estándarizados",
x = "Residuos Estándarizados",
y = "Frecuencia") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
Podemos observar una distribución normal general de los
residuos, con una ligera cola larga a la izquierda.
El gráfico QQ plot nos permite visualizar la normalidad de los residuos estandarizados:
ggplot(df_limpio, aes(sample = residuos_stand)) +
stat_qq(color = "orange") +
stat_qq_line(color = "darkblue") +
labs(title = "Q-Q Plot de Residuos Estándarizados",
x = "Cuantiles Teóricos",
y = "Cuantiles de Residuos") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
Podemos observar que los puntos estan distribuidos a lo largo de la
linea azul, lo que indica normalidad en los
residuos.
A. Visualización y presentación de resultados
Vamos a visualizar mediante una muestra los resultados obtenidos y las predicciones mediante el modelo de regresión logística usando variables logarítmicas:
Podemos ver que, por ejemplo, los porteros tienen una probabilidad muy baja de marcar gol, comparados con otros jugadores que ocupan otras posiciones más adelantadas en el terreno de juego.
B. Exploración de variables
Para este punto, analizamos la matriz de correlación entre las variables de interés:
# Seleccionamos solo las variables logarítmicas y otras adicionales de nuestro interés
variables_seleccionadas <- df_limpio %>% select(
contains("_log"),
edad_actual,
partidos_jugados,
tarj_rojas,
Altura,
goles_bin
)
# Calculamos la matriz de correlación
matriz_correlacion_log <- cor(variables_seleccionadas, use = "complete.obs")
# Graficamos la matriz de correlación
library(ggcorrplot)
ggcorrplot(matriz_correlacion_log, hc.order = TRUE, type = "upper",
lab = TRUE, lab_size = 3,
colors = c("red", "white", "blue"),
title = "Matriz de Correlación_log y otras variables") +
theme(plot.title = element_text(hjust = 0.5))
Podemos observar las correlaciones entre todas las variables seleccionadas, para así poder realizar nuevos analisis entre ellas.