7  Modelos lineales generalizados: Logit”

7.1 Paquetería

#install.packages("sjPlot", dependencies=T) # solito porque da problmas
library(sjPlot)

if (!require("pacman")) install.packages("pacman") # instala pacman si se requiere
Cargando paquete requerido: pacman
pacman::p_load(tidyverse, # sobretodo para dplyr
              haven, #importación
              janitor, #tablas
              sjlabelled, # etiquetas
              DescTools, # Paquete para estimaciones y pruebas
              infer, # tidy way 
              broom,  # Una escobita para limpiar (pero es para arreglar)
              estimatr, car, stargazer, ggpubr, 
              jtools, lm.beta, robustbase, sandwich,
              officer,flextable,huxtable, ggstance, kableExtra,
               ResourceSelection, lmtest, mlogit, nnet) # Nuevos

E importamos la base e incluimos los cambios anteriores

etiqueta_sex<-c("Hombre", "Mujer")

concentradohogar <- haven::read_dta("datos/concentradohogar.dta")  %>% 
  mutate(sexo_jefe=as.numeric(sexo_jefe)) %>% ## para quitar el "string"
  sjlabelled::set_labels(sexo_jefe, labels=etiqueta_sex) %>% 
  mutate(clase_hog=as.numeric(clase_hog)) %>% ## para quitar el "string"
  sjlabelled::set_labels(clase_hog, labels=c("unipersonal",
                                             "nuclear", 
                                             "ampliado",
                                             "compuesto",
                                             "corresidente")) %>% 
  mutate(educa_jefe=as.numeric(educa_jefe)) %>% 
  set_labels(educa_jefe,
             labels=c("Sin instrucción", 
                      "Preescolar",
                      "Primaria incompleta",
                      "Primaria completa",
                      "Secundaria incompleta",
                      "Secundaria completa",
                      "Preparatoria incompleta",
                      "Preparatoria completa",
                      "Profesional incompleta",
                      "Profesional completa",
                      "Posgrado")) %>% 
    mutate(ent=stringr::str_sub(folioviv, start=1, end=2 )) %>% 
    mutate(ing_per=ing_cor/tot_integ) %>% 
    mutate(recibe_rem=remesas>0)

7.1.1 Sub-setting y modelos clase anterior

Vamos a hacer una sub-base de nuestras posibles variables explicativas. Esto es importante porque sólo podemos comparar modelos con la misma cantidad de observaciones.

mydata<- concentradohogar %>% 
  select(folioviv, foliohog, tam_loc, ing_per, sexo_jefe, recibe_rem, 
         educa_jefe, edad_jefe, tot_integ, clase_hog, ent) %>%  
  mutate_at(vars(educa_jefe, sexo_jefe, clase_hog), ~ as_label(.x)) %>% 
  mutate(log_ing_per=log(ing_per)) %>% 
  filter(!is.infinite(log_ing_per)) %>% 
  mutate(recibe_rem=as.numeric(recibe_rem))


tail(mydata)
foliovivfoliohogtam_locing_persexo_jeferecibe_remeduca_jefeedad_jefetot_integclase_hogentlog_ing_per
3260797706147.64e+03Hombre0Posgrado346nuclear328.94
3260797907141.1e+04 Hombre0Secundaria completa283nuclear329.3 
3260797908148.5e+03 Hombre0Secundaria incompleta523nuclear329.05
3260797909146.76e+03Hombre0Primaria completa374nuclear328.82
3260797910147.97e+04Hombre0Secundaria completa632nuclear3211.3 
3260797912141.45e+04Hombre0Profesional completa293nuclear329.58

7.2 Introducción

En esta práctica vamos a revisar los elementos básicos para la regresión logística. El proceso en R para todos los modelos generalizados se parece mucho. Por tanto, no será difícil que luego puedas utilizar otras funciones de enlace.

Vamos a hacer una sub-base de nuestras posibles variables explicativas. Esto es importante porque sólo podemos comparar modelos con la misma cantidad de observaciones. Intentaremos predecir la participación económica

7.3 Regresión Logística

\[ ln\frac{p(x=1)}{p(x=0)}=\beta_o+\beta_1x +\epsilon\]

7.3.1 Un solo predictor

modelo0 <- glm(recibe_rem ~ edad_jefe,
               family = binomial("logit"),
               data=mydata, 
               na.action=na.exclude) # opcional

summary(modelo0)

Call:
glm(formula = recibe_rem ~ edad_jefe, family = binomial("logit"), 
    data = mydata, na.action = na.exclude)

Coefficients:
              Estimate Std. Error z value Pr(>|z|)    
(Intercept) -3.2532372  0.0493028 -65.985   <2e-16 ***
edad_jefe    0.0088474  0.0008864   9.982   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 39801  on 90092  degrees of freedom
Residual deviance: 39702  on 90091  degrees of freedom
AIC: 39706

Number of Fisher Scoring iterations: 5
confint(modelo0)
Waiting for profiling to be done...
                   2.5 %      97.5 %
(Intercept) -3.350156887 -3.15688294
edad_jefe    0.007109184  0.01058387

Con jtools:

summ(modelo0)
Observations 90093
Dependent variable recibe_rem
Type Generalized linear model
Family binomial
Link logit
𝛘²(1) 98.97
p 0.00
Pseudo-R² (Cragg-Uhler) 0.00
Pseudo-R² (McFadden) 0.00
AIC 39706.33
BIC 39725.15
Est. S.E. z val. p
(Intercept) -3.25 0.05 -65.98 0.00
edad_jefe 0.01 0.00 9.98 0.00
Standard errors: MLE

7.3.2 Predicción de probabilidades

Para predecir la probabilidad, primero chequemos el rango de nuestra variabe explicativa

range(mydata$edad_jefe)
[1]  13 109

Hacemos un vector con los valores que queremos predecir

xedad_jefe <- 12:109

Vamos a utilizar el comando “predict” para predecir los valores. Podemos el argumento “response” para que nos dé el logito

y_logito <- predict(modelo0, list(edad_jefe = xedad_jefe))
y_prob<- predict(modelo0, list(edad_jefe = xedad_jefe), type= "response")

results_m0<-cbind(y_logito, y_prob, xedad_jefe)
results_m0<-as.data.frame(results_m0)

Hoy podemos graficar

ggplot(data=results_m0, aes(x=xedad_jefe, y=y_prob)) +
  geom_point()

7.3.3 Coeficientes exponenciados

Para interpretar mejor los coeficientes suelen exponenciarse y hablar de las veces que aumentan o disminuyen los momios con respecto a la unidad como base. Si exponenciamos a ambos lados de nuestra ecuación:

\[ e^{ln\frac{p(x=1)}{p(x=0)}}=e^{\beta_o+\beta_1x +\epsilon}\]

\[ \frac{p(x=1)}{p(x=0)}=e^{\beta_o+\beta_1x +\epsilon}\] Al exponenciar los coeficientes, tenemos los resultados en términos de momios.

\[ \frac{p}{1-p}=e^{\beta_o}*+*e^{\beta_1x}*e^{\epsilon}\] Por tantopodemos establecer por cuánto se multiplican los momios de probabilidad. Lo cual es una manera más sencilla para interpretar nuestros resultados

exp(coef(modelo0))
(Intercept)   edad_jefe 
 0.03864889  1.00888670 

Es muy fácil con la librería jtools, sacar los coeficientes exponenciados. La ventaja es que nos dan también los intervalos:

summ(modelo0, exp=T )
Observations 90093
Dependent variable recibe_rem
Type Generalized linear model
Family binomial
Link logit
𝛘²(1) 98.97
p 0.00
Pseudo-R² (Cragg-Uhler) 0.00
Pseudo-R² (McFadden) 0.00
AIC 39706.33
BIC 39725.15
exp(Est.) 2.5% 97.5% z val. p
(Intercept) 0.04 0.04 0.04 -65.98 0.00
edad_jefe 1.01 1.01 1.01 9.98 0.00
Standard errors: MLE

7.3.4 Agregando una variable

modelo1<- glm(recibe_rem ~ edad_jefe + sexo_jefe, 
               family = binomial("logit"),
               data=mydata,
               na.action=na.exclude)

summary(modelo1)

Call:
glm(formula = recibe_rem ~ edad_jefe + sexo_jefe, family = binomial("logit"), 
    data = mydata, na.action = na.exclude)

Coefficients:
                 Estimate Std. Error z value Pr(>|z|)    
(Intercept)    -3.4304208  0.0499645 -68.657  < 2e-16 ***
edad_jefe       0.0061378  0.0008898   6.898 5.29e-12 ***
sexo_jefeMujer  0.8042078  0.0288932  27.834  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 39801  on 90092  degrees of freedom
Residual deviance: 38945  on 90090  degrees of freedom
AIC: 38951

Number of Fisher Scoring iterations: 6
confint(modelo1)
Waiting for profiling to be done...
                      2.5 %       97.5 %
(Intercept)    -3.528639636 -3.332777762
edad_jefe       0.004392861  0.007881067
sexo_jefeMujer  0.747572110  0.860837334

Este modelo tiene coeficientes que deben leerse “condicionados”. Es decir, en este caso tenemos que el coeficiente asociado a la edad_jefe, mantiene constante el sexo y viceversa.

Veamos con los valores exponenciados:

exp(coef(modelo1))
   (Intercept)      edad_jefe sexo_jefeMujer 
    0.03237331     1.00615671     2.23492532 

7.4 Bondad de Ajuste

7.4.1 Devianza

La devianza es una medida de la bondad de ajuste de los modelos lineales generalizados. O más bien, es una medida de la no-bondad del ajust, puesto que valores más altos indican un peor ajuste.

R nos da medidas de devianza: la devianza nula y la desviación residual. La devianza nula muestra qué tan bien la variable de respuesta se predice mediante un modelo que incluye solo la intersección (gran media).

7.4.2 Prueba de Verosimilitud

\[D(y, \hat{\mu}) = 2 \left( \log \left( p(y \mid \hat{\theta}_s) \right) - \log \left( p(y \mid \hat{\theta}_0) \right) \right) \]

¿Cómo saber si ha mejorado nuestro modelo? Podemos hacer un test que compare las devianzas(tendría la misma lógica que nuestra prueba F del modelo lineal). Para esto tenemos que instalar un paquete “lmtest”

lrtest0<-lmtest::lrtest(modelo0, modelo1)
lrtest0
#DfLogLikDfChisqPr(>Chisq)
2-1.99e+04        
3-1.95e+0417588.56e-167

Como puedes ver, el resultado muestra un valor p muy pequeño (<.001). Esto significa que agregar el sexo de la jefatura al modelo lleva a un ajuste significativamente mejor sobre el modelo original.

Podemos seguir añadiendo variables sólo “sumando” en la función

modelo2<-glm(recibe_rem ~ edad_jefe + sexo_jefe + tot_integ,
             family = binomial("logit"), 
             data=mydata, 
             na.action=na.exclude)

summary(modelo2)

Call:
glm(formula = recibe_rem ~ edad_jefe + sexo_jefe + tot_integ, 
    family = binomial("logit"), data = mydata, na.action = na.exclude)

Coefficients:
                 Estimate Std. Error z value Pr(>|z|)    
(Intercept)    -3.7274626  0.0618770 -60.240  < 2e-16 ***
edad_jefe       0.0072699  0.0009066   8.019 1.06e-15 ***
sexo_jefeMujer  0.8356191  0.0291609  28.655  < 2e-16 ***
tot_integ       0.0649789  0.0077567   8.377  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 39801  on 90092  degrees of freedom
Residual deviance: 38877  on 90089  degrees of freedom
AIC: 38885

Number of Fisher Scoring iterations: 6

Y podemos ver si introducir esta variable afectó al ajuste global del modelo

lrtest1<-lrtest(modelo1, modelo2)
lrtest1
#DfLogLikDfChisqPr(>Chisq)
3-1.95e+04         
4-1.94e+04167.61.97e-16

7.4.3 Test Hosmer-Lemeshow Goodness of Fit “GOF”

El teste Homer-Lemeshow se calcula sobre los datos una vez que las observaciones se han segmentado en grupos basados en probabilidades predichas similares. Este teste examina si las proporciones observadas de eventos son similares a las probabilidades predichas de ocurrencia en subgrupos del conjunto de datos, y lo hace con una prueba de chi cuadrado de Pearson.

¡Ojo! No queremos rechazar la hipótesis nula. La hipótesis nula sostiene que el modelo se ajusta a los datos por lo tanto no queremos rechazarla.

hoslem.test(mydata$recibe_rem, fitted(modelo2))

    Hosmer and Lemeshow goodness of fit (GOF) test

data:  mydata$recibe_rem, fitted(modelo2)
X-squared = 382.48, df = 8, p-value < 2.2e-16

No obstante, esta prueba ha sido criticada. Checa la postura de Paul Allison https://statisticalhorizons.com/hosmer-lemeshow

Es un problema que tenemos en muestras grandes. Casi siempre preferimos el enfoque de la devianza.

7.5 Tabla de modelos estimados

#stargazer(modelo0, modelo1,modelo2, type = 'latex', header=FALSE)
stargazer(modelo0, modelo1,modelo2, 
          type = 'text', header=FALSE)

=====================================================
                          Dependent variable:        
                  -----------------------------------
                              recibe_rem             
                      (1)         (2)         (3)    
-----------------------------------------------------
edad_jefe          0.009***    0.006***    0.007***  
                    (0.001)     (0.001)     (0.001)  
                                                     
sexo_jefeMujer                 0.804***    0.836***  
                                (0.029)     (0.029)  
                                                     
tot_integ                                  0.065***  
                                            (0.008)  
                                                     
Constant           -3.253***   -3.430***   -3.727*** 
                    (0.049)     (0.050)     (0.062)  
                                                     
-----------------------------------------------------
Observations        90,093      90,093      90,093   
Log Likelihood    -19,851.170 -19,472.330 -19,438.510
Akaike Inf. Crit. 39,706.330  38,950.650  38,885.020 
=====================================================
Note:                     *p<0.1; **p<0.05; ***p<0.01

Para sacar los coeficientes exponenciados

stargazer(modelo0, modelo1,modelo2, 
          type = 'text', header=FALSE,
          apply.coef = exp,
          apply.se   = exp)

=====================================================
                          Dependent variable:        
                  -----------------------------------
                              recibe_rem             
                      (1)         (2)         (3)    
-----------------------------------------------------
edad_jefe            1.009       1.006       1.007   
                    (1.001)     (1.001)     (1.001)  
                                                     
sexo_jefeMujer                  2.235**     2.306**  
                                (1.029)     (1.030)  
                                                     
tot_integ                                    1.067   
                                            (1.008)  
                                                     
Constant             0.039       0.032       0.024   
                    (1.051)     (1.051)     (1.064)  
                                                     
-----------------------------------------------------
Observations        90,093      90,093      90,093   
Log Likelihood    -19,851.170 -19,472.330 -19,438.510
Akaike Inf. Crit. 39,706.330  38,950.650  38,885.020 
=====================================================
Note:                     *p<0.1; **p<0.05; ***p<0.01

Veamos con jtools:

export_summs(modelo0, modelo1,modelo2, exp=T)
Model 1Model 2Model 3
(Intercept)0.04 ***0.03 ***0.02 ***
(0.05)   (0.05)   (0.06)   
edad_jefe1.01 ***1.01 ***1.01 ***
(0.00)   (0.00)   (0.00)   
sexo_jefeMujer       2.23 ***2.31 ***
       (0.03)   (0.03)   
tot_integ              1.07 ***
              (0.01)   
N90093       90093       90093       
AIC39706.33    38950.65    38885.02    
BIC39725.15    38978.88    38922.65    
Pseudo R20.00    0.03    0.03    
*** p < 0.001; ** p < 0.01; * p < 0.05.

También la librería “sjPlot” tiene el comando “plot_model()”

plot_model(modelo2)
Profiled confidence intervals may take longer time to compute.
  Use `ci_method="wald"` for faster computation of CIs.

Por default nos da los coeficientes exponenciados.

¿Cómo saber lo que tiene esos gráficos? Es bueno guardar siempre estos resultados en un objeto. Este objeto es una lista de dos listas

get<-plot_model(modelo2)
Profiled confidence intervals may take longer time to compute.
  Use `ci_method="wald"` for faster computation of CIs.
get$data
termestimatestd.errorconf.levelconf.lowconf.highstatisticdf.errorp.valuep.starsp.labelgroupxposxminxmax
edad_jefe1.010.0009070.951.011.018.02Inf1.06e-15 ***1.01 ***pos32.83 3.17
sexo_jefeMujer2.310.0292  0.952.182.4428.7 Inf1.37e-180***2.31 ***pos21.82 2.17
tot_integ1.070.00776 0.951.051.088.38Inf5.42e-17 ***1.07 ***pos10.8251.18
plot_model(modelo2, terms= c("edad_jefe", "sexo_jefe"), type="pred")
Data were 'prettified'. Consider using `terms="edad_jefe [all]"` to get
  smooth plots.

Para poner más de un modelo:

plot_models(modelo1, modelo2) + ggtitle("P(recibe remesa)")

7.6 Regresión Probit

7.6.1 Un solo predictor

mprobit<- glm(recibe_rem ~ edad_jefe, 
               family = binomial("probit"), 
               data=mydata,
               na.action=na.exclude)
summary(mprobit)

Call:
glm(formula = recibe_rem ~ edad_jefe, family = binomial("probit"), 
    data = mydata, na.action = na.exclude)

Coefficients:
              Estimate Std. Error z value Pr(>|z|)    
(Intercept) -1.7852701  0.0230511 -77.448   <2e-16 ***
edad_jefe    0.0040694  0.0004196   9.699   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 39801  on 90092  degrees of freedom
Residual deviance: 39705  on 90091  degrees of freedom
AIC: 39709

Number of Fisher Scoring iterations: 5

Comparando probit con logit:

jtools::export_summs(modelo0, mprobit)
Model 1Model 2
(Intercept)-3.25 ***-1.79 ***
(0.05)   (0.02)   
edad_jefe0.01 ***0.00 ***
(0.00)   (0.00)   
N90093       90093       
AIC39706.33    39708.89    
BIC39725.15    39727.70    
Pseudo R20.00    0.00    
*** p < 0.001; ** p < 0.01; * p < 0.05.

¿Cuál es la diferencia?

https://tutorials.methodsconsultants.com/posts/what-is-the-difference-between-logit-and-probit-models.

Y Allison:

https://statisticalhorizons.com/in-defense-of-logit-part-1 https://statisticalhorizons.com/in-defense-of-logit-part-2