Los algoritmos de clustering en general buscan detectar grupos (clusters) entre los datos que reĆŗnan dos caracterĆsticas:
Hay dos grandes grupos de algoritmos de clustering.
Supervisados
No supervisados
En los algoritmos supervisados, los datos tienen que tener etiquetados, para que el algoritmo pueda aprender las clasificaciones, y luego detectar a qué grupos pertenecen los nuevos casos. Estos algoritmos se los usa para problemas de regresión y clasificación.
Por otro lado, tenemos los algoritmos no supervisados, que en donde nosotros no definimos la caracterĆstica de cada grupo: por ejemplo en los algoritmos de k-means, le decimos al algoritmo cuĆ”ntos grupos queremos, y el algoritmo se encarga de clasificarlos los casos en los grupos sin que nosotros tengamos mĆ”s control sobre las definiciones de las caracterĆsticas de cada cluster. Algunos de los algoritmos no supervisados permiten detectar novedades en los datos, fraudes, similitudes entre textos para clasificar de igual manera variantes de texto, etc..
En los métodos de partición los datos se van a agrupar en una cantidad (indicada con la letra k) de grupos. Los grupos tienen que cumplir con estas condiciones:
Cada grupo tiene que tener un objeto.
Y cada objeto tiene que pertenecer exactamente a un sólo grupo.
Dentro de los mƩtodos jerƔrquicos, tenemos dos tipos esenciales de algoritmos:
Los algoritmos aglomerativos que empiezan con una cantidad n de clusters de una observación cada uno, y luego se combinan dos grupos hasta terminar con un solo cluster con n observaciones (sĆ, como el anillo de Sauron mĆ”s o menos).
Y por otro lado tenemos los métodos divisorios que comienzan con un sólo cluster de n observaciones y en cada paso se divide un grupo en dos hasta tener n clusters con una observación cada uno. Es prÔcticamente la estrategia inversa a la anterior.
Hoy nos centraremos en los primeros.
En figuras geomƩtricas simƩtricas, es muy sencillo definir el centro.
Ahora, en figuras geomĆ©tricas complejas, como puede ser los lĆmites de una provincia o de un barrio, encontrar el centro es un problema.
Comuna 2 de la Ciudad Autónoma de Buenos Aires
library(tidyverse)
library(sf)
comunas <- st_read('https://bitsandbricks.github.io/data/CABA_comunas.geojson')## Reading layer `CABA_comunas' from data source `https://bitsandbricks.github.io/data/CABA_comunas.geojson' using driver `GeoJSON'
## Simple feature collection with 15 features and 4 fields
## geometry type: MULTIPOLYGON
## dimension: XY
## bbox: xmin: -58.53152 ymin: -34.70529 xmax: -58.33514 ymax: -34.52754
## geographic CRS: WGS 84
comuna_2 <- comunas %>%
filter(comunas == 2)
comuna_2 <- comuna_2 %>%
st_centroid()
ggplot()+
geom_sf(data=comunas, color="gray")+
geom_sf(data=comuna_2, color="red", shape=4, stroke=2, size=1) +
theme_minimal()Con los clusters en k-means vamos a hacer algo anĆ”logo. Vamos a tener una nube de puntos, con algunas caracterĆsticas, vamos a indicar la cantidad de grupos que queremos (el k), y el algoritmo va a buscar, en función de la distribución de los datos un punto medio de referencia para cada cluster.
Ese āpunto medioā, es lo que se conoce como centroide.
Internamente la secuencia es la siguiente:
Vamos a usar una versión de la encuesta de Sysarmy y repasar lo que vimos en la sesión 9 con Pablo Casas.
library(funModeling)
library(DataExplorer)
# Cargamos el archivo
sysarmy <- read_delim("Datasets/sysarmy_clean.csv", delim = ";")
status(sysarmy)## variable
## 1 Me.identifico
## 2 Tengo
## 3 Dónde.estÔs.trabajando
## 4 AƱos.de.experiencia
## 5 AƱos.en.la.empresa.actual
## 6 AƱos.en.el.puesto.actual
## 7 X.Gente.a.cargo.
## 8 Nivel.de.estudios.alcanzado
## 9 Estado
## 10 Carrera
## 11 Universidad
## 12 Realizaste.cursos.de.especialización
## 13 X.ContribuĆs.a.proyectos.open.source.
## 14 X.ProgramƔs.como.hobbie.
## 15 Trabajo.de
## 16 Plataformas
## 17 Lenguajes.de.programación
## 18 Frameworks..herramientas.y.librerĆas
## 19 Bases.de.datos
## 20 QA...Testing
## 21 IDEs
## 22 X.QuƩ.SO.usƔs.en.tu.laptop.PC.para.trabajar.
## 23 X.Y.en.tu.celular.
## 24 X.TenƩs.guardias.
## 25 CuƔnto.cobrƔs.por.guardia
## 26 X.Porcentaje..bruto.o.neto.
## 27 Tipo.de.contrato
## 28 Salario.mensual.BRUTO..en.tu.moneda.local.
## 29 Salario.mensual.NETO..en.tu.moneda.local.
## 30 X.QuƩ.tan.conforme.estƔs.con.tu.sueldo.
## 31 Cómo.creés.que.estÔ.tu.sueldo.con.respecto.al.último.semestre
## 32 RecibĆs.algĆŗn.tipo.de.bono
## 33 A.quƩ.estƔ.atado.el.bono
## 34 X.Tuviste.ajustes.por.inflación.en.2019.
## 35 X.De.quƩ...fue.el.ajuste.total.
## 36 X.En.qué.mes.fue.el.último.ajuste.
## 37 X.Sufriste.o.presenciaste.situaciones.de.violencia.laboral.
## 38 Orientación.sexual
## 39 X.Tenés.algún.tipo.de.discapacidad.
## 40 X.SentĆs.que.esto.te.dificultó.el.conseguir.trabajo.
## 41 X.A.quĆ©.eventos.de.tecnologĆa.asististe.en.el.Ćŗltimo.aƱo.
## 42 Cantidad.de.empleados
## 43 Actividad.principal
## 44 X.La.recomendƔs.como.un.buen.lugar.para.trabajar.
## 45 X.Cómo.calificĆ”s.las.polĆticas.de.diversidad.e.inclusión.
## 46 X.A.cuÔntos.kilómetros.de.tu.casa.queda.la.oficina.
## 47 Beneficios.extra
## 48 X.CuƔles.considerƔs.que.son.las.mejores.empresas.de.IT.para.trabajar.en.este.momento..en.tu.ciudad.
## q_zeros p_zeros q_na p_na q_inf p_inf type unique
## 1 0 0.0000000000 0 0.000000000 0 0 character 3
## 2 0 0.0000000000 0 0.000000000 0 0 numeric 50
## 3 0 0.0000000000 0 0.000000000 0 0 character 25
## 4 469 0.0784018723 0 0.000000000 0 0 numeric 51
## 5 1821 0.3044132397 0 0.000000000 0 0 numeric 25
## 6 1564 0.2614510197 0 0.000000000 0 0 numeric 43
## 7 4479 0.7487462387 0 0.000000000 0 0 numeric 60
## 8 0 0.0000000000 0 0.000000000 0 0 character 7
## 9 0 0.0000000000 0 0.000000000 0 0 character 3
## 10 1 0.0001671682 336 0.056168506 0 0 character 575
## 11 1 0.0001671682 623 0.104145771 0 0 character 853
## 12 0 0.0000000000 0 0.000000000 0 0 character 6
## 13 0 0.0000000000 0 0.000000000 0 0 character 2
## 14 0 0.0000000000 0 0.000000000 0 0 character 2
## 15 1 0.0001671682 0 0.000000000 0 0 character 409
## 16 0 0.0000000000 1098 0.183550652 0 0 character 1175
## 17 0 0.0000000000 1100 0.183884988 0 0 character 1724
## 18 0 0.0000000000 2317 0.387328653 0 0 character 1366
## 19 0 0.0000000000 1482 0.247743230 0 0 character 1214
## 20 0 0.0000000000 3802 0.635573387 0 0 character 608
## 21 0 0.0000000000 1703 0.284687396 0 0 character 1100
## 22 0 0.0000000000 0 0.000000000 0 0 character 4
## 23 0 0.0000000000 0 0.000000000 0 0 character 4
## 24 0 0.0000000000 0 0.000000000 0 0 character 3
## 25 5532 0.9247743230 0 0.000000000 0 0 numeric 119
## 26 0 0.0000000000 0 0.000000000 0 0 character 3
## 27 0 0.0000000000 0 0.000000000 0 0 character 5
## 28 0 0.0000000000 0 0.000000000 0 0 numeric 1204
## 29 0 0.0000000000 214 0.035773989 0 0 numeric 1138
## 30 0 0.0000000000 0 0.000000000 0 0 numeric 4
## 31 0 0.0000000000 0 0.000000000 0 0 numeric 4
## 32 0 0.0000000000 0 0.000000000 0 0 character 5
## 33 0 0.0000000000 0 0.000000000 0 0 character 174
## 34 0 0.0000000000 0 0.000000000 0 0 character 5
## 35 1467 0.2452357071 0 0.000000000 0 0 character 127
## 36 1320 0.2206619860 0 0.000000000 0 0 character 19
## 37 0 0.0000000000 50 0.008358409 0 0 character 3
## 38 0 0.0000000000 111 0.018555667 0 0 character 39
## 39 2 0.0003343363 5455 0.911902374 0 0 character 60
## 40 0 0.0000000000 3019 0.504680709 0 0 character 2
## 41 38 0.0063523905 3860 0.645269141 0 0 character 1332
## 42 0 0.0000000000 0 0.000000000 0 0 character 10
## 43 0 0.0000000000 0 0.000000000 0 0 character 3
## 44 0 0.0000000000 0 0.000000000 0 0 numeric 10
## 45 0 0.0000000000 0 0.000000000 0 0 numeric 10
## 46 55 0.0091942494 0 0.000000000 0 0 numeric 129
## 47 7 0.0011701772 0 0.000000000 0 0 character 2984
## 48 5 0.0008358409 4293 0.717652959 0 0 character 1281
Luego, definimos con quƩ variables nos vamos a quedar:
# Seleccionamos variables categóricas
variables_c <- status(sysarmy) %>%
filter(type=="character" & unique<10) %>%
pull(variable)
variables_n <- status(sysarmy) %>%
filter(type=="numeric") %>%
pull(variable)
# Nos quedamos con estas variables candidatas
v_sel <- c("X.TenƩs.guardias.",
"X.ProgramƔs.como.hobbie.",
"X.Tuviste.ajustes.por.inflación.en.2019.",
"Salario.mensual.BRUTO..en.tu.moneda.local.",
"Tengo")De acÔ en mÔs, copiamos descaradamente el script de Pablo Casas de la sesión 9 del Club. Primero creamos un dataframe con las variables seleccionadas
## variable q_zeros p_zeros q_na p_na q_inf
## 1 X.TenƩs.guardias. 0 0 0 0 0
## 2 X.ProgramƔs.como.hobbie. 0 0 0 0 0
## 3 X.Tuviste.ajustes.por.inflación.en.2019. 0 0 0 0 0
## 4 Salario.mensual.BRUTO..en.tu.moneda.local. 0 0 0 0 0
## 5 Tengo 0 0 0 0 0
## p_inf type unique
## 1 0 character 3
## 2 0 character 2
## 3 0 character 5
## 4 0 numeric 1204
## 5 0 numeric 50
Luego, transformamos las variables, a variables de tipo dummy. Esto crea para cada una de las categorĆas de la variable categórica, una columna nueva con un 1 o un 0 segĆŗn corresponda.
## Rows: 5,982
## Columns: 12
## $ Salario.mensual.BRUTO..en.tu.moneda.local. <dbl> 41000, 60000, ...
## $ Tengo <dbl> 40, 35, 39, 42...
## $ X.TenƩs.guardias._No <int> 1, 1, 1, 1, 1,...
## $ X.TenĆ©s.guardias._SĆ..activa <int> 0, 0, 0, 0, 0,...
## $ X.TenĆ©s.guardias._SĆ..pasiva <int> 0, 0, 0, 0, 0,...
## $ X.ProgramƔs.como.hobbie._No <int> 1, 0, 0, 1, 0,...
## $ X.ProgramÔs.como.hobbie._Sà <int> 0, 1, 1, 0, 1,...
## $ X.Tuviste.ajustes.por.inflación.en.2019._Dos <int> 0, 0, 0, 0, 0,...
## $ X.Tuviste.ajustes.por.inflación.en.2019._MÔs.de.tres <int> 0, 0, 0, 0, 0,...
## $ X.Tuviste.ajustes.por.inflación.en.2019._No <int> 0, 0, 1, 0, 1,...
## $ X.Tuviste.ajustes.por.inflación.en.2019._Tres <int> 1, 1, 0, 1, 0,...
## $ X.Tuviste.ajustes.por.inflación.en.2019._Uno <int> 0, 0, 0, 0, 0,...
Escalamos los datos para que funcione mejor el algoritmo de k-means y creamos los clusters
## Salario.mensual.BRUTO..en.tu.moneda.local. Tengo X.TenƩs.guardias._No
## [1,] -0.01293006 1.0322152 0.5235223
## [2,] -0.01292984 0.3790278 0.5235223
## [3,] -0.01292847 0.9015777 0.5235223
## [4,] -0.01292881 1.2934901 0.5235223
## [5,] -0.01293024 -0.4047971 0.5235223
## [6,] -0.01292881 -0.5354346 0.5235223
## X.TenĆ©s.guardias._SĆ..activa X.TenĆ©s.guardias._SĆ..pasiva
## [1,] -0.1985884 -0.4640297
## [2,] -0.1985884 -0.4640297
## [3,] -0.1985884 -0.4640297
## [4,] -0.1985884 -0.4640297
## [5,] -0.1985884 -0.4640297
## [6,] -0.1985884 -0.4640297
## X.ProgramĆ”s.como.hobbie._No X.ProgramĆ”s.como.hobbie._SĆ
## [1,] 1.053502 -1.053502
## [2,] -0.949056 0.949056
## [3,] -0.949056 0.949056
## [4,] 1.053502 -1.053502
## [5,] -0.949056 0.949056
## [6,] 1.053502 -1.053502
## X.Tuviste.ajustes.por.inflación.en.2019._Dos
## [1,] -0.6682814
## [2,] -0.6682814
## [3,] -0.6682814
## [4,] -0.6682814
## [5,] -0.6682814
## [6,] 1.4961254
## X.Tuviste.ajustes.por.inflación.en.2019._MÔs.de.tres
## [1,] -0.3052641
## [2,] -0.3052641
## [3,] -0.3052641
## [4,] -0.3052641
## [5,] -0.3052641
## [6,] -0.3052641
## X.Tuviste.ajustes.por.inflación.en.2019._No
## [1,] -0.5594118
## [2,] -0.5594118
## [3,] 1.7872932
## [4,] -0.5594118
## [5,] 1.7872932
## [6,] -0.5594118
## X.Tuviste.ajustes.por.inflación.en.2019._Tres
## [1,] 2.248068
## [2,] 2.248068
## [3,] -0.444752
## [4,] 2.248068
## [5,] -0.444752
## [6,] -0.444752
## X.Tuviste.ajustes.por.inflación.en.2019._Uno
## [1,] -0.5037686
## [2,] -0.5037686
## [3,] -0.5037686
## [4,] -0.5037686
## [5,] -0.5037686
## [6,] -0.5037686
Incorporamos los clusters al dataframe
Y visualizamos los resultados
# Visualizando. Vamos a contar una historia!!
coord_plot(data=d_fit,
group_var="cluster",
group_func=mean,
print_table=TRUE)## cluster Salario.mensual.BRUTO..en.tu.moneda.local. Tengo
## 1 1 108438.9 33.72000
## 2 2 2955946779.8 32.62000
## 3 3 92589.7 30.98000
## 4 All_Data 1133123425.2 32.09863
## X.TenĆ©s.guardias._No X.TenĆ©s.guardias._SĆ..activa
## 1 0.0000000 0.00000000
## 2 0.9500000 0.05000000
## 3 0.9600000 0.04000000
## 4 0.7848546 0.03794717
## X.TenĆ©s.guardias._SĆ..pasiva X.ProgramĆ”s.como.hobbie._No
## 1 1.0000000 0.5100000
## 2 0.0000000 1.0000000
## 3 0.0000000 0.0000000
## 4 0.1771983 0.4739218
## X.ProgramÔs.como.hobbie._Sà X.Tuviste.ajustes.por.inflación.en.2019._Dos
## 1 0.4900000 0.3500000
## 2 0.0000000 0.3100000
## 3 1.0000000 0.2900000
## 4 0.5260782 0.3087596
## X.Tuviste.ajustes.por.inflación.en.2019._MÔs.de.tres
## 1 0.10000000
## 2 0.09000000
## 3 0.08000000
## 4 0.08525577
## X.Tuviste.ajustes.por.inflación.en.2019._No
## 1 0.1600000
## 2 0.2300000
## 3 0.2700000
## 4 0.2383818
## X.Tuviste.ajustes.por.inflación.en.2019._Tres
## 1 0.2000000
## 2 0.1700000
## 3 0.1400000
## 4 0.1651622
## X.Tuviste.ajustes.por.inflación.en.2019._Uno
## 1 0.1900000
## 2 0.2000000
## 3 0.2100000
## 4 0.2024407
Hasta los comentarios del script le robamos a Pablo.
Ariadna Angulo-Brunet, una psicóloga catalana, para la iniciativa #30dĆasdegrĆ”ficos de la comunidad hispanoparlante de R publicó en su cuenta de Twitter una serie de grĆ”ficos con una paleta de colores usando fotos de sus vacaciones. Su repo en github no tiene desperdicio.
Inspirado en su trabajo hice lo mismo usando una foto de mi hija.
Vayamos por partes con el código
library(jpeg)
library(scales)
library(extrafont) # la primera vez ejecutar font_import()
loadfonts(quiet = TRUE)
font <- "Ubuntu Mono"
foto <- readJPEG("Archivos/beauty.jpg")
class(foto)## [1] "array"
La foto estÔ cargada en un tipo de objeto llamado array. Este es un tipo de vector multidimensional, es decir que a diferencia de un vector común (que solo tiene una dimensión, los arrays pueden tener varias dimensiones. En nuestro caso, tiene 3.
## [1] 4160 3120 3
Luego convertimos el array en un dataframe.
foto_rgb <- data.frame(
x = rep(1:foto_dim[2], each = foto_dim[1]),
y = rep(foto_dim[1]:1, foto_dim[2]),
R = as.vector(foto[,,1]), #slicing our array into three
G = as.vector(foto[,,2]),
B = as.vector(foto[,,3]))
head(foto_rgb)## x y R G B
## 1 1 4160 0.7921569 0.9843137 1.0000000
## 2 1 4159 0.7843137 0.9764706 1.0000000
## 3 1 4158 0.7686275 0.9607843 0.9882353
## 4 1 4157 0.7607843 0.9529412 0.9803922
## 5 1 4156 0.7607843 0.9529412 0.9803922
## 6 1 4155 0.7764706 0.9686275 0.9960784
## x y R G
## Min. : 1.0 Min. : 1 Min. :0.0000 Min. :0.01961
## 1st Qu.: 780.8 1st Qu.:1041 1st Qu.:0.3412 1st Qu.:0.29412
## Median :1560.5 Median :2080 Median :0.4863 Median :0.43529
## Mean :1560.5 Mean :2080 Mean :0.4986 Mean :0.44677
## 3rd Qu.:2340.2 3rd Qu.:3120 3rd Qu.:0.6314 3rd Qu.:0.56863
## Max. :3120.0 Max. :4160 Max. :1.0000 Max. :1.00000
## B
## Min. :0.0000
## 1st Qu.:0.2588
## Median :0.3686
## Mean :0.3926
## 3rd Qu.:0.5098
## Max. :1.0000
El siguiente paso es crear los clusters, vamos a definir 20 clusters diferentes (puede tardar unos segundos)
## List of 9
## $ cluster : int [1:12979200] 2 2 2 2 2 2 2 2 2 2 ...
## $ centers : num [1:20, 1:3] 0.685 0.702 0.113 0.235 0.521 ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : chr [1:20] "1" "2" "3" "4" ...
## .. ..$ : chr [1:3] "R" "G" "B"
## $ totss : num 1481947
## $ withinss : num [1:20] 3517 2553 2496 2934 2753 ...
## $ tot.withinss: num 87487
## $ betweenss : num 1394460
## $ size : int [1:20] 512135 224741 538529 181382 454295 592079 1124951 416637 175704 1129067 ...
## $ iter : int 12
## $ ifault : int 0
## - attr(*, "class")= chr "kmeans"
Finalmente tenemos una paleta de colores de la foto que podemos usar en cualquier grƔfico.
Y con esa paleta podemos crear un grÔfico. Vamos a usar las columnas Puesto y Sueldo_Bruto para calcular el sueldo promedio de 20 posiciones. Asà que nuestro primer paso es crear un dataframe con esas dos columnas, y luego filtrar las respuestas de 20 puestos con mayor cantidad de casos.
analisis <- sysarmy %>%
select(Trabajo.de, Salario.mensual.BRUTO..en.tu.moneda.local.) %>%
rename(Puesto = Trabajo.de,
Sueldo_Bruto = Salario.mensual.BRUTO..en.tu.moneda.local.)
top_20_puestos <- analisis %>%
select(Puesto, Sueldo_Bruto) %>%
group_by(Puesto) %>%
tally(sort = TRUE) %>%
top_n(20) %>%
select(Puesto)
top_20_puestos <- as.vector(top_20_puestos$Puesto)Y ahora podemos hacer nuestro grƔfico, usando la paleta de colores que acabamos de crear.
analisis %>%
filter(Puesto %in% top_20_puestos) %>%
group_by(Puesto) %>%
summarise(Sueldo_Promedio = mean(Sueldo_Bruto)) %>%
ungroup() %>%
ggplot(aes(x = reorder(Puesto, Sueldo_Promedio), y = Sueldo_Promedio, fill = Puesto))+
geom_col(position = "dodge") +
scale_fill_manual(values = rgb(foto_kmeans$centers))+
scale_y_continuous(labels = scales::comma_format(big.mark = ".", decimal.mark = ","))+
coord_flip()+
theme(panel.background = element_blank(),
panel.grid.major.x = element_line(colour = "#D7DBDD"),
text = element_text(family = "Ubuntu Mono")) +
labs(title = "Sueldo bruto promedio por puesto (en AR$)",
subtitle = "Fuente: Encuesta de Sueldos de Sysarmy",
caption = "Club de R para RRHH",
x = "",
y = "")