Clustering

El arte de ordenar el último cajón de la cocina

Los algoritmos de clustering en general buscan detectar grupos (clusters) entre los datos que reĆŗnan dos caracterĆ­sticas:

  1. Las caracterĆ­sticas de los casos dentro del grupo son similares.
  2. Las caracterĆ­sticas de los casos fuera del grupo son diferentes.

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..

Tipos de algoritmos de clustering

Métodos de partición

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.

MƩtodos jerƔrquicos

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.

k-means

En figuras geomƩtricas simƩtricas, es muy sencillo definir el centro.

Centro de una circunferenciaCentro de un cuadrado

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:

  1. Definimos la cantidad de clusters (el k).
  2. El algoritmo define un centro aleatorio para los puntos.
  3. Para cada punto de los datos, define cuĆ”l es el centro que estĆ” mĆ”s cerca y cada centro se ā€œapropiaā€ de un set de puntos de datos.
  4. Para cada centro y los datos apropiados, define un centroide.

A practicar!

Sysarmy

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

# Copiado descaradamente del script de Pablo Casas
d5=select(sysarmy, all_of(v_sel))

status(d5)
##                                     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.

d5_dum=dummify(d5)  # variables dummy o one hot encoding

glimpse(d5_dum)
## 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

# Escalar para kmeans!
d6=scale(d5_dum)

head(d6)
##      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
fit_cluster=kmeans(d6, 3)

Incorporamos los clusters al dataframe

# Copia!
d_fit=d5_dum

# Asignacion del cluster
d_fit$cluster=fit_cluster$cluster

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.

Un uso copado: paleta de colores

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.

foto_dim <- dim(foto)
foto_dim
## [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
summary(foto_rgb)
##        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)

foto_kmeans <- kmeans(foto_rgb[,c("R","G","B")], centers = 20, iter.max = 30)

str(foto_kmeans)
## 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.

# La paleta de colores de la foto de mi hija
show_col(rgb(foto_kmeans$centers))

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 = "")

LS0tDQp0aXRsZTogIlNlc2nDs24gMzMgLSBDbHVzdGVycyINCmF1dGhvcjogIkNsdWIgZGUgUiBwYXJhIFJSSEgiDQpkYXRlOiAiMTIvMTIvMjAyMCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGRwaSA9IDIwMCkNCmBgYA0KDQojIENsdXN0ZXJpbmcNCg0KIyMgRWwgYXJ0ZSBkZSBvcmRlbmFyIGVsIMO6bHRpbW8gY2Fqw7NuIGRlIGxhIGNvY2luYQ0KDQpMb3MgYWxnb3JpdG1vcyBkZSBjbHVzdGVyaW5nIGVuIGdlbmVyYWwgYnVzY2FuIGRldGVjdGFyIGdydXBvcyAoY2x1c3RlcnMpIGVudHJlIGxvcyBkYXRvcyBxdWUgcmXDum5hbiBkb3MgY2FyYWN0ZXLDrXN0aWNhczoNCg0KMS4gIExhcyBjYXJhY3RlcsOtc3RpY2FzIGRlIGxvcyBjYXNvcyAqKmRlbnRybyoqIGRlbCBncnVwbyBzb24gc2ltaWxhcmVzLg0KMi4gIExhcyBjYXJhY3RlcsOtc3RpY2FzIGRlIGxvcyBjYXNvcyAqKmZ1ZXJhKiogZGVsIGdydXBvIHNvbiBkaWZlcmVudGVzLg0KDQpIYXkgZG9zIGdyYW5kZXMgZ3J1cG9zIGRlIGFsZ29yaXRtb3MgZGUgY2x1c3RlcmluZy4NCg0KLSAgIFN1cGVydmlzYWRvcw0KDQotICAgTm8gc3VwZXJ2aXNhZG9zDQoNCkVuIGxvcyAqKmFsZ29yaXRtb3Mgc3VwZXJ2aXNhZG9zKiosIGxvcyBkYXRvcyB0aWVuZW4gcXVlIHRlbmVyIGV0aXF1ZXRhZG9zLCBwYXJhIHF1ZSBlbCBhbGdvcml0bW8gcHVlZGEgYXByZW5kZXIgbGFzIGNsYXNpZmljYWNpb25lcywgeSBsdWVnbyBkZXRlY3RhciBhIHF1w6kgZ3J1cG9zIHBlcnRlbmVjZW4gbG9zIG51ZXZvcyBjYXNvcy4gRXN0b3MgYWxnb3JpdG1vcyBzZSBsb3MgdXNhIHBhcmEgcHJvYmxlbWFzIGRlICpyZWdyZXNpw7NuKiB5ICpjbGFzaWZpY2FjacOzbiouDQoNClBvciBvdHJvIGxhZG8sIHRlbmVtb3MgbG9zICoqYWxnb3JpdG1vcyBubyBzdXBlcnZpc2Fkb3MqKiwgcXVlIGVuIGRvbmRlIG5vc290cm9zIG5vIGRlZmluaW1vcyBsYSBjYXJhY3RlcsOtc3RpY2EgZGUgY2FkYSBncnVwbzogcG9yIGVqZW1wbG8gZW4gbG9zIGFsZ29yaXRtb3MgZGUgKmstbWVhbnMsKiBsZSBkZWNpbW9zIGFsIGFsZ29yaXRtbyBjdcOhbnRvcyBncnVwb3MgcXVlcmVtb3MsIHkgZWwgYWxnb3JpdG1vIHNlIGVuY2FyZ2EgZGUgY2xhc2lmaWNhcmxvcyBsb3MgY2Fzb3MgZW4gbG9zIGdydXBvcyBzaW4gcXVlIG5vc290cm9zIHRlbmdhbW9zIG3DoXMgY29udHJvbCBzb2JyZSBsYXMgZGVmaW5pY2lvbmVzIGRlIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGRlIGNhZGEgY2x1c3Rlci4gQWxndW5vcyBkZSBsb3MgYWxnb3JpdG1vcyBubyBzdXBlcnZpc2Fkb3MgcGVybWl0ZW4gZGV0ZWN0YXIgbm92ZWRhZGVzIGVuIGxvcyBkYXRvcywgZnJhdWRlcywgc2ltaWxpdHVkZXMgZW50cmUgdGV4dG9zIHBhcmEgY2xhc2lmaWNhciBkZSBpZ3VhbCBtYW5lcmEgdmFyaWFudGVzIGRlIHRleHRvLCBldGMuLg0KDQojIyBUaXBvcyBkZSBhbGdvcml0bW9zIGRlIGNsdXN0ZXJpbmcNCg0KIyMjIE3DqXRvZG9zIGRlIHBhcnRpY2nDs24NCg0KRW4gbG9zIG3DqXRvZG9zIGRlIHBhcnRpY2nDs24gbG9zIGRhdG9zIHNlIHZhbiBhIGFncnVwYXIgZW4gdW5hIGNhbnRpZGFkIChpbmRpY2FkYSBjb24gbGEgbGV0cmEgKioqayoqKikgZGUgZ3J1cG9zLiBMb3MgZ3J1cG9zIHRpZW5lbiBxdWUgY3VtcGxpciBjb24gZXN0YXMgY29uZGljaW9uZXM6DQoNCi0gICBDYWRhIGdydXBvIHRpZW5lIHF1ZSB0ZW5lciB1biBvYmpldG8uDQoNCi0gICBZIGNhZGEgb2JqZXRvIHRpZW5lIHF1ZSBwZXJ0ZW5lY2VyIGV4YWN0YW1lbnRlIGEgdW4gc8OzbG8gZ3J1cG8uDQoNCiMjIyBNw6l0b2RvcyBqZXLDoXJxdWljb3MNCg0KRGVudHJvIGRlIGxvcyBtw6l0b2RvcyBqZXLDoXJxdWljb3MsIHRlbmVtb3MgZG9zIHRpcG9zIGVzZW5jaWFsZXMgZGUgYWxnb3JpdG1vczoNCg0KTG9zIGFsZ29yaXRtb3MgKiphZ2xvbWVyYXRpdm9zKiogcXVlIGVtcGllemFuIGNvbiB1bmEgY2FudGlkYWQgbiBkZSBjbHVzdGVycyBkZSB1bmEgb2JzZXJ2YWNpw7NuIGNhZGEgdW5vLCB5IGx1ZWdvIHNlIGNvbWJpbmFuIGRvcyBncnVwb3MgaGFzdGEgdGVybWluYXIgY29uIHVuIHNvbG8gY2x1c3RlciBjb24gbiBvYnNlcnZhY2lvbmVzIChzw60sIGNvbW8gZWwgYW5pbGxvIGRlIFNhdXJvbiBtw6FzIG8gbWVub3MpLg0KDQpZIHBvciBvdHJvIGxhZG8gdGVuZW1vcyBsb3MgbcOpdG9kb3MgKipkaXZpc29yaW9zKiogcXVlICpjb21pZW56YW4gY29uIHVuIHPDs2xvIGNsdXN0ZXIqIGRlIG4gb2JzZXJ2YWNpb25lcyB5IGVuIGNhZGEgcGFzbyBzZSBkaXZpZGUgdW4gZ3J1cG8gZW4gZG9zIGhhc3RhIHRlbmVyIG4gY2x1c3RlcnMgY29uIHVuYSBvYnNlcnZhY2nDs24gY2FkYSB1bm8uIEVzIHByw6FjdGljYW1lbnRlIGxhIGVzdHJhdGVnaWEgaW52ZXJzYSBhIGxhIGFudGVyaW9yLg0KDQpIb3kgbm9zIGNlbnRyYXJlbW9zIGVuIGxvcyBwcmltZXJvcy4NCg0KIyBrLW1lYW5zDQoNCkVuIGZpZ3VyYXMgZ2VvbcOpdHJpY2FzIHNpbcOpdHJpY2FzLCBlcyBtdXkgc2VuY2lsbG8gZGVmaW5pciBlbCBjZW50cm8uDQoNCiFbQ2VudHJvIGRlIHVuYSBjaXJjdW5mZXJlbmNpYV0oaHR0cHM6Ly9sYWVzY3VlbGFlbmNhc2EuY29tL3dvcmRwcmVzcy93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wOS8wMy4tQ2VudHJvLW9wdGltaXphZGEucG5nKXt3aWR0aD0iMjMyIn0hW0NlbnRybyBkZSB1biBjdWFkcmFkb10oaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL3Byb3h5L2hPQ1lDa3pQV2NLeVdTTjZBTjJqNElyQTk1c0hQbWpNZ05jZ1lqd0hieWt5RTltVThyaFpwQ1VEdTJFa2NVV1ppWWczUzlqbmZtcS1Gell2MWM4VURLVjhNY0ZDaEh5LVIzZjFuMjN3WnNsbXBMcWhYVUJkcWVQTlVGVUJFVVJoVm10Vy1Xb3lEYU1DNFRDVUVCNXltYUtUKQ0KDQpBaG9yYSwgZW4gZmlndXJhcyBnZW9tw6l0cmljYXMgY29tcGxlamFzLCBjb21vIHB1ZWRlIHNlciBsb3MgbMOtbWl0ZXMgZGUgdW5hIHByb3ZpbmNpYSBvIGRlIHVuIGJhcnJpbywgZW5jb250cmFyIGVsIGNlbnRybyBlcyB1biBwcm9ibGVtYS4NCg0KIVtDb211bmEgMiBkZSBsYSBDaXVkYWQgQXV0w7Nub21hIGRlIEJ1ZW5vcyBBaXJlc10oaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL3Byb3h5L0pkTkYtYzhoRzJocUs1VWZ4LUpKZHdIX3hiaGZWLXRfUFZQcFZZaDhvREZYcExvT05nUzB3eEF5c05iUnhfeXBCMVpkUWE2YjR1cFRqTkpIMnFidzBnbHU2QTVmMEN4anJaQjVpOXFLakVTb1dGMUdJRVB6QXZqeXR1N1F2blE1WlpxdURERFg5b3d4d1Q0ell5WSkNCg0KYGBge3J9DQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShzZikNCg0KY29tdW5hcyA8LSBzdF9yZWFkKCdodHRwczovL2JpdHNhbmRicmlja3MuZ2l0aHViLmlvL2RhdGEvQ0FCQV9jb211bmFzLmdlb2pzb24nKQ0KDQpjb211bmFfMiA8LSBjb211bmFzICU+JSANCiAgZmlsdGVyKGNvbXVuYXMgPT0gMikNCg0KY29tdW5hXzIgPC0gY29tdW5hXzIgJT4lIA0KICBzdF9jZW50cm9pZCgpDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGE9Y29tdW5hcywgY29sb3I9ImdyYXkiKSsNCiAgZ2VvbV9zZihkYXRhPWNvbXVuYV8yLCBjb2xvcj0icmVkIiwgc2hhcGU9NCwgc3Ryb2tlPTIsIHNpemU9MSkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KYGBgDQoNCkNvbiBsb3MgY2x1c3RlcnMgZW4gay1tZWFucyB2YW1vcyBhIGhhY2VyIGFsZ28gYW7DoWxvZ28uIFZhbW9zIGEgdGVuZXIgdW5hIG51YmUgZGUgcHVudG9zLCBjb24gYWxndW5hcyBjYXJhY3RlcsOtc3RpY2FzLCB2YW1vcyBhIGluZGljYXIgbGEgY2FudGlkYWQgZGUgZ3J1cG9zIHF1ZSBxdWVyZW1vcyAoZWwgKmsqKSwgeSBlbCBhbGdvcml0bW8gdmEgYSBidXNjYXIsIGVuIGZ1bmNpw7NuIGRlIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbG9zIGRhdG9zIHVuIHB1bnRvIG1lZGlvIGRlIHJlZmVyZW5jaWEgcGFyYSBjYWRhIGNsdXN0ZXIuDQoNCkVzZSAicHVudG8gbWVkaW8iLCBlcyBsbyBxdWUgc2UgY29ub2NlIGNvbW8gKipjZW50cm9pZGUqKi4NCg0KSW50ZXJuYW1lbnRlIGxhIHNlY3VlbmNpYSBlcyBsYSBzaWd1aWVudGU6DQoNCjEuICBEZWZpbmltb3MgbGEgY2FudGlkYWQgZGUgY2x1c3RlcnMgKGVsIGspLg0KMi4gIEVsIGFsZ29yaXRtbyBkZWZpbmUgdW4gY2VudHJvIGFsZWF0b3JpbyBwYXJhIGxvcyBwdW50b3MuDQozLiAgUGFyYSBjYWRhIHB1bnRvIGRlIGxvcyBkYXRvcywgZGVmaW5lIGN1w6FsIGVzIGVsICpjZW50cm8qIHF1ZSBlc3TDoSBtw6FzIGNlcmNhIHkgY2FkYSBjZW50cm8gc2UgImFwcm9waWEiIGRlIHVuIHNldCBkZSBwdW50b3MgZGUgZGF0b3MuDQo0LiAgUGFyYSBjYWRhIGNlbnRybyB5IGxvcyBkYXRvcyBhcHJvcGlhZG9zLCBkZWZpbmUgdW4gY2VudHJvaWRlLg0KDQohW10oQXJjaGl2b3Mva21lYW5zLnBuZyl7d2lkdGg9IjQ1MCJ9DQoNCiMgQSBwcmFjdGljYXIhDQoNCiMjIFN5c2FybXkNCg0KVmFtb3MgYSB1c2FyIHVuYSB2ZXJzacOzbiBkZSBsYSBlbmN1ZXN0YSBkZSBTeXNhcm15IHkgcmVwYXNhciBsbyBxdWUgdmltb3MgZW4gbGEgc2VzacOzbiA5IGNvbiBQYWJsbyBDYXNhcy4NCg0KYGBge3J9DQpsaWJyYXJ5KGZ1bk1vZGVsaW5nKQ0KbGlicmFyeShEYXRhRXhwbG9yZXIpDQoNCiMgQ2FyZ2Ftb3MgZWwgYXJjaGl2bw0Kc3lzYXJteSA8LSByZWFkX2RlbGltKCJEYXRhc2V0cy9zeXNhcm15X2NsZWFuLmNzdiIsIGRlbGltID0gIjsiKQ0KDQpzdGF0dXMoc3lzYXJteSkNCg0KYGBgDQoNCkx1ZWdvLCBkZWZpbmltb3MgY29uIHF1w6kgdmFyaWFibGVzIG5vcyB2YW1vcyBhIHF1ZWRhcjoNCg0KYGBge3J9DQojIFNlbGVjY2lvbmFtb3MgdmFyaWFibGVzIGNhdGVnw7NyaWNhcw0KdmFyaWFibGVzX2MgPC0gc3RhdHVzKHN5c2FybXkpICU+JSANCiAgZmlsdGVyKHR5cGU9PSJjaGFyYWN0ZXIiICYgdW5pcXVlPDEwKSAlPiUgDQogIHB1bGwodmFyaWFibGUpDQoNCg0KdmFyaWFibGVzX24gPC0gc3RhdHVzKHN5c2FybXkpICU+JSANCiAgZmlsdGVyKHR5cGU9PSJudW1lcmljIikgJT4lIA0KICBwdWxsKHZhcmlhYmxlKQ0KDQojIE5vcyBxdWVkYW1vcyBjb24gZXN0YXMgdmFyaWFibGVzIGNhbmRpZGF0YXMNCnZfc2VsIDwtIGMoIlguVGVuw6lzLmd1YXJkaWFzLiIsIA0KICAgICAgICAgICAiWC5Qcm9ncmFtw6FzLmNvbW8uaG9iYmllLiIsIA0KICAgICAgICAgICAiWC5UdXZpc3RlLmFqdXN0ZXMucG9yLmluZmxhY2nDs24uZW4uMjAxOS4iLCANCiAgICAgICAgICAgIlNhbGFyaW8ubWVuc3VhbC5CUlVUTy4uZW4udHUubW9uZWRhLmxvY2FsLiIsDQogICAgICAgICAgICJUZW5nbyIpDQoNCmBgYA0KDQpEZSBhY8OhIGVuIG3DoXMsIGNvcGlhbW9zIGRlc2NhcmFkYW1lbnRlIGVsIHNjcmlwdCBkZSBQYWJsbyBDYXNhcyBkZSBsYSBzZXNpw7NuIDkgZGVsIENsdWIuIFByaW1lcm8gY3JlYW1vcyB1biBkYXRhZnJhbWUgY29uIGxhcyB2YXJpYWJsZXMgc2VsZWNjaW9uYWRhcw0KDQpgYGB7cn0NCiMgQ29waWFkbyBkZXNjYXJhZGFtZW50ZSBkZWwgc2NyaXB0IGRlIFBhYmxvIENhc2FzDQpkNT1zZWxlY3Qoc3lzYXJteSwgYWxsX29mKHZfc2VsKSkNCg0Kc3RhdHVzKGQ1KQ0KDQpgYGANCg0KTHVlZ28sIHRyYW5zZm9ybWFtb3MgbGFzIHZhcmlhYmxlcywgYSB2YXJpYWJsZXMgZGUgdGlwbyAqKmR1bW15KiouIEVzdG8gY3JlYSBwYXJhIGNhZGEgdW5hIGRlIGxhcyBjYXRlZ29yw61hcyBkZSBsYSB2YXJpYWJsZSBjYXRlZ8OzcmljYSwgdW5hIGNvbHVtbmEgbnVldmEgY29uIHVuIDEgbyB1biAwIHNlZ8O6biBjb3JyZXNwb25kYS4NCg0KYGBge3J9DQpkNV9kdW09ZHVtbWlmeShkNSkgICMgdmFyaWFibGVzIGR1bW15IG8gb25lIGhvdCBlbmNvZGluZw0KDQpnbGltcHNlKGQ1X2R1bSkNCmBgYA0KDQpFc2NhbGFtb3MgbG9zIGRhdG9zIHBhcmEgcXVlIGZ1bmNpb25lIG1lam9yIGVsIGFsZ29yaXRtbyBkZSBrLW1lYW5zIHkgY3JlYW1vcyBsb3MgY2x1c3RlcnMNCg0KYGBge3J9DQojIEVzY2FsYXIgcGFyYSBrbWVhbnMhDQpkNj1zY2FsZShkNV9kdW0pDQoNCmhlYWQoZDYpDQoNCmZpdF9jbHVzdGVyPWttZWFucyhkNiwgMykNCmBgYA0KDQpJbmNvcnBvcmFtb3MgbG9zIGNsdXN0ZXJzIGFsIGRhdGFmcmFtZQ0KDQpgYGB7cn0NCiMgQ29waWEhDQpkX2ZpdD1kNV9kdW0NCg0KIyBBc2lnbmFjaW9uIGRlbCBjbHVzdGVyDQpkX2ZpdCRjbHVzdGVyPWZpdF9jbHVzdGVyJGNsdXN0ZXINCmBgYA0KDQpZIHZpc3VhbGl6YW1vcyBsb3MgcmVzdWx0YWRvcw0KDQpgYGB7cn0NCiMgVmlzdWFsaXphbmRvLiBWYW1vcyBhIGNvbnRhciB1bmEgaGlzdG9yaWEhIQ0KY29vcmRfcGxvdChkYXRhPWRfZml0LCANCiAgICAgICAgICAgZ3JvdXBfdmFyPSJjbHVzdGVyIiwgDQogICAgICAgICAgIGdyb3VwX2Z1bmM9bWVhbiwgDQogICAgICAgICAgIHByaW50X3RhYmxlPVRSVUUpDQoNCg0KYGBgDQoNCkhhc3RhIGxvcyBjb21lbnRhcmlvcyBkZWwgc2NyaXB0IGxlIHJvYmFtb3MgYSBQYWJsby4NCg0KIyMgVW4gdXNvIGNvcGFkbzogcGFsZXRhIGRlIGNvbG9yZXMNCg0KW0FyaWFkbmEgQW5ndWxvLUJydW5ldF0oaHR0cHM6Ly93d3cubGlua2VkaW4uY29tL2luL2FyaWFkbmEtYW5ndWxvLWJydW5ldC05OGJhODc3YS8pLCB1bmEgcHNpY8OzbG9nYSBjYXRhbGFuYSwgcGFyYSBsYSBpbmljaWF0aXZhIFwjMzBkw61hc2RlZ3LDoWZpY29zIGRlIGxhIGNvbXVuaWRhZCBoaXNwYW5vcGFybGFudGUgZGUgUiBwdWJsaWPDsyBlbiBzdSBbY3VlbnRhIGRlIFR3aXR0ZXJdKGh0dHBzOi8vdHdpdHRlci5jb20vQW5ndWxvQnJ1bmV0L3N0YXR1cy8xMjYzMzY1MTE1NjYwODk4MzA0P3M9MjApIHVuYSBzZXJpZSBkZSBncsOhZmljb3MgY29uIHVuYSBwYWxldGEgZGUgY29sb3JlcyB1c2FuZG8gZm90b3MgZGUgc3VzIHZhY2FjaW9uZXMuIFN1IFtyZXBvIGVuIGdpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL0FuZ3Vsb0IvZGF0b3NkZW1pZXJjb2xlcy90cmVlL21hc3Rlci8wMF8zMGRpYXNEZUdyYWZpY29zKSBubyB0aWVuZSBkZXNwZXJkaWNpby4NCg0KSW5zcGlyYWRvIGVuIHN1IHRyYWJham8gaGljZSBsbyBtaXNtbyB1c2FuZG8gdW5hIGZvdG8gZGUgbWkgaGlqYS4NCg0KIVtdKEFyY2hpdm9zL2JlYXV0eS5qcGcpe3dpZHRoPSI0MDAifQ0KDQpWYXlhbW9zIHBvciBwYXJ0ZXMgY29uIGVsIGPDs2RpZ28NCg0KYGBge3J9DQpsaWJyYXJ5KGpwZWcpDQpsaWJyYXJ5KHNjYWxlcykNCmxpYnJhcnkoZXh0cmFmb250KSAjIGxhIHByaW1lcmEgdmV6IGVqZWN1dGFyIGZvbnRfaW1wb3J0KCkNCg0KbG9hZGZvbnRzKHF1aWV0ID0gVFJVRSkNCmZvbnQgPC0gIlVidW50dSBNb25vIg0KDQoNCmZvdG8gPC0gcmVhZEpQRUcoIkFyY2hpdm9zL2JlYXV0eS5qcGciKQ0KDQpjbGFzcyhmb3RvKQ0KYGBgDQoNCkxhIGZvdG8gZXN0w6EgY2FyZ2FkYSBlbiB1biB0aXBvIGRlIG9iamV0byBsbGFtYWRvICoqYXJyYXkqKi4gRXN0ZSBlcyB1biB0aXBvIGRlIHZlY3RvciBtdWx0aWRpbWVuc2lvbmFsLCBlcyBkZWNpciBxdWUgYSBkaWZlcmVuY2lhIGRlIHVuIHZlY3RvciBjb23Dum4gKHF1ZSBzb2xvIHRpZW5lIHVuYSBkaW1lbnNpw7NuLCBsb3MgYXJyYXlzIHB1ZWRlbiB0ZW5lciBbdmFyaWFzIGRpbWVuc2lvbmVzXShodHRwczovL2Jvb2tkb3duLm9yZy9qYm9zY29tZW5kb3phL3ItcHJpbmNpcGlhbnRlczQvbWF0cmljZXMteS1hcnJheXMuaHRtbCkuIEVuIG51ZXN0cm8gY2FzbywgdGllbmUgMy4NCg0KYGBge3J9DQpmb3RvX2RpbSA8LSBkaW0oZm90bykNCmZvdG9fZGltDQoNCmBgYA0KDQpMdWVnbyBjb252ZXJ0aW1vcyBlbCBhcnJheSBlbiB1biBkYXRhZnJhbWUuDQoNCmBgYHtyfQ0KZm90b19yZ2IgPC0gZGF0YS5mcmFtZSgNCiAgeCA9IHJlcCgxOmZvdG9fZGltWzJdLCBlYWNoID0gZm90b19kaW1bMV0pLA0KICB5ID0gcmVwKGZvdG9fZGltWzFdOjEsIGZvdG9fZGltWzJdKSwNCiAgUiA9IGFzLnZlY3Rvcihmb3RvWywsMV0pLCAjc2xpY2luZyBvdXIgYXJyYXkgaW50byB0aHJlZQ0KICBHID0gYXMudmVjdG9yKGZvdG9bLCwyXSksDQogIEIgPSBhcy52ZWN0b3IoZm90b1ssLDNdKSkNCg0KaGVhZChmb3RvX3JnYikNCg0Kc3VtbWFyeShmb3RvX3JnYikNCg0KYGBgDQoNCkVsIHNpZ3VpZW50ZSBwYXNvIGVzIGNyZWFyIGxvcyBjbHVzdGVycywgdmFtb3MgYSBkZWZpbmlyIDIwIGNsdXN0ZXJzIGRpZmVyZW50ZXMgKHB1ZWRlIHRhcmRhciB1bm9zIHNlZ3VuZG9zKQ0KDQpgYGB7cn0NCmZvdG9fa21lYW5zIDwtIGttZWFucyhmb3RvX3JnYlssYygiUiIsIkciLCJCIildLCBjZW50ZXJzID0gMjAsIGl0ZXIubWF4ID0gMzApDQoNCnN0cihmb3RvX2ttZWFucykNCmBgYA0KDQpGaW5hbG1lbnRlIHRlbmVtb3MgdW5hIHBhbGV0YSBkZSBjb2xvcmVzIGRlIGxhIGZvdG8gcXVlIHBvZGVtb3MgdXNhciBlbiBjdWFscXVpZXIgZ3LDoWZpY28uDQoNCmBgYHtyfQ0KIyBMYSBwYWxldGEgZGUgY29sb3JlcyBkZSBsYSBmb3RvIGRlIG1pIGhpamENCnNob3dfY29sKHJnYihmb3RvX2ttZWFucyRjZW50ZXJzKSkNCmBgYA0KDQpZIGNvbiBlc2EgcGFsZXRhIHBvZGVtb3MgY3JlYXIgdW4gZ3LDoWZpY28uIFZhbW9zIGEgdXNhciBsYXMgY29sdW1uYXMgYFB1ZXN0b2AgeSBgU3VlbGRvX0JydXRvYCBwYXJhIGNhbGN1bGFyIGVsIHN1ZWxkbyBwcm9tZWRpbyBkZSAyMCBwb3NpY2lvbmVzLiBBc8OtIHF1ZSBudWVzdHJvIHByaW1lciBwYXNvIGVzIGNyZWFyIHVuIGRhdGFmcmFtZSBjb24gZXNhcyBkb3MgY29sdW1uYXMsIHkgbHVlZ28gZmlsdHJhciBsYXMgcmVzcHVlc3RhcyBkZSAyMCBwdWVzdG9zIGNvbiBtYXlvciBjYW50aWRhZCBkZSBjYXNvcy4NCg0KYGBge3J9DQoNCmFuYWxpc2lzIDwtIHN5c2FybXkgJT4lDQogIHNlbGVjdChUcmFiYWpvLmRlLCBTYWxhcmlvLm1lbnN1YWwuQlJVVE8uLmVuLnR1Lm1vbmVkYS5sb2NhbC4pICU+JQ0KICByZW5hbWUoUHVlc3RvID0gVHJhYmFqby5kZSwNCiAgICAgICAgIFN1ZWxkb19CcnV0byA9IFNhbGFyaW8ubWVuc3VhbC5CUlVUTy4uZW4udHUubW9uZWRhLmxvY2FsLikNCg0KdG9wXzIwX3B1ZXN0b3MgPC0gYW5hbGlzaXMgJT4lDQogIHNlbGVjdChQdWVzdG8sIFN1ZWxkb19CcnV0bykgJT4lDQogIGdyb3VwX2J5KFB1ZXN0bykgJT4lDQogIHRhbGx5KHNvcnQgPSBUUlVFKSAlPiUgDQogIHRvcF9uKDIwKSAlPiUNCiAgc2VsZWN0KFB1ZXN0bykNCg0KdG9wXzIwX3B1ZXN0b3MgPC0gYXMudmVjdG9yKHRvcF8yMF9wdWVzdG9zJFB1ZXN0bykNCg0KDQpgYGANCg0KWSBhaG9yYSBwb2RlbW9zIGhhY2VyIG51ZXN0cm8gZ3LDoWZpY28sIHVzYW5kbyBsYSBwYWxldGEgZGUgY29sb3JlcyBxdWUgYWNhYmFtb3MgZGUgY3JlYXIuDQoNCmBgYHtyIGRwaT0zMDAsIGZpZy5oZWlnaHQ9Nn0NCmFuYWxpc2lzICU+JQ0KICBmaWx0ZXIoUHVlc3RvICVpbiUgdG9wXzIwX3B1ZXN0b3MpICU+JQ0KICBncm91cF9ieShQdWVzdG8pICU+JQ0KICBzdW1tYXJpc2UoU3VlbGRvX1Byb21lZGlvID0gbWVhbihTdWVsZG9fQnJ1dG8pKSAlPiUNCiAgdW5ncm91cCgpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihQdWVzdG8sIFN1ZWxkb19Qcm9tZWRpbyksIHkgPSBTdWVsZG9fUHJvbWVkaW8sIGZpbGwgPSBQdWVzdG8pKSsNCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHJnYihmb3RvX2ttZWFucyRjZW50ZXJzKSkrDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hX2Zvcm1hdChiaWcubWFyayA9ICIuIiwgZGVjaW1hbC5tYXJrID0gIiwiKSkrDQogIGNvb3JkX2ZsaXAoKSsNCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICIjRDdEQkREIiksDQogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIlVidW50dSBNb25vIikpICsNCiAgbGFicyh0aXRsZSA9ICJTdWVsZG8gYnJ1dG8gcHJvbWVkaW8gcG9yIHB1ZXN0byAoZW4gQVIkKSIsDQogICAgICAgc3VidGl0bGUgPSAiRnVlbnRlOiBFbmN1ZXN0YSBkZSBTdWVsZG9zIGRlIFN5c2FybXkiLA0KICAgICAgIGNhcHRpb24gPSAiQ2x1YiBkZSBSIHBhcmEgUlJISCIsDQogICAgICAgeCA9ICIiLA0KICAgICAgIHkgPSAiIikNCmBgYA0K