Estadística 2

1 Modelos Lineales

1.1 La Ruta hacia la Regresión

1.1.1 Explorando la Variable de Interés

Supongamos que estás interesado en la “situación de los locales escolares en el Perú”. Ese simple interés te lleva a buscar datos para saber tal situación. De pronto te encuentras con estos datos (basado en CEPLAN):

Imaginemos que estos datos son lo ‘mejor’ que podrás conseguir:

  • Variable: Locales Publicos en buen estado:

    • Representación: Porcentaje.
    • Corte: Transversal - sólo 2018.
  • Unidad de Observación: Local Público

  • Unidad de Análisis: Departamento

Teniendo ello en claro, pasemos a explorar los datos:

1.1.1.1 Cargamos los datos

Cargamos los datos, como están en GoogleDrive los podemos leer desde ahí:

rm(list = ls()) # limpiar el working environment

linkADrive='https://docs.google.com/spreadsheets/d/e/2PACX-1vS2ZSNM8BIZtoufVTO4Mw3ZmTWW1rAAtsGzFg0shHTJXX-3GmtLsgU-Nqkw5RzDgrNX31GTC9L7LnEz/pub?gid=0&single=true&output=csv'

estadoLocales=read.csv(linkADrive)

head(estadoLocales)
##   DEPARTAMENTO UBIGEO buenEstado contribuyentes_sunat peaOcupada TOTALpob
## 1     AMAZONAS  10000       18.6                75035     130019   417365
## 2       ÁNCASH  20000       13.9               302906     387976  1139115
## 3     APURÍMAC  30000        8.7               103981     140341   424259
## 4     AREQUIPA  40000       27.4               585628     645001  1460433
## 5     AYACUCHO  50000       17.0               151191     235857   650940
## 6    CAJAMARCA  60000       18.0               277457     461312  1427527
##    URBANO  RURAL
## 1  205976 211389
## 2  806065 333050
## 3  243354 180905
## 4 1383694  76739
## 5  444473 206467
## 6  567141 860386

La vista preliminar nos muestra varias columnas, pero la de nuestro interés es la última columna de la derecha. A esta altura, antes de explorar los datos de manera estadística, debemos primero verificar cómo R ha intepretado el tipo de datos:

str(estadoLocales)
## 'data.frame':    25 obs. of  8 variables:
##  $ DEPARTAMENTO        : chr  "AMAZONAS" "ÁNCASH" "APURÍMAC" "AREQUIPA" ...
##  $ UBIGEO              : int  10000 20000 30000 40000 50000 60000 70000 80000 90000 100000 ...
##  $ buenEstado          : num  18.6 13.9 8.7 27.4 17 18 33.8 11.9 10.1 15.6 ...
##  $ contribuyentes_sunat: int  75035 302906 103981 585628 151191 277457 499257 466883 80353 185658 ...
##  $ peaOcupada          : int  130019 387976 140341 645001 235857 461312 445072 496399 111996 253200 ...
##  $ TOTALpob            : int  417365 1139115 424259 1460433 650940 1427527 1046953 1315220 367252 759962 ...
##  $ URBANO              : int  205976 806065 243354 1383694 444473 567141 1046953 865771 166163 447620 ...
##  $ RURAL               : int  211389 333050 180905 76739 206467 860386 0 449449 201089 312342 ...

Nuestra variable de interés, buenEstado, es de tipo numérico y R también lo ha interpretado así:

1.1.1.2 Tablas, Gráficos y Estadígrafos

Si los datos están en el tipo adecuado, puede iniciarse la exploración estadística.

Tenemos 25 observaciones a nivel departamental. La manera más rápida de ver los estadígragos es usando el comando summary()

summary(estadoLocales$buenEstado)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    7.70   13.90   17.30   18.61   21.80   40.50

Ahora, podemos nos faltarían algunos estadígrados, que podemos verlo así:

library(DescTools)
## Warning: package 'DescTools' was built under R version 4.4.2
allStats=c(summary(estadoLocales$buenEstado),
  sd=sd(estadoLocales$buenEstado),
  skew=Skew(estadoLocales$buenEstado),
  kurt=Kurt(estadoLocales$buenEstado),
  cv=CoefVar(estadoLocales$buenEstado))
allStats
##       Min.    1st Qu.     Median       Mean    3rd Qu.       Max.         sd 
##  7.7000000 13.9000000 17.3000000 18.6120000 21.8000000 40.5000000  8.2596065 
##       skew       kurt         cv 
##  0.9008641  0.2026850  0.4437786

A esta altura, los gráficos que necesitamos son el histograma:

library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.4.2
base=ggplot(data=estadoLocales,
            aes(x=buenEstado))
histogram= base + geom_histogram(aes(y = after_stat(density)),
                 colour = 1, fill = "white",bins=10) +  
    stat_function(fun = dnorm,
                  args = list(mean = allStats['Mean'],
                              sd = allStats['sd']),col='red')
    
histogram

Y el boxplot

base=ggplot(data=estadoLocales,
            aes(y=buenEstado))
boxplot=base + geom_boxplot()

boxplot

A esta altura solo falta identificar los atípicos del boxplot, lo cual podemos recuperar con ggplot_build:

data_boxLocales=ggplot_build(boxplot)$data[[1]]
data_boxLocales
##   ymin lower middle upper ymax   outliers notchupper notchlower x flipped_aes
## 1  7.7  13.9   17.3  21.8 31.4 33.8, 40.5    19.7964    14.8036 0       FALSE
##   PANEL group ymin_final ymax_final   xmin  xmax xid newx new_width weight
## 1     1    -1        7.7       40.5 -0.375 0.375   1    0      0.75      1
##   colour  fill alpha shape linetype linewidth
## 1 grey20 white    NA    19    solid       0.5

Los outliers están en una lista, por lo que debemos escribir

data_boxLocales$outliers
## [[1]]
## [1] 33.8 40.5

Nota la utilidad de los “bigotes” del boxplot, cuando hay atípicos:

data_boxLocales[c('ymin','ymax')]
##   ymin ymax
## 1  7.7 31.4

En este caso, sabemos que los valores que exceden 31.4 serán considerados atípicos.

Hasta aquí, podrías sustentar que los valores de la variable buen estado de colegios públicos a nivel regional se distribuyen asimétricamente, con una dispersión baja, y con presencia de valores atípicos altos.

1.1.2 Análisis Bivariado

1.1.2.1 Numérica - Numérica

Consideramos que nos interesa explorar la posible relación entre nuestra variable de interés y la PEA ocupada. Traigamos esa variable. Asimismo, como son dos variables de tipo numérico, la estrategia a seguir es el análisis de correlación. Veamos este scatterplot:

library(ggrepel)
## Warning: package 'ggrepel' was built under R version 4.4.2
base=ggplot(data=estadoLocales, aes(x=peaOcupada, y=buenEstado))
scatter = base + geom_point()
scatterText = scatter + geom_text_repel(aes(label=DEPARTAMENTO),size=2)
scatterText

Calculemos ahora los índices de correlación:

f1=formula(~peaOcupada + buenEstado)
1.1.2.1.1 Camino paramétrico
pearsonf1=cor.test(f1,data=estadoLocales)[c('estimate','p.value')]
pearsonf1
## $estimate
##       cor 
## 0.3567442 
## 
## $p.value
## [1] 0.08002776
1.1.2.1.2 Camino no paramétrico
spearmanf1=cor.test(f1,data=estadoLocales,method='spearman',exact=F)[c('estimate','p.value')]
spearmanf1
## $estimate
##       rho 
## 0.2785151 
## 
## $p.value
## [1] 0.1776146

Hasta aquí, dudamos que esa variables estén correlacionaas.

Otro caso importante es cuando analizamos nuestra variable versus una variable categórica. Por ejemplo, saber

1.1.2.2 Numérica - Categórica

Creamos aquí una variable categórica (factor) que represente “populoso” como tener más de un millón de habitantes

library(magrittr)
estadoLocales$populoso=ifelse(estadoLocales$TOTALpob>1000000,'Si','No')%>%as.factor()

# veamos conteo de cada categoría
table(estadoLocales$populoso)
## 
## No Si 
## 14 11

Pidamos un boxplot, pero en este caso por grupos:

base=ggplot(data=estadoLocales, aes(x=populoso, y=buenEstado))
base + geom_boxplot(notch = T) +  geom_jitter(color="black", size=0.4, alpha=0.9)
## Notch went outside hinges
## ℹ Do you want `notch = FALSE`?

Los boxplots tienen un notch flanqueando a la mediana, para sugerir igualdad de medianas si éstos se intersectan; de ahí que parece no haber diferencia sustantiva entre las categorías.

Verificar si hay o no igualdad entre distribuciones depende si las variables se distribuyen o no de manera normal por grupo. Como ello no es fácil de discernir visualmente, complementemos el análisis con los test de normlalidad. Usemos Shapiro-Wilk en cada grupo:

f2=formula(buenEstado~populoso)


tablag= aggregate(f2, estadoLocales,
          FUN = function(x) {y <- shapiro.test(x); c(y$statistic, y$p.value)})




shapiroTest=as.data.frame(tablag[,2])
names(shapiroTest)=c("W","Prob")
shapiroTest
##           W        Prob
## 1 0.8126339 0.007182561
## 2 0.9583503 0.750951875

Para que se vea mejor en html:

library(knitr)
## Warning: package 'knitr' was built under R version 4.4.2
library(magrittr)
library(kableExtra)
## Warning: package 'kableExtra' was built under R version 4.4.3
kable(cbind(tablag[1],shapiroTest))%>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = F, position = "left")
populoso W Prob
No 0.8126339 0.0071826
Si 0.9583503 0.7509519

Habría normalidad en un grupo (populoso sí) y no en otro (populoso no). Usemos entonces tanto la prueba de Mann-Whitney (no paramétrica) como la prueba t (paramétrica) para analizar diferencias

1.1.2.2.1 Prueba paramétrica
(student_T=t.test(f2,data=estadoLocales)[c('estimate','p.value')])
## $estimate
## mean in group No mean in group Si 
##             17.6             19.9 
## 
## $p.value
## [1] 0.5025783
1.1.2.2.2 Prueba no paramétrica
(Mann_Whitnery=wilcox.test(f2,data=estadoLocales,exact=F)['p.value'])
## $p.value
## [1] 0.3960432

Con toda esta información, iríamos concluyendo también que ser populoso o no, no tendría efecto en nuestra variable.

1.1.3 Regresión Lineal

La regresión es una técnica donde hay que definir una variable dependiente y una o más independientes. Las independientes pueden tener un rol predictor, dependiedo del diseño de investigación, aunque por defecto tiene un rol explicativo.

La regresión sí quiere informar cuánto una variable (independiente) puede explicar la variación de otra variable (dependiente), de ahí que es una técnica para probar hipótesis direccionales o asimétricas (las correlaciones tiene hipótesis simétricas).

La regresión devuelve un modelo, es decir una ecuación, que recoge cómo una o más variables explicarían a otra. Para nuestro caso la variable dependiente es el estado de los locales

modelo1=formula(buenEstado~peaOcupada + populoso)

Por ejemplo, para la hipótesis “el estado de los locales escolares públicos en una región depende de la PEA ocupada regional y si la región tiene más de un millón de pobladores o no”, la regresión arrojaría este resultado:

regre1=lm(modelo1, data=estadoLocales)
summary(regre1)
## 
## Call:
## lm(formula = modelo1, data = estadoLocales)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -10.7149  -4.2847  -0.7847   1.7428  22.9695 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  1.697e+01  2.187e+00   7.760 9.74e-08 ***
## peaOcupada   3.438e-06  2.089e-06   1.646    0.114    
## populosoSi  -1.231e-01  3.565e+00  -0.035    0.973    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 8.059 on 22 degrees of freedom
## Multiple R-squared:  0.1273, Adjusted R-squared:  0.04798 
## F-statistic: 1.605 on 2 and 22 DF,  p-value: 0.2236

Hasta aquí, vemos que lo que nos informaba el análisis bivariado se mantiene en la regresión: ningún predictor tiene efecto, pues la probabilidad de que el efecto sea cero, en cada caso, es muy alta (mayor a 0.1).

Sin embargo, es aquí donde reflexionamos si los datos crudos que tenemos podrían necesitar alguna transformación:

# la pea como porcentaje
estadoLocales$peaOcu_pct=estadoLocales$peaOcupada/estadoLocales$TOTAL


modelo2=formula(buenEstado~peaOcu_pct + populoso)
regre2=lm(modelo2,data = estadoLocales)
summary(regre2)
## 
## Call:
## lm(formula = modelo2, data = estadoLocales)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -10.9294  -4.0314  -0.0228   4.8027  11.3151 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -26.3941    10.4069  -2.536 0.018814 *  
## peaOcu_pct  119.8259    27.9708   4.284 0.000302 ***
## populosoSi    0.5927     2.5719   0.230 0.819861    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 6.306 on 22 degrees of freedom
## Multiple R-squared:  0.4657, Adjusted R-squared:  0.4171 
## F-statistic: 9.586 on 2 and 22 DF,  p-value: 0.001014

O en mejor versión con ayuda de stargazer:

library(stargazer)
## 
## Please cite as:
##  Hlavac, Marek (2022). stargazer: Well-Formatted Regression and Summary Statistics Tables.
##  R package version 5.2.3. https://CRAN.R-project.org/package=stargazer
stargazer(regre2,type = "html",intercept.bottom = FALSE)
## 
## <table style="text-align:center"><tr><td colspan="2" style="border-bottom: 1px solid black"></td></tr><tr><td style="text-align:left"></td><td><em>Dependent variable:</em></td></tr>
## <tr><td></td><td colspan="1" style="border-bottom: 1px solid black"></td></tr>
## <tr><td style="text-align:left"></td><td>buenEstado</td></tr>
## <tr><td colspan="2" style="border-bottom: 1px solid black"></td></tr><tr><td style="text-align:left">Constant</td><td>-26.394<sup>**</sup></td></tr>
## <tr><td style="text-align:left"></td><td>(10.407)</td></tr>
## <tr><td style="text-align:left"></td><td></td></tr>
## <tr><td style="text-align:left">peaOcu_pct</td><td>119.826<sup>***</sup></td></tr>
## <tr><td style="text-align:left"></td><td>(27.971)</td></tr>
## <tr><td style="text-align:left"></td><td></td></tr>
## <tr><td style="text-align:left">populosoSi</td><td>0.593</td></tr>
## <tr><td style="text-align:left"></td><td>(2.572)</td></tr>
## <tr><td style="text-align:left"></td><td></td></tr>
## <tr><td colspan="2" style="border-bottom: 1px solid black"></td></tr><tr><td style="text-align:left">Observations</td><td>25</td></tr>
## <tr><td style="text-align:left">R<sup>2</sup></td><td>0.466</td></tr>
## <tr><td style="text-align:left">Adjusted R<sup>2</sup></td><td>0.417</td></tr>
## <tr><td style="text-align:left">Residual Std. Error</td><td>6.306 (df = 22)</td></tr>
## <tr><td style="text-align:left">F Statistic</td><td>9.586<sup>***</sup> (df = 2; 22)</td></tr>
## <tr><td colspan="2" style="border-bottom: 1px solid black"></td></tr><tr><td style="text-align:left"><em>Note:</em></td><td style="text-align:right"><sup>*</sup>p<0.1; <sup>**</sup>p<0.05; <sup>***</sup>p<0.01</td></tr>
## </table>

Si lo paso a código html se vería mejor… xd

Aquí aparece algo interesane, primero peaOcupada tiene efecto, pues es significativo (p-valor menor a 0.05); segundo, que ese efecto es directo, pues el coeficiente calculado es positivo (signo de columna Estimate); y tercero que la magnitud de ese efecto es 119.826, lo que indica cuánto aumenta, en promedio, la variable dependiente, cuando la variable independiente se incrementa en una unidad.

1.2 Regresión Gaussiana

1.2.1 Introducción

La regresión es una técnica donde hay que definir una variable dependiente y una o más independientes. Las independientes pueden tener rol predictor, dependiendo del diseño de investigación, pero cumple siempre un rol asociativo; así, la regresión quiere informar cuánto la variabilidad de la variable (independiente) puede asociarse a la variabilidad de la dependiente, controlando el efecto de otras variables, de ahí que es una técnica para probar hipótesis direccionales o asimétricas (las correlaciones tiene hipótesis no direccionales o simétricas).

La regresión Gaussiana busca proponer un modelo relacional entre variable, y es aplicable sólo cuando la variable dependiente (la Y) es numérica, continua, y no acotada.

Caso de Estudio: Pavimentando con votos

Profesores de la Universidad de los Andes (Mejia Guinand, Botero, and Rodriguez Raga 2008) decidieron estudiar cómo la distribución de fondos públicos fue afectada por factores políticos durante el primer periodo del Presidente Uribe (2002-2006). Las hipótesis que se plantean son:

  • H1: la asignación presupuestal en infraestructura víal en Colombia responde a los criterios técnicos y económicos determinados en el Plan Nacional de Desarrollo y otros documentos de carácter técnico elaborados por el gobierno.

  • H2: la asignación presupuestal en infraestructura vial en Colombia responde a negociaciones bilaterales entre el ejecutivo y el legislativo basadas en necesidades políticas y electorales de corto plazo.

  • H3: la asignación presupuestal en infraestructura vial en Colombia responde al esfuerzo del gobierno por fortalecer su base social de apoyo local a través de los Consejos Comunales de Gobierno.

1.2.1.1 Preparando la data

#carga de data
linkToData='https://docs.google.com/spreadsheets/d/e/2PACX-1vSRpCC8gKIxMxpK0wjgLcl-GQWdw6sAeB16Sixkq6kZXeTfMS8_n70twbEbQ2tMcGp8tm-6x8qf8ieo/pub?gid=234740779&single=true&output=csv'
pavi=read.csv(linkToData)
str(pavi)
## 'data.frame':    1096 obs. of  9 variables:
##  $ municipio       : chr  "m1" "m2" "m3" "m4" ...
##  $ apropiaciondolar: num  102.17 4.19 1.59 0 0 ...
##  $ consejocomunal  : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ ejecucion       : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ uribista        : int  0 1 1 1 1 1 1 1 1 NA ...
##  $ pctopo          : num  14.82 14.51 15.08 6.15 47.31 ...
##  $ priorizado      : int  0 1 0 0 0 0 0 0 1 0 ...
##  $ poblacioncienmil: num  20.9155 0.2406 0.0398 0.0558 0.2723 ...
##  $ nbi             : num  12.2 33.8 28.5 33.1 27.1 ...

Recordar que es mejor usar datos sin valores perdidos en los análisis multivariados

pavi=pavi[complete.cases(pavi),]

Los datos debemos darle el formato adecuado. En este caso hay que cambiar a categóricos algunas variables:

seleccion=c("consejocomunal","ejecucion","uribista","priorizado")
pavi[,seleccion]=lapply(pavi[,seleccion],as.factor)

1.2.1.2 Explorando las variables

paviStats=summary(pavi[,-1])
paviStats
##  apropiaciondolar  consejocomunal ejecucion uribista     pctopo      
##  Min.   :  0.000   0:827          0:844     0:325    Min.   : 0.000  
##  1st Qu.:  0.000   1: 49          1: 32     1:551    1st Qu.: 6.285  
##  Median :  0.000                                     Median :19.607  
##  Mean   :  8.926                                     Mean   :27.306  
##  3rd Qu.: 11.436                                     3rd Qu.:44.455  
##  Max.   :132.643                                     Max.   :99.419  
##  priorizado poblacioncienmil        nbi       
##  0:639      Min.   : 0.00629   Min.   : 6.84  
##  1:237      1st Qu.: 0.07460   1st Qu.:27.33  
##             Median : 0.14593   Median :40.26  
##             Mean   : 0.45639   Mean   :41.72  
##             3rd Qu.: 0.27433   3rd Qu.:53.78  
##             Max.   :69.26836   Max.   :97.32

¿Qué puedes decir de estos resultados?

1.2.1.2.1 Análisis numérica - numérica

Luego, es importante saber qué relación se revela entre la variable dependiente y las demás. Se puede comenzar con las que son numéricas, es decir, haciendo un análisis de correlación. Eso lo apreciamos en la siguiente figura:

library(ggcorrplot)
## Warning: package 'ggcorrplot' was built under R version 4.4.3
colNums=names(pavi)[c(2,6,8,9)]  #las columnas numéricas
numXs=pavi[,colNums]
ggcorrplot(cor(numXs),lab = T,show.diag = F)

1.2.1.2.2 Análisis numérica - categórica

Por otro lado, podemos revisar la relación con las categóricas. Rapidamente podemos ver la diferencia estadística, paramétrica y no paramétrica, por grupos en relación a nuestra variable dependiente

library(magrittr)
library(kableExtra)

colCats=setdiff(names(pavi), colNums)[-1]
diffPara=c()
diffNoPara=c()

for (col in colCats){
    diffPara=c(diffPara,t.test(pavi[,"apropiaciondolar"]~pavi[,col])['p.value']<=0.05)
    diffNoPara=c(diffNoPara,wilcox.test(pavi[,"apropiaciondolar"]~pavi[,col])['p.value']<=0.05)
}
data.frame(cbind(colCats,diffPara,diffNoPara),
           row.names = 1:length(colCats))%>%
           kable(caption = "Diferencia de 'VD:Apropiacion' por Grupo")%>%
            kableExtra::kable_styling(full_width = FALSE)
Diferencia de ‘VD:Apropiacion’ por Grupo
colCats diffPara diffNoPara
consejocomunal TRUE TRUE
ejecucion FALSE FALSE
uribista TRUE FALSE
priorizado FALSE TRUE

Lo podemos visualizar de la siguiente forma

par(mfrow = c(2, 2))  

for (col in colCats) { 
  boxplot(pavi$apropiaciondolar~pavi[,col],
       main = col,xlab="",ylab = "")
}

1.2.2 Análisis de Regresión

1.2.2.1 Hipótesis 1

Una hipótesis: - El Beneficio recibido en un municipio ha sido afectado por el porcentaje de votos recibidos por los candidatos de la oposición a Uribe a la cámara de representantes, controlado por tamaño de población.

# hipotesis en R
modelo1=formula(apropiaciondolar~pctopo+poblacioncienmil)

Probamos la hipótesis

reg1=lm(modelo1,data=pavi)
summary(reg1)
## 
## Call:
## lm(formula = modelo1, data = pavi)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -53.809  -8.671  -7.448   2.602  90.426 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       8.80319    0.79976  11.007   <2e-16 ***
## pctopo           -0.03146    0.02150  -1.463    0.144    
## poblacioncienmil  2.15125    0.20073  10.717   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 15.85 on 873 degrees of freedom
## Multiple R-squared:  0.1181, Adjusted R-squared:  0.1161 
## F-statistic: 58.45 on 2 and 873 DF,  p-value: < 2.2e-16

El resultado anterior puede ser presentado de mejor manera usando modelsummary package, como se puede ver en la Tabla 2.1 (el error típico está entre parentesis).

library(modelsummary)
## `modelsummary` 2.0.0 now uses `tinytable` as its default table-drawing
##   backend. Learn more at: https://vincentarelbundock.github.io/tinytable/
## 
## Revert to `kableExtra` for one session:
## 
##   options(modelsummary_factory_default = 'kableExtra')
##   options(modelsummary_factory_latex = 'kableExtra')
##   options(modelsummary_factory_html = 'kableExtra')
## 
## Silence this message forever:
## 
##   config_modelsummary(startup_message = FALSE)
## 
## Adjuntando el paquete: 'modelsummary'
## The following objects are masked from 'package:DescTools':
## 
##     Format, Mean, Median, N, SD, Var
model1=list('apropiacion (I)'=reg1)
modelsummary(model1, title = "Regresion: modelo 1",
             stars = TRUE,
             output = "kableExtra")
Regresion: modelo 1
&nbsp;apropiacion (I)
(Intercept) 8.803***
(0.800)
pctopo -0.031
(0.021)
poblacioncienmil 2.151***
(0.201)
Num.Obs. 876
R2 0.118
R2 Adj. 0.116
AIC 7332.6
BIC 7351.7
Log.Lik. -3662.298
F 58.447
RMSE 15.83
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Al probar esta hipótesis vemos…

  1. que pctopo tiene signo negativo (relación inversa con la VD)
  2. que la magnitud de ese efecto es -0.031, lo que indica cuanto varía apropiaciondolar en promedio cuando pctopo se incrementa en una unidad, controlando por poblacioncienmil
  3. que pctopo NO tiene efecto significativo

Justamente el R cuadrado ajustado (0.1180881) nos brinda un porcentaje (multiplicalo por 100) que da una puesta de nuestra cercanía a una situación perfecta (cuando vale 1).

1.2.2.2 Hipótesis 2

¿Y si queremos ver el efecto de consejo comunal (consecocomunal)?

modelo2=formula(apropiaciondolar~pctopo+consejocomunal+poblacioncienmil)

Esta nueva hipótesis desea evaluar si la vista de Uribe a un Consejo Comunal influye en la asignación de presupuesto. El resultado de este proceso lo vemos a continuación:

reg2=lm(modelo2,data=pavi)
summary(reg2)
## 
## Call:
## lm(formula = modelo2, data = pavi)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -62.378  -7.933  -6.834   1.573  91.247 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       8.04119    0.79129  10.162  < 2e-16 ***
## pctopo           -0.02987    0.02103  -1.420    0.156    
## consejocomunal1  14.79601    2.32127   6.374 2.98e-10 ***
## poblacioncienmil  1.91236    0.19987   9.568  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 15.51 on 872 degrees of freedom
## Multiple R-squared:  0.1573, Adjusted R-squared:  0.1545 
## F-statistic: 54.28 on 3 and 872 DF,  p-value: < 2.2e-16
model2=list('apropiacion (II)'=reg2)
modelsummary(model2, title = "Regresion: modelo 2",
             stars = TRUE,
             output = "kableExtra")
Regresion: modelo 2
&nbsp;apropiacion (II)
(Intercept) 8.041***
(0.791)
pctopo -0.030
(0.021)
consejocomunal1 14.796***
(2.321)
poblacioncienmil 1.912***
(0.200)
Num.Obs. 876
R2 0.157
R2 Adj. 0.154
AIC 7294.7
BIC 7318.6
Log.Lik. -3642.351
F 54.277
RMSE 15.47
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Al probar esta hipótesis vemos que… - pctopo tiene signo negativo; NO tiene efecto significativo; y la magnitud de ese efecto es -0.03, lo que indica cuando variaría apropiaciondolar en promedio cuando pctopo se incremente en una unidad, controlando por las demás variables. - consejocomunal SÍ tiene efecto significativo al 0.001: ese efecto es directo, pues el coeficiente calculado es positivo; y la magnitud de ese efecto es 14.796, lo que indica cuando varía apropiaciondolar en promedio cuando consejocomunal es 1 y no 0; también controlando por las demas variables.

Nótese la lectura del efecto cuando la variable independiente es categórica (o factor). Primero, nota que R indica el valor 1 con el nombre de la variable: eso indica que el valor 0 (cuando el consejocomunal de ese municipio NO fue visitado) es la categoría de referencia; es decir, el coeficiente nos indica cuanto se modifica el valor promedio de la dependiente cuando se pasa de 0 a 1. Si la variable independiente es politómica (no ordinal), aparecerá cada categoría menos la de referencia, y el efecto siempre se debe interpretar como el efecto de las variables mostrada versus la de referencia.

1.2.2.3 Hipótesis 3

# le agregamos uribista
modelo3=formula(apropiaciondolar~pctopo+consejocomunal+uribista+poblacioncienmil)

reg3=lm(modelo3,data=pavi)
summary(reg3)
## 
## Call:
## lm(formula = modelo3, data = pavi)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -61.004  -7.546  -6.482   1.847  91.958 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       9.80542    1.11705   8.778  < 2e-16 ***
## pctopo           -0.03753    0.02126  -1.765   0.0779 .  
## consejocomunal1  14.73795    2.31613   6.363 3.19e-10 ***
## uribista1        -2.45126    1.09801  -2.232   0.0258 *  
## poblacioncienmil  1.89052    0.19965   9.469  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 15.47 on 871 degrees of freedom
## Multiple R-squared:  0.1621, Adjusted R-squared:  0.1583 
## F-statistic: 42.14 on 4 and 871 DF,  p-value: < 2.2e-16
model3=list('apropiacion (III)'=reg3)
modelsummary(model3, title = "Regresion: modelo 3",
             stars = TRUE,
             output = "kableExtra")
Regresion: modelo 3
&nbsp;apropiacion (III)
(Intercept) 9.805***
(1.117)
pctopo -0.038+
(0.021)
consejocomunal1 14.738***
(2.316)
uribista1 -2.451*
(1.098)
poblacioncienmil 1.891***
(0.200)
Num.Obs. 876
R2 0.162
R2 Adj. 0.158
AIC 7291.7
BIC 7320.4
Log.Lik. -3639.852
F 42.140
RMSE 15.43
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Al probar esta hipótesis vemos que…

  • pctopo tiene signo negativo; y ahora SÍ tiene efecto significativo; y la magnitud de ese efecto es -0.038, lo que indica cuanto variaría apropiaciondolar en promedio cuando pctopo se incrementa en una unidad, controlando por las demás variables.

  • consejocomunal MANTIENE efecto significativo al 0.001; ese efecto es directo, pues el coeficiente calculado es positivo; y la magnitud de ese efecto es 14.738, lo que indica cuanto varía apropiaciondolar en promedio cuando consejocomunal es 1 y no 0, también controlando por las demás variables.

  • uribista tiene efecto significativo al 0.05; ese efecto es inverso, pues el coeficiente calculado es negativo; y la magnitud de ese efecto es -2.451, lo que indica cuanto varía apropiaciondolar en promedio cuando uribista es 1 y no 0, también controlando por las demás variables

1.2.2.4 Estandarización de Coeficientes

Del resultado de la última tabla NO podemos directamente decir que consejo comunal tiene más efecto que los demás por el solo hecho de que el valor estimado sea mayor al resto. Para saber cuál tiene más efecto, cuando los predictores tienen, como en este caso, unidades diferentes, estandarizamos los datos y volvemos a correr la regresión.

modelo3_st=formula(scale(apropiaciondolar)~scale(pctopo)+scale(as.numeric(consejocomunal))+scale(as.numeric(uribista))+scale(poblacioncienmil))

modelo3_st=lm(modelo3_st,data=pavi)

modelo3_st=list('apropiacion (III_st)'=modelo3_st)
modelsummary(modelo3_st, title = "Regresion: modelo 3 con \ncoeficientes estandarizados",
             stars = TRUE,
             output = "kableExtra")
Regresion: modelo 3 con coeficientes estandarizados
&nbsp;apropiacion (III_st)
(Intercept) 0.000
(0.031)
scale(pctopo) -0.055+
(0.031)
scale(as.numeric(consejocomunal)) 0.201***
(0.032)
scale(as.numeric(uribista)) -0.070*
(0.031)
scale(poblacioncienmil) 0.299***
(0.032)
Num.Obs. 876
R2 0.162
R2 Adj. 0.158
AIC 2342.0
BIC 2370.7
Log.Lik. -1165.004
F 42.140
RMSE 0.91
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Interpretación: De los resultados de este tabla se despeja que, dejando de lado la variable de control (población por cien mil), la que tiene mayor efecto es consejo comunal, algo que no era evidente en la anterior tabla. Esto nos indica cuantas desviaciones estándar varía la variable dependiente (apropiacion dolar) cuando la dependiente varía en una (1) desviación estándar. Nota además que hemos tenido que estandarizar variables categóricas, lo cuál NO es posible. Ello no debe preocupar pues esta etapa es sólo para comparar tamaño de efecto entre variable.

Podemos simplificar los pasos si utlizamos funciones del paquete lb.beta. La función que lleva el mismo nombre nos da el resultados rapidamente

library(lm.beta)
## Warning: package 'lm.beta' was built under R version 4.4.2
model3beta=list('apropiacion (III)'=lm.beta(reg3))
modelsummary(model3beta, title = "Regresion: modelo 3 con \ncoeficientes estandarizados usando lm.beta()",
             stars = TRUE,
             output = "kableExtra")
Regresion: modelo 3 con coeficientes estandarizados usando lm.beta()
&nbsp;apropiacion (III)
pctopo -0.055
(0.021)
consejocomunal1 0.201*
(2.316)
uribista1 -0.070
(1.098)
poblacioncienmil 0.299
(0.200)
Num.Obs. 876
R2 0.162
R2 Adj. 0.158
AIC 7291.7
BIC 7320.4
Log.Lik. -3639.852
RMSE 15.43
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

1.2.3 Selección de modelos

Veamos todos los modelos anteriores en la tabla:

models=list('apropiacion (I)'=reg1,
            'apropiacion (II)'=reg2,
            'apropiacion (III)'=reg3)
modelsummary(models, title = "Resultados de todos los modelos",
             stars = TRUE,
             output = "kableExtra")
Resultados de todos los modelos
&nbsp;apropiacion (I) &nbsp;apropiacion (II) &nbsp;apropiacion (III)
(Intercept) 8.803*** 8.041*** 9.805***
(0.800) (0.791) (1.117)
pctopo -0.031 -0.030 -0.038+
(0.021) (0.021) (0.021)
poblacioncienmil 2.151*** 1.912*** 1.891***
(0.201) (0.200) (0.200)
consejocomunal1 14.796*** 14.738***
(2.321) (2.316)
uribista1 -2.451*
(1.098)
Num.Obs. 876 876 876
R2 0.118 0.157 0.162
R2 Adj. 0.116 0.154 0.158
AIC 7332.6 7294.7 7291.7
BIC 7351.7 7318.6 7320.4
Log.Lik. -3662.298 -3642.351 -3639.852
F 58.447 54.277 42.140
RMSE 15.83 15.47 15.43
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Hagamos un cambio en la tabla anterior, para que en lugar de los errores típicos, se muestre el intervalo de ocnfianza del coeficiente estimado:

models=list('apropiacion (I)'=reg1,
            'apropiacion (II)'=reg2,
            'apropiacion (III)'=reg3)
modelsummary(models, title = "Resultados de todos los modelos",statistic = "conf.int",
             stars = TRUE,
             output = "kableExtra")
Resultados de todos los modelos
&nbsp;apropiacion (I) &nbsp;apropiacion (II) &nbsp;apropiacion (III)
(Intercept) 8.803*** 8.041*** 9.805***
[7.234, 10.373] [6.488, 9.594] [7.613, 11.998]
pctopo -0.031 -0.030 -0.038+
[-0.074, 0.011] [-0.071, 0.011] [-0.079, 0.004]
poblacioncienmil 2.151*** 1.912*** 1.891***
[1.757, 2.545] [1.520, 2.305] [1.499, 2.282]
consejocomunal1 14.796*** 14.738***
[10.240, 19.352] [10.192, 19.284]
uribista1 -2.451*
[-4.606, -0.296]
Num.Obs. 876 876 876
R2 0.118 0.157 0.162
R2 Adj. 0.116 0.154 0.158
AIC 7332.6 7294.7 7291.7
BIC 7351.7 7318.6 7320.4
Log.Lik. -3662.298 -3642.351 -3639.852
F 58.447 54.277 42.140
RMSE 15.83 15.47 15.43
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

La tabla muestra que los valores no significativos incluyen al cero en el intervalo de confianza (salvo en el caso de pctopo para el modelo 3, con significancia al 0.1). Graficamente:

library(ggplot2)
library(sjPlot)
## Warning: package 'sjPlot' was built under R version 4.4.2
## Install package "strengejacke" from GitHub (`devtools::install_github("strengejacke/strengejacke")`) to load all sj-packages at once!
plot_models(reg1,reg2,reg3,vline.color = "black",m.labels=c("Modelo 1","Modelo 2","Modelo 3"),dot.size = 1,line.size = 0.6)

1.2.3.1 Tabla ANOVA para comparar modelos

En todos los modelos, el error no tiene coeficiente, representamos su variación usando el error típico de los residuos o residual standard error (RSE). Nótese que este ha variado de un modelo ha otro, y en el último modelo se tiene un R2Adj mayor (el menor RSE). Aquí vale la pena preguntarse si esta disminución del error es significativa:

library(magrittr)
library(knitr)
tanova=anova(reg1,reg2,reg3)

kable(tanova,
      caption = "Tabla ANOVA para comparar modelos")%>%kableExtra::kable_styling(full_width = FALSE)
Tabla ANOVA para comparar modelos
Res.Df RSS Df Sum of Sq F Pr(>F)
873 219454.2 NA NA NA NA
872 209684.3 1 9769.883 40.814973 0.0000000
871 208491.3 1 1192.989 4.983868 0.0258382

La comparación dem odelos usando la tabla de análisis de varianza (anova) propone como hipótesis nula que los modelos no difieren (no se ha reducido el error al pasado de un modelo a otro). Cuando la comparación es significativa (observar el Pr(<F)), rechazamos igualdad entre modelos: el modelo 2 presenta menos error que el modelo 1, pero el modelo 2, sí presenta mayot error que el modelo 3, por lo que el modelo 3 debe ser elegido (hasta aquí).

1.2.4 Diagnósticos de la Regresión

Con el apoyo de las computadoras y el software estadístico es relativamente fácil calcular una regresión. Sin embargo, hay que analizar los resultados obtenidos para poder tener una mejor conclusión. Revisemos el resultado de la segunda regresión en las siguientes subsecciones.

1.2.4.1 Linealidad

Se asume relación lineal entre la variable dependiente (Y) y las independientes Xs. Para ello analizamos la relación entre los residuos y los valores que predice el modelo de regresión.

# linea roja debe tender a horizontal
plot(reg2, 1)

La falta de linealidad provocaría que el modelo no sirva para explicar las mismas variables con datos diferentes en otros estudios.

1.2.4.2 Homocedasticidad

Se asume que la dispersión de los errores de la estimación (apropiaciondolar) mantiene una variación homogénea. Si analizamos las raíces de los errores estandarizados versus los valores estimados, la distancia de los puntos a la linea de referencia debe ser similar.

# linea roja debe tender a horizontal
plot(reg2, 3)

1.2.4.2.1 Breusch-Pagan

También podemos utilzar el testo de Breusch-Pagan.

  • p-valor < 0.05 = no hay homocedasticidad
  • p-valor > 0.05 = hay homocedasticidad
library(lmtest)
## Cargando paquete requerido: zoo
## 
## Adjuntando el paquete: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
library(kableExtra)
# null: modelo homocedastico
resBP=bptest(reg2)
data.frame(list('BP'=resBP$statistic,
             'df'=resBP$parameter,
             "p-value"=resBP$p.value))%>%
    kable(caption = resBP$method)%>%kable_styling(full_width = F)
studentized Breusch-Pagan test
BP df p.value
BP 34.83587 3 1e-07

El estadístico de BP sale 34.8358715 con un p-valor de 1.3195008^{-7}; de ahí se rechaza que el modelo muestre homocedasticidad.

La presencia de heterocedasticidad afecta el cálculo de los p-valores, lo que afectará la validéz de todo el modelo.

1.2.4.3 Normalidad de residuos

Los residuos deben distribuirse de manera normal. Un qq-plot nos permite relevar si hay normalidad

# puntos cerca a la diagonal?
plot(reg2, 2)

1.2.4.3.1 Shapiro

Otra opción es aplicar el test de Shapiro a los residuos

  • p-valor > 0.05 = normalidad
  • p-valor < 0.05 = no hay normalidad
#NULL: Datos se distribuyen de manera normal
resSW=shapiro.test(reg2$residuals)
data.frame(list('SW'=resSW$statistic,
             "p-value"=resSW$p.value))%>%
    kable(caption = resSW$method)%>%kable_styling(full_width = F)
Shapiro-Wilk normality test
SW p.value
W 0.6820371 0

La falta de normalidad limita la capacidad de hacer inferencias a partir de lo encontrado.

1.2.4.4 No Multicolinealidad

Si los predictores tienen una correlación muy alta entre sí, hay multicolinealidad:

  • Mayor a 5 es problemático
library(DescTools)

# > 5 es problematico
VIF(reg2) %>%kable(col.names = "VIF",caption ="Evaluando Multicolinealidad usando VIF (Variance Inflation Factors)" )%>%kable_styling(full_width = F)
Evaluando Multicolinealidad usando VIF (Variance Inflation Factors)
VIF
pctopo 1.000152
consejocomunal 1.036568
poblacioncienmil 1.036454

La presencia de la multicolinealidad no perjudica tanto al calculo, pero evitar calcular bien el efecto de cada regresor.

1.2.4.5 Valores Influyentes

Hay casos particulares, que tienen la capacidad de trastocar lo que el modelo representa. A veces detectándolos y suprimiéndolos podemos ver un mejor modelo. La siguiente figura muestra la posible existencia de valores influyentes.

plot(reg2, 5)

Si queremos ver más de cerca lo posible los casos influyentes, le prestamos atención al índice de Cook y a los valores predeciros (hat values) de la siguiente tabla:

checkReg2=as.data.frame(influence.measures(reg2)$is.inf)
checkReg2[checkReg2$cook.d & checkReg2$hat,c('cook.d','hat')]%>%kable(caption = "Valores Influyentes criticos")%>%kable_styling(full_width = F)
Valores Influyentes criticos
cook.d hat
146 TRUE TRUE

Si alguna fila aparece en el resultado, en ese caso está afectando los cálculos de la regresión (sin él habría otro resultado)

2 Modelos No Lineales

2.1 Regresión Poisson

2.1.1 Preparando la data

library(rio)
## Warning: package 'rio' was built under R version 4.4.3
censo2007=import("https://github.com/Estadistica-AnalisisPolitico/Sesion3/raw/main/data2007peru.xlsx")
head(censo2007)
##   Pais Departamento Provincia Distrito              Tema
## 1 Perú     Amazonas     Bagua Aramango       Demográfico
## 2 Perú     Amazonas     Bagua Aramango       Demográfico
## 3 Perú     Amazonas     Bagua Aramango       Demográfico
## 4 Perú     Amazonas     Bagua Aramango       Demográfico
## 5 Perú     Amazonas     Bagua Aramango       Demográfico
## 6 Perú     Amazonas     Bagua Aramango Económico-Laboral
##                          Sub Tema
## 1                         General
## 2                         General
## 3                         General
## 4                         General
## 5                         General
## 6 Población Económicamente Activa
##                                                                      Descripcion
## 1                                             Total de habitantes del censo 2007
## 2                                             Total de habitantes del censo 2007
## 3                                             Total de habitantes del censo 2007
## 4                                             Total de habitantes del censo 2007
## 5                                             Total de habitantes del censo 2007
## 6 Porcentaje de Trabajadores Independientes o por cuenta propia no profesionales
##           Clase Indicadores Valor
## 1         Total          NA 11442
## 2   Área Urbana          NA  2657
## 3    Área Rural          NA  8785
## 4 Sexo - Hombre          NA  6141
## 5  Sexo - Mujer          NA  5301
## 6         Total          NA 54.61
str(censo2007)
## 'data.frame':    36620 obs. of  10 variables:
##  $ Pais        : chr  "Perú" "Perú" "Perú" "Perú" ...
##  $ Departamento: chr  "Amazonas" "Amazonas" "Amazonas" "Amazonas" ...
##  $ Provincia   : chr  "Bagua" "Bagua" "Bagua" "Bagua" ...
##  $ Distrito    : chr  "Aramango" "Aramango" "Aramango" "Aramango" ...
##  $ Tema        : chr  "Demográfico" "Demográfico" "Demográfico" "Demográfico" ...
##  $ Sub Tema    : chr  "General" "General" "General" "General" ...
##  $ Descripcion : chr  "Total de habitantes del censo 2007" "Total de habitantes del censo 2007" "Total de habitantes del censo 2007" "Total de habitantes del censo 2007" ...
##  $ Clase       : chr  "Total" "Área Urbana" "Área Rural" "Sexo - Hombre" ...
##  $ Indicadores : logi  NA NA NA NA NA NA ...
##  $ Valor       : chr  "11442" "2657" "8785" "6141" ...

La columna Valor ha sido interpretada como texto y no como tipo numérico. R hará eso si es que alguna celda tiene un carácter que no sea número o punto. Veamos

# qué celda tiene algo que no sea número
censo2007$Valor[!grepl("[0-9]", censo2007$Valor)]
##  [1] "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-"
## [20] "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-"
## [39] "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-"
## [58] "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-"
## [77] "-" "-"

Sabiendo ello, podemos convertir la columna a numérica sin temor a perder valores.

censo2007$Valor=as.numeric(censo2007$Valor)
## Warning: NAs introducidos por coerción

Ahora podemos quedarnos las columnas que utilizaremos en el ejemplo:

censo2007_sub=censo2007[,-c(1,5,6,9)] # -c() elimina esas columnas
# queda
str(censo2007_sub)
## 'data.frame':    36620 obs. of  6 variables:
##  $ Departamento: chr  "Amazonas" "Amazonas" "Amazonas" "Amazonas" ...
##  $ Provincia   : chr  "Bagua" "Bagua" "Bagua" "Bagua" ...
##  $ Distrito    : chr  "Aramango" "Aramango" "Aramango" "Aramango" ...
##  $ Descripcion : chr  "Total de habitantes del censo 2007" "Total de habitantes del censo 2007" "Total de habitantes del censo 2007" "Total de habitantes del censo 2007" ...
##  $ Clase       : chr  "Total" "Área Urbana" "Área Rural" "Sexo - Hombre" ...
##  $ Valor       : num  11442 2657 8785 6141 5301 ...

Este último data frame está en formato long

library(magrittr)
head(censo2007_sub,20)%>%
    rmarkdown::paged_table(options = )

Reconocemos el formato long cuando, sabiendo que la unidad de análisis es el distrito, vemos que el distrito se repite en muchas filas. Arriba vemos que todas las variable de Aramango aparecen al lado de este distrito. Nota además que los nombres de las variables están en las filas (no como títulos de las columnas) y que los valores de la variable aparecen en la última fila.

Pasémoslo a formato wide:

library(reshape2)
## Warning: package 'reshape2' was built under R version 4.4.2
censo2007_wide=dcast(data=censo2007_sub,
                     formula=Departamento+Provincia+Distrito ~ Descripcion+Clase,
                     value.var="Valor")

Los nombres de columnas son muy largos luego de esta conversión serán muy largos:

# columnas del formato wide
names(censo2007_wide)
##  [1] "Departamento"                                                                                
##  [2] "Provincia"                                                                                   
##  [3] "Distrito"                                                                                    
##  [4] "Personas que tienen algún tipo de seguro_Área Rural"                                         
##  [5] "Personas que tienen algún tipo de seguro_Área Urbana"                                        
##  [6] "Personas que tienen algún tipo de seguro_Sexo - Hombre"                                      
##  [7] "Personas que tienen algún tipo de seguro_Sexo - Mujer"                                       
##  [8] "Personas que tienen algún tipo de seguro_Total"                                              
##  [9] "Porcentaje de personas analfabetas de 15 años y más_Área Rural"                              
## [10] "Porcentaje de personas analfabetas de 15 años y más_Área Urbana"                             
## [11] "Porcentaje de personas analfabetas de 15 años y más_Sexo - Hombre"                           
## [12] "Porcentaje de personas analfabetas de 15 años y más_Sexo - Mujer"                            
## [13] "Porcentaje de personas analfabetas de 15 años y más_Total"                                   
## [14] "Porcentaje de Trabajadores Independientes o por cuenta propia no profesionales_Área Rural"   
## [15] "Porcentaje de Trabajadores Independientes o por cuenta propia no profesionales_Área Urbana"  
## [16] "Porcentaje de Trabajadores Independientes o por cuenta propia no profesionales_Sexo - Hombre"
## [17] "Porcentaje de Trabajadores Independientes o por cuenta propia no profesionales_Sexo - Mujer" 
## [18] "Porcentaje de Trabajadores Independientes o por cuenta propia no profesionales_Total"        
## [19] "Total de habitantes del censo 2007_Área Rural"                                               
## [20] "Total de habitantes del censo 2007_Área Urbana"                                              
## [21] "Total de habitantes del censo 2007_Sexo - Hombre"                                            
## [22] "Total de habitantes del censo 2007_Sexo - Mujer"                                             
## [23] "Total de habitantes del censo 2007_Total"

Pero podemos cambiarlos por nombres más cortos así:

# uso de gsub()
# donde diga "Area Rural" cambiar a "rural":
names(censo2007_wide)=gsub(pattern = "Área Rural",
                           replacement = "rural",
                           x = names(censo2007_wide))

names(censo2007_wide)=gsub(pattern = "Área Urbana",
                           replacement = "urban",
                           x = names(censo2007_wide))

names(censo2007_wide)=gsub(pattern = "Sexo - Hombre",
                           replacement = "Hombre",
                           x = names(censo2007_wide))

names(censo2007_wide)=gsub(pattern = "Sexo - Mujer",
                           replacement = "Mujer",
                           x = names(censo2007_wide))

names(censo2007_wide)=gsub(pattern = "Sexo - Mujer",
                           replacement = "Mujer",
                           x = names(censo2007_wide))

names(censo2007_wide)=gsub(pattern = "Personas que tienen algún tipo de seguro",
                           replacement = "conSeguro",
                           x = names(censo2007_wide))

names(censo2007_wide)=gsub(pattern = "Porcentaje de personas analfabetas de 15 años y más",
                           replacement = "analfa",
                           x = names(censo2007_wide))

names(censo2007_wide)=gsub(pattern = "Porcentaje de Trabajadores Independientes o por cuenta propia no profesionales",
                           replacement = "indep",
                           x = names(censo2007_wide))

names(censo2007_wide)=gsub(pattern = "Total de habitantes del censo 2007",
                           replacement = "poblacion",
                           x = names(censo2007_wide))

# nuevos nombres
names(censo2007_wide)
##  [1] "Departamento"     "Provincia"        "Distrito"         "conSeguro_rural" 
##  [5] "conSeguro_urban"  "conSeguro_Hombre" "conSeguro_Mujer"  "conSeguro_Total" 
##  [9] "analfa_rural"     "analfa_urban"     "analfa_Hombre"    "analfa_Mujer"    
## [13] "analfa_Total"     "indep_rural"      "indep_urban"      "indep_Hombre"    
## [17] "indep_Mujer"      "indep_Total"      "poblacion_rural"  "poblacion_urban" 
## [21] "poblacion_Hombre" "poblacion_Mujer"  "poblacion_Total"

¿Cómo nos quedariamos por ahora con las columnas de “Totales”?

# grep() devuelve las posiciones
grep(pattern = "Total",names(censo2007_wide))
## [1]  8 13 18 23

Procedemos:

colsTotal=grep(pattern = "Total",names(censo2007_wide))
censo2007_wide_Totales=censo2007_wide[,c(1,2,3,colsTotal)]

# ahora:
head(censo2007_wide_Totales)
##   Departamento Provincia   Distrito conSeguro_Total analfa_Total indep_Total
## 1     Amazonas     Bagua   Aramango            5057        13.81       54.61
## 2     Amazonas     Bagua   Copallin            2593        13.46       35.05
## 3     Amazonas     Bagua   El Parco             524        12.17       20.91
## 4     Amazonas     Bagua      Imaza           11294        17.29       47.94
## 5     Amazonas     Bagua    La Peca           14310         7.62       36.51
## 6     Amazonas   Bongará Chisquilla             206         3.29       42.95
##   poblacion_Total
## 1           11442
## 2            6126
## 3            1274
## 4           21409
## 5           31506
## 6             346

2.1.2 Realizamos una regresión lineal (está mal)

Planteamos la siguiente hipótesis: A nivel distrital, la cantidad de personas con algún seguro de salud está afectada por el nivel de analfabetismo.

De lo visto en la sesión anterior, podríamos intentar realizar una regresión lineal, que nos daría estos resultados:

library(knitr)
library(modelsummary)

h1=formula(conSeguro_Total~analfa_Total)

rl1=lm(h1, data = censo2007_wide_Totales)

model1=list('OLS asegurados (I)'=rl1)
modelsummary(model1, title = "Resumen de Regresion Lineal",
             stars = TRUE,
             output = "kableExtra")
Resumen de Regresion Lineal
&nbsp;OLS asegurados (I)
(Intercept) 12397.369***
(748.564)
analfa_Total -444.967***
(45.970)
Num.Obs. 1831
R2 0.049
R2 Adj. 0.048
AIC 40986.3
BIC 41002.8
Log.Lik. -20490.143
F 93.694
RMSE 17531.34
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Como vemos en la tabla, el covariado (conocido como predictor o variable independiente) salió con un valor absoluto alto, negativo y significativo (es muy poco probable -menos de 0.1%- que no tenga efecto), pero con un R-2 ajustado muy bajo. Como el modelo no nos da un buen ajuste, es muy probable que la evaluación del modelo no sea satisfactoria. Así, vemos en la siguiente figura que dificilmente este modelo puede ser útil:

par(mfrow = c(2, 2))  
plot(rl1, 1,caption = '');title(main="Linealidad")
plot(rl1, 2, caption = '');title(main="Normalidad")
plot(rl1, 3, caption = '');title(main="Homocedasticidad")
plot(rl1, 5, caption = '');title(main="Influyentes")

Pensamos… podemos mejorar este modelo si controlásemos el tamaño de la población en el modelo:

library(knitr)
library(modelsummary)

h1control=formula(conSeguro_Total~analfa_Total + poblacion_Total )

rl2=lm(h1control, data = censo2007_wide_Totales)

modelslm=list('OLS asegurados (I)'=rl1,'OLS asegurados (II)'=rl2)
modelsummary(modelslm, title = "Regresiones Lineales",
             stars = TRUE,
             output = "kableExtra")
Regresiones Lineales
&nbsp;OLS asegurados (I) &nbsp;OLS asegurados (II)
(Intercept) 12397.369*** 710.956***
(748.564) (189.771)
analfa_Total -444.967*** -12.293
(45.970) (11.186)
poblacion_Total 0.387***
(0.002)
Num.Obs. 1831 1831
R2 0.049 0.946
R2 Adj. 0.048 0.946
AIC 40986.3 35719.9
BIC 41002.8 35742.0
Log.Lik. -20490.143 -17855.965
F 93.694 16156.363
RMSE 17531.34 4159.25
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Se ve una gran mejora en el R2 ajustado con el modelo II de la anterior tabla. Veamos sus gráficas de diagnóstico en la siguiente figura:

par(mfrow = c(2, 2))  
plot(rl2, 1,caption = '');title(main="Linealidad")
plot(rl2, 2, caption = '');title(main="Normalidad")
plot(rl2, 3, caption = '');title(main="Homocedasticidad")
plot(rl2, 5, caption = '');title(main="Influyentes")

Los gráficos de la Figura nos muestran un mejor escenario, pero nota que nuestro predictor dejó de ser significativo ante la presencia de la variable de control. Quizá debimos dar un paso más sencillo: analizar la naturaleza de la variable dependiente:

library(ggplot2)
VarDep=censo2007_wide_Totales$conSeguro_Total
descris=list(min=min(VarDep),
             max=max(VarDep),
             media=round(mean(VarDep),2),
             var=round(var(VarDep),2),
             asim=round(e1071::skewness(VarDep),2),
             kurt=round(e1071::kurtosis(VarDep),2))

base=ggplot(data=censo2007_wide_Totales, aes(x=conSeguro_Total)) + theme_classic()
hist=base + geom_histogram(bins=50)
histInfo=hist + annotate("text", x = 100000, y = 1050,
                         color='grey50',
                       label = paste0("Minimo: ",descris$min))
histInfo = histInfo + annotate("text", x = 100000, y = 900,
                       color='grey50',
                       label = paste0("Máximo: ",descris$max))

histInfo = histInfo + annotate("text", x = 100000, y = 650,
                       color='grey50',
                       label = paste0("Media: ",descris$media))

histInfo = histInfo + annotate("text", x = 100000, y = 500,
                       color='grey50',
                       label = paste0("Varianza: ",descris$var))

histInfo = histInfo + annotate("text", x = 100000, y = 350,
                       color='grey50',
                       label = paste0("Asimetría: ",descris$asim))

histInfo = histInfo + annotate("text", x = 100000, y = 200,
                       color='grey50',
                       label = paste0("Curtosis: ",descris$kurt))

histInfo

El histograma nos muestra una distribución con asimetría positiva (cola a la derehca) y super leptocurtica. Ells nos hace reflexionar que nuestra variable dependiente representa conteos, valores enteros positivos. La regresión lineal tendrá problemas pues asume que la variable dependiente tiene valores reales y no acotados. Así mismo, el sesgo presente lo aleja de una “campana” de Gauss, por lo que debilitaría los cálculos de significancia si los datos no siguen una tendencia lineal; claro está que uno puede transformar la variable dependiente, pero ello a la vez complicará la interpretación.

2.1.3 Regresión Poisson

La regresión Poisson tiene los siguientes supuestos (Glen 2016):

  1. Variable Respuesta: Es un conteo (Y) por unidad de tiempo o espacio, que puede ser descrita por la distribución Poisson (por ejemplo asaltos por día). Puede además ser un ratio cuando la unidad de tiempo o espacio varía para cada conteo (por ejemplo votos a favor del total de votos),

  2. Independencia: Las observaciones (filas) no deben tener relación entre sí.

  3. Media = Varianza: Por definición, la media de una variable que se distribuye como Poisson debe ser igual a su varianza (equidispersión). Si la varianza supera significativamente a la media hablamos de sobredispersión, pero si la media fuera mucho mayor que la varianza tendríamos subdispersión.

  4. Linealidad: El logaritmo de la variable dependiente (conteos) debe ser una función lineal de los covariados.

2.1.3.1 Hipótesis replanteada

Reimplementemos nuestra hipótesis original usando la regresión Poisson. Los resultados lso vemos en la siguiente tabla, comparándolos con el resultado de la regresión lineal controlada por la población:

rp1=glm(h1, data = censo2007_wide_Totales, 
        offset=log(poblacion_Total), #exposure 
        family = poisson(link = "log"))

# displaying results
modelslmpoi=list('OLS asegurados (II)'=rl2,
                 'POISSON asegurados'=rp1)

modelsummary(modelslmpoi, title = "Regresiones OLS y Poisson",
             stars = TRUE,
             output = "kableExtra")
Regresiones OLS y Poisson
&nbsp;OLS asegurados (II) &nbsp;POISSON asegurados
(Intercept) 710.956*** -0.900***
(189.771) (0.000)
analfa_Total -12.293 0.005***
(11.186) (0.000)
poblacion_Total 0.387***
(0.002)
Num.Obs. 1831 1831
R2 0.946
R2 Adj. 0.946
AIC 35719.9 895923.3
BIC 35742.0 895934.4
Log.Lik. -17855.965 -447959.669
F 16156.363 21585.036
RMSE 4159.25 4353.10
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Ahora que tenemos ambas regresiones, vemos que el modelo Poisson le devuelve efecto a la independiente. Nótese que la Poisson está modelando los conteos, teniendo en cuenta la exposición (exposure), añadida usando offset. Esto no siempre es necesario, pero en este caso sí lo era, pues necesitamos controlar la exposure de manera explícita (población). Sin embargo, el AIC de la Poisson es aún mayor que el de la Gaussiana.

2.1.3.2 Interpretación

Ya que sabemos cuándo usar una regresión Poisson, alteremos la hipótesis anterior:

A nivel distrital, la cantidad de personas con algún seguro de salud está afectada por el nivel de analfabetismo y por la presencia de trabajadores independientes

h2=formula(conSeguro_Total~analfa_Total + indep_Total)
    
rp2=glm(h2, data = censo2007_wide_Totales, 
        offset=log(poblacion_Total),
        family = poisson(link = "log"))


modelsPois=list('POISSON asegurados (I)'=rp1,
                'POISSON asegurados (II)'=rp2)
modelsummary(modelsPois, 
             title = "Regresiones Poisson anidadas",
             stars = TRUE,
             output = "kableExtra")
Regresiones Poisson anidadas
&nbsp;POISSON asegurados (I) &nbsp;POISSON asegurados (II)
(Intercept) -0.900*** -0.615***
(0.000) (0.001)
analfa_Total 0.005*** 0.016***
(0.000) (0.000)
indep_Total -0.011***
(0.000)
Num.Obs. 1831 1831
AIC 895923.3 771399.1
BIC 895934.4 771415.6
Log.Lik. -447959.669 -385696.535
F 21585.036 75214.880
RMSE 4353.10 3748.51
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

La interpretación de la anterior tabla NO es tan directa como lo era en la regresión lineal. Demos una interpretación simple, sin hacer ningún cálculo, de los resultados del modelo II:

  • En el modelo II, ambos predictores son significativos.
  • En el modelo II, a mayor analfabetismo, mayor cantidad de asegurados.
  • En el modelo II, a mayor cantidad de trabjadores independientes, menor cantidad de asegurados.

Sin embargo, la interpretación precisa de los coeficientes de la tabla requiere más cálculos; tengamos primero en cuenta lo que la regresión Poisson ha calculado usando la ecuación. Cuando el exposure es constante modelamos conteos (Y); cuando no lo es modelso ratios. Pero, como vemos en la Ecuación, los coeficientes necesitan ser exponenciados para saber el efecto sobre Y

Veamos la siguiente tabla:

# formula para limitar a 4 digitos decimales, 
# sin que se muestre notación científica:

formatoNum <- function(x) format(x, digits = 4, scientific = FALSE)

modelsummary(modelsPois,
             fmt=formatoNum, # uso mi formula
             exponentiate = T, # exponenciar!!!!!
             statistic = 'conf.int',
             title = "Regresión Poisson - coeficientes exponenciados",
             stars = TRUE,
             output = "kableExtra")
Regresión Poisson - coeficientes exponenciados
&nbsp;POISSON asegurados (I) &nbsp;POISSON asegurados (II)
(Intercept) 0.4066*** 0.5405***
[0.4063, 0.4069] [0.5396, 0.5415]
analfa_Total 1.0051*** 1.0166***
[1.0050, 1.0051] [1.0165, 1.0167]
indep_Total 0.9893***
[0.9893, 0.9894]
Num.Obs. 1831 1831
AIC 895923.3 771399.1
BIC 895934.4 771415.6
Log.Lik. -447959.669 -385696.535
F 21585.036 75214.880
RMSE 4353.10 3748.51
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
#más simple:
#cbind(exp(coef(rp2)),exp(confint(rp2)))

La tabla anterior tiene los coeficientes exponenciados, así mismo, muestra los intervalos de confianza (exponenciados) en vez de los errores típicos. Nota que mientras en la regresión lineal no deseábamos que nuestro coeficiente esté cerca al cero, es decir, que su intervalo de confianza no incluya al cero, aquí no deseamos que el intervalo de confianza incluya al uno

Una vez exponenciado, podemos interpretar los coeficientes de manera más sencilla. Así, para el modelo II se ve que:

  • por cada unidad que aumente el analfabetismo, la cantidad esperada de asegurados se multiplicar por 1.016, es decir, aumentaría en un 1.6% (100 x |1-1.016|) en promedio.

  • por cada unidad que aumente los trabajadores independientes, la cantidad esperada de asegurados se multiplica por 0.99, es decir, disminuiría en 1% (100 x |1-0.99|) en promedio.

Nótese que la regresión propone un efecto multiplicativo sobre el promedio de la respuesta (la regresión OLS o Gaussiana propone un efecto aditivo).

2.1.4 Equidispersión

Uno de los supuestos en la Regresión Poisson es que la media y la varianza sean iguales. De los estadigrados de la primera figura, se mostró que estos valores no están cercanos, de hecho la razón varianza - media es:

round(descris$var/descris$media,2)
## [1] 51032.13

Aquí es clara la sobredispersión, pero en caso haya dudas podemos poner a prueba la hipótesis de la equidispersión.

library(magrittr)
overdispersion=AER::dispersiontest(rp2,alternative='greater')$ p.value<0.05
underdispersion=AER::dispersiontest(rp2,alternative='less')$ p.value<0.05
# tabla
testResult=as.data.frame(rbind(overdispersion,underdispersion))
names(testResult)='Es probable?'
testResult%>%kable(caption = "Test de Equidispersión")%>%kableExtra::kable_styling()
Test de Equidispersión
Es probable?
overdispersion TRUE
underdispersion FALSE

Esta tabla nos muestra que es altamente improbable que la varianza sea igual a la media, por lo que se opta por aceptar que lo más probable es que tengamos sobredispersión.

2.1.4.1 Quasi Poisson

Útil para subdispersión y sobredispersión.

rqp = glm(h2, data = censo2007_wide_Totales,
          offset=log(poblacion_Total),
          family = quasipoisson(link = "log"))

modelsPQP=list('POISSON asegurados (II)'=rp2,'QUASIPOISSON asegurados (II)'=rqp)

modelsummary(modelsPQP, 
             title = "Regresiones Poisson y QuasiPoisson",
             stars = TRUE,
             output = "kableExtra")
Regresiones Poisson y QuasiPoisson
&nbsp;POISSON asegurados (II) &nbsp;QUASIPOISSON asegurados (II)
(Intercept) -0.615*** -0.615***
(0.001) (0.018)
analfa_Total 0.016*** 0.016***
(0.000) (0.001)
indep_Total -0.011*** -0.011***
(0.000) (0.001)
Num.Obs. 1831 1831
AIC 771399.1
BIC 771415.6
Log.Lik. -385696.535
F 75214.880 179.984
RMSE 3748.51 3748.51
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

La tabla nos muestra cosas interesantes:

  • Los coeficientes son los mismos para ambos modelos:
cbind(coef_Poi=coef(rp2),coef_QuasiPoi=coef(rqp))
##                 coef_Poi coef_QuasiPoi
## (Intercept)  -0.61523114   -0.61523114
## analfa_Total  0.01649913    0.01649913
## indep_Total  -0.01073999   -0.01073999
  • Pero no los errores típicos
library(arm)
## Warning: package 'arm' was built under R version 4.4.2
## Cargando paquete requerido: MASS
## Cargando paquete requerido: Matrix
## Warning: package 'Matrix' was built under R version 4.4.2
## Cargando paquete requerido: lme4
## 
## Adjuntando el paquete: 'lme4'
## The following object is masked from 'package:rio':
## 
##     factorize
## 
## arm (Version 1.14-4, built: 2024-4-1)
## Working directory is C:/Users/USUARIO/Documents/UNIVERSIDAD/Manuales/Estadística2
cbind(se_Poi=se.coef(rp2),se_QuasiPoi=se.coef(rqp))
##                    se_Poi  se_QuasiPoi
## (Intercept)  8.910449e-04 0.0182152135
## analfa_Total 4.609011e-05 0.0009421985
## indep_Total  3.037210e-05 0.0006208826

La regresión quasipoisson lidia con la sobredispersión al recalcular los errores típicos, lo que afectaría la significancia de los predictores; de ahí que calcula nuevos intervalos de confianza:

#Exponenciamos!!!

modelsQPexp=list('QuasiPoisson asegurados (II) exponenciado'=rqp)


modelsummary(modelsQPexp,fmt=formatoNum,
             exponentiate = T, 
             statistic = 'conf.int',
             title = "EXP() de la Regresión Quasi Poisson (II) para Interpretación",
             stars = TRUE,
             output = "kableExtra")
EXP() de la Regresión Quasi Poisson (II) para Interpretación
&nbsp;QuasiPoisson asegurados (II) exponenciado
(Intercept) 0.5405***
[0.5215, 0.5601]
analfa_Total 1.0166***
[1.0148, 1.0185]
indep_Total 0.9893***
[0.9881, 0.9905]
Num.Obs. 1831
F 179.984
RMSE 3748.51
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

2.1.4.2 Binomial Negativa

Solo resulta útil para la sobredispersión

h2off=formula(conSeguro_Total ~ analfa_Total + indep_Total + offset(log(poblacion_Total)))

rbn=glm.nb(h2off,data=censo2007_wide_Totales)

modelsQP_BN=list('Poisson asegurados (II)'=rp2,
                 'QuasiPoisson asegurados (II)'=rqp,
                 'Binomial Negativa asegurados (II)'=rbn)


modelsummary(modelsQP_BN,fmt=formatoNum,
             exponentiate = T, 
             statistic = 'conf.int',
             title = "EXP() de la Regresiones Poisson, Quasi Poisson  y Binomial Negativa",
             stars = TRUE,
             output = "kableExtra")
EXP() de la Regresiones Poisson, Quasi Poisson y Binomial Negativa
&nbsp;Poisson asegurados (II) &nbsp;QuasiPoisson asegurados (II) &nbsp;Binomial Negativa asegurados (II)
(Intercept) 0.5405*** 0.5405*** 0.4485***
[0.5396, 0.5415] [0.5215, 0.5601] [0.4271, 0.4711]
analfa_Total 1.0166*** 1.0166*** 1.0142***
[1.0165, 1.0167] [1.0148, 1.0185] [1.0122, 1.0162]
indep_Total 0.9893*** 0.9893*** 0.9951***
[0.9893, 0.9894] [0.9881, 0.9905] [0.9940, 0.9963]
Num.Obs. 1831 1831 1831
AIC 771399.1 29080.4
BIC 771415.6 29102.5
Log.Lik. -385696.535 -14536.206
F 75214.880 179.984 106.748
RMSE 3748.51 3748.51 4011.51
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Notamos en la tabla que los coeficientes obtenidos en la regresión binomial negativa son diferentes a los demás. Además, los AIC son mucho mejores para caso de la Binomial Negativa; los AIC/BIC podemos verlos así de manera alternativa

data.frame(Model = c("poisson", "quasipoisson", "negative-binomial"),
           AIC = c(AIC(rp2), AIC(rqp), AIC(rbn)),
           BIC = c(BIC(rp2), BIC(rqp), BIC(rbn)),stringsAsFactors = FALSE
)%>%
kable(caption = "AIC / BIC para los modelos de conteos")%>%kableExtra::kable_styling(full_width = FALSE)
AIC / BIC para los modelos de conteos
Model AIC BIC
poisson 771399.07 771415.61
quasipoisson NA NA
negative-binomial 29080.41 29102.46

2.1.5 Comparación de modelos

La tabla anterior es la mejor alternativa ante modelos no anidados. Una alternativa adicional sería verificar la sobredispersión producida en los modelos

# poisson case
performance::check_overdispersion(rp2)
## # Overdispersion test
## 
##        dispersion ratio =    417.897
##   Pearson's Chi-Squared = 763915.368
##                 p-value =    < 0.001
## Overdispersion detected.
# quasipoisson case
performance::check_overdispersion(rqp)
## # Overdispersion test
## 
##        dispersion ratio =    417.897
##   Pearson's Chi-Squared = 763915.368
##                 p-value =    < 0.001
## Overdispersion detected.
# negative binomial case
performance::check_overdispersion(rbn)
## # Overdispersion test
## 
##  dispersion ratio = 0.386
##           p-value = 0.008
## Underdispersion detected.

Como estos modelos NO son anidados, usaremos con cuidado la tabla anova, esta vez pidiendo un test chi-cuadrado; veamos el resultado:

anova(rp2,rqp,rbn,test = "Chisq") %>%
kable(caption = "Tabla ANOVA para comparar modelos")%>%kableExtra::kable_styling(full_width = FALSE)
Tabla ANOVA para comparar modelos
Resid. Df Resid. Dev Df Deviance Pr(>Chi)
1828 754118.559 NA NA NA
1828 754118.559 0 0.0 NA
1828 1868.019 0 752250.5 NA

La caída del Deviance es tanta para el último caso (de 754119 a 1868) que la mejor opción es la binomial negativa. Sin embargo, la quasipoisson no tiene ni AIC/BIC ni enlace Log, por lo que se produce p-valores como perdidos. Veamos la alternativa del loglikelihood ratio test como se recomiendo por Bobbitt (2021) para casos no anidados, pero sin usar la quasipoisson:

lmtest::lrtest(rp2,rbn)%>%
kable(caption = "loglikelihood ratio test")%>%kableExtra::kable_styling(full_width = FALSE)
loglikelihood ratio test
#Df LogLik Df Chisq Pr(>Chisq)
3 -385696.53 NA NA NA
4 -14536.21 1 742320.7 0

La tabla sugiera la binomial negativa. Por lo general, la binomial negativa es la más utilizada que la quasipoisson, pero la binomial negativa no es apropiada para la subdispersión, mientras que la quasipoisson sí se usa para ese caso. Una manera adicional de comparar es la gráfica.

library(ggplot2)
dotwhisker::dwplot(list(Poisson=rp2,CuasiPoisso=rqp,BinomialNegativa=rbn),exp=T) + scale_y_discrete(labels=c("trabajo\nindependiente","analfabetismo")) + scale_color_discrete(name="Modelos para:\nCantidad de Asegurados") + geom_vline(
           xintercept = 1,
           colour = "grey60",
           linetype = 2
       )

2.1.5.1 Estandarización de coeficientes

Finalmente, podemos calcular los coeficientes estandarizados para saber cuál de los predictores tiene mayor efecto

sdVD=sd(censo2007_wide_Totales$conSeguro_Total)
sdVIs=apply(censo2007_wide_Totales[,c("analfa_Total","indep_Total")],2,sd)
DF=list(Poisson=sdVIs*coef(rp2)[c(2,3)]/sdVD,
     CuasiPoisson=sdVIs*coef(rqp)[c(2,3)]/sdVD,
     BinomNegativa=sdVIs*coef(rbn)[c(2,3)]/sdVD)%>%
       data.frame()

DF%>% kable(caption = "Coeficientes Standarizados (ordenar vía valores absolutos)")%>%
          kableExtra::kable_styling(full_width = F)
Coeficientes Standarizados (ordenar vía valores absolutos)
Poisson CuasiPoisson BinomNegativa
analfa_Total 8.2e-06 8.2e-06 7.0e-06
indep_Total -8.4e-06 -8.4e-06 -3.8e-06

2.2 Regresión Logística Binaria

2.2.1 Presentación

El trabajo en esta sección se centran en investigar quién es más proclive a ser voluntario en experimentos sociales.

knitr::knit_hooks$set(inline = as.character) # inline as string

gitLink="https://vincentarelbundock.github.io/Rdatasets/csv/carData/Cowles.csv"
vol=read.csv(gitLink)[,-1] #no first column

# formatting:
vol[,c(3,4)]=lapply(vol[,c(3,4)],as.factor)

# display table
library(magrittr) # needed for pipe %>% 
vol%>%
    rmarkdown::paged_table()

Según la información proporcionada en el repositorio, esta es la metadata: - neuroticism: Una medida de inestabilidad emocional, los valores están en escala de Eysenck.

  • extraversion: Una medida del interés por el mundo exterior de la gente y de las cosas, los valores están en la escala de Eysenck.

  • sex: dos categorías: female y male

  • volunteer: Un factor dicotómico que representa ser o no ser (yes o no) voluntario en experimentos sociales.

Esta investigación hipotiza que: El ofrecerse como voluntario a experimentos sociales está relacionado con el sexo de la persona, su nivel de neuroticismo y su nivel de extraversión.

Hasta ahora hemos querido modelar la hipótesis donde la variable dependiente eran valores continuos, y de conteos pero, como vemos, en esta hipótesis la variable dependiente es volunteer (ofrecerse como voluntario), la cuál es categórica (dicotómica). Esto no puede ser tratado con las técnicas anteriores.

Resumimos la tabla

library(summarytools)
library(kableExtra)

# usemos la función "dfSummary" de paquete "summarytools"
dfSummary(vol,
          plain.ascii  = FALSE,
          varnumbers = FALSE,
          style        = "grid", 
          graph.col=F,
          na.col    = FALSE) %>%
    kable(caption = "Descriptivos Univariados")%>%
    kable_styling(full_width = F)
Descriptivos Univariados
Variable Stats / Values Freqs (% of Valid) Valid
neuroticism
[integer]
|Mean (sd) : 11.5 (4.9)
min < med < max:
0 < 11 < 24
IQR (CV) : 7 (0
  1. |25 distinct values
|1421
(100
extraversion
[integer]
|Mean (sd) : 12.4 (3.9)
min < med < max:
2 < 13 < 23
IQR (CV) : 5 (0
  1. |22 distinct values
|1421
(100
sex
[factor]
|1. female
2. male
|\780 (54.9%)
\641 (45.1
) |1421
(100.
volunteer
[factor]
|1. no
2. yes
|\824 (58.0%)
\597 (42.0
) |1421
(100.

2.2.2 Probabilidades y ODDS

Hagamos un breve repaso de cómo usar tablas de contingencia para analizar variables categóricas. En este caso, analicemos la relación entre sexo del entrevistado y ser voluntario. Al ser ambas categóricas, y ser la hipótesis asimétrica o direccional, preparemos una tabla de contingencia que muestre ello.

dep=vol$volunteer # a la fila
ind=vol$sex # a la columna

volsexTable=table(dep,ind,dnn = c('volunteer','sex'))

### suma por fila y columna
addmargins(volsexTable)%>%
    kable(caption = "Tabla de Contingencia: 'Sexo' y 'Ser Voluntario'")%>%
    kableExtra::kable_styling(full_width = F)
Tabla de Contingencia: ‘Sexo’ y ‘Ser Voluntario’
female male Sum
no 431 393 824
yes 349 248 597
Sum 780 641 1421

Es normal que la variable independiente se sitúe en las columnas, y la variable dependiente en filas. A partir de esta información tratemos de epxlorar la hipótesis: Ser mujer está relacionado con ser voluntario en proyectos de investigación

Saber si ser mujer está relacionado con ser voluntario implica responderse: - Si elijo una mujer al azar, ¿qué probabilidad se tiene que ésta sea voluntaria? - ¿Cómo comparo el “voluntarismo” de la mujer con el “voluntarismo” del hombre?

Para ello usaremos los conceptos de probabilidad, y en particular de Odds Ratio (Szumillas 2010). Y todos los cálculos usarán los valores de la tabla anterior.

2.2.2.1 Probabilidad y Odds para caso de la mujer:

La probabilidad (entre 0 y 1) que una mujer sea voluntaria es la división del ser voluntaria entre el total de mujeres:

ProbMujVol=volsexTable[2,1]/sum(volsexTable[,1])
MASS::fractions(ProbMujVol)
## [1] 349/780

Es decir:

ProbMujVol
## [1] 0.4474359

Representemos el odds que sea voluntaria: la división entre ser o no ser voluntaria

OddsMujVol=volsexTable[2,1]/volsexTable[1,1]
MASS::fractions(OddsMujVol)
## [1] 349/431

Es decir:

OddsMujVol
## [1] 0.8097448

El odds suele representarse además como la razón entre dos probabilidades: la probabilidad que SÍ ocurra un evento dividido por la probabilidad que NO ocurra ese evento:

ProbMujVol/(1-ProbMujVol)
## [1] 0.8097448

2.2.2.2 Probabilidad y Odds para caso del hombre

La probabilidad que un hombre sea voluntario

ProbHomVol=volsexTable[2,2]/sum(volsexTable[,2])
MASS::fractions(ProbHomVol)
## [1] 248/641

Es decir:

ProbHomVol
## [1] 0.3868955

Ahora, calculamos los odds:

OddsHomVol=ProbHomVol/(1-ProbHomVol)
OddsHomVol
## [1] 0.6310433

2.2.2.3 Comparando Mujer y Hombre

Hasta aquí sabemos cuál odds ha salido mayor. Eso es información clara. Para precisar esa información, podemos dividir los odds, lo que nos da el odds ratio, que en este caso nos dirá qué diferencia produce el sexo en el voluntario:

(OR_MujHom=OddsMujVol/OddsHomVol)
## [1] 1.283184

Con ese valor, ya sabemos que el odds de ser mujer supera al odds del hombre por un factor de 0.28. El odds ratio (OR) puede ir de 0 a infinito. Un OR de 1 implica que no hay diferencias.

Veámoslo como fracción:

MASS::fractions(OddsMujVol/OddsHomVol)
## [1] 137157/106888

Si encuentras la cantidad de voluntarias del numerador, se espera la cantidad de voluntarios del denominador.

Veamos la tabla de contingencia de manera gráfica:

mosaicplot( t(volsexTable),col = c("orange", "green"),main = "")

La diferencia es clara, pero ¿será estadísticamente significativa?

2.2.3 Regresión Logística Binaria

La regresión logística modela el comportamiento de la probabilidad del evento de interés. Veamos la tabla.

### semilla
set.seed(2019)

### first hypothesis
h1=formula(volunteer~sex)

#regression
rlog1=glm(h1, data=vol,family = binomial)
modelrl=list('Ser Voluntario (I)'=rlog1)

#f <- function(x) format(x, digits = 4, scientific = FALSE)
library(modelsummary)
modelsummary(modelrl,
             title = "Regresión Logística",
             stars = TRUE,
             output = "kableExtra")
Regresión Logística
&nbsp;Ser Voluntario (I)
(Intercept) -0.211**
(0.072)
sexmale -0.249*
(0.108)
Num.Obs. 1421
AIC 1932.2
BIC 1942.7
Log.Lik. -964.101
F 5.286
RMSE 0.49
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

En general, se nota que el sexo (ser hombre) tiene efecto negativo y significativo sobre el logaritmo del odds de ser voluntario. Pero antes de ir a la interpretación detallada veamos otras consideraciones.

2.2.3.1 Consideraciones Previas

La regresión es sencilla de calcular, pero la presencia de variables categóricas puede traer complicaciones. Veamos como R está utilizando la variable dependiente:

# la representación numérica
as.numeric(as.factor(c("no","yes")))
## [1] 1 2

Qué pasaría si tuvieramos otras columnas:

vol$IsVolunteer=ifelse(vol$volunteer=="yes",T,F)
vol$IsVolunteer_1=as.integer(ifelse(vol$volunteer=="yes",1,0))
vol$IsVolunteer_claro=as.factor(ifelse(vol$volunteer=="yes",
                             "claro","nunca"))

# nos queda:
library(magrittr)
vol[,c("volunteer","IsVolunteer","IsVolunteer_1","IsVolunteer_claro")]%>%
    rmarkdown::paged_table(options = )

Reescribamos la hipótesis inicial con las nuevas variables:

h1b=formula(IsVolunteer~sex)
h1c=formula(IsVolunteer_1~sex)
h1d=formula(IsVolunteer_claro~sex)

Veamos:

  • Dicotómica como Booleana (lógica)
coef(glm(h1b, data=vol,family = binomial))
## (Intercept)     sexmale 
##  -0.2110362  -0.2493447
  • Dicotómica como Entero:
coef(glm(h1c, data=vol,family = binomial))
## (Intercept)     sexmale 
##  -0.2110362  -0.2493447
  • Dicotómica como otras palabras
coef(glm(h1d, data=vol,family = binomial))
## (Intercept)     sexmale 
##   0.2110362   0.2493447

Como se ve, R usa bien los casos Booleanos y enteros, pero ante la presencia de otros textos en la respuesta sólo usará el orden alfabético.

Otro tema es el uso de categóricas en las variables independientes. El resultado anterior ha usado a mujer como referencia: nos informa cuánto afecta ser hombre al logaritmo del odd de ser voluntario, en comparación con ser mujer (la referencia). La primera tabla nos confirma que ser hombre disminuye (aquí el signo ayuda) el logaritmo del odds (y por ende la probabilidad) de ser voluntario. Veamos alternativamente la tabla, con la misma regresión pero con la categoría hombre como referencia.

# cambio de referencia
vol$sex=relevel(vol$sex,ref = "male")

#regresion
rlog1=glm(h1, data=vol,family = binomial)
modelrl=list('Ser Voluntario (I)'=rlog1)
modelsummary(modelrl,
             title = "Regresión Logística (con cambio de referencia en sexo)",
             stars = TRUE,
             output = "kableExtra")
Regresión Logística (con cambio de referencia en sexo)
&nbsp;Ser Voluntario (I)
(Intercept) -0.460***
(0.081)
sexfemale 0.249*
(0.108)
Num.Obs. 1421
AIC 1932.2
BIC 1942.7
Log.Lik. -964.101
F 5.286
RMSE 0.49
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

La tabla nos muestra el efecto (significativo al 0.05) positivo (nuevamente el signo ayuda) de ser mujer en ser voluntario. Recuerda que este tema de la categoría de referencia se aplica a todo tipo de regresión.

2.2.3.2 Interpretación

Los coeficientes de la anterior tabla, como modelan al logaritmo del odds, no son fáciles de interpretar. Pero, si le aplicamos exponencial obtendrás un odds ratio: veamos el caso del coeficiente 0.249 de la anterior tabla.

sexF=coef(rlog1)["sexfemale"]
exp(sexF)
## sexfemale 
##  1.283184

Este valor ya lo conocíamos, pero ahora sabemos que el efecto de sexo es significativo gracias a la regresión logística, alque no sabíamos con las tabla de contingencia. Lo que es más, sabemos que el hecho de ser mujer eleva en promedio en un factor de 0.28 al odds ratio de ser voluntario que si el entrevistado fuera hombre.

Algo importante ahora es que con la regresión logística ya podemos tener predictores numéricos, lo que escapa de la utilidad de las tablas de contingencia. Así, podemos en el modelo II añadir una numérica los predictores: El ofrecerse como voluntario está relacionado con el sexo de la persona, y su nivel de neuroticismo.

La siguiente tabla nos muestra los resultados para el modelo I y II, ya con los coeficientes exponenciados:

### second hypothesis
h2=formula(volunteer~sex + neuroticism)
rlog2=glm(h2, data=vol,family = binomial)

modelsrl=list('Ser Voluntario (I)'=rlog1,
             'Ser Voluntario (II)'=rlog2)

# formato creado para modelsummary
formatoNumero = function(x) format(x, digits = 4, scientific = FALSE)
modelsummary(modelsrl,
             fmt=formatoNumero, # usa función que creé antes
             exponentiate = T, # coeficientes sin logaritmo
             statistic = 'conf.int', # mostrar ICs
             title = "Regresión Logísticas (Coeficientes Exponenciados)",
             stars = TRUE,
             output = "kableExtra")
Regresión Logísticas (Coeficientes Exponenciados)
&nbsp;Ser Voluntario (I) &nbsp;Ser Voluntario (II)
(Intercept) 0.631*** 0.6268**
[0.5377, 0.7391] [0.4733, 0.8281]
sexfemale 1.283* 1.2817*
[1.0378, 1.5878] [1.0329, 1.5917]
neuroticism 1.0007
[0.9789, 1.0228]
Num.Obs. 1421 1421
AIC 1932.2 1934.2
BIC 1942.7 1950.0
Log.Lik. -964.101 -964.099
F 5.286 2.645
RMSE 0.49 0.49
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

En el modelo II notamos que el efecto de ser mujer disminuye, pero no mucho. Probemos ahora el modelo III, el que completa la hipótesis de los investigadores: El ofrecerse como voluntario está relacionado con el sexo de la persona, su nivel de neuroticismo y su nivel de extraversión.

2.2.3.2.1 Hipótesis final

Veamos lo siguiente

h3=formula(volunteer~sex + neuroticism + extraversion)
rlog3=glm(h3, data=vol,family = binomial)

summary(rlog3)
## 
## Call:
## glm(formula = h3, family = binomial, data = vol)
## 
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  -1.351658   0.239818  -5.636 1.74e-08 ***
## sexfemale     0.235161   0.111185   2.115   0.0344 *  
## neuroticism   0.006362   0.011357   0.560   0.5754    
## extraversion  0.066325   0.014260   4.651 3.30e-06 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 1933.5  on 1420  degrees of freedom
## Residual deviance: 1906.1  on 1417  degrees of freedom
## AIC: 1914.1
## 
## Number of Fisher Scoring iterations: 4

Ahora todos los modelos juntos exponenciados:

modelsrl=list('Ser Voluntario (I)'=rlog1,
             'Ser Voluntario (II)'=rlog2,
             'Ser Voluntario (III)'=rlog3)

f <- function(x) format(x, digits = 4, scientific = FALSE)
modelsummary(modelsrl,
             fmt=formatoNumero,
             exponentiate = T, 
             statistic = 'conf.int',
             title = "Comparando Regresión Logísticas (Coeficientes Exponenciados)",
             stars = TRUE,
             gof_map = c("nobs","aic","bic","rmse","logLik"), #comparar
             gof_omit = c("F"),
             output = "kableExtra")
Comparando Regresión Logísticas (Coeficientes Exponenciados)
&nbsp;Ser Voluntario (I) &nbsp;Ser Voluntario (II) &nbsp;Ser Voluntario (III)
(Intercept) 0.631*** 0.6268** 0.2588***
[0.5377, 0.7391] [0.4733, 0.8281] [0.1611, 0.4127]
sexfemale 1.283* 1.2817* 1.2651*
[1.0378, 1.5878] [1.0329, 1.5917] [1.0177, 1.5738]
neuroticism 1.0007 1.0064
[0.9789, 1.0228] [0.9842, 1.0291]
extraversion 1.0686***
[1.0393, 1.0991]
Num.Obs. 1421 1421 1421
AIC 1932.2 1934.2 1914.1
BIC 1942.7 1950.0 1935.1
RMSE 0.49 0.49 0.49
Log.Lik. -964.101 -964.099 -953.031
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Del modelo III tenemos que: - Tres coeficientes son mayores que uno (podrían tener un efecto positivo, pero falta revisar otra información). - El intervalo de confianza de la neuroticism incluye al 1. - El p-valor de neuroticism es mayor que 0.1 (la probabilidad que sea 1 -no tenga efecto- es mayor al 10%, lo que contribuye a aceptar que no tendría efecto), por lo que consideramos no significativo.

2.2.3.2.2 Interpretación de la tabla exponenciada

El coeficiente de la variable sexo con hombre como referencia es:

round(exp(coef(rlog3)["sexfemale"]),4)
## sexfemale 
##    1.2651

Ese número es el factor por el que se multiplica en promedio el odds ratio de ser voluntario cuando se es mujer, o dicho de otra manera, el odds ratio de ser voluntario se eleva en promedio en ese porcentaje:

100*round(abs(1-exp(coef(rlog3)["sexfemale"])),4)
## sexfemale 
##     26.51

De manera similar, el coeficiente de la variable extraversión es:

round(exp(coef(rlog3)["extraversion"]),4)
## extraversion 
##       1.0686

Ese número es el factor por el que se multiplica en promedio el odds ratio de ser voluntario cada vez que la escala de extroversión aumenta en uno, o dicho de otra manera, el odds ratio de ser voluntario se eleva en promedio en este porcentaje cada vez que la escala de extroversión aumenta en uno:

100*round(abs(1-exp(coef(rlog3)["extraversion"])),4)
## extraversion 
##         6.86
2.2.3.2.3 Estandarización de coeficientes

Podemos calcular los coeficientes estandarizados para saber cuál de los predictores del modelo III tiene mayor efecto:

vol$sexFem_num=ifelse(vol$sex=='male',0,1) # 1 es para mujer
sdVIs=apply(vol[,c("sexFem_num","neuroticism", "extraversion")],2,sd)
DF=list(LogitSt=sdVIs*coef(rlog3)[c(2,3,4)])%>%
       data.frame()

# DF tiene los coeficientes estandarizados

DF%>% kable(caption = "Coeficientes Estandarizados (ordenar vía valores absolutos)")%>%
          kableExtra::kable_styling(full_width = F)
Coeficientes Estandarizados (ordenar vía valores absolutos)
LogitSt
sexFem_num 0.1170580
neuroticism 0.0311676
extraversion 0.2582503

Usando valores absolutos notamos cuál es el orden de importancia, en este caso extraversion es la que más efecto tiene. Pero no usemos esos valores para interpretar el efecto numérico, sólo para saber cuál afecta más que otro.

2.2.3.3 Decidiendo mejor modelo

R entrega algunos índice muy buenos para comparar modelos logísticos, el BIC, AIC, RMSE, y Log.Lik. Los primeros tres indican que el modelo es mejor si tienen valores bajos; mientras que el último será mejor si su valor aumenta.

Sin embargo, las diferencias pueden ser mínimas entre estos índices, por lo que es apropiado usar el test de razón de verosimilitud (likelihood ratio test-LRT), cuyo resultado es el siguiente:

library(lmtest)

lrtest(rlog1,rlog2, rlog3) %>%
kable(caption = "Tabla LRT para comparar modelos")%>%kableExtra::kable_styling(full_width = FALSE)
Tabla LRT para comparar modelos
#Df LogLik Df Chisq Pr(>Chisq)
2 -964.1009 NA NA NA
3 -964.0993 1 0.0033761 0.9536657
4 -953.0306 1 22.1372287 0.0000025

Como vemos en la anterior tabla, pasar del modelo 1 al modelo 2 no resulta significativo (probabilidad de similitud entre los modelos es mayor que 90%), por lo que no se rechaza la hipótesis nula de igualdad de modelos. Pero sí resultado significativo pasar del modelo 2 al modelo 3.

Podemos comparar los modelo I, II y III de manera gráfica:

library(ggplot2)
dotwhisker::dwplot(list(Logit_I=rlog1,Logit_II=rlog2,Logit_III=rlog3),
                   exp=T) + #exponenciados!
            scale_y_discrete(labels=c("extraversion",
                                      "neuroticism",
                                      "sex (ref: male)")) +
            scale_color_discrete(name="Modelos para:\nSer Voluntario") +
            geom_vline(xintercept = 1,
                       colour = "grey60",
                       linetype = 2)

Acá se puede apreciar claramente por qué el neuroticismo no es significativo.

2.2.3.4 Evaluando modelo seleccionado

Cuando usamos una regresión logística queremos predecir probabilidades, en este caso, ante una combinación de los predictores X, qué probabilidad se obtiene con esta regresión para ser voluntario. Esos son los valores ajustados (fitted) que entrega el modelo, y nos servirá para evaluar si nos hemos acercado al Y original. Calculemos tales probabilidades:

vol$predictedProbs <- predict(rlog3, vol,type = "response")
# veamos algunos de ellos
head(vol$predictedProbs)
## [1] 0.4619546 0.4080078 0.4356976 0.5648592 0.4914463 0.4210156

Aquí podemos usar estos valores para crear el 0 o 1:

vol$predicted=ifelse(vol$predictedProbs>0.5,"yes","no")

Veamos algunas filas de estas dos columnas:

head(vol[,c("predicted","volunteer")],10)
##    predicted volunteer
## 1         no        no
## 2         no        no
## 3         no        no
## 4        yes        no
## 5         no        no
## 6         no        no
## 7         no        no
## 8         no        no
## 9         no        no
## 10        no        no

Las columnas predicted busca ser la misma que la columna volunteer, pero en algunos casos fallará.

Para saber qué tanto se acertó creamos una Matriz de Confusión usando el paquete cvms en la siguiente figura:

library(cvms)
## Warning: package 'cvms' was built under R version 4.4.3
ConfMatrix=confusion_matrix(predictions =  vol$predicted,
                      targets= vol$volunteer)
library(cvms)
plot_confusion_matrix(ConfMatrix,
                      class_order=c("yes","no"))
## Warning in plot_confusion_matrix(ConfMatrix, class_order = c("yes", "no")):
## 'ggimage' is missing. Will not plot arrows and zero-shading.
## Warning in plot_confusion_matrix(ConfMatrix, class_order = c("yes", "no")):
## 'rsvg' is missing. Will not plot arrows and zero-shading.

La diagonal secundaria o antidiagonal debería tener solo 0s si tuviésemos una predicción perfecta, y los demás valores estar en la diagonal principal. El modelo, debería servir para reducir sustantivamente ese error.

Note que en la matriz de confusión se puede calcular valores clave: Verdaderos Positivos (VP), Verdaderos Negativos (VN), Falsos Positivos (FP), Falsos Negativos (FN).

Y a partir de ellos: - La Sensitividad (VP/(VP+FN)): Utilidad del modelo para determinar el “voluntario” (modelo que predice a un voluntario que realmente era voluntario en la data orignial)

ConfMatrix$Sensitivity
## [1] 0.1474037
  • La Especificidad (VN/(VN+FP)): Utilidad delm odelo para determinar el “no voluntario” (modelo predice a un no voluntario que realmente no era voluntario en la data original)
ConfMatrix$Specificity
## [1] 0.8932039

Una medida general de resumen es el índice de Kappa: Este índice vale 1 si la predicción es perfecta, y 0 si faltan todas las predicciones:

ConfMatrix$Kappa
## [1] 0.04497635

2.2.3.5 Prediciendo valores concretos

Si nos quedamos con el modelo III, ya que es el mejor, podemos utilizarlo para predicciones. Veamos:

  • Ejemplo 1: Predecir probabilidad de voluntariado de una mujer con nivel de neuroticismo=13 y extraversion=8.
ToInput1 <- data.frame(sex=factor(c("female")), 
                      neuroticism=13, extraversion=8)
predict(object = rlog3, newdata = ToInput1, type = "response")
##         1 
## 0.3767916
  • Ejemplo 2: Predecir probabilidad de voluntariado de una mujer y hombre con los mismos niveles anteriores de neuroticismo y extraversión.
ToInput2 <- data.frame(sex=factor(c("female","male")), 
                      neuroticism=13, extraversion=8)
predict(object = rlog3, newdata = ToInput2, type = "response")
##         1         2 
## 0.3767916 0.3233650
  • Ejemplo 3: Predecir probabilidad de voluntariado de dos mujeres, con nivel de neuroticismo de 13 y 10, y extraversión de 8 y 21, respectivamente:
ToInput3 <- data.frame(sex=factor(c("female","female")), 
                      neuroticism=c(13,10), extraversion=c(8,21))
predict(object = rlog3, newdata = ToInput3, type = "response")
##         1         2 
## 0.3767916 0.5841798

2.2.3.6 Efectos marginales

Recuérdese qué tenemos en volunteer, predicted y predictedProbs:

head(vol[,c('volunteer','predicted','predictedProbs')])
##   volunteer predicted predictedProbs
## 1        no        no      0.4619546
## 2        no        no      0.4080078
## 3        no        no      0.4356976
## 4        no       yes      0.5648592
## 5        no        no      0.4914463
## 6        no        no      0.4210156

Cuando analizábamos la regresión Gaussiana decíamos que si el covariado aumentaba en una unidad, el coeficiente del covariado representaba el cambio promedio en la dependiente. Ese es el efecto marginal del covariado.

Esa interpretación no se puede saber con ninguno de los valores calculados hasta ahora para la regresión logística. Para ello, necesitamos calcular de manera expresa esos efectos marginales

# interpracion usando marginal effects:
library(margins)
## Warning: package 'margins' was built under R version 4.4.2
# 
margins(rlog3)
## Average marginal effects
## glm(formula = h3, family = binomial, data = vol)
##  neuroticism extraversion sexfemale
##      0.00152      0.01585   0.05623

Estos coeficientes sí indican cuanto aumentaría en promedio la probabilidad de ser voluntario, cuando el predictor aumenta en una unidad. Sin embargo, recordemos que sabemos de antemano que no todos estos covariados son significativos. Veamos los intervalos de confianza:

marginalsData=summary(margins(rlog3))
marginalsData%>% kable(caption = "Efectos Marginales Promedio (AME)- Modelo III") %>%kableExtra::kable_styling(full_width = T)
Efectos Marginales Promedio (AME)- Modelo III
factor AME SE z p lower upper
extraversion 0.0158493 0.0033068 4.7928761 0.0000016 0.0093680 0.0223306
neuroticism 0.0015202 0.0027128 0.5603962 0.5752092 -0.0037968 0.0068373
sexfemale 0.0562289 0.0264868 2.1229032 0.0337620 0.0043157 0.1081420

Podemos verlo gráficamente también:

library(ggplot2)
base= ggplot(marginalsData,aes(x=factor, y=AME)) + geom_point()
base +  geom_errorbar(aes(ymin=lower, ymax=upper))

Ya sabemos que si el intervalo incluye al cero, ese covariado no será significativo.

2.3 Regresión Cox

2.3.1 Introducción

Estos datos nos dan información sobre ex convictos y el tiempo que demoran en volver a la cárcel:

link="https://docs.google.com/spreadsheets/d/e/2PACX-1vQSlGaMI8Q8qlXI0Bp3m7BQcEh8ZLzaP7RymVtRYkg3ah1sZVlCi6-HmeKCic1RjfuH3gL_wrbMms88/pub?gid=1573532387&single=true&output=csv"
carcel=read.csv(link, stringsAsFactors = T)
str(carcel)
## 'data.frame':    432 obs. of  10 variables:
##  $ semanasLibre       : int  20 17 25 52 52 52 23 52 52 52 ...
##  $ fueArrestado       : int  1 1 1 0 0 0 1 0 0 0 ...
##  $ tuvoApoyoDinero    : int  0 0 0 1 0 0 0 1 0 0 ...
##  $ edad               : int  27 18 19 23 19 24 25 21 22 20 ...
##  $ esNegro            : int  1 1 0 1 0 1 1 1 1 1 ...
##  $ expLaboralPrevia   : int  0 0 1 1 1 1 1 1 0 1 ...
##  $ casado             : int  0 0 0 1 0 0 1 0 0 0 ...
##  $ libertadCondicional: int  1 1 1 1 1 0 1 1 0 0 ...
##  $ vecesEnCarcel      : int  3 8 13 1 3 2 0 4 6 0 ...
##  $ nivelEduca         : int  2 3 2 4 2 3 3 2 2 4 ...

Tenemos que ajustar algunos datos

carcel[,c(2,3,5,6,7,8)]=lapply(carcel[,c(2,3,5,6,7,8)], as.factor)
carcel$nivelEduca=as.ordered(carcel$nivelEduca)
str(carcel)
## 'data.frame':    432 obs. of  10 variables:
##  $ semanasLibre       : int  20 17 25 52 52 52 23 52 52 52 ...
##  $ fueArrestado       : Factor w/ 2 levels "0","1": 2 2 2 1 1 1 2 1 1 1 ...
##  $ tuvoApoyoDinero    : Factor w/ 2 levels "0","1": 1 1 1 2 1 1 1 2 1 1 ...
##  $ edad               : int  27 18 19 23 19 24 25 21 22 20 ...
##  $ esNegro            : Factor w/ 2 levels "0","1": 2 2 1 2 1 2 2 2 2 2 ...
##  $ expLaboralPrevia   : Factor w/ 2 levels "0","1": 1 1 2 2 2 2 2 2 1 2 ...
##  $ casado             : Factor w/ 2 levels "0","1": 1 1 1 2 1 1 2 1 1 1 ...
##  $ libertadCondicional: Factor w/ 2 levels "0","1": 2 2 2 2 2 1 2 2 1 1 ...
##  $ vecesEnCarcel      : int  3 8 13 1 3 2 0 4 6 0 ...
##  $ nivelEduca         : Ord.factor w/ 5 levels "1"<"2"<"3"<"4"<..: 2 3 2 4 2 3 3 2 2 4 ...

El propósito de este trabajo es entender qué factores afectan la reincidencia de los presos dejados en libertad. Revisando la metada, tenemos dos variables que pueden ser usadas como dependiente. Exploremos ambas:

  • semanasLibre
summary(carcel$semanasLibre)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    1.00   50.00   52.00   45.85   52.00   52.00
  • fueArrestada
table(carcel$fueArrestado)
## 
##   0   1 
## 318 114

Con la data disponible podemos proponer estas hipótesis:

  1. H1: El tiempo que permanece en libertad un exreo hasta que vuelve a la cárcel está afectada si tuvo financiamiento, por su nivel educativo, y por sus encarcelamientos previos.

  2. H2: El que un exreo vuelva a la cárcel está afectado si tuvo financiamiento, por su nivel educativo, y por sus encarcelamientos previos

Estas dos hipótesis resultan ser una regresión Gaussiana y una regresión Logística Binaria

#
h1=formula(semanasLibre~tuvoApoyoDinero+nivelEduca+vecesEnCarcel)
h2=formula(fueArrestado~tuvoApoyoDinero+nivelEduca+vecesEnCarcel)

#
rGauss=lm(h1,data=carcel)
rLogit=glm(h2,data=carcel, family = binomial)

#
models=list('Tiempo en Libertad (Gauss)'=rGauss, "Ser Arrestado (Logit)"=rLogit)

#
library(modelsummary)
modelsummary(models,
             title = "Regresiones Gauss y Logit",
             stars = TRUE,
             output = "kableExtra")
Regresiones Gauss y Logit
Tiempo en Libertad (Gauss) Ser Arrestado (Logit)
(Intercept) 48.474*** -1.597***
(1.297) (0.295)
tuvoApoyoDinero1 2.159+ -0.473*
(1.206) (0.227)
nivelEduca.L 1.061 -0.618
(2.965) (0.769)
nivelEduca.Q 4.032 -0.717
(2.566) (0.659)
nivelEduca.C -0.555 0.507
(1.992) (0.493)
nivelEduca^4 0.551 -0.049
(1.423) (0.321)
vecesEnCarcel -0.737*** 0.097**
(0.212) (0.037)
Num.Obs. 432 432
R2 0.046
R2 Adj. 0.033
AIC 3413.9 488.7
BIC 3446.5 517.2
Log.Lik. -1698.952 -237.357
F 3.431 3.601
RMSE 12.35 0.43
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

De la presente tabla podemos señalar que para la Regresión Gaussiana:

  • El tener apoyo financiero aumenta el tiempo en libertad con una significancia de 0.1.
  • El nivel educativo no tendría efecto en el tiempo en libertad (probabilidad de efecto cero es mayor al 0.1)
  • El tener encarcelamientos previos disminuye el tiempo en libertad con una significancia de 0.001.

Para el caso de los resultados de la Regresión Logística: - El tener apoyo financiero disminuye el Log Odds Ratio de ser arrestado con una significancia de 0.05. - El nivel educativo no tendría efecto en el Log Odds Ratio de ser arrestado (probabilidad de efecto cero es mayor al 0.1) - El tener encarcelamientos previos aumenta el Log Odds Ratio de ser arrestado con una significancia de 0.01.

Asimismo, nota que en la tabla la variable ordinal educación viene con una letras L-Q, etc. Esto informa si el efecto de la ordinal es lineal, cuadrático, cúbico, etc.

¿Qué problema no estamos advirtiendo? 1. Que la duración de la libertad está condicionada a ser arrestado, ambas son un todo. Una situación dura hasta que algo sucede y la situación cambia. 2. Que el hecho de ser arrestado es un evento, NO una característica y, lo que es más, el NO ser arrestado es algo que puede variar en el tiempo, sólo que la investigación acabó y el libertado aún seguía libre.

En situaciones que combinan duración y observación de eventos, debemos usar el EHA o Análisis de Eventos Históricos. Esta técnica funciona de tal manera que puede lidear con el hecho de no darse el evento, en este caso, no ser arrestado: esto representa un caso censurado.

2.3.2 Analizando Eventos Históricos

El primer paso para usar EHA es indicarla a R que trate a la data de esa manera. Creamos una nueva columna survival en nuestra tabla actual.

library(survival)
## Warning: package 'survival' was built under R version 4.4.2
# note que necesito el factor como numérico, ambas variables como numéricas
carcel$survival=with(carcel,Surv(time = semanasLibre,event =  as.numeric(fueArrestado)))
# que es:

library(magrittr) # needed for pipe %>% 
carcel%>%
    rmarkdown::paged_table()

2.3.2.1 Análisis Kaplan-Meier (KM)

KM es el procedimiento descriptivo básico que se utiliza para ver la dinámica de sobrevivencia. Así, vemos que la siguiente figura nos muestra el comportamiento genérico de permanecer libre.

library(ggplot2)
library(ggfortify)
## Warning: package 'ggfortify' was built under R version 4.4.3
#aqui el generico
KM.generico = survfit(survival ~ 1, data = carcel)

###graficando
ejeX='SEMANAS\n curva cae cuando alguien es arrestado'
ejeY='Probabilidad \n(PERMANECER LIBRE)'
titulo="Curva de Sobrevivencia: permanecer libre"
autoplot(KM.generico,xlab=ejeX,ylab=ejeY, main = titulo,conf.int = F)

Esta figura nos da una idea de cómo se comporta esta población; por ejemplo, la gráfica nos dice que si pasan 40 semanas, la probabilidad de seguir libre está cerca al 80%.

El análisis KM es más interesante para ver una comparación:

KM_H1=formula(survival ~ tuvoApoyoDinero)

KM.fondos = survfit(KM_H1, data = carcel)

###
ejeX='SEMANAS\n curva cae cuando alguien es arrestado'
ejeY="Prob ('seguir libre')"
titulo="Curva de Sobrevivencia: ¿Beneficia el apoyo financiero?"

autoplot(KM.fondos,xlab=ejeX,ylab=ejeY, 
         main = titulo,conf.int = F)  + 
        labs(colour = "Apoyo Financiero?") + 
         scale_color_discrete(labels = c("No", "Sí"))

La posición de las curvas nos hace pensar que le cuesta más a los que no tuvieron fondos permanecer libres. Para una mayor confianza en tal hipótesis, podemos hacer la prueba de Mantel-Cox (LogRank), obteniendo este nivel de significancia:

LogRank=survdiff(KM_H1, data = carcel)
# ver p-valor
LogRank$pvalue
## [1] 0.05011612

Según la H0 de KM: no hay diferencias entre grupos, con el p-valor obtenido la diferencia no es significativa al 0.05. (pero sí al 0.1) Esta figura lo aclara más:

autoplot(KM.fondos,xlab=ejeX,ylab=ejeY, 
         main = titulo,conf.int = T)+ 
        labs(colour = "Apoyo Financiero?") + 
         scale_color_discrete(labels = c("No", "Sí"))

De nuevo, como sólo hay dos variables, es difícil saber qué más interviene. De ahí que necesitamos un modelo que permita análisis multivariado, o sea la Regresión Cox.

2.3.2.2 Regresión Cox

Como toda regresión, esta técnica permite utilizar regresores o predictores o covariados, pero no modela la duración sino el riesgo de que el evento suceda (ser re arrestado)

COX_H1= formula(survival~tuvoApoyoDinero+nivelEduca+vecesEnCarcel)

#regression
rcox1 <- coxph(COX_H1,data=carcel)
modelcox=list('Riesgo - Re arrestado'=rcox1,'Riesgo- Re arrestado (exponenciado)'=rcox1)

#f <- function(x) format(x, digits = 4, scientific = FALSE)
library(modelsummary)
modelsummary(modelcox,
             #fmt=f,
             exponentiate = c(F,T), 
             statistic = 'conf.int',
             title = "Regresión Cox",
             stars = TRUE,
             output = "kableExtra")
Regresión Cox
Riesgo - Re arrestado Riesgo- Re arrestado (exponenciado)
tuvoApoyoDinero1 -0.415* 0.660*
[-0.789, -0.042] [0.454, 0.959]
nivelEduca.L -0.558 0.573
[-1.976, 0.861] [0.139, 2.366]
nivelEduca.Q -0.694 0.500
[-1.907, 0.519] [0.149, 1.680]
nivelEduca.C 0.396 1.486
[-0.506, 1.298] [0.603, 3.662]
nivelEduca^4 -0.008 0.992
[-0.582, 0.566] [0.559, 1.762]
vecesEnCarcel 0.087** 1.091**
[0.032, 0.142] [1.032, 1.152]
Num.Obs. 432 432
AIC 1338.1 1338.1
BIC 1362.6 1362.6
RMSE 0.51 0.51
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

La tabla muestra los coeficientes originales y exponenciados (las razones de riesgo o hazard ratios). Veamos cada uno:

  • Se puede inferir que dar financiamiento disminuye el riesgo de ser re arrestado con una significancia del 0.05 La columna a la derecha (los HRs) nos da el promedio de ese efecto en el riesgo como la distancia absoluta al uno (1); en este caso:
(apoyoDinero=abs(1-exp(coef(rcox1)[1])))
## tuvoApoyoDinero1 
##        0.3399735

De ahí que usando el hazard ratio, podemos sostener que el riesgo de volver a la cárcel para alguien con apoyo financiero es en promedio 34% menor a la de los que no lo tienen.

  • Se puede inferir que es muy poco probable que el nivel educativo tenga efecto en el riesgo de ser re arrestado.

  • Se puede inferir que haber estado previamente en la cárcel aumenta el riesgo de ser re arrestado con una significancia del 0.01. El promedio de ese riesgo es la distancia absoluta al uno (1); en este caso:

(carcelantes=abs(1-exp(coef(rcox1)[6])))
## vecesEnCarcel 
##     0.0907188

De ahí que podemos sostener que el riesgo de volver a la cárcel para alguien con apoyo financiero es en promedio 9.07% mayor cada vez que aumenta en uno los años de encarcelamientos previos.

Los hazard ratios de la tabla anterior (columna a la derecha, exponenciados) podemos verlos gráficamente en la siguiente figura:

library(survminer)
## Warning: package 'survminer' was built under R version 4.4.3
## Cargando paquete requerido: ggpubr
## Warning: package 'ggpubr' was built under R version 4.4.2
## 
## Adjuntando el paquete: 'ggpubr'
## The following object is masked from 'package:cvms':
## 
##     font
## 
## Adjuntando el paquete: 'survminer'
## The following object is masked from 'package:survival':
## 
##     myeloma
ggforest(rcox1, data = carcel,main = "¿Quiénes tienen mayor riesgo de volver a ser encarcelados?")

2.3.2.3 Comparación

Podemos plantear un modelo sin educación

COX_H1= formula(survival~tuvoApoyoDinero+nivelEduca+vecesEnCarcel)
COX_H2= formula(survival~tuvoApoyoDinero+vecesEnCarcel)

#regression
rcox2 <- coxph(COX_H2,data=carcel)
modelcox=list('Riesgo de Re arrestado (I)'=rcox2,'Riesgo de Re arrestado (II)'=rcox1)

#f <- function(x) format(x, digits = 4, scientific = FALSE)
library(modelsummary)
modelsummary(modelcox,
             #fmt=f,
             exponentiate = T, 
             statistic = 'conf.int',
             title = "Regresión Cox (sólo Hazard Ratios)",
             stars = TRUE,
             output = "kableExtra")
Regresión Cox (sólo Hazard Ratios)
&nbsp;Riesgo de Re arrestado (I) &nbsp;Riesgo de Re arrestado (II)
tuvoApoyoDinero1 0.671* 0.660*
[0.462, 0.974] [0.454, 0.959]
vecesEnCarcel 1.110*** 1.091**
[1.053, 1.169] [1.032, 1.152]
nivelEduca.L 0.573
[0.139, 2.366]
nivelEduca.Q 0.500
[0.149, 1.680]
nivelEduca.C 1.486
[0.603, 3.662]
nivelEduca^4 0.992
[0.559, 1.762]
Num.Obs. 432 432
AIC 1338.3 1338.1
BIC 1346.4 1362.6
RMSE 0.51 0.51
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Vemos que las medidas globales de ajuste no son tan diferentes, por lo que es mejor evaluar la significancia de esas diferencias.

anova(rcox2,rcox1)%>%
knitr::kable(caption = "Tabla anova para comparar modelos")%>%kableExtra::kable_styling(full_width = FALSE)
Tabla anova para comparar modelos
loglik Chisq Df Pr(>&#124;Chi&#124;)
-667.1444 NA NA NA
-663.0728 8.143309 4 0.0864674

Así, añadir nivel educativo no es significativo al 0.05, pero sí lo es al 0.1. Esto daría espacio para sostener la inclusión de nivel educativo en el modelo.

3 Análisis Factorial / Análisis de Variables Latentes

Muchas veces queremos saber si algún conjunto de variables representa algún concepto, al cual se le denomina técnicamente variable latente. Las técnicas son variadas, pero en esta unidad veremos análisis factorial exploratorio, y en la próxima el confirmatorio para tratar de reducir varias variables en otra u otra más simples.

En esta unidad trabajaremos con la data de: - Índice de Desarrollo Humano (IDH) - Índice de Democracia del Economist (IDE)

Este ejemplo usa dos índices calculados cada uno a partir de un conjunto de indicadores. Nuestro interés no está en los índices, sino en utilizar los indicadores para producir los índices usando las técnicas que veremos.

3.1 Cargar los datos

dataPreparada="https://github.com/Estadistica-AnalisisPolitico/Sesion6/raw/main/idhdemo.csv"
idhdemo=read.csv(dataPreparada)

3.2 Análisis Factorial Exploratorio (EFA)

El análisis factorial exploratorio (Watkins 2018), como su nombre indica, explora la data y nos entrega posibles factores que resumen cada uno un conjunto de variables. Seleccionemos la data que necesitamos:

names(idhdemo)
##  [1] "country"          "hdiRanking"       "hdi"              "hdiLife"         
##  [5] "hdiSchoolExpec"   "hdiMeanEduc"      "hdiGni"           "ideRanking"      
##  [9] "ideRegime"        "ide"              "ideElectoral"     "ideFunctioning"  
## [13] "ideParticipation" "ideCulture"       "ideLiberties"

Seleccionamos:

dontselect=c("country","hdiRanking","hdi",
             "ideRanking","ideRegime","ide")
select=setdiff(names(idhdemo),dontselect) 
theData=idhdemo[,select]

# usaremos:
library(magrittr)
head(theData,10)%>%
    rmarkdown::paged_table()

Como apreciamos, dejamos afuera el hdi y el ide. Ahora, calculemos las correlaciones entre todas las variables:

library(polycor)
## Warning: package 'polycor' was built under R version 4.4.3
corMatrix=polycor::hetcor(theData)$correlations

El objeto corMatrix guarda las correlaciones entre todas las variables

round(corMatrix,2)
##                  hdiLife hdiSchoolExpec hdiMeanEduc hdiGni ideElectoral
## hdiLife             1.00           0.79        0.75   0.78         0.51
## hdiSchoolExpec      0.79           1.00        0.80   0.73         0.54
## hdiMeanEduc         0.75           0.80        1.00   0.71         0.50
## hdiGni              0.78           0.73        0.71   1.00         0.43
## ideElectoral        0.51           0.54        0.50   0.43         1.00
## ideFunctioning      0.65           0.66        0.60   0.62         0.85
## ideParticipation    0.51           0.57        0.53   0.45         0.80
## ideCulture          0.44           0.46        0.37   0.57         0.49
## ideLiberties        0.54           0.60        0.55   0.53         0.91
##                  ideFunctioning ideParticipation ideCulture ideLiberties
## hdiLife                    0.65             0.51       0.44         0.54
## hdiSchoolExpec             0.66             0.57       0.46         0.60
## hdiMeanEduc                0.60             0.53       0.37         0.55
## hdiGni                     0.62             0.45       0.57         0.53
## ideElectoral               0.85             0.80       0.49         0.91
## ideFunctioning             1.00             0.75       0.65         0.87
## ideParticipation           0.75             1.00       0.51         0.80
## ideCulture                 0.65             0.51       1.00         0.59
## ideLiberties               0.87             0.80       0.59         1.00

Podemos graficar las correlaciones:

library(ggcorrplot)

ggcorrplot(corMatrix)

Si puedes ver “bloques” de colores en la gráfica significa que esas variables representan algunos factores.

3.2.1 Veamos los pasos que el EFA requiere:

3.2.1.1 Verificar si los datos permiten factorizar

Si el Overall MSA resulta cercano al 1 indica que es muy adecuado hacer un EFA. Valores bajos indican que las correlaciones pueden ser débiles o espurias.

  • 0.90 Excelente

  • 0.80-0.89 Muy bueno
  • 0.70-0.79 Bueno
  • 0.60-0.69 Aceptable
  • 0.50-0.59 Mediocre
  • < 0.50 No aceptable
library(psych)
## Warning: package 'psych' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'psych'
## The following object is masked from 'package:polycor':
## 
##     polyserial
## The following objects are masked from 'package:arm':
## 
##     logit, rescale, sim
## The following object is masked from 'package:modelsummary':
## 
##     SD
## The following objects are masked from 'package:ggplot2':
## 
##     %+%, alpha
## The following objects are masked from 'package:DescTools':
## 
##     AUC, ICC, SD
psych::KMO(corMatrix) 
## Kaiser-Meyer-Olkin factor adequacy
## Call: psych::KMO(r = corMatrix)
## Overall MSA =  0.9
## MSA for each item = 
##          hdiLife   hdiSchoolExpec      hdiMeanEduc           hdiGni 
##             0.90             0.92             0.91             0.88 
##     ideElectoral   ideFunctioning ideParticipation       ideCulture 
##             0.84             0.93             0.96             0.87 
##     ideLiberties 
##             0.88

3.2.1.2 Verificar si la matriz de correlaciones es adecuada

Aquí hay dos pruebas:

  • Hnula: La matriz de correlación es una matriz identidad
cortest.bartlett(corMatrix,n=nrow(theData))$p.value>0.05
## [1] FALSE

Este test evalúa la hipótesis nula de que la matriz de correlaciones es una matriz identidad, es decir, que no hay correlación entre variables (lo cual sería malo para el EFA). Nosotros queremos rechazar esta hipótesis (y para ello necesitamos que el p-valor < 0.05). El FALSE sinifica que el p-valor es menor a 0.05, por lo tanto rechazamos la hipótesis nula.

  • Hnula: La matriz de correlación es una matriz singular
library(matrixcalc)

is.singular.matrix(corMatrix)
## [1] FALSE

Esta prueba evalúa si la matriz es singular, es decir, no invertible, lo cual causaría problemas matemáticos al hacer el EFA. Para que el análisis factorial funciones correctamente, la matriz debe NO ser singular (es decir, debe ser invertible). Si sale FALSE nos indica que la matriz NO es singular

3.2.1.3 Número de factores o variables latentes

Necesitamos determinar en cuántos factores o variables latentes podríamos redimensionar la data: En este caso, la función fa.parallel nos dará la sugerencia:

fa.parallel(theData, fa = 'fa',correct = T,plot = F)
## Warning in fa.stats(r = r, f = f, phi = phi, n.obs = n.obs, np.obs = np.obs, :
## The estimated weights for the factor scores are probably incorrect.  Try a
## different factor score estimation method.
## Warning in fa.stats(r = r, f = f, phi = phi, n.obs = n.obs, np.obs = np.obs, :
## The estimated weights for the factor scores are probably incorrect.  Try a
## different factor score estimation method.
## Warning in fac(r = r, nfactors = nfactors, n.obs = n.obs, rotate = rotate, : An
## ultra-Heywood case was detected.  Examine the results carefully
## Parallel analysis suggests that the number of factors =  2  and the number of components =  NA

La prueba nos sugiere 2, lo esperado en teoría.

3.2.1.4 Redimensionar a número menor de factores

La rotación en el análisis factorial se utiliza para mejorar la interpretabilidad de los factores extraídos. No cambia los resultados fundamentales (como la varianza total explicada), pero sí cambia cómo se distribuyen las cargas (loadings) entre los factores.

Varimax: Rotación de tipo ortogonal (factores no correlacionados), supone que los factores son independientes entre sí, se emplea cuando quieres mantener independencia entre dimensiones, el resultado es más claro (cada variable tiende a cargar alto en un solo factor)

Oblimin: Rotación de tipo oblicua (factores pueden correlacionarse), supone que los factores pueden estar relacionados, se emplea cuando sospechas que los factores tienen relación conceptual, el resultado puede mostrar una variable cargando en más de un factor si están relacionados.

  • Resultado inicial:
library(GPArotation)
## 
## Adjuntando el paquete: 'GPArotation'
## The following objects are masked from 'package:psych':
## 
##     equamax, varimin
resfa <- fa(theData,
            nfactors = 2,
            cor = 'mixed',
            rotate = "varimax", #oblimin?
            fm="minres")
print(resfa$loadings)
## 
## Loadings:
##                  MR1   MR2  
## hdiLife          0.307 0.836
## hdiSchoolExpec   0.368 0.811
## hdiMeanEduc      0.316 0.782
## hdiGni           0.283 0.813
## ideElectoral     0.906 0.249
## ideFunctioning   0.801 0.470
## ideParticipation 0.768 0.331
## ideCulture       0.498 0.382
## ideLiberties     0.901 0.334
## 
##                  MR1   MR2
## SS loadings    3.522 3.280
## Proportion Var 0.391 0.364
## Cumulative Var 0.391 0.756
  • Resultados mejorado (solo apropiado si hay más de un factor):
print(resfa$loadings,cutoff = 0.5)
## 
## Loadings:
##                  MR1   MR2  
## hdiLife                0.836
## hdiSchoolExpec         0.811
## hdiMeanEduc            0.782
## hdiGni                 0.813
## ideElectoral     0.906      
## ideFunctioning   0.801      
## ideParticipation 0.768      
## ideCulture                  
## ideLiberties     0.901      
## 
##                  MR1   MR2
## SS loadings    3.522 3.280
## Proportion Var 0.391 0.364
## Cumulative Var 0.391 0.756

Cuando logramos que cada variable se vaya a un factor, tenemos una estructura simple.

  • Resultado visual: El resultado lo podemos ver de forma gráfica
fa.diagram(resfa,main = "Resultados del EFA")

#### Evaluando Resultado obtenido:

  • ¿Qué variables aportaron más a los factores?

    • Proporción de la varianza de cada variable que es explicada por los factores extraídos.
    • Va de 0 a 1: mientras más alta, mejor representa esa variable el modelo factorial.
    • Variables con comunalidad cercana a 1: se explican bien con los factores.
    • Variables con comunalidad <0.4: quizás no encajan bien en la estructura.
sort(resfa$communality)
##       ideCulture ideParticipation      hdiMeanEduc           hdiGni 
##        0.3941682        0.6993302        0.7119301        0.7405067 
##          hdiLife   hdiSchoolExpec   ideFunctioning     ideElectoral 
##        0.7933223        0.7936103        0.8629055        0.8835971 
##     ideLiberties 
##        0.9226321
  • ¿Qué variables contribuyen a la construcción de más de un factor?

    • Mide cuántos factores distintos “usan” una variable para construir su carga factorial.
    • Medida numérica del grado en que una variable está asociada con más de un factor.
    • Complejidad = 1 –> la variable carga en solo un factor, lo deseable para una interpretación clara
    • Complejidad > 2 –> la variable carga en más de un factor, lo cual puede dificultar la interpretación
sort(resfa$complexity)
##     ideElectoral           hdiGni          hdiLife     ideLiberties 
##         1.150198         1.238563         1.265339         1.270410 
##      hdiMeanEduc ideParticipation   hdiSchoolExpec   ideFunctioning 
##         1.317113         1.358907         1.395523         1.614353 
##       ideCulture 
##         1.874466
  • ¿Tucker Lewis > 0.9?

    • Es un índice de ajuste comparativo que evalúa si el modelo factorial mejora sustancialmente con respecto a un modelo nulo (sin relaciones entre variables)
    • Se interpreta similar al R cuadrado en regresión.
    • 0.95 Excelente ajuste

    • 0.90-0.95 Buena ajuste
    • 0.80-0.89 Aceptable, pero mejorable
    • < 0.80 Mal ajuste
resfa$TLI
## [1] 0.9263416
  • ¿RMS cerca a cero?

    • Medida cuadrática de las diferencias entre las correlaciones observadas y las reproducidas por el modelo.
    • Evalúa qué tan bien el modelo reproduce las correlaciones reales.
    • =< 0.05 Excelente ajuste
    • 0.05-0.08 Aceptable
    • 0.08 Mal ajuste

resfa$rms
## [1] 0.0346878
  • ¿RMSEA cerca a cero?

    • Mide la discrepancia por grado de libertad.
    • Evalúa el ajuste del modelo a la estructura poblacional (no solo muestral)
    • =< 0.05 Muy buen ajuste
    • 0.05-0.08 Buen ajuste
    • 0.08-0.10 Aceptable/regular
    • 0.10 Mal ajuste

resfa$RMSEA
##      RMSEA      lower      upper confidence 
##  0.1319641  0.1014027  0.1651144  0.9000000
  • ¿BIC?
    • Bayseian Information Criterion
    • No se interpreta en valores absolutos
    • Sirve para comparar modelos: cuanto más bajo, mejor
    • No hay valor bueno o malo
    • Se usa para comparar el BIC entre modelos con diferentes número de factores (si empleamos uno con 3,4 o 5)
resfa$BIC
## [1] -23.30257

3.3 Obtención de índices

Podemos calcular dos índices que resuman los dos factores encontrados

library(dplyr)
## Warning: package 'dplyr' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'dplyr'
## The following object is masked from 'package:MASS':
## 
##     select
## The following object is masked from 'package:kableExtra':
## 
##     group_rows
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
as.data.frame(resfa$scores)%>%head()
##          MR1         MR2
## 1 -1.6803401 -0.83207114
## 2  0.4219025  0.17017645
## 3 -0.8499736  0.38447261
## 4 -0.3312612 -0.83527332
## 5  0.6137348  0.55689007
## 6  0.1404683  0.08167183

Les daremos por nombre ‘ide_efa’ y ‘idh_efa’ a esa dos columnas. Dado que tenemos el índice de democracia en la data original, comparémoslo con el recién calculado, via el scatterplot de la siguiente figura.

idhdemo$ide_efa=resfa$scores[,1]
idhdemo$idh_efa=resfa$scores[,2]

ggplot(data=idhdemo,aes(x=ide,y=ide_efa)) + geom_point() + theme_minimal() + labs(x="Indice de Democracia (original)", y="Indice de Democracia EFA")

Nota que los rangos de los valores en la gráfica no son los mismos. Podemos cambiar tales rangos

# normalizando
library(BBmisc)
## Warning: package 'BBmisc' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'BBmisc'
## The following objects are masked from 'package:dplyr':
## 
##     coalesce, collapse, symdiff
## The following object is masked from 'package:lme4':
## 
##     namedList
## The following object is masked from 'package:DescTools':
## 
##     %nin%
## The following object is masked from 'package:base':
## 
##     isFALSE
efa_scores_norm=normalize(resfa$scores, 
                       method = "range", 
                       margin=2, # by column
                       range = c(0, 10))

# nuevas variables
idhdemo$ide_efa_norm=efa_scores_norm[,1]
idhdemo$idh_efa_norm=efa_scores_norm[,2]

La siguiente figura nos muestra las nuevas variables para IDE.

# graficando

ggplot(data=idhdemo,aes(x=ide,y=ide_efa_norm)) + geom_point() + theme_minimal() + labs(x="Indice de Democracia (original)", y="Indice de Democracia EFA (cambiado)")

La siguiente figura nos muestras las nuevas variables para IDH:

ggplot(data=idhdemo,aes(x=hdi,y=idh_efa_norm)) + geom_point() + theme_minimal() + labs(x="Indice de Desarrollo Humano (original)", y="Indice de Desarrollo Humano EFA (cambiado)")

Queda a esta altura preguntarse: - ¿Qué ventaja hay entre calcular un índice como un resultado aritmético simple a partir de los indicadores versus usar análisis factorial con el mismo propósito? - Finalmente, nota este resultado:

cor(idhdemo$ide_efa_norm,idhdemo$idh_efa_norm)
## [1] 0.0632508

Se aplicó una correlación de pearson entre los dos nuevos índices calculados. Resulta muy bajo (cerca a 0, positivo pero prácticamente nulo). El valor indica que no hay relación lineal sustancial entre los dos índices.

En términos más prácticos, la independencia entre ide_efa_norm e idh_efa_norm sugiere que ambos factores están capturando dimensiones distintas, lo cual es bueno si nuestro objetivo es realizar el análisis factorial.

4 Análisis de Conglomerados

4.1 Presentación

La idea intuitiva de conglomerar es poder organizar los casos (filas) en subconjuntos o grupos, tal que la similitud entre casos justifique que pertenezca a un grupo y no a otro.

En la sesión anterior, vimos como crear un índice numérico que resuma indicadores de un concepto. Utilicemos los datos ahí preprocesados:

Traigamos algunos datos de los países del mundo para el ejemplo de esta sesión:

link_idhdemo="https://docs.google.com/spreadsheets/d/e/2PACX-1vRB4YNe2KdIrTmQUAMScYuWcA2ig8d5fKgJBQIlRPVKcryiurAY3dz4Dy8-fpa_MjqmPeTeYet1ggDR/pub?gid=1870508685&single=true&output=csv"

idhdemo=read.csv(link_idhdemo)
names(idhdemo)
##  [1] "country"          "hdiRanking"       "hdi"              "hdiLife"         
##  [5] "hdiSchoolExpec"   "hdiMeanEduc"      "hdiGni"           "ideRanking"      
##  [9] "ideRegime"        "ide"              "ideElectoral"     "ideFunctioning"  
## [13] "ideParticipation" "ideCulture"       "ideLiberties"

4.2 Transformación de datos

Para este ejercicio solo usaremos los componentes del IDH. La distribución de los componentes del IDH podemos verla en la siguiente figura:

boxplot(idhdemo[,c(4:7)],horizontal = F,las=2,cex.axis = 0.5)

Debemos normalizar los valores, ya que se presenta mucha diferente entre las distribuciones. Como primera estrategia cambiemos sus rangos. Elijamos un rango del 0 al 10, cuyo resultado se ve en la siguiente figura

library(BBmisc)
boxplot(normalize(idhdemo[,c(4:7)],method='range',range=c(0,10)))

Una segunda estrategia sería tipificarla. El resulta es el siguiente:

boxplot(normalize(idhdemo[,c(4:7)],method='standardize'))

Nos quedamos con la segunda opción!!!

idhdemo[,c(4:7)]=normalize(idhdemo[,c(4:7)],method='standardize')

4.3 Correlación

Veamos correlaciones entre estas variables tipificadas:

cor(idhdemo[,c(4:7)])
##                  hdiLife hdiSchoolExpec hdiMeanEduc    hdiGni
## hdiLife        1.0000000      0.8057425   0.7659252 0.7838335
## hdiSchoolExpec 0.8057425      1.0000000   0.8159101 0.7311884
## hdiMeanEduc    0.7659252      0.8159101   1.0000000 0.7139462
## hdiGni         0.7838335      0.7311884   0.7139462 1.0000000

Si hubiera alguna correlación negativa sería bueno invertir el rango, tal que el menor sea el mayor y viceversa. Esto no sucede aquí, por lo que no se hace ningún ajuste.

4.4 Preparación de los datos para la clusterización

No podemos usar la columna País en la clusterización, pero tampoco debemos perderla, por lo que se recomienda usar esos nombres en lugar del nombre de la fila.

dataClus=idhdemo[,c(4:7)]
row.names(dataClus)=idhdemo$country

Ya con los datos en el objeto dataClus, calculemos la matriz de distancias entre los casos (países):

library(cluster)
## Warning: package 'cluster' was built under R version 4.4.3
g.dist = daisy(dataClus, metric="gower")

Hay diversas maneras de calcular matrices de distancias entre casos. Cuando las variables son todas numéricas, es común usar la distancia Euclidiana. Hay otras técnica útiles como la Mahattan. En nuestro caso, usaremos la distancia Gower útil cuando las variables (columnas) están de diversos tipos de escalas.

4.5 Procesos de clusterización

Hay diversas estrategias de clusterización. Veremos dos de ella en nuestro curso:

  • La técnica de Partición (PAM)
  • La técnica de Jerarquización
    • Jerarquización Aglomerativa (AGNES)
    • Jerarquización Divisiva (DIANA)

4.5.1 Estrategia de Partición

Como su nombre lo indica, la estrategia de partición busca partir los casos en grupos. El algoritmo básico establece puntos que traten de ser el centro de los demás casos, tal que estos se separen. Claro está, que estos centros de atracción van moviéndose conforme los grupos se van formando, hasta que al final se han partido todos los casos.

Hay diversos algoritmos que buscan una implementación de estos principios básicos. El más conocido es el de K-medias, pero para ciencias sociales tiene la desventaja que requiere que todas las variables sean numéricas, no siendo muy adecuado cuando haya presencia de variables categóricas. La alternativa a las necesidades en ciencias sociales es la técnica de K-medoids.

4.5.1.1 Decidir cantidad de clusters

La siguiente figura nos sirve para determinar la cantidad de cllusters a solicitar (usando el estadístico gap):

## para PAM

library(factoextra)
## Warning: package 'factoextra' was built under R version 4.4.3
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
fviz_nbclust(dataClus, pam,diss=g.dist,method = "gap_stat",k.max = 10,verbose = F)

4.5.1.2 Clusterizar via PAM:

La técnica de k-medoids se implementa en la función PAM. Esta función retorna diversos valores, en este caso crearemos una columna con la etiqueta del cluster. Usemos la sugerencia de la anterior figura y hallamos:

library(kableExtra)
set.seed(123)
res.pam=pam(g.dist,3,cluster.only = F)

#nueva columna
dataClus$pam=res.pam$cluster

# ver

head(dataClus,15)%>%kbl()%>%kable_styling()
hdiLife hdiSchoolExpec hdiMeanEduc hdiGni pam
Afghanistan -1.1720163 -1.1223433 -1.7899045 -0.9036118 1
Albania 0.6655042 0.2825071 0.7113515 -0.2954005 2
Algeria 0.6546035 0.3425723 -0.2580009 -0.4600137 2
Angola -1.2150350 -0.4816372 -1.0570320 -0.7236515 1
Argentina 0.5293798 1.4330952 0.6694140 0.0403686 2
Armenia 0.1046748 -0.1644630 0.7245624 -0.3434922 2
Australia 1.6888036 2.5007014 1.1453419 1.4396130 3
Austria 1.3148582 0.8062709 1.0036890 1.6560856 3
Azerbaijan -0.2350715 -0.0368326 0.4873350 -0.2891916 2
Bahrain 0.9571050 0.9035056 0.6390787 0.9582010 3
Bangladesh 0.1475666 -0.3910236 -0.4659696 -0.7233309 2
Belarus 0.1547871 0.5249118 0.9696084 -0.0622427 2
Belgium 1.3528009 2.0137324 1.0395414 1.5905903 3
Benin -1.4462954 -0.9535138 -1.3922662 -0.8252918 1
Bhutan 0.0757291 -0.1281194 -1.1314933 -0.5273580 1

4.5.1.3 Evaluando el uso de PAM

Una manera práctica de ver el desempeño del algoritmo es calcular las silhouettes. Para el caso reciente, vemamos la siguiente figura:

fviz_silhouette(res.pam,print.summary = F)

La figura muestra barras, donde cada una es un país (caso). Mientras más alta la barra, la pertenencia a ese cluster es clara. La barra negativa indica un país mal clusterizado. Para este caso, estos serían los mal clusterizados:

silPAM=data.frame(res.pam$silinfo$widths)
silPAM$country=row.names(silPAM)
poorPAM=silPAM[silPAM$sil_width<0,'country']%>%sort()
poorPAM
## [1] "Chile"  "Latvia"

4.5.1.4 Verificando etiqueta de clusters

Exploremos el promedio de cada cluster:

aggregate(.~ pam, data=dataClus,mean)
##   pam    hdiLife hdiSchoolExpec hdiMeanEduc     hdiGni
## 1   1 -1.0811743     -1.0681695  -1.1822831 -0.8111138
## 2   2  0.1340138      0.1819924   0.3393855 -0.2301917
## 3   3  1.2520905      1.1502463   1.0317146  1.5181170

El número asignado al cluster no tiene significado necesariamente, por lo que recomiendo hacer cálculos para dárselo. En este caso, las etiquetas ascienden al igual que el promedio, por lo que no es necesario recodificar la etiqueta.

Antes de continuar, guardemos la columna de PAM en la data integrada, y eliminemos la de dataClaus

idhdemo$pamIDHpoor=idhdemo$country%in%poorPAM
idhdemo$pamIDH=as.ordered(dataClus$pam)
dataClus$pam=NULL

4.5.2 Estrategia Jerárquica

La jerarquización busca clusterizar por etapas, hasta que todas las posibilidades de clusterización sean visibles. Este enfoque tiene dos familias de algoritmos: - Aglomerativos - Divisivos

4.5.2.1 Estrategia Aglomerativa

En esta estrategia se parte por considerar cada caso (fila) como un cluster, para de ahí ir creando miniclusters hasta que todos los casos sean un solo cluster. El proceso va mostrando qué tanto esfuerzo toma juntar los elementos cluster tras cluster.

4.5.2.1.1 Decidir linkages

Aunque se tiene la distancia entre elementos, tenemos que decidir como se irá calculando la distancia entre los clusters que se van formando (ya no son casos individuales). Los tres más simples métodos: - Linkage tipo SINGLE: https://www.youtube.com/embed/RdT7bhm1M3E - Linkage tipo COMPLETE: https://www.youtube.com/embed/Cy3ci0Vqs3Y - Linkage tipo AVERAGE: https://www.youtube.com/embed/T1ObCUpjq3o

Otro método adicional, y muy eficiente, es el de Ward. Al final, lo que necesitamos saber cual de ellos nos entregará una mejor propuesta de clusters. Usemos ese último para nuestro caso.

4.5.2.1.2 Decidir cantidad de Clusters

La siguiente figura sirve para determinar la cantidad de clusters a solicitar (usando el estadístico gap)

## PARA JERARQUICO

fviz_nbclust(dataClus, hcut,diss=g.dist,method = "gap_stat",k.max = 10,verbose = F,hc_func = "agnes")

4.5.2.1.3 Clusterizar vía AGNES

La función hcut es la que usaremos para el método jerárquico, y el algoritmo aglomerativo se emplea usando agnes. El linkage será ward (aquí ward.D):

set.seed(123)
library(factoextra)

res.agnes<- hcut(g.dist, k = 3,hc_func='agnes',hc_method = "ward.D")

dataClus$agnes=res.agnes$cluster

# ver

head(dataClus,15)%>%kbl()%>%kable_styling()
hdiLife hdiSchoolExpec hdiMeanEduc hdiGni agnes
Afghanistan -1.1720163 -1.1223433 -1.7899045 -0.9036118 1
Albania 0.6655042 0.2825071 0.7113515 -0.2954005 2
Algeria 0.6546035 0.3425723 -0.2580009 -0.4600137 2
Angola -1.2150350 -0.4816372 -1.0570320 -0.7236515 1
Argentina 0.5293798 1.4330952 0.6694140 0.0403686 2
Armenia 0.1046748 -0.1644630 0.7245624 -0.3434922 2
Australia 1.6888036 2.5007014 1.1453419 1.4396130 3
Austria 1.3148582 0.8062709 1.0036890 1.6560856 3
Azerbaijan -0.2350715 -0.0368326 0.4873350 -0.2891916 2
Bahrain 0.9571050 0.9035056 0.6390787 0.9582010 2
Bangladesh 0.1475666 -0.3910236 -0.4659696 -0.7233309 2
Belarus 0.1547871 0.5249118 0.9696084 -0.0622427 2
Belgium 1.3528009 2.0137324 1.0395414 1.5905903 3
Benin -1.4462954 -0.9535138 -1.3922662 -0.8252918 1
Bhutan 0.0757291 -0.1281194 -1.1314933 -0.5273580 2

El dendograma de la siguiente figura nos muestra el proceso de conglomeración AGNES:

# Visualize
fviz_dend(res.agnes, cex = 0.7, horiz = T,main = "")
## Warning: The `<scale>` argument of `guides()` cannot be `FALSE`. Use "none" instead as
## of ggplot2 3.3.4.
## ℹ The deprecated feature was likely used in the factoextra package.
##   Please report the issue at <https://github.com/kassambara/factoextra/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

El eje “Height” nos muestra el “costo” de conglomerar: mientras más corta la distancia mayor similitud y la conglomeración es más rápida.

4.5.2.1.4 Evaluando el uso de AGNES

La siguiente figura nos muestra las silhouettes para AGNES.

fviz_silhouette(res.agnes,print.summary = F)

Nótese que también se presentan valores mal clusterizados. Los identificamos son estos:

silAGNES=data.frame(res.agnes$silinfo$widths)
silAGNES$country=row.names(silAGNES)
poorAGNES=silAGNES[silAGNES$sil_width<0,'country']%>%sort()
poorAGNES
##  [1] "Bahrain"        "Bhutan"         "Cape Verde"     "Czech Republic"
##  [5] "Estonia"        "Greece"         "Kuwait"         "Lithuania"     
##  [9] "Poland"         "Portugal"       "Saudi Arabia"   "Spain"
4.5.2.1.5 Verificando etiqueta de clusters

Exploremos el promedio de cada cluster

aggregate(.~ agnes, data=dataClus,mean)
##   agnes    hdiLife hdiSchoolExpec hdiMeanEduc      hdiGni
## 1     1 -1.1025984     -1.0855778  -1.1832237 -0.81636850
## 2     2  0.2356679      0.2867675   0.3801487 -0.08496801
## 3     3  1.3867428      1.2105608   1.1283409  1.76038883

Estas etiquetas no necesitan recodificación tampoco. Guardemos la columna de AGNES en la data integrada, y eliminemosla de dataClus

idhdemo$agnesIDHpoor=idhdemo$country%in%poorAGNES
idhdemo$agnesIDH=as.ordered(dataClus$agnes)
dataClus$agnes=NULL
4.5.2.1.6 Comparando

Veamos qué tanto se parece a la clasificación jerárquica a la de partición:

# verificar recodificacion
table(idhdemo$pamIDH,idhdemo$agnesIDH,dnn = c('Particion','Aglomeracion'))
##          Aglomeracion
## Particion  1  2  3
##         1 54  1  0
##         2  0 70  0
##         3  0 11 29

4.5.2.2 Estrategia Divisiva

Esta estrategia comienza con todos los casos como un gran cluster; para de ahí dividir en clusters más pequeños.

4.5.2.2.1 Decidir Cantidad de Clusters

La siguiente figura nos sirve para determinar la cantidad de clusters a solicitar (usando el estadístico gap)

## PARA JERARQUICO

fviz_nbclust(dataClus, hcut,diss=g.dist,method = "gap_stat",k.max = 10,verbose = F,hc_func = "diana")

4.5.2.2.2 Clusterizar vía DIANA

La función hcut es la que usaremos para el método jerárquico, y el algoritmo divisivo se emplea usando diana. Aquí una muestra del resultado:

set.seed(123)
res.diana <- hcut(g.dist, k = 4,hc_func='diana')
dataClus$diana=res.diana$cluster
# veamos
head(dataClus,15)%>%kbl%>%kable_styling()
hdiLife hdiSchoolExpec hdiMeanEduc hdiGni diana
Afghanistan -1.1720163 -1.1223433 -1.7899045 -0.9036118 1
Albania 0.6655042 0.2825071 0.7113515 -0.2954005 2
Algeria 0.6546035 0.3425723 -0.2580009 -0.4600137 2
Angola -1.2150350 -0.4816372 -1.0570320 -0.7236515 1
Argentina 0.5293798 1.4330952 0.6694140 0.0403686 2
Armenia 0.1046748 -0.1644630 0.7245624 -0.3434922 2
Australia 1.6888036 2.5007014 1.1453419 1.4396130 3
Austria 1.3148582 0.8062709 1.0036890 1.6560856 3
Azerbaijan -0.2350715 -0.0368326 0.4873350 -0.2891916 2
Bahrain 0.9571050 0.9035056 0.6390787 0.9582010 3
Bangladesh 0.1475666 -0.3910236 -0.4659696 -0.7233309 4
Belarus 0.1547871 0.5249118 0.9696084 -0.0622427 2
Belgium 1.3528009 2.0137324 1.0395414 1.5905903 3
Benin -1.4462954 -0.9535138 -1.3922662 -0.8252918 1
Bhutan 0.0757291 -0.1281194 -1.1314933 -0.5273580 4

El dendograma de la siguiente figura nos muestra el proceso de conglomeración AGNES:

# Visualize
fviz_dend(res.diana, cex = 0.7, horiz = T, main = "")

4.5.2.2.3 Evaluando el uso de DIANA

La siguiente figura nos muestra las silhouettes para DIANA

fviz_silhouette(res.diana,print.summary = F)

Nótese que también se presentan valores mal clusterizados. Los identificados son estos:

silDIANA=data.frame(res.diana$silinfo$widths)
silDIANA$country=row.names(silDIANA)
poorDIANA=silDIANA[silDIANA$sil_width<0,'country']%>%sort()
poorDIANA
## [1] "Azerbaijan"   "Ecuador"      "Mongolia"     "Turkmenistan"
4.5.2.2.4 Verificando Etiqueta

Exploremos el promedio de cada cluster:

aggregate(.~ diana, data=dataClus,mean)
##   diana    hdiLife hdiSchoolExpec hdiMeanEduc       hdiGni
## 1     1 -1.1287230     -1.1169561 -1.24824598 -0.823370585
## 2     2  0.3137105      0.4546312  0.57266780 -0.006070452
## 3     3  1.3052423      1.1835769  1.01701723  1.586748700
## 4     4 -0.1965156     -0.2767992 -0.04875179 -0.539435372

Aquí vemos que las etiquetas no muestran un orden. Este sería el orden:

original=aggregate(.~ diana, data=dataClus,mean)
original[order(original$hdiLife),]
##   diana    hdiLife hdiSchoolExpec hdiMeanEduc       hdiGni
## 1     1 -1.1287230     -1.1169561 -1.24824598 -0.823370585
## 4     4 -0.1965156     -0.2767992 -0.04875179 -0.539435372
## 2     2  0.3137105      0.4546312  0.57266780 -0.006070452
## 3     3  1.3052423      1.1835769  1.01701723  1.586748700

Esas posiciones hay que usarlas para recodificar:

dataClus$diana=dplyr::recode(dataClus$diana, `1` = 1, `4`=2,`2`=3,`3`=4)

Guardemos la columna de DIANA en la data integrada, y eliminemosla de dataClus

idhdemo$dianaIDHpoor=idhdemo$country%in%poorDIANA
idhdemo$dianaIDH=as.ordered(dataClus$diana)
dataClus$diana=NULL

4.6 Visualización comparativa

Vamos a usar la matriz de distancia para darle a cada país una coordenada, tal que la distancia entre esos países se refleje en sus posiciones. Eso requiere una técnica que proyecte las dimensiones originales en una plano bidimensional. Para ello usaremos la técnica llamada escalamiento multidimensional. Veamos algunas coordenadas.

# k es la cantidad de dimensiones
proyeccion = cmdscale(g.dist, k=2,add = T) 
head(proyeccion$points,20)
##                                [,1]        [,2]
## Afghanistan             0.444400179  0.13113731
## Albania                -0.147175274 -0.13701510
## Algeria                -0.023300723 -0.09169449
## Angola                  0.331282576  0.03588831
## Argentina              -0.236409012 -0.08313538
## Armenia                -0.048973926 -0.16503890
## Australia              -0.529876811  0.16845260
## Austria                -0.429300483  0.11885998
## Azerbaijan             -0.006636829 -0.16267282
## Bahrain                -0.325596910  0.02662569
## Bangladesh              0.138226916 -0.08817999
## Belarus                -0.165689524 -0.13349112
## Belgium                -0.494630520  0.16277289
## Benin                   0.419354114  0.10895568
## Bhutan                  0.172062052 -0.04766684
## Bolivia                 0.067447666 -0.09021494
## Bosnia and Herzegovina -0.094332269 -0.15218399
## Botswana                0.109290126 -0.06847570
## Brazil                 -0.025209702 -0.10957750
## Bulgaria               -0.118824558 -0.14195524

Habiendo calculado la proyección, recuperemos las coordenadas del mapa de mundo basado en nuestras dimensiones nuevas:

# data frame prep:
idhdemo$dim1 <- proyeccion$points[,1]
idhdemo$dim2 <- proyeccion$points[,2]

Aquí puedes ver el mapa:

library(ggrepel)
base= ggplot(idhdemo,aes(x=dim1, y=dim2,label=row.names(dataClus))) 
base + geom_text_repel(size=3, max.overlaps = 50,min.segment.length = unit(0, 'lines'))

Colorremos el mapa anterior según el cluster al que corresponden.

4.6.1 Gráfica PAM

# solo paises mal clusterizados
PAMlabels=ifelse(idhdemo$pamIDHpoor,idhdemo$country,'')

#base
base= ggplot(idhdemo,aes(x=dim1, y=dim2))  +
    scale_color_brewer(type = 'qual',palette ='Dark2'  ) + labs(subtitle = "Se destacan los países mal clusterizados")

pamPlot=base + geom_point(size=3, 
                          aes(color=pamIDH))  + 
        labs(title = "PAM") 
# hacer notorios los paises mal clusterizados
pamPlot + geom_text_repel(size=4,
                          aes(label=PAMlabels),
                          max.overlaps = 50,
                          min.segment.length = unit(0, 'lines'))

4.6.2 Gráfica AGNES

# solo paises mal clusterizados
AGNESlabels=ifelse(idhdemo$agnesIDHpoor,idhdemo$country,'')

agnesPlot=base + geom_point(size=3, 
                            aes(color=as.factor(agnesIDH))) +
          labs(title = "AGNES") 
# hacer notorios los paises mal clusterizados
agnesPlot + geom_text_repel(size=4,
                            aes(label=AGNESlabels),
                            max.overlaps = 50,
                            min.segment.length = unit(0, 'lines'))

4.6.3 Gráfica DIANA

# solo paises mal clusterizados
DIANAlabels=ifelse(idhdemo$dianaIDHpoor,idhdemo$country,'')

dianaPlot=base + geom_point(size=3,
                            aes(color=dianaIDH)) + 
          labs(title = "DIANA")

# hacer notorios los paises mal clusterizados
dianaPlot + geom_text_repel(size=4,
                            aes(label=DIANAlabels), 
                            max.overlaps = 50,
                            min.segment.length = unit(0, 'lines'))

Nota que en estas técnicas (partición y jerárquica) todo elemento termina siendo parte de un cluster