> library(patchwork)
> library(tidyverse)
> library(tidymodels)
> library(GDAtools)
En esta notebook vamos a explorar dos métodos de clusterización vistos en la clase teórica. Corresponden al rama de ML de aprendizaje no supervisado. Recordemos: la idea principal es encontrar grupos en los datos que sean similares a otros datos del mismo grupo y lo menos similar posible a datos en otros.
Hoy vamos a trabajar con K-medias. Vamos a implementarlo con un set
de datos ya conocido: un dataset (wb_bank_data_2019.csv
)
con información construida por el Banco Mundial acerca de la
distribución de la población ocupada según grandes sectores de actividad
y categoría ocupacional:
SL.AGR.EMPL.ZS
: % total el empleo en agricultura:
consistente en las actividades de agricultura, silvicultura, caza y
pescaSL.IND.EMPL.ZS
: % total de empleo en industria: en
actividades de minas y canteras, manufacturas, construcción y energía,
gas y agua.SL.SERV.EMPL.ZS
% total de empleo el servicios
comprende comercio al por mayor y menor y restaurantes y hoteles,
transporte, almacenamiento y comunicaciones, finanzas, seguros,
servicios inmobiliarios y a las empresas y servicios personales,
sociales y comunales.SL.FAM.WORK.ZS
: % total de trabajadores familiaresSL.EMP.MPYR.ZS
: % total de empleadoresSL.EMP.SELF.ZS
: % total de independientes (suma de
empleadores y cuenta propia y familiares)SL.EMP.WORK.ZS
: % total de trabajadores
asalariadosLo que vamos a tratar de hacer es construir una tipología de países en función de su estructura ocupacional. La idea va a ser poder agrupar países que tengan perfiles parecidos en la distribución de la población según rama y según cateogría ocupacional.
De alguna forma (imperfecta porque los datos no tienen toda la información que querríamos utilizar) vamos a estar tratando de agrupar a los países en función de dos grandes dimensiones clásicas para el análisis sociologico.
Importante: ¿qué variables relevantes no estamos considerando?
El método de clusterización “K-Means” busca encontrar \(k\) clusters de modo tal que se minimice la varianza intra-cluster medida, usualmente, como el cuadrado de la distancia euclídea.
\[W(C_{k})=\frac{1}{|C_{k}|} \sum_{i,i^{'} \in C_{k}} \sum_{j=1}^p (x_{ij} - x_{i^{'}j})^2\]
En otra palabras, la variación dentro del grupo para el k-ésimo grupo es la suma de todos los las distancias euclidianas al cuadrado por pares entre las observaciones en el k-ésimo conglomerado, dividido por el número total de observaciones en el k-ésimo conglomerado. Así, se obtiene el problema de optimización que define a K-medias,
\[\min_{C_{1},...,C_{k}} \left\{W(C_{k})=\frac{1}{|C_{k}|} \sum_{i,i^{'} \in C_{k}} \sum_{j=1}^p (x_{ij} - x_{i^{'}j})^2\right\}\] Necesitamos, entonces, un algoritmo que nos permita ejecutar esta minimización:
Algoritmo K-medias
Asignar aleatoriamente un número, del 1 al K, a cada una de las observaciones. Estos sirven como asignaciones de grupos iniciales para las observaciones.
Iterar hasta que las asignaciones de clústeres dejen de cambiar:
2.1 Para cada uno de los K conglomerados, calcular el centroide del conglomerado. El k-ésimo centroide del conglomerado es el vector de la función p media para el observaciones en el grupo k-ésimo.
2.2 Asignar cada observación al conglomerado cuyo centroide esté más cerca (donde más cercano se define usando la distancia euclidiana).
Para ello el algoritmo empieza encontrando \(k\) centroides y le asigna a cada sample la etiqueta correspondiente a la del centroid más cercano. Una vez actualizadas las etiquetas de todas las samples, calcula la posición de los \(k\) centroids (como el promedio de las features de las samples de cada cluster) y vuelve a asignar etiquetas a cada sample de acuerdo a la distancia al centroid más cercano. Hace estos dos pasos hasta que no haya más cambios de etiqueta.
Debido a esta inicialización aleatoria de los \(k\) centroids el output del modelo puede variar al aplicarlo otra vez a la misma data. Por ello se repite este procedimiento n_init=10 veces y el output final es el que mejor resultado tuvo al minimizar la función objetivo (varianza intra-cluster).
Vamos a cargar los datos y a preprocesarlos.
> df <- read_csv("./data/wb_bank_data_2019.csv")
>
> head(df)
## # A tibble: 6 × 7
## iso3c date value indicatorID indicator iso2c country
## <chr> <dbl> <dbl> <chr> <chr> <chr> <chr>
## 1 AFG 2019 42.8 SL.AGR.EMPL.ZS Employment in agriculture (… AF Afghan…
## 2 AFG 2019 18.2 SL.IND.EMPL.ZS Employment in industry (% o… AF Afghan…
## 3 AFG 2019 39.0 SL.SRV.EMPL.ZS Employment in services (% o… AF Afghan…
## 4 AFG 2019 43.5 SL.EMP.TOTL.SP.ZS Employment to population ra… AF Afghan…
## 5 ALB 2019 36.7 SL.AGR.EMPL.ZS Employment in agriculture (… AL Albania
## 6 ALB 2019 20.0 SL.IND.EMPL.ZS Employment in industry (% o… AL Albania
Como vemos la tabla está apilada en formato “long”. Vamos a pasarla a formato “wide” de forma tal que cada variable quede como una columna.
> df <- df %>%
+ pivot_wider(id_cols = c(iso3c, iso2c, country), names_from = indicatorID, values_from = value)
Como se puede apreciar de las definiciones anteriores, la fuente no presenta información para los trabajadores por cuenta propia, los cuales deben deducirse como resultado de la sustracción de trabajadores independientes menos empleadores y familiares.
> df <- df %>%
+ mutate(prop_tcp = SL.EMP.SELF.ZS - SL.FAM.WORK.ZS - SL.EMP.MPYR.ZS) %>%
+ rename(prop_emp = SL.EMP.MPYR.ZS, prop_familiar = SL.FAM.WORK.ZS, prop_asal = SL.EMP.WORK.ZS)
Luego, vamos a dicotomizar las categorías ocupacionales. En líneas generales, vamos a dividir dos situaciones:
> df <- df %>%
+ mutate(prop_pob_rel_sal = prop_asal + prop_emp, prop_pob_rel_no_sal = prop_familiar +
+ prop_tcp)
Se tomó esta decisión debido a que la categoría de patrones concentraba, en general, poca cantidad de casos. Como señalamos, a partir de estos datos construimos una clasificación de países según el grado de desarrollo de la división del trabajo y la extensión de las relaciones salariales.
Veamos qué relación existe entre la población inserta en relaciones salariales y la población según gran rama de actividad.
Gráfico 1. % de población inserta en relación salariales (eje x) según % de población inserta en agricultura
> df %>%
+ ggplot(aes(x = prop_pob_rel_sal, y = SL.AGR.EMPL.ZS)) + geom_point(color = "red") +
+ xlim(0, 100) + ylim(0, 100) + labs(x = "% pob. rel. sal.", y = "% pop. agric. ") +
+ theme_minimal()
Gráfico 2. % de población inserta en relación salariales (eje x) según % de población inserta en industria
> df %>%
+ ggplot(aes(x = prop_pob_rel_sal, y = SL.IND.EMPL.ZS)) + geom_point(color = "red") +
+ xlim(0, 100) + ylim(0, 100) + labs(x = "% pob. rel. sal.", y = "% pop. industrial ") +
+ theme_minimal()
Gráfico 3. % de población inserta en relación salariales (eje x) según % de población inserta en servicios
> df %>%
+ ggplot(aes(x = prop_pob_rel_sal, y = SL.SRV.EMPL.ZS)) + geom_point(color = "red") +
+ xlim(0, 100) + ylim(0, 100) + labs(x = "% pob. rel. sal.", y = "% pop. servicios ") +
+ theme_minimal()
Vamos a usar 3 variables para construir nuestro clustering: - % de población inserta en relaciones salariales - % de población ocupada en agricultura - % de población ocupada en servicios
Supongamos que no tenemos demasiada idea de cómo armar esta tipología y nos imaginamos que debería haber solo dos clusters: uno con alto peso del campo y bajo peso de relaciones salariales y otro con las características opuestas. Antes de esto, vamos a normalizar las variables.
> recipe_km <- df %>%
+ recipe(~.) %>%
+ step_normalize(c(prop_pob_rel_sal, SL.AGR.EMPL.ZS, SL.SRV.EMPL.ZS))
>
> set.seed(123)
> km_2clst <- recipe_km %>%
+ prep() %>%
+ bake(df) %>%
+ select(prop_pob_rel_sal, SL.AGR.EMPL.ZS, SL.SRV.EMPL.ZS) %>%
+ kmeans(x = ., centers = 2)
¿Qué pasó acá?
> recipe_km <- df %>%
+ recipe(~.) %>%
+ step_normalize(c(prop_pob_rel_sal, SL.AGR.EMPL.ZS, SL.SRV.EMPL.ZS))
kmeans
de r-base.
La misma tiene dos argumentos centrales:x
: el dataset a clusterizarcenters
: cantidad de centroides (o sea de clústers), es
decir, lo que hace un rato llamamos \(k\).> km_2clst <- recipe_km %>%
+ prep() %>%
+ bake(df) %>%
+ select(prop_pob_rel_sal, SL.AGR.EMPL.ZS, SL.SRV.EMPL.ZS) %>%
+ kmeans(x = ., centers = 2)
La salida es una lista de vectores, donde cada componente tiene una longitud diferente.
cluster
de longitud 187, el mismo que nuestro conjunto
de datos original; contiene información sobre cada puntowithinss
y
tot.withinss
) y centers
son una matriz con 3
filas; presentan información sobre cada grupototss
,
tot.withinss
, betweenss
e iter
;
presentan información sobre el agrupamiento completo.> km_2clst
## K-means clustering with 2 clusters of sizes 110, 77
##
## Cluster means:
## prop_pob_rel_sal SL.AGR.EMPL.ZS SL.SRV.EMPL.ZS
## 1 0.7205467 -0.7011594 0.6678908
## 2 -1.0293524 1.0016563 -0.9541297
##
## Clustering vector:
## [1] 2 2 1 2 1 2 1 1 2 1 1 2 1 1 1 1 2 2 2 1 1 1 1 1 2 2 1 2 2 1 2 2 1 1 2 1 2
## [38] 2 2 1 2 1 1 1 1 1 2 1 2 1 1 2 2 1 1 2 2 1 1 1 1 2 2 1 2 1 1 2 2 2 1 2 2 1
## [75] 1 1 2 2 1 1 1 1 1 1 1 1 1 2 2 1 1 1 2 1 1 1 2 1 1 1 1 2 2 1 1 2 1 2 1 1 2
## [112] 2 1 2 2 2 1 2 1 1 1 2 2 2 1 1 1 2 1 2 1 2 1 1 1 1 1 1 1 2 2 1 1 2 1 2 1 1
## [149] 1 2 2 1 2 1 2 1 1 2 1 1 1 1 2 2 2 2 2 2 1 1 1 1 2 1 1 1 1 1 2 2 1 2 1 1 1
## [186] 2 2
##
## Within cluster sum of squares by cluster:
## [1] 64.8211 103.9811
## (between_SS / total_SS = 69.7 %)
##
## Available components:
##
## [1] "cluster" "centers" "totss" "withinss" "tot.withinss"
## [6] "betweenss" "size" "iter" "ifault"
> summary(km_2clst)
## Length Class Mode
## cluster 187 -none- numeric
## centers 6 -none- numeric
## totss 1 -none- numeric
## withinss 2 -none- numeric
## tot.withinss 1 -none- numeric
## betweenss 1 -none- numeric
## size 2 -none- numeric
## iter 1 -none- numeric
## ifault 1 -none- numeric
¿Cuál de estos queremos extraer? No hay respuesta correcta; cada uno
de ellos puede ser interesante para un analista. Debido a que comunican
información completamente diferente (sin mencionar que no hay una forma
sencilla de combinarlos), se extraen mediante funciones separadas.
augment
agrega las clasificaciones de puntos al conjunto de
datos original:
> df <- augment(km_2clst, df)
Es lo mismo que hacer lo siguiente:
> df %>%
+ bind_cols(as_tibble(km_2clst$cluster))
## # A tibble: 187 × 16
## iso3c iso2c country SL.AGR.EMPL.ZS SL.IND.EMPL.ZS SL.SRV.EMPL.ZS
## <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 AFG AF Afghanistan 42.8 18.2 39.0
## 2 ALB AL Albania 36.7 20.0 43.3
## 3 DZA DZ Algeria 9.86 30.7 59.4
## 4 AGO AO Angola 50.4 8.12 41.5
## 5 ARG AR Argentina 0.0920 21.4 78.5
## 6 ARM AM Armenia 29.6 17.2 53.2
## 7 AUS AU Australia 2.56 19.8 77.6
## 8 AUT AT Austria 3.58 25.3 71.2
## 9 AZE AZ Azerbaijan 35.9 14.9 49.3
## 10 BHS BS Bahamas, The 2.14 14.2 83.6
## # ℹ 177 more rows
## # ℹ 10 more variables: SL.EMP.TOTL.SP.ZS <dbl>, prop_familiar <dbl>,
## # prop_emp <dbl>, SL.EMP.SELF.ZS <dbl>, prop_asal <dbl>, prop_tcp <dbl>,
## # prop_pob_rel_sal <dbl>, prop_pob_rel_no_sal <dbl>, .cluster <fct>,
## # value <int>
Es decir, entrar al objeto km_2clst
, extraer el vector
de cluster
, transformarlo en tibble
y
agregarlo como columna a df
Grafico 4. % de población inserta en relación salariales (eje x) según % de población inserta en servicios por pertenencia a cada clúster
> df %>%
+ ggplot(aes(x = prop_pob_rel_sal, y = SL.AGR.EMPL.ZS, color = .cluster)) + geom_point() +
+ xlim(0, 100) + ylim(0, 100) + labs(x = "% pob. rel. sal.", y = "% pop. agr. ") +
+ theme_minimal()
tidy
nos ayuda a resumir la información por cluster. Es
decir, calcula estadísticos de todas las variables del dataset original
por cada cluster. En este caso, la media pero de las variables
estandarizadas:
> tidy(km_2clst)
## # A tibble: 2 × 6
## prop_pob_rel_sal SL.AGR.EMPL.ZS SL.SRV.EMPL.ZS size withinss cluster
## <dbl> <dbl> <dbl> <int> <dbl> <fct>
## 1 0.721 -0.701 0.668 110 64.8 1
## 2 -1.03 1.00 -0.954 77 104. 2
Como puede verse, pareciera que el cluster 2 se caracteriza por una menor participación de población inserta en relaciones salariales, una menor proporción de empleo en servicios y una mayor proporción de ocupades en la agricultura.
El problema aquí es que las variables están normalizadas, lo cual hace más difícil su interpretación. Vamos a tratar de generar algún insight sobre qué son los clústers que generamos.
Como vimos más arriba, con augment
pegamos la
pertenencia de cada país a cada cluster en la tabla original. A partir
de allí podemos pasar a tratar de interpretar qué nos dice cada clúster.
Generemos un boxplot de la distirbución de cada variable para cada
clúster. Para eso vamos a llevar las tres variables con las que
generamos el cluster y la variable de pertenencia al cluster a un
formato long.
Gráfico 5. Boxplot de variables vinculadas a la división del trabajo y a la cateogría ocupacional según cluster
> df %>%
+ pivot_longer(cols = SL.AGR.EMPL.ZS:prop_pob_rel_sal, ) %>%
+ ggplot(aes(y = value, fill = .cluster)) + geom_boxplot() + theme_minimal() +
+ theme(axis.title.x = element_blank(), axis.text.x = element_blank(), axis.ticks.x = element_blank()) +
+ facet_wrap(~name)
El cluster 1 presenta
Es así que cada cluster constituye una aproximación dicotómica al grado de desarrollo de la división del trabajo y de la asalarización.
Ahora, ¿qué pasa si queremos probar otra \(k\)? ¿Estamos seguros de que \(k=2\) es un valor razonable? ¿No existen situaciones intermedias entre un polo de alto desarrollo capitalista y otro de bajo?
Vamos, entonces, a probar diferentes \(k\) y tratar de evaluarlos.
> # Generamos un vector con la secuencia de clústers
> centers <- 1:10
>
> # Incializamos dos tibbles vacías para llenar con las asignaciones de clusters
> # y con algunas métricas
> assignments <- tibble()
> clusterings <- tibble()
>
> # Iteramos sobre cada uno de los elementos de centers
> for (i in centers) {
+
+ # Corremos la receta, seleccionamos las variables para clusterizar y
+ # corremos el k-medias con el
+ km <- recipe_km %>%
+ prep() %>%
+ bake(df) %>%
+ select(prop_pob_rel_sal, SL.AGR.EMPL.ZS, SL.SRV.EMPL.ZS) %>%
+ kmeans(x = ., centers = i)
+
+ # Ejecutamos glance sobre km para extraer las métricas de variabilidad
+ # intra y extracluster y las agregamos a una de las tibbles
+ clusterings <- clusterings %>%
+ bind_rows(glance(km) %>%
+ mutate(k = i) %>%
+ select(k, everything()))
+
+ # Ejecutamos augment sobre km y df para agregar las pertenencias a los
+ # clusters a la tabla original; luego, apilamos todas las tablas en una
+ # sola
+ assignments <- assignments %>%
+ bind_rows(augment(km, df) %>%
+ mutate(k = i) %>%
+ select(k, everything()))
+ }
Al terminar este proceso, obtenemos lo siguiente:
clusterings
con las métricas de los 10
modelos entrenados> clusterings
## # A tibble: 10 × 5
## k totss tot.withinss betweenss iter
## <int> <dbl> <dbl> <dbl> <int>
## 1 1 558 558. 4.55e-13 1
## 2 2 558 169. 3.89e+ 2 1
## 3 3 558 97.6 4.60e+ 2 2
## 4 4 558 68.6 4.89e+ 2 2
## 5 5 558 55.0 5.03e+ 2 3
## 6 6 558 46.7 5.11e+ 2 3
## 7 7 558 43.5 5.15e+ 2 3
## 8 8 558 38.7 5.19e+ 2 5
## 9 9 558 31.8 5.26e+ 2 2
## 10 10 558 29.0 5.29e+ 2 3
assignments
con las diferentes asignaciones
de clusterings pegadas a la tabla df
original.> assignments
## # A tibble: 1,870 × 16
## k iso3c iso2c country SL.AGR.EMPL.ZS SL.IND.EMPL.ZS SL.SRV.EMPL.ZS
## <int> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 1 AFG AF Afghanistan 42.8 18.2 39.0
## 2 1 ALB AL Albania 36.7 20.0 43.3
## 3 1 DZA DZ Algeria 9.86 30.7 59.4
## 4 1 AGO AO Angola 50.4 8.12 41.5
## 5 1 ARG AR Argentina 0.0920 21.4 78.5
## 6 1 ARM AM Armenia 29.6 17.2 53.2
## 7 1 AUS AU Australia 2.56 19.8 77.6
## 8 1 AUT AT Austria 3.58 25.3 71.2
## 9 1 AZE AZ Azerbaijan 35.9 14.9 49.3
## 10 1 BHS BS Bahamas, The 2.14 14.2 83.6
## # ℹ 1,860 more rows
## # ℹ 9 more variables: SL.EMP.TOTL.SP.ZS <dbl>, prop_familiar <dbl>,
## # prop_emp <dbl>, SL.EMP.SELF.ZS <dbl>, prop_asal <dbl>, prop_tcp <dbl>,
## # prop_pob_rel_sal <dbl>, prop_pob_rel_no_sal <dbl>, .cluster <fct>
Atención ¿Cuántas filas tiene
assignments
? ¿Por qué?
Ambas nos van a servir para evaluar nuestros clustering.
A partir de la primera tibble que genearmos podemos generar un
gráfico que nos cuantifique la variabilidad intra-cluster para cada
\(k\). Particularmente, vamos a tomar
la columna tot.withinss
es decir, la suma total de los
cuadrados en todos los clusters. La lógica es que a menor
tot.withinss
más homogéneos son los clústeres:
Gráfico 6. Suma total de los cuadrados de las distancias para diferentes k
> ggplot(clusterings, aes(x = as.factor(k), y = tot.withinss, group = 1)) + geom_line() +
+ geom_point() + geom_vline(xintercept = 4, linetype = "dashed") + theme_minimal() +
+ labs(x = "Cantidad clústers (k)", y = "Variabilidad intra-cluster")
Podemos utilizar el llamado “método del codo”: podemos ver que en \(k=4\) parece darse la mayor caída de la suma de los cuadrados de todos los clústers. En ese sentido, \(k=4\) parece un buen candidato para empezar una exploración.
Existen otros métodos para evaluar una determinada clusterización:
De hecho, estos métodos podrían usarse también para evaluar clusters con otros métodos como el jerárquico que vamos a trabajar en breve.
Intentemos entender qué es lo que pasa en cada clusterización. Vamos a generar diferentes boxplots, para los diferentes \(k\). Lo mismo que hicimos más arriba.
Gráfico 7. Boxplot de variables vinculadas a la división del trabajo y a la cateogría ocupacional según cluster y según especificación
> assignments %>%
+ filter(k >= 2 & k <= 5) %>%
+ select(k, country, .cluster, SL.AGR.EMPL.ZS:SL.SRV.EMPL.ZS, prop_pob_rel_sal) %>%
+ pivot_longer(cols = SL.AGR.EMPL.ZS:prop_pob_rel_sal) %>%
+ ggplot(aes(x = value, y = name, fill = .cluster)) + geom_boxplot() + theme_minimal() +
+ facet_wrap(~k)
En general, se observa que las diferentes especificaciones se van
ordenando en un gradiente que va desde el bajo peso de relaciones
salariales y alto peso de población ocupada en el campo hasta el otro
extremo.
Veamos específicamente el \(k=5\)
Gráfico 8. Boxplot de variables vinculadas a la división del trabajo y a la cateogría ocupacional según cluster (k=5)
> assignments %>%
+ filter(k == 5) %>%
+ select(k, country, .cluster, SL.AGR.EMPL.ZS:SL.SRV.EMPL.ZS, prop_pob_rel_sal) %>%
+ pivot_longer(cols = SL.AGR.EMPL.ZS:prop_pob_rel_sal) %>%
+ ggplot(aes(y = value, fill = .cluster)) + geom_boxplot() + theme_minimal() +
+ theme(axis.title.x = element_blank(), axis.text.x = element_blank(), axis.ticks.x = element_blank()) +
+ facet_wrap(~name)
Puede verse que el cluster 1 es el que mayor desarrollo muestra. En
efecto, las relaciones salariales y el peso de los servicios son las de
mayor importancia, el campo muestra su menor peso.
En el otro extremo, nos encontramos con el cluster 2 que muestras las características opuestas. Podemos clasificar estos países como de pequeña propiedad en el campo.
Luego nos encontramos con un degradé de situaciones que se van acercando desde el cluster 2 al 1. Así en el grupo 5 (al que podríamos caracterizar como Capitalismo de extensión reciente con rasgos de desarrollo en profundidad) predominan relaciones salariales y servicios, pero en menor grado que en el anterior. Convive cierto peso remanente del campo con un muy alto peso relativo de la industria, lo que puede indicar persistencia de pequeña propiedad rural y urbana.
El grupo 4, a diferencia de los anteriores, la suma de agro e industria iguala o supera a la población en servicios. La población en el agro, además, suele ser mayor a la de la industria (a diferencia de lo que ocurre en los dos grupos anteriores). El asalariado está extendido, pero no llega la mitad de la población. Podemos etiquetarlo como capitalismo en proceso de extensión con peso del campo.
Finalmente, en el grupo 3 (capitalismo de escasa extensión con peso del campo) la población agrícola supera a industria y servicios, considerados por separado. La población no asalariada es mayoritaria.
Si quisiéramos ver cómo quedan agrupados los países podríamos repetir el scatter 4.
> assignments %>%
+ filter(k >= 2 & k <= 7) %>%
+ select(k, country, .cluster, SL.AGR.EMPL.ZS:SL.SRV.EMPL.ZS, prop_pob_rel_sal) %>%
+ ggplot(aes(x = prop_pob_rel_sal, y = SL.AGR.EMPL.ZS, color = .cluster)) + geom_point() +
+ theme_minimal() + facet_wrap(~k)
Pero mucho más interesante sería generar un mapa:
> worldmap <- map_data("world")
> worldmap$iso3c <- maps::iso.alpha(x = worldmap$region, n = 3)
>
> worldmap <- worldmap %>%
+ left_join(assignments %>%
+ filter(k == 5) %>%
+ select(country, iso3c, .cluster))
>
> worldmap %>%
+ drop_na(.cluster) %>%
+ ggplot() + geom_polygon(aes(x = long, y = lat, group = group, fill = as.factor(.cluster)),
+ color = "black") + labs(fill = "") + theme_minimal() + theme(legend.position = "bottom",
+ legend.text = element_text(size = 23))
Hasta donde se han desarrollado las fuerzas productivas de una nación lo indica del modo más palpable el grado hasta el cual se ha desarrollado en ella la división del trabajo. Toda nueva fuerza productiva, cuando no se trata de una simple extensión cuantitativa de fuerzas productivas ya conocidas con anterioridad (…) trae como consecuencia un nuevo desarrollo de la división del trabajo. La división del trabajo dentro de una nación se traduce, ante todo, en la separación de la ciudad y el campo y en la contradicción de los intereses entre una y otro. Su desarrollo ulterior conduce a la separación del trabajo comercial del industrial (Marx, Karl y Federico Engels; “La Ideología Alemana” en Karl Marx, La cuestión judía (y otros escritos), Planeta - Agostini, España, 1992, pp: 150-151)↩︎