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.
Traigamos algunos datos de los paises del mundo para el ejemplo de esta sesión:
Índice de Democracia - IDE (link). Índice de desarrollo humano - IDH (link). Estos datos, vendrán desde su origen con una serie de problemitas. En el caso del IDE, veamos que viene de esta manera:
WhereIDH='https://github.com/Estadistica-AnalisisPolitico/DataFiles-estadistica/raw/main/HDR21-22_Statistical_Annex_HDI_Table.xlsx'
#carga
idh = rio::import(WhereIDH,skip=4,.name_repair='minimal')
head(idh, 15)%>%kbl()%>%
kable_styling(bootstrap_options = "striped", font_size = 10)
| Human Development Index (HDI) | Life expectancy at birth | Expected years of schooling | Mean years of schooling | Gross national income (GNI) per capita | GNI per capita rank minus HDI rank | HDI rank | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| HDI rank | Country | Value | NA | (years) | NA | (years) | NA | (years) | NA | (2017 PPP $) | NA | NA | NA | NA |
| NA | NA | 2021 | NA | 2021 | NA | 2021 | a | 2021 | a | 2021 | NA | 2021 | b | 2020 |
| NA | VERY HIGH HUMAN DEVELOPMENT | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA |
| 1 | Switzerland | 0.96199999999999997 | NA | 83.987200000000001 | NA | 16.50029945 | NA | 13.85966015 | NA | 66933.004539999994 | NA | 5 | NA | 3 |
| 2 | Norway | 0.96099999999999997 | NA | 83.233900000000006 | NA | 18.185199740000002 | c | 13.00362968 | NA | 64660.106220000001 | NA | 6 | NA | 1 |
| 3 | Iceland | 0.95899999999999996 | NA | 82.678200000000004 | NA | 19.163059230000002 | c | 13.76716995 | NA | 55782.049809999997 | NA | 11 | NA | 2 |
| 4 | Hong Kong, China (SAR) | 0.95199999999999996 | NA | 85.473399999999998 | d | 17.278169630000001 | NA | 12.22620964 | NA | 62606.845399999998 | NA | 6 | NA | 4 |
| 5 | Australia | 0.95099999999999996 | NA | 84.526499999999999 | NA | 21.054590229999999 | c | 12.726819989999999 | NA | 49238.433349999999 | NA | 18 | NA | 5 |
| 6 | Denmark | 0.94799999999999995 | NA | 81.375299999999996 | NA | 18.714799880000001 | c | 12.96049023 | NA | 60364.785949999998 | NA | 6 | NA | 5 |
| 7 | Sweden | 0.94699999999999995 | NA | 82.9833 | NA | 19.418529509999999 | c | 12.609720230000001 | NA | 54489.37401 | NA | 9 | NA | 9 |
| 8 | Ireland | 0.94499999999999995 | NA | 81.997600000000006 | NA | 18.94522095 | c | 11.58222303 | e | 76168.984429999997 | f | -3 | NA | 8 |
| 9 | Germany | 0.94199999999999995 | NA | 80.630099999999999 | NA | 17.010139469999999 | NA | 14.090966910000001 | e | 54534.216820000001 | NA | 6 | NA | 7 |
| 10 | Netherlands | 0.94099999999999995 | NA | 81.687299999999993 | NA | 18.693165220000001 | c,e | 12.581629749999999 | NA | 55979.411 | NA | 3 | NA | 10 |
| 11 | Finland | 0.94 | NA | 82.0381 | NA | 19.051929470000001 | c | 12.87362003 | NA | 49452.166720000001 | NA | 11 | NA | 12 |
| 12 | Singapore | 0.93899999999999995 | NA | 82.754499999999993 | NA | 16.524320599999999 | NA | 11.924880030000001 | NA | 90918.644709999993 | f | -10 | NA | 10 |
1.1 Selección En ambas tablas debemos hacer diversas operaciones de selección, renombramiento, y limpieza. Luego de ello, verifiquemos los tipos de datos en IDE:
#seleccionando columns
idh=idh[,c(2,3,5,7,9,11)]
demo=demo[,-c(1,2,6)]
# recombrando columns
newDemo=c("Pais","RegimeType","Score","Electoral","Functioning","participation","culture",'Civilliberties')
newIDH=c('Pais','puntuacion','EsperanzaVida','EscolaridadDuracion','EscolaridadPromedio','PBI')
names(demo)=newDemo
names(idh)=newIDH
#seleccionando filas
idh=idh[c(1:202),]
idh=idh[!is.na(idh$Pais),]
# tipo de datos
str(demo)
## 'data.frame': 167 obs. of 8 variables:
## $ Pais : chr " Norway" " New Zealand" " Finland" " Sweden" ...
## $ RegimeType : chr "Full democracy" "Full democracy" "Full democracy" "Full democracy" ...
## $ Score : chr "9.75" "9.37" "9.27" "9.26" ...
## $ Electoral : chr "10.00" "10.00" "10.00" "9.58" ...
## $ Functioning : chr "9.64" "8.93" "9.29" "9.29" ...
## $ participation : chr "10.00" "9.44" "8.89" "8.33" ...
## $ culture : chr "10.00" "8.75" "8.75" "10.00" ...
## $ Civilliberties: chr "9.12" "9.71" "9.41" "9.12" ...
Y para el caso de IDH tenemos estos tipos de datos:
str(idh)
## 'data.frame': 201 obs. of 6 variables:
## $ Pais : chr "Country" "VERY HIGH HUMAN DEVELOPMENT" "Switzerland" "Norway" ...
## $ puntuacion : chr "Value" NA "0.96199999999999997" "0.96099999999999997" ...
## $ EsperanzaVida : chr "(years)" NA "83.987200000000001" "83.233900000000006" ...
## $ EscolaridadDuracion: chr "(years)" NA "16.50029945" "18.185199740000002" ...
## $ EscolaridadPromedio: chr "(years)" NA "13.85966015" "13.00362968" ...
## $ PBI : chr "(2017 PPP $)" NA "66933.004539999994" "64660.106220000001" ...
1.2 Formateo Ambas tablas requieren convertir columnas de tipo texto a numérico, y una columns de tipo texto a categórica ordinal. Muchas veces, esta etapa de formateo produce valores perdidos, cuando algun valor no puede transformarse al tipo de dato requerido. La data de IDH tuvo ese problema, y el warning se muestra así (un warning cada vez que no se pudo transformar):
# formateo: texto a ordinal
OrdinalVector=c('Authoritarian','Hybrid regime','Flawed democracy','Full democracy')
demo$RegimeType=factor(demo$RegimeType,
levels = OrdinalVector,
ordered = T)
# for mateo: texto a numero
idh[,-1]=lapply(idh[,-1], as.numeric)
## Warning in lapply(idh[, -1], as.numeric): NAs introducidos por coerción
## Warning in lapply(idh[, -1], as.numeric): NAs introducidos por coerción
## Warning in lapply(idh[, -1], as.numeric): NAs introducidos por coerción
## Warning in lapply(idh[, -1], as.numeric): NAs introducidos por coerción
## Warning in lapply(idh[, -1], as.numeric): NAs introducidos por coerción
demo[,3:8]=lapply(demo[,3:8],as.numeric)
1.3 Valores perdidos Sabemos que IDH ha generado valores perdidos durante el formateo. Es importante saber si podemos recuperar alguno de ellos. Veamos dónde tenemos estos valores perdidos (**NA*s):
idh[!complete.cases(idh[,-1]),]%>%kbl()%>%
kable_styling(bootstrap_options = "striped", font_size = 10)
| Pais | puntuacion | EsperanzaVida | EscolaridadDuracion | EscolaridadPromedio | PBI | |
|---|---|---|---|---|---|---|
| 1 | Country | NA | NA | NA | NA | NA |
| 3 | VERY HIGH HUMAN DEVELOPMENT | NA | NA | NA | NA | NA |
| 70 | HIGH HUMAN DEVELOPMENT | NA | NA | NA | NA | NA |
| 120 | MEDIUM HUMAN DEVELOPMENT | NA | NA | NA | NA | NA |
| 165 | LOW HUMAN DEVELOPMENT | NA | NA | NA | NA | NA |
| 198 | OTHER COUNTRIES OR TERRITORIES | NA | NA | NA | NA | NA |
| 199 | Korea (Democratic People’s Rep. of) | NA | 73.2845 | 10.78317 | NA | NA |
| 200 | Monaco | NA | 85.9463 | NA | NA | NA |
| 201 | Nauru | NA | 63.6170 | 11.69042 | NA | 17729.741 |
| 202 | Somalia | NA | 55.2803 | NA | NA | 1017.968 |
Podemos comprobar que en efecto esas filas no se usarán. De ahí que podemos eliminar esas filas.
idh=idh[complete.cases(idh[,-1]),]
row.names(idh)=NULL # resetear numero de filas
1.4 Merging Tenemos dos tablas, con la misma unidad de análisis (Pais). Pasemos a integrarlas en una sola. Como el campo común (la “key”) es Pais, asegurémonos que no haya espacios en blanco en sus alrededores.
idh$Pais= trimws(idh$Pais,whitespace = "[\\h\\v]")
demo$Pais= trimws(demo$Pais,whitespace = "[\\h\\v]")
Como queremos integrar ambas, debemos investigar si hay países tiene en común pero que se han escrito diferente en cada tabla. Estos son los que no tiene IDE:
sort(setdiff(idh$Pais,demo$Pais))
## [1] "Andorra" "Antigua and Barbuda"
## [3] "Bahamas" "Barbados"
## [5] "Belize" "Bolivia (Plurinational State of)"
## [7] "Brunei Darussalam" "Cabo Verde"
## [9] "Congo" "Congo (Democratic Republic of the)"
## [11] "Côte d'Ivoire" "Czechia"
## [13] "Dominica" "Eswatini (Kingdom of)"
## [15] "Grenada" "Hong Kong, China (SAR)"
## [17] "Iran (Islamic Republic of)" "Kiribati"
## [19] "Korea (Republic of)" "Lao People's Democratic Republic"
## [21] "Liechtenstein" "Maldives"
## [23] "Marshall Islands" "Micronesia (Federated States of)"
## [25] "Moldova (Republic of)" "Palau"
## [27] "Palestine, State of" "Russian Federation"
## [29] "Saint Kitts and Nevis" "Saint Lucia"
## [31] "Saint Vincent and the Grenadines" "Samoa"
## [33] "San Marino" "Sao Tome and Principe"
## [35] "Seychelles" "Solomon Islands"
## [37] "South Sudan" "Syrian Arab Republic"
## [39] "Tanzania (United Republic of)" "Timor-Leste"
## [41] "Tonga" "Türkiye"
## [43] "Tuvalu" "Vanuatu"
## [45] "Venezuela (Bolivarian Republic of)" "Viet Nam"
Estos son los que no tiene IDH:
sort(setdiff(demo$Pais,idh$Pais))
## [1] "Bolivia" "Cape Verde"
## [3] "Czech Republic" "Democratic Republic of the Congo"
## [5] "East Timor" "Eswatini"
## [7] "Hong Kong" "Iran"
## [9] "Ivory Coast" "Laos"
## [11] "Moldova" "North Korea"
## [13] "Palestine" "Republic of the Congo"
## [15] "Russia" "South Korea"
## [17] "Syria" "Taiwan"
## [19] "Tanzania" "Turkey"
## [21] "Venezuela" "Vietnam"
Como hay paises que sí están en ambos, pero que no se están escribiendo igual, podemos renombrar esas celdas. Aquí no será un proceso automático, pero podría serlo.
idh[idh$Pais=="Bolivia (Plurinational State of)",'Pais']= "Bolivia"
idh[idh$Pais=="Cabo Verde",'Pais']= "Cape Verde"
idh[idh$Pais=="Czechia",'Pais']= "Czech Republic"
idh[idh$Pais=="Congo (Democratic Republic of the)",'Pais']= "Democratic Republic of the Congo"
idh[idh$Pais=="Timor-Leste",'Pais']= "East Timor"
idh[idh$Pais=="Eswatini (Kingdom of)",'Pais']= "Eswatini"
idh[idh$Pais=="Hong Kong, China (SAR)",'Pais']= "Hong Kong"
idh[idh$Pais=="Iran (Islamic Republic of)",'Pais']= "Iran"
idh[idh$Pais=="Côte d'Ivoire",'Pais']= "Ivory Coast"
idh[idh$Pais=="Lao People's Democratic Republic" ,'Pais']= "Laos"
idh[idh$Pais=="Moldova (Republic of)",'Pais']= "Moldova"
idh[idh$Pais=="Palestine, State of",'Pais']= "Palestine"
idh[idh$Pais=="Congo",'Pais']= "Republic of the Congo"
idh[idh$Pais=="Russian Federation",'Pais']= "Russia"
idh[idh$Pais=="Korea (Republic of)",'Pais']= "South Korea"
idh[idh$Pais=="Syrian Arab Republic",'Pais']="Syria"
idh[idh$Pais=="Tanzania (United Republic of)",'Pais']= "Tanzania"
idh[idh$Pais=="Türkiye" ,'Pais']= "Turkey"
idh[idh$Pais=="Venezuela (Bolivarian Republic of)",'Pais']="Venezuela"
idh[idh$Pais=="Viet Nam" ,'Pais']="Vietnam"
Ahora sí, el merge no perderá tantas filas (países).
idhdemo=merge(idh,demo)
Si los tipos de datos se corrigieron antes, eso sería todo el proceso para producir una tabla integrada.
1.5 Exploración de datos Ahora, pasemos a describirlos estadísticamente:
summary(idhdemo)
## Pais puntuacion EsperanzaVida EscolaridadDuracion
## Length:165 Min. :0.3940 Min. :52.53 Min. : 6.957
## Class :character 1st Qu.:0.5860 1st Qu.:65.27 1st Qu.:11.468
## Mode :character Median :0.7310 Median :71.91 Median :13.644
## Mean :0.7204 Mean :71.22 Mean :13.607
## 3rd Qu.:0.8480 3rd Qu.:76.94 3rd Qu.:15.765
## Max. :0.9620 Max. :85.47 Max. :21.055
## EscolaridadPromedio PBI RegimeType Score
## Min. : 2.115 Min. : 731.8 Authoritarian :58 Min. :0.320
## 1st Qu.: 5.916 1st Qu.: 4566.3 Hybrid regime :34 1st Qu.:3.220
## Median : 9.424 Median :12578.2 Flawed democracy:53 Median :5.610
## Mean : 8.926 Mean :20108.4 Full democracy :20 Mean :5.284
## 3rd Qu.:11.654 3rd Qu.:30690.5 3rd Qu.:7.060
## Max. :14.091 Max. :90918.6 Max. :9.750
## Electoral Functioning participation culture
## Min. : 0.000 Min. :0.000 Min. : 0.000 Min. : 1.250
## 1st Qu.: 1.500 1st Qu.:2.710 1st Qu.: 3.890 1st Qu.: 3.750
## Median : 7.000 Median :5.000 Median : 5.560 Median : 5.000
## Mean : 5.636 Mean :4.623 Mean : 5.401 Mean : 5.389
## 3rd Qu.: 9.170 3rd Qu.:6.430 3rd Qu.: 6.670 3rd Qu.: 6.250
## Max. :10.000 Max. :9.640 Max. :10.000 Max. :10.000
## Civilliberties
## Min. :0.000
## 1st Qu.:3.240
## Median :5.590
## Mean :5.378
## 3rd Qu.:7.650
## Max. :9.710
Noten que los rangos no son los mismos para los componentes del IDH. Es muy común que tengamos diferentes unidades, por lo que debemos transformar los datos para evitar confundir a los algoritmos de conglomeración.
1.6 Transformación de datos Para este ejercicio sólo usaremos los componentes del IDH. La distribución de los componentes del IDH podemos verla en la Figura 1.1.
boxplot(idhdemo[,c(3:6)],horizontal = F,las=2,cex.axis = 0.5)
Figure 1.1: Distribución de los componentes del IDH
Como primera estrategia cambiemos sus rangos. Elijamos un rango del 0 al 1, cuyo resultado se ve en la Figura ??
library(BBmisc)
##
## Attaching package: 'BBmisc'
## The following object is masked from 'package:base':
##
## isFALSE
boxplot(normalize(idhdemo[,c(3:6)],method='range',range=c(0,10)))
Figure 1.2: Distribución de los componentes del IDH con nuevo rango (0-1)
Una segunda estrategia sería tipificarla 1. El resultado se muestra en la Figura 1.3.
boxplot(normalize(idhdemo[,c(3:6)],method='standardize'))
Figure 1.3: Distribución de los componentes del IDH tipificados
Nos quedaremos con la segunda opción.
idhdemo[,c(3:6)]=normalize(idhdemo[,c(3:6)],method='standardize')
1.7 Correlación Veamos correlaciones entre estas variables tipificadas:
cor(idhdemo[,c(3:6)])
## EsperanzaVida EscolaridadDuracion EscolaridadPromedio
## EsperanzaVida 1.0000000 0.8057425 0.7659252
## EscolaridadDuracion 0.8057425 1.0000000 0.8159101
## EscolaridadPromedio 0.7659252 0.8159101 1.0000000
## PBI 0.7838335 0.7311884 0.7139462
## PBI
## EsperanzaVida 0.7838335
## EscolaridadDuracion 0.7311884
## EscolaridadPromedio 0.7139462
## PBI 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.
2 Preparación de los datos para la clusterización No podemos usar la columna Pais en la clusterización, pero tampoco debemos perderla, por lo que se recomienda usar esos nombres en lugar del nombre de fila.
dataClus=idhdemo[,c(3:6)]
row.names(dataClus)=idhdemo$Pais
Ya con los datos en el objeto dataClus, calculemos la matriz de distancias entre los casos (paises):
library(cluster)
g.dist = daisy(dataClus, metric="gower")
Hay diversas maneras de calculas matrices de distancias entre casos. Cuando las variables son todas numéricas, es comun usar la distancia Euclideana). Hay otras técnicas útiles como la Mahattan (revisar este debate). En nuestro caso, usaremos la distancia Gower útil cuando las variables (columnas) están de diversos tipos de escalas.
3 Procesos de clusterización Hay diversas estrategías de clusterización. Veremos dos de ellas:
La técnica de Partición La técnica de Jerarquización Jerarquización Aglomerativa Jerarquización Divisiva 3.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 deben atraer a los casos, tal que estos se separen. Claro está, que estos puntos atractores van moviendose 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 ante categorías. La alternativa a las necesidades en ciencias sociales es la técnica de k-medoides.
3.1.1 Decidir cantidad de clusters: La Figura 3.1 sirve para determinar la cantidad de clusters a solicitar (usando el estadístico gap).
## para PAM
library(factoextra)
## Loading required package: ggplot2
## 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)
Figure 3.1: Clusters sugeridos para algoritmo PAM.
3.1.2 Clusterizar via PAM: La técnica de k-medoides 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 Figura 3.1, y hallamos:
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()
| EsperanzaVida | EscolaridadDuracion | EscolaridadPromedio | PBI | 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 |
3.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, veamos la Figura 3.2.
fviz_silhouette(res.pam,print.summary = F)
Figure 3.2: Evaluando resultados de PAM
La Figura 3.2 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"
3.1.4 Verificando etiqueta de clusters Exploremos el promedio de cada cluster:
aggregate(.~ pam, data=dataClus,mean)
## pam EsperanzaVida EscolaridadDuracion EscolaridadPromedio PBI
## 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 asigando al cluster no tiene significado necesariamente, por lo que recomiendo 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 eliminemosla de dataClus.
idhdemo$pamIDHpoor=idhdemo$Pais%in%poorPAM
idhdemo$pamIDH=as.ordered(dataClus$pam)
dataClus$pam=NULL
3.2 Estrategia Jerarquica La jerarquización busca clusterizar por etapas, hasta que todas las posibilidades de clusterizacion sean visible. Este enfoque tiene dos familias de algoritmos:
Aglomerativos Divisivos 3.2.1 Estrategia Aglomerativa En esta estrategia se parte por considerar cada caso (fila) como un cluster, para de ahi 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.
3.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 mas simples metodos:
Linkage tipo SINGLE.
Linkage tipo COMPLETE.
Linkage tipo AVERAGE
Otro metodo 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 este último para nuestro caso.
3.2.1.2 Decidir cantidad de Clusters La Figura 3.3 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")
Figure 3.3: Clusters sugeridos para algoritmo AGNES.
3.2.1.3 Clusterizar vía AGNES La función hcut es la que usaremos para el método jerarquico, 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()
| EsperanzaVida | EscolaridadDuracion | EscolaridadPromedio | PBI | 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 Figura 3.4 nos muestra el proceso de conglomeración AGNES:
# Visualize
fviz_dend(res.agnes, cex = 0.7, horiz = T,main = "")
## Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> =
## "none")` instead.
Figure 3.4: Dendograma de AGNES
El eje ‘Height’ nos muestra el “costo” de conglomerar: mientras más corta la distancia mayor similitud y la conglomeracion es más rápida.
3.2.1.4 Evaluando el uso de AGNES La Figura 3.5 nos muestra las silhouettes para AGNES.
fviz_silhouette(res.agnes,print.summary = F)
Figure 3.5: Evaluando resultados de AGNES
Nótese que también se presentan valores mal clusterizados. Los identificados 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"
3.2.1.5 Verificando etiqueta de clusters Exploremos el promedio de cada cluster:
aggregate(.~ agnes, data=dataClus,mean)
## agnes EsperanzaVida EscolaridadDuracion EscolaridadPromedio PBI
## 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$Pais%in%poorAGNES
idhdemo$agnesIDH=as.ordered(dataClus$agnes)
dataClus$agnes=NULL
3.2.1.6 Comparando Veamos qué tanto se parece a la clasificación jerarquica 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
3.2.2 Estrategia Divisiva Esta estrategia comienza con todos los casos como un gran cluster; para de ahi dividir en clusters más pequeños.
3.2.2.1 Decidir Cantidad de Clusters La Figura 3.6 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")
Figure 3.6: Clusters sugeridos para algoritmo DIANA
3.2.2.2 Clusterizar vía DIANA La función hcut es la que usaremos para el método jerarquico, 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()
| EsperanzaVida | EscolaridadDuracion | EscolaridadPromedio | PBI | 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 Figura 3.7 nos muestra el proceso de conglomeración AGNES:
# Visualize
fviz_dend(res.diana, cex = 0.7, horiz = T, main = "")
## Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> =
## "none")` instead.
Figure 3.7: Dendograma de DIANA
3.2.2.3 Evaluando el uso de DIANA La Figura 3.8 nos muestra las silhouettes para DIANA.
fviz_silhouette(res.diana,print.summary = F)
Figure 3.8: Evaluando resultados de DIANA
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"
3.2.2.4 Verificando Etiqueta Exploremos el promedio de cada cluster:
aggregate(.~ diana, data=dataClus,mean)
## diana EsperanzaVida EscolaridadDuracion EscolaridadPromedio PBI
## 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$EsperanzaVida),]
## diana EsperanzaVida EscolaridadDuracion EscolaridadPromedio PBI
## 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$Pais%in%poorDIANA
idhdemo$dianaIDH=as.ordered(dataClus$diana)
dataClus$diana=NULL
4 Visualización comparativa Vamos a usar la matriz de distancia para darle a cada país una coordenada, tal que la distancia entre esos paises se refleje en sus posiciones. Eso requiere una técnica que proyecte las dimensiones originales en un plano bidimensional. Para ello usaremos la técnica llamada escalamiento multidimensional. Veams 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 proyeccción, recuperemos las coordenadas del mapa del 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'))
Coloreemos el mapa anterior segun el cluster al que corresponden.
4.1 Gráfica de PAM
# solo paises mal clusterizados
PAMlabels=ifelse(idhdemo$pamIDHpoor,idhdemo$Pais,'')
#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'))
Figure 4.1: Conglomera dos PAM en Mapa Bidimensonal de países
4.2 Gráfica de AGNES
# solo paises mal clusterizados
AGNESlabels=ifelse(idhdemo$agnesIDHpoor,idhdemo$Pais,'')
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'))
Figure 4.2: Conglomerados AGNES en Mapa Bidimensonal de países
4.3 Gráfica de DIANA
# solo paises mal clusterizados
DIANAlabels=ifelse(idhdemo$dianaIDHpoor,idhdemo$Pais,'')
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'))
Figure 4.3: Conglomerados DIANA en Mapa Bidimensonal de países
Nota que en estas técnicas (partición y jerarquica) todo elemento termina siendo parte de un cluster.