Regresión Logística

La regresión logística es un modelo estadístico utilizado cuando la variable dependiente es categórica binaria, es decir, toma solo dos posibles valores (por ejemplo: 0/1, sí/no, éxito/fracaso).

La regresión logística se utiliza cuando se quiere predecir la probabilidad de que ocurra un evento binario, es decir, un resultado con solo dos posibles valores (por ejemplo, éxito/fracaso, sí/no, 1/0).

A diferencia de la regresión lineal, que predice cualquier número real, la regresión logística necesita que el valor predicho esté siempre entre 0 y 1, porque está modelando una probabilidad.

Se usa para estimar la probabilidad de que ocurra un evento en función de una o más variables predictoras.

Horas_sueno <- c(3,2,1,6,8,8,9,5,6,6,7,8,7,8,5,3,2,1,6,8,8,9,5,6,6,7,8,7,8,5,8,8,8,7,7,6,7)
Hora_estudio <- c(2,3,1.5,4,3.5,5,6,2.5,7,8,1,4.5,5.5,6.5,7.5,3,2,4.2,3.8,5.8,6,8,4,5,5,6,9,6,8,4,8,8,9,7,6,5,4)
promedio <- c(60, 58, 50, 70, 75, 80, 85, 65, 78, 88, 55, 72, 76, 84, 90, 62, 59, 61, 74, 81, 83, 89, 68, 73, 75, 77, 91, 79, 86, 70, 87, 88, 89, 85, 84, 82, 80)

Variable Binaria: asistencia a clase

asistencia <- c("sí", "no", "no", "sí", "sí", "sí", "sí", "no", "sí", "sí", 
                "no", "sí", "sí", "sí", "sí", "no", "no", "no", "sí", "sí", 
                "sí", "sí", "no", "sí", "sí", "sí", "sí", "sí", "sí", "no", 
                "sí", "sí", "sí", "sí", "sí", "sí", "sí")

# data frame
datos <- data.frame(Horas_sueno, Hora_estudio, promedio, asistencia)

# Ver los primeros registros
Horas_sueno <- c(3,2,1,6,8,8,9,5,6,6,7,8,7,8,5,3,2,1,6,8,8,9,5,6,6,7,8,7,8,5,8,8,8,7,7,6,7)
Hora_estudio <- c(2,3,1.5,4,3.5,5,6,2.5,7,8,1,4.5,5.5,6.5,7.5,3,2,4.2,3.8,5.8,6,8,4,5,5,6,9,6,8,4,8,8,9,7,6,5,4)
promedio <- c(60, 58, 50, 70, 75, 80, 85, 65, 78, 88, 55, 72, 76, 84, 90, 62, 59, 61, 74, 81, 83, 89, 68, 73, 75, 77, 91, 79, 86, 70, 87, 88, 89, 85, 84, 82, 80)

Variable Binaria: asistencia a clase

asistencia <- c("sí", "no", "no", "sí", "sí", "sí", "sí", "no", "sí", "sí", 
                "no", "sí", "sí", "sí", "sí", "no", "no", "no", "sí", "sí", 
                "sí", "sí", "no", "sí", "sí", "sí", "sí", "sí", "sí", "no", 
                "sí", "sí", "sí", "sí", "sí", "sí", "sí")

# Crear un data frame
datos <- data.frame(Horas_sueno, Hora_estudio, promedio, asistencia)

# Ver los primeros registros
head(datos)
##   Horas_sueno Hora_estudio promedio asistencia
## 1           3          2.0       60         sí
## 2           2          3.0       58         no
## 3           1          1.5       50         no
## 4           6          4.0       70         sí
## 5           8          3.5       75         sí
## 6           8          5.0       80         sí
# Convertir la variable 
datos$asistencia <- as.factor(datos$asistencia)

Modelo de Regresión Logística Multiple

modelo_logistico <- glm(asistencia ~ Horas_sueno + Hora_estudio + promedio, 
                        data = datos, 
                        family = binomial)

# Resumen del modelo
summary(modelo_logistico)
## 
## Call:
## glm(formula = asistencia ~ Horas_sueno + Hora_estudio + promedio, 
##     family = binomial, data = datos)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)
## (Intercept)  -26.6778    18.9633  -1.407    0.159
## Horas_sueno    0.1963     0.7592   0.259    0.796
## Hora_estudio  -0.6259     1.4403  -0.435    0.664
## promedio       0.4152     0.3810   1.090    0.276
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 41.054  on 36  degrees of freedom
## Residual deviance: 12.521  on 33  degrees of freedom
## AIC: 20.521
## 
## Number of Fisher Scoring iterations: 7
# coeficiente del modelo

coef(modelo_logistico)
##  (Intercept)  Horas_sueno Hora_estudio     promedio 
##  -26.6778028    0.1963387   -0.6258775    0.4151634
# Interpretación:

#intercepto del modelo
#estiamte:-26.6778. este resultado nos indica las probabilidades de que el evento ocurra cunado las variables independientes son iguales a 0.
#Horas_sueno
#Estimate: 0.1963. El coeficiente de "Horas_sueno" indica que, por cada hora adicional de sueño, la log-odds de que ocurra el evento (dependiente) aumentan en 0.1963.
#z value: 0.259. Este resultado sugiere que el efecto de "Horas_sueno" no es significativo.
#Pr(>|z|): 0.796. Este valor p es mayor que 0.05, lo cual muestra  que la variable "Horas_sueno" no tiene un efecto significativo sobre la variable dependiente.

#Hora_estudio
#Estimate: -0.6259. El coeficiente de "Hora_estudio" indica que, por cada hora adicional de estudio, la log-odds de que ocurra el evento disminuyen en 0.6259.
#z value: sugiriere que el efecto de "Hora_estudio" no es significativo.
#Pr(>|z|): No tiene un efecto estadísticamente significativo sobre el resultado.

#Promedio
#Estimate: 0.4152. El coeficiente de "promedio" sugiere que, por cada unidad adicional en el promedio, las log-odds de que ocurra el evento aumentan en 0.4152. 
#z value: 1.090. el efecto de promedio no es significativo.
#Pr(>|z|): 0.276. Un valor p mayor que 0.05 indica que promedio no presenta un efecto significativo.

Supuesto de Precision

#predicciones y residuos

predicciones <- predict(modelo_logistico)
residuos <- residuals(modelo_logistico)

# Graficar residuos vs predicciones

plot(predicciones, residuos, 
     xlab = "Predicciones", ylab = "Residuos", 
     main = "Residuos vs Predicciones")
abline(h = 0, col = "red")

# Interpretación:

#Como se puede observar en la gráfica los residuos no están centrados de forma clara alrededor de cero. Existen valores extremos que podrían afectar el supuesto de precisión

Supuesto de Sensibilidad

# Calcular la distancia de Cook

distancia_cook <- cooks.distance(modelo_logistico)

# Graficar la distancia de Cook
plot(distancia_cook, type = "h", main = "Distancia de Cook", 
     ylab = "Distancia de Cook", xlab = "Observaciones")

# Umbral de influencia (generalmente, se considera un punto influyente si la distancia de Cook > 4/n)
abline(h = 4/nrow(datos), col = "blue")

# Interpretación:

#Como podemos apreciar las Observaciones por encima de la línea azul, son datos muy influyentes que podrían estar dominando el modelo de forma indebida. Se sugiere revisar de forma individual para robustecer el modelo

Supuesto Test de Especificación de Ramsey

# Instalar el paquete

#install.packages("lmtest")
library(lmtest)
## Cargando paquete requerido: zoo
## 
## Adjuntando el paquete: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
# Realizar la prueba RESET

resettest(modelo_logistico)
## 
##  RESET test
## 
## data:  modelo_logistico
## RESET = 3.2317, df1 = 2, df2 = 31, p-value = 0.05311
#Interpretacion:

#El valor p = 0. 0.05311 es mayor que el umbral típico de 0.05. no podemos rechazar la hipótesis nula. Esto significa que no hay suficiente evidencia para sugerir que el modelo esté mal especificado. En otras palabras, el modelo parece estar correctamente especificado. pero esta al límite de significancia por lo que se recomienda revisarlo minuciosamente.

Cálculo del AIC

# Cálculo del AIC del modelo de regresion logistica 

aic_value <- AIC(modelo_logistico)
print(aic_value)
## [1] 20.52074

calculo log-odds y Probalilidad

# Calcular las log-odds para todo el conjunto de datos
log_odds_all <- predict(modelo_logistico, type = "link")
log_odds_all
##           1           2           3           4           5           6 
## -2.43073635 -4.08327937 -6.66210926  1.05815908  3.83959234  4.97659325 
##           7           8           9          10          11          12 
##  6.62287161 -0.27518055  2.50183405  6.02759079 -3.09532113  1.96822461 
##          13          14          15          16          17          18 
##  2.80666212  5.69843074  6.97451765 -2.22628697 -3.04223848 -3.78518078 
##          19          20          21          22          23          24 
##  2.84398825  4.89105470  5.59620605  7.03177036  0.03149352  1.67777187 
##          25          26          27          28          29          30 
##  2.50809872  2.90888681  7.03988102  3.73921365  5.58994138  0.86182036 
##          31          32          33          34          35          36 
##  6.00510480  6.42026822  6.20955418  5.60431671  5.81503076  5.41424266 
##          37 
##  5.40613200
# Convertir log-odds a odds
odds_all <- exp(log_odds_all)
odds_all
##            1            2            3            4            5            6 
## 8.797203e-02 1.685211e-02 1.278447e-03 2.881062e+00 4.650651e+01 1.449796e+02 
##            7            8            9           10           11           12 
## 7.521017e+02 7.594350e-01 1.220486e+01 4.147147e+02 4.526048e-02 7.157957e+00 
##           13           14           15           16           17           18 
## 1.655457e+01 2.983988e+02 1.069041e+03 1.079284e-01 4.772793e-02 2.270476e-02 
##           19           20           21           22           23           24 
## 1.718416e+01 1.330939e+02 2.694024e+02 1.132033e+03 1.031995e+00 5.353614e+00 
##           25           26           27           28           29           30 
## 1.228156e+01 1.833638e+01 1.141252e+03 4.206490e+01 2.677199e+02 2.367466e+00 
##           31           32           33           34           35           36 
## 4.054935e+02 6.141678e+02 4.974794e+02 2.715963e+02 3.353017e+02 2.245824e+02 
##           37 
## 2.227683e+02
# Odds proporcionadas
odds_all <- c(8.797203e-02, 1.685211e-02, 1.278447e-03, 2.881062e+00, 4.650651e+01, 
              1.449796e+02, 7.521017e+02, 7.594350e-01, 1.220486e+01, 4.147147e+02, 
              4.526048e-02, 7.157957e+00, 1.655457e+01, 2.983988e+02, 1.069041e+03, 
              1.079284e-01, 4.772793e-02, 2.270476e-02, 1.718416e+01, 1.330939e+02, 
              2.694024e+02, 1.132033e+03, 1.031995e+00, 5.353614e+00, 1.228156e+01, 
              1.833638e+01, 1.141252e+03, 4.206490e+01, 2.677199e+02, 2.367466e+00, 
              4.054935e+02, 6.141678e+02, 4.974794e+02, 2.715963e+02, 3.353017e+02, 
              2.245824e+02, 2.227683e+02)

# Convertir odds a probabilidades
probabilidades <- odds_all / (1 - odds_all)
probabilidades
##  [1]   0.096457601   0.017140972   0.001280084  -1.531614588  -1.021974878
##  [6]  -1.006945428  -1.001331378   3.156880677  -1.089246987  -1.002417125
## [11]   0.047406103  -1.162391520  -1.064289788  -1.003362488  -1.000936294
## [16]   0.120986253   0.050120057   0.023232242  -1.061788811  -1.007570372
## [21]  -1.003725749  -1.000884148 -32.254883576  -1.229694226  -1.088640224
## [26]  -1.057682169  -1.000876999  -1.024351697  -1.003749252  -1.731279608
## [31]  -1.002472228  -1.001630875  -1.002014182  -1.003695542  -1.002991310
## [36]  -1.004472624  -1.004509211
# Mostrar las probabilidades
probabilidades
##  [1]   0.096457601   0.017140972   0.001280084  -1.531614588  -1.021974878
##  [6]  -1.006945428  -1.001331378   3.156880677  -1.089246987  -1.002417125
## [11]   0.047406103  -1.162391520  -1.064289788  -1.003362488  -1.000936294
## [16]   0.120986253   0.050120057   0.023232242  -1.061788811  -1.007570372
## [21]  -1.003725749  -1.000884148 -32.254883576  -1.229694226  -1.088640224
## [26]  -1.057682169  -1.000876999  -1.024351697  -1.003749252  -1.731279608
## [31]  -1.002472228  -1.001630875  -1.002014182  -1.003695542  -1.002991310
## [36]  -1.004472624  -1.004509211
# Mostrar las primeras odds calculadas
head(odds_all)
## [1] 8.797203e-02 1.685211e-02 1.278447e-03 2.881062e+00 4.650651e+01
## [6] 1.449796e+02
# Predicción de probabilidades
probabilidades <- predict(modelo_logistico, type = "response")
probabilidades
##           1           2           3           4           5           6 
## 0.080858724 0.016572824 0.001276815 0.742338585 0.978950254 0.993149729 
##           7           8           9          10          11          12 
## 0.998672158 0.431635723 0.924270294 0.997594504 0.043300667 0.877420291 
##          13          14          15          16          17          18 
## 0.943034773 0.996659973 0.999065457 0.097414621 0.045553746 0.022200696 
##          19          20          21          22          23          24 
## 0.945007095 0.992542538 0.996301808 0.999117413 0.507872729 0.842609265 
##          25          26          27          28          29          30 
## 0.924707623 0.948283999 0.999124536 0.976779233 0.996278653 0.703040840 
##          31          32          33          34          35          36 
## 0.997539936 0.998374427 0.997993899 0.996331571 0.997026480 0.995567030 
##          37 
## 0.995531091
# Mostrar las primeras predicciones
head(probabilidades)
##           1           2           3           4           5           6 
## 0.080858724 0.016572824 0.001276815 0.742338585 0.978950254 0.993149729
#Interpretación:

#La Distribución general de las probabilidades muestran lo siguiente: El modelo predice varias probabilidades muy cercanas a 0 (como obs. 2, 3, 18), pero también muchas predicciones muy cercanas a 1 (como obs. 7, 15, 27, 35). Las que están en el rango medio (obs. 8: 0.43, obs. 23: 0.51, obs. 30: 0.70). Esto da evidendencia de que el modelo discrimina bien entre las clases en la mayoría de los casos: produce valores extremos cuando está “seguro”, y valores intermedios cuando la predicción es más incierta.

#Al Analizar las primeras predicciones podemos afirmar que la observación 1:  es 0.08086 Probabilidad baja de que la observación pertenezca a la clase positiva.  Observación 2: 0.01657 Probabilidad aún más baja, casi nula, de que la observación pertenezca a la clase positiva. Observación 3: 0.00128 → Probabilidad muy baja. Observación 4: 0.74234 Probabilidad alta de que la observación pertenezca a la clase positiva y la observación 5: 0.97895 Probabilidad muy alta de que la observación pertenezca a la clase positiva.
# Clasificación basada en el umbral de 0.5
predicciones_clasificadas <- ifelse(probabilidades > 0.5, "sí", "no")
predicciones_clasificadas
##    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16 
## "no" "no" "no" "sí" "sí" "sí" "sí" "no" "sí" "sí" "no" "sí" "sí" "sí" "sí" "no" 
##   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32 
## "no" "no" "sí" "sí" "sí" "sí" "sí" "sí" "sí" "sí" "sí" "sí" "sí" "sí" "sí" "sí" 
##   33   34   35   36   37 
## "sí" "sí" "sí" "sí" "sí"
# Ver las primeras predicciones clasificadas
head(predicciones_clasificadas)
##    1    2    3    4    5    6 
## "no" "no" "no" "sí" "sí" "sí"
#Interpretación

#Mirando el umbral de 0.5 se puede afirmar que sí" aparece 25 veces.
#"no" aparece 12 veces, es decir el 25 presenta el evento.

Matriz de confusión

# Crear la matriz de confusión
matriz_confusion <- table(Predicho = predicciones_clasificadas, Real = datos$asistencia)

# Convertir la matriz de confusión a un data frame para graficar
conf_df <- as.data.frame(matriz_confusion)
names(conf_df) <- c("Predicho", "Real", "Frecuencia")

# Cargar el paquete ggplot2 para gráficos
library(ggplot2)

# Gráfico de la matriz de confusión
ggplot(conf_df, aes(x = Real, y = Predicho, fill = Frecuencia)) +
  geom_tile(color = "white") +  
  geom_text(aes(label = Frecuencia), size = 6, color = "black") +  
  scale_fill_gradient(low = "white", high = "steelblue") +  
  labs(
    title = "Matriz de Confusion",  
    x = "Asistencia Real",               
    y = "Asistencia Predicha"       
  ) +
  theme_minimal()  

# Interpretación:

#La matriz de confusión mostro los siguientes datos: Verdaderos Negativos: 7 Estos son los casos donde el modelo predijo "no" y la clase real también es "no". Falsos Positivos (FP): 1 Estos son los casos donde el modelo predijo "sí", pero la clase real era "no". El modelo ha  clasificado "falsamente" un "no" como "sí". Falsos Negativos (FN): 2 Estos son los casos donde el modelo predijo "no", pero la clase real era "sí". El modelo ha "falsamente" clasificado un "sí" como "no". Verdaderos Positivos (VP): 27 Estos son los casos donde el modelo predijo "sí" y la clase real también es "sí".

CONCLUSIÓN FINAL

El modelo parece estar funcionando bien, ya que tiene una adecuado equilibrio entre los supuestos evaluados.