Teoría

Agrupamiento o clustering es una técnica de aprendizaje automático no supervisado que agrupa datos en función de su similitud.

Algunos usos típicos de esta técnico son:

  • Segmentación de clientes.
  • Detección de anormalidades.
  • Categorización de documentos.

Instalar paquetes y llamar librerías

#install.packages("cluster") # análisis de agrupamiento
library(cluster) 

#install.packages("ggplot2") # graficar
library(ggplot2)

#install.packages("data.table") # manejo de muchos datos
library(data.table)

#install.packages("factoextra") # gráfica de optimización del número de clusters
library(factoextra) 

#install.packages("tidyverse")
library(tidyverse)

Ejercicio 1: Puntos

Contexto

Agrupa los siguientes 8 puntos

Obtener datos

df1 <- data.frame(x=c(2,2,8,5,7,6,1,4), y=c(10,5,4,8,5,4,2,9)) 

Entender datos

summary(df1)
##        x               y         
##  Min.   :1.000   Min.   : 2.000  
##  1st Qu.:2.000   1st Qu.: 4.000  
##  Median :4.500   Median : 5.000  
##  Mean   :4.375   Mean   : 5.875  
##  3rd Qu.:6.250   3rd Qu.: 8.250  
##  Max.   :8.000   Max.   :10.000
str(df1) 
## 'data.frame':    8 obs. of  2 variables:
##  $ x: num  2 2 8 5 7 6 1 4
##  $ y: num  10 5 4 8 5 4 2 9
plot(df1$x,df1$y)

Escalar datos

# datos_escalados <- scale(datos_originales) 

Asignar número de grupos

grupo1 <- 3

Agrupar los puntos

set.seed(123)
clusters1 <- kmeans(df1,grupo1) 
clusters1
## K-means clustering with 3 clusters of sizes 2, 3, 3
## 
## Cluster means:
##          x        y
## 1 1.500000 3.500000
## 2 3.666667 9.000000
## 3 7.000000 4.333333
## 
## Clustering vector:
## [1] 2 1 3 2 3 3 1 2
## 
## Within cluster sum of squares by cluster:
## [1] 5.000000 6.666667 2.666667
##  (between_SS / total_SS =  85.8 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

Optimizar número de grupos

set.seed(123)
optimizacion1 <- clusGap(df1, FUN=kmeans, nstart=1, K.max = 7) 
# El K.max normalmente es 10, en este jercicio al ser de 8 datos se dejó en 7. 
plot(optimizacion1, xlab = "Número de clusters K", main = "Optimización de Clusters")  

# Se selecciona como óptimo el primer punto más alto 
# Si es diferente al original, regresar y ajustar

Graficar los grupos

fviz_cluster(clusters1, data = df1) 

Agregar grupos a la base de datos

df1_clusters <- cbind(df1, cluster= clusters1$cluster) 
head(df1_clusters) 
##   x  y cluster
## 1 2 10       2
## 2 2  5       1
## 3 8  4       3
## 4 5  8       2
## 5 7  5       3
## 6 6  4       3

Conclusiones

La técnica de clustering permite identificar patrones o grupos naturales en los datos sin necesidad de etiquetas previas.

Ejercicio 2: USArrests

Contexto

La base de datos USArrests contiene estadísticas en arrestos por cada 100,000 residentes por agresión, asesinatos y violación en cada uno de los 50 estados de EE.UU en 1973.

Obtener datos

df2 <- USArrests
df2 <- df2 %>% select(-UrbanPop) 

Entender datos

summary(df2)
##      Murder          Assault           Rape      
##  Min.   : 0.800   Min.   : 45.0   Min.   : 7.30  
##  1st Qu.: 4.075   1st Qu.:109.0   1st Qu.:15.07  
##  Median : 7.250   Median :159.0   Median :20.10  
##  Mean   : 7.788   Mean   :170.8   Mean   :21.23  
##  3rd Qu.:11.250   3rd Qu.:249.0   3rd Qu.:26.18  
##  Max.   :17.400   Max.   :337.0   Max.   :46.00
str(df2)
## 'data.frame':    50 obs. of  3 variables:
##  $ Murder : num  13.2 10 8.1 8.8 9 7.9 3.3 5.9 15.4 17.4 ...
##  $ Assault: int  236 263 294 190 276 204 110 238 335 211 ...
##  $ Rape   : num  21.2 44.5 31 19.5 40.6 38.7 11.1 15.8 31.9 25.8 ...

Escalar datos

df2_escalados <- scale(df2)
summary(df2_escalados)
##      Murder           Assault             Rape        
##  Min.   :-1.6044   Min.   :-1.5090   Min.   :-1.4874  
##  1st Qu.:-0.8525   1st Qu.:-0.7411   1st Qu.:-0.6574  
##  Median :-0.1235   Median :-0.1411   Median :-0.1209  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000  
##  3rd Qu.: 0.7949   3rd Qu.: 0.9388   3rd Qu.: 0.5277  
##  Max.   : 2.2069   Max.   : 1.9948   Max.   : 2.6444

Asignar número de grupos

grupo2 <- 5

Agrupar los puntos

set.seed(123)
clusters2 <- kmeans(df2_escalados,grupo2) 
clusters2
## K-means clustering with 5 clusters of sizes 7, 14, 17, 4, 8
## 
## Cluster means:
##       Murder    Assault        Rape
## 1  0.8292944  1.3313823  0.90560938
## 2 -1.0812577 -1.0779212 -1.00700542
## 3 -0.2754591 -0.2999280 -0.12336985
## 4  0.4562038  0.9358314  2.26533514
## 5  1.5238170  0.8908337  0.09934463
## 
## Clustering vector:
##        Alabama         Alaska        Arizona       Arkansas     California 
##              5              4              1              3              4 
##       Colorado    Connecticut       Delaware        Florida        Georgia 
##              4              2              3              1              5 
##         Hawaii          Idaho       Illinois        Indiana           Iowa 
##              2              2              1              3              2 
##         Kansas       Kentucky      Louisiana          Maine       Maryland 
##              3              3              5              2              1 
##  Massachusetts       Michigan      Minnesota    Mississippi       Missouri 
##              3              1              2              5              3 
##        Montana       Nebraska         Nevada  New Hampshire     New Jersey 
##              3              2              4              2              3 
##     New Mexico       New York North Carolina   North Dakota           Ohio 
##              1              1              5              2              3 
##       Oklahoma         Oregon   Pennsylvania   Rhode Island South Carolina 
##              3              3              3              2              5 
##   South Dakota      Tennessee          Texas           Utah        Vermont 
##              2              5              5              3              2 
##       Virginia     Washington  West Virginia      Wisconsin        Wyoming 
##              3              3              2              2              3 
## 
## Within cluster sum of squares by cluster:
## [1] 3.380664 5.645542 9.205038 1.346517 4.683603
##  (between_SS / total_SS =  83.5 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

Optimizar número de grupos

set.seed(123)
optimizacion2 <- clusGap(df2_escalados, FUN=kmeans, nstart=1, K.max = 10) 
# El K.max normalmente es 10, en este jercicio al ser de 8 datos se dejó en 7. 
plot(optimizacion2, xlab = "Número de clusters K", main = "Optimización de Clusters")  

# Se selecciona como óptimo el primer punto más alto 
# Si es diferente al original, regresar y ajustar

Graficar los grupos

fviz_cluster(clusters2, data = df2_escalados) 

Agregar grupos a la base de datos

df2_clusters <- cbind(df2, cluster = clusters2$cluster)
head(df2_clusters)
##            Murder Assault Rape cluster
## Alabama      13.2     236 21.2       5
## Alaska       10.0     263 44.5       4
## Arizona       8.1     294 31.0       1
## Arkansas      8.8     190 19.5       3
## California    9.0     276 40.6       4
## Colorado      7.9     204 38.7       4
df2_clusters %>% group_by(cluster) %>% summarise_all(mean) %>%
  mutate(indicador_inseguridad=Murder+Assault+Rape)
## # A tibble: 5 × 5
##   cluster Murder Assault  Rape indicador_inseguridad
##     <int>  <dbl>   <dbl> <dbl>                 <dbl>
## 1       1  11.4    282.   29.7                 323. 
## 2       2   3.08    80.9  11.8                  95.8
## 3       3   6.59   146.   20.1                 172. 
## 4       4   9.78   249.   42.4                 301. 
## 5       5  14.4    245    22.2                 282.
df2_clusters <- df2_clusters  %>%
  mutate(cluster= case_when(
    cluster == 1 ~ "Inseguridad Muy Alta",
    cluster == 4 ~ "Inseguridad Alta",
    cluster == 5 ~ "Inseguridad Media",
    cluster == 3 ~ "Inseguridad Baja",
    cluster == 2 ~ "Inseguridad Muy Baja"
  ))
head(df2_clusters) 
##            Murder Assault Rape              cluster
## Alabama      13.2     236 21.2    Inseguridad Media
## Alaska       10.0     263 44.5     Inseguridad Alta
## Arizona       8.1     294 31.0 Inseguridad Muy Alta
## Arkansas      8.8     190 19.5     Inseguridad Baja
## California    9.0     276 40.6     Inseguridad Alta
## Colorado      7.9     204 38.7     Inseguridad Alta

Ejercicio 3: Segmentación de clientes

Contexto

La base de datos ventas tiene los registros entre el 1 de diciembre de 2010 y el 9 de diciembre de 2011 de las ventas de una empresa minorista en línea sin tienda física, basada en Reino Unido. La empresa vende principalmente regalos únicos para toda ocasión, y muchos de sus clientes son mayoristas. Objetivo: Segmentar clientes, asignarles nombre y características de comportamiento, y proponer sugerencias a la empresa para aumentar ventas.

LS0tCnRpdGxlOiAiQ2x1c3RlcnMgLSBQdW50b3MsIFVTQXJyZXN0cyB5IENsaWVudGVzIgphdXRob3I6ICJEYW5hcmEgRXNwYXJ6YSBBMDA4NDA2MDIiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUKICAgIHRoZW1lOiBkYXJrbHkKLS0tCgohW10oaHR0cHM6Ly93d3cuc2h1dHRlcnN0b2NrLmNvbS9pbWFnZS1waG90by9jbHVzdGVyLWFuYWx5c2lzLW1hcmtldGluZy1zdGF0aXN0aWNhbC1kYXRhLTI2MG53LTI2ODQzMTg5NTkuanBnKSAKCiMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gVGVvcsOtYSA8L3NwYW4+IAoqKkFncnVwYW1pZW50byoqIG8gKmNsdXN0ZXJpbmcqIGVzIHVuYSB0w6ljbmljYSBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyBubyBzdXBlcnZpc2FkbyBxdWUgYWdydXBhIGRhdG9zIGVuIGZ1bmNpw7NuIGRlIHN1IHNpbWlsaXR1ZC4gCgpBbGd1bm9zIHVzb3MgdMOtcGljb3MgZGUgZXN0YSB0w6ljbmljbyBzb246IAoKKiBTZWdtZW50YWNpw7NuIGRlIGNsaWVudGVzLiAKKiBEZXRlY2Npw7NuIGRlIGFub3JtYWxpZGFkZXMuIAoqIENhdGVnb3JpemFjacOzbiBkZSBkb2N1bWVudG9zLiAKCiMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gSW5zdGFsYXIgcGFxdWV0ZXMgeSBsbGFtYXIgbGlicmVyw61hcyA8L3NwYW4+CmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiNpbnN0YWxsLnBhY2thZ2VzKCJjbHVzdGVyIikgIyBhbsOhbGlzaXMgZGUgYWdydXBhbWllbnRvCmxpYnJhcnkoY2x1c3RlcikgCgojaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpICMgZ3JhZmljYXIKbGlicmFyeShnZ3Bsb3QyKQoKI2luc3RhbGwucGFja2FnZXMoImRhdGEudGFibGUiKSAjIG1hbmVqbyBkZSBtdWNob3MgZGF0b3MKbGlicmFyeShkYXRhLnRhYmxlKQoKI2luc3RhbGwucGFja2FnZXMoImZhY3RvZXh0cmEiKSAjIGdyw6FmaWNhIGRlIG9wdGltaXphY2nDs24gZGVsIG7Dum1lcm8gZGUgY2x1c3RlcnMKbGlicmFyeShmYWN0b2V4dHJhKSAKCiNpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgojIDxzcGFuIHN0eWxlPSAiY29sb3I6IHllbGxvdyI+IEVqZXJjaWNpbyAxOiBQdW50b3MgPC9zcGFuPgoKIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gQ29udGV4dG8gPC9zcGFuPgpBZ3J1cGEgbG9zIHNpZ3VpZW50ZXMgOCBwdW50b3MKCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6IHllbGxvdyI+IE9idGVuZXIgZGF0b3MgPC9zcGFuPgpgYGB7cn0KZGYxIDwtIGRhdGEuZnJhbWUoeD1jKDIsMiw4LDUsNyw2LDEsNCksIHk9YygxMCw1LDQsOCw1LDQsMiw5KSkgCmBgYAoKIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gRW50ZW5kZXIgZGF0b3MgPC9zcGFuPgpgYGB7cn0Kc3VtbWFyeShkZjEpCnN0cihkZjEpIApwbG90KGRmMSR4LGRmMSR5KQpgYGAKCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6IHllbGxvdyI+IEVzY2FsYXIgZGF0b3MgPC9zcGFuPgpgYGB7cn0KIyBkYXRvc19lc2NhbGFkb3MgPC0gc2NhbGUoZGF0b3Nfb3JpZ2luYWxlcykgCmBgYAoKIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gQXNpZ25hciBuw7ptZXJvIGRlIGdydXBvcyA8L3NwYW4+CmBgYHtyfQpncnVwbzEgPC0gMwpgYGAKCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6IHllbGxvdyI+IEFncnVwYXIgbG9zIHB1bnRvcyA8L3NwYW4+IApgYGB7cn0Kc2V0LnNlZWQoMTIzKQpjbHVzdGVyczEgPC0ga21lYW5zKGRmMSxncnVwbzEpIApjbHVzdGVyczEKYGBgCgojIyA8c3BhbiBzdHlsZT0gImNvbG9yOiB5ZWxsb3ciPiBPcHRpbWl6YXIgbsO6bWVybyBkZSBncnVwb3MgPC9zcGFuPiAKYGBge3J9CnNldC5zZWVkKDEyMykKb3B0aW1pemFjaW9uMSA8LSBjbHVzR2FwKGRmMSwgRlVOPWttZWFucywgbnN0YXJ0PTEsIEsubWF4ID0gNykgCiMgRWwgSy5tYXggbm9ybWFsbWVudGUgZXMgMTAsIGVuIGVzdGUgamVyY2ljaW8gYWwgc2VyIGRlIDggZGF0b3Mgc2UgZGVqw7MgZW4gNy4gCnBsb3Qob3B0aW1pemFjaW9uMSwgeGxhYiA9ICJOw7ptZXJvIGRlIGNsdXN0ZXJzIEsiLCBtYWluID0gIk9wdGltaXphY2nDs24gZGUgQ2x1c3RlcnMiKSAgCiMgU2Ugc2VsZWNjaW9uYSBjb21vIMOzcHRpbW8gZWwgcHJpbWVyIHB1bnRvIG3DoXMgYWx0byAKIyBTaSBlcyBkaWZlcmVudGUgYWwgb3JpZ2luYWwsIHJlZ3Jlc2FyIHkgYWp1c3RhcgpgYGAKCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6IHllbGxvdyI+IEdyYWZpY2FyIGxvcyBncnVwb3MgPC9zcGFuPgpgYGB7cn0KZnZpel9jbHVzdGVyKGNsdXN0ZXJzMSwgZGF0YSA9IGRmMSkgCmBgYAoKIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gQWdyZWdhciBncnVwb3MgYSBsYSBiYXNlIGRlIGRhdG9zIDwvc3Bhbj4KYGBge3J9CmRmMV9jbHVzdGVycyA8LSBjYmluZChkZjEsIGNsdXN0ZXI9IGNsdXN0ZXJzMSRjbHVzdGVyKSAKaGVhZChkZjFfY2x1c3RlcnMpIApgYGAKCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6IHllbGxvdyI+IENvbmNsdXNpb25lcyA8L3NwYW4+CkxhIHTDqWNuaWNhIGRlICoqY2x1c3RlcmluZyoqIHBlcm1pdGUgaWRlbnRpZmljYXIgcGF0cm9uZXMgbyBncnVwb3MgbmF0dXJhbGVzIGVuIGxvcyBkYXRvcyBzaW4gbmVjZXNpZGFkIGRlIGV0aXF1ZXRhcyBwcmV2aWFzLiAKCgojIDxzcGFuIHN0eWxlPSAiY29sb3I6IHllbGxvdyI+IEVqZXJjaWNpbyAyOiBVU0FycmVzdHMgPC9zcGFuPgoKIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gQ29udGV4dG8gPC9zcGFuPgpMYSBiYXNlIGRlIGRhdG9zICoqVVNBcnJlc3RzKiogY29udGllbmUgZXN0YWTDrXN0aWNhcyBlbiBhcnJlc3RvcyBwb3IgY2FkYSAxMDAsMDAwIHJlc2lkZW50ZXMgcG9yIGFncmVzacOzbiwgYXNlc2luYXRvcyB5IHZpb2xhY2nDs24gZW4gY2FkYSB1bm8gZGUgbG9zIDUwIGVzdGFkb3MgZGUgRUUuVVUgZW4gMTk3My4gCgojIyA8c3BhbiBzdHlsZT0gImNvbG9yOiB5ZWxsb3ciPiBPYnRlbmVyIGRhdG9zIDwvc3Bhbj4KYGBge3J9CmRmMiA8LSBVU0FycmVzdHMKZGYyIDwtIGRmMiAlPiUgc2VsZWN0KC1VcmJhblBvcCkgCmBgYAoKIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gRW50ZW5kZXIgZGF0b3M8L3NwYW4+CmBgYHtyfQpzdW1tYXJ5KGRmMikKc3RyKGRmMikKYGBgCgojIyA8c3BhbiBzdHlsZT0gImNvbG9yOiB5ZWxsb3ciPiBFc2NhbGFyIGRhdG9zIDwvc3Bhbj4KYGBge3J9CmRmMl9lc2NhbGFkb3MgPC0gc2NhbGUoZGYyKQpzdW1tYXJ5KGRmMl9lc2NhbGFkb3MpCmBgYAoKIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gQXNpZ25hciBuw7ptZXJvIGRlIGdydXBvcyA8L3NwYW4+CmBgYHtyfQpncnVwbzIgPC0gNQpgYGAKCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6IHllbGxvdyI+IEFncnVwYXIgbG9zIHB1bnRvcyA8L3NwYW4+IApgYGB7cn0Kc2V0LnNlZWQoMTIzKQpjbHVzdGVyczIgPC0ga21lYW5zKGRmMl9lc2NhbGFkb3MsZ3J1cG8yKSAKY2x1c3RlcnMyCmBgYAoKIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gT3B0aW1pemFyIG7Dum1lcm8gZGUgZ3J1cG9zIDwvc3Bhbj4gCmBgYHtyfQpzZXQuc2VlZCgxMjMpCm9wdGltaXphY2lvbjIgPC0gY2x1c0dhcChkZjJfZXNjYWxhZG9zLCBGVU49a21lYW5zLCBuc3RhcnQ9MSwgSy5tYXggPSAxMCkgCiMgRWwgSy5tYXggbm9ybWFsbWVudGUgZXMgMTAsIGVuIGVzdGUgamVyY2ljaW8gYWwgc2VyIGRlIDggZGF0b3Mgc2UgZGVqw7MgZW4gNy4gCnBsb3Qob3B0aW1pemFjaW9uMiwgeGxhYiA9ICJOw7ptZXJvIGRlIGNsdXN0ZXJzIEsiLCBtYWluID0gIk9wdGltaXphY2nDs24gZGUgQ2x1c3RlcnMiKSAgCiMgU2Ugc2VsZWNjaW9uYSBjb21vIMOzcHRpbW8gZWwgcHJpbWVyIHB1bnRvIG3DoXMgYWx0byAKIyBTaSBlcyBkaWZlcmVudGUgYWwgb3JpZ2luYWwsIHJlZ3Jlc2FyIHkgYWp1c3RhcgpgYGAKCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6IHllbGxvdyI+IEdyYWZpY2FyIGxvcyBncnVwb3MgPC9zcGFuPgpgYGB7cn0KZnZpel9jbHVzdGVyKGNsdXN0ZXJzMiwgZGF0YSA9IGRmMl9lc2NhbGFkb3MpIApgYGAKCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6IHllbGxvdyI+IEFncmVnYXIgZ3J1cG9zIGEgbGEgYmFzZSBkZSBkYXRvcyA8L3NwYW4+CmBgYHtyfQpkZjJfY2x1c3RlcnMgPC0gY2JpbmQoZGYyLCBjbHVzdGVyID0gY2x1c3RlcnMyJGNsdXN0ZXIpCmhlYWQoZGYyX2NsdXN0ZXJzKQoKZGYyX2NsdXN0ZXJzICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgc3VtbWFyaXNlX2FsbChtZWFuKSAlPiUKICBtdXRhdGUoaW5kaWNhZG9yX2luc2VndXJpZGFkPU11cmRlcitBc3NhdWx0K1JhcGUpCgpkZjJfY2x1c3RlcnMgPC0gZGYyX2NsdXN0ZXJzICAlPiUKICBtdXRhdGUoY2x1c3Rlcj0gY2FzZV93aGVuKAogICAgY2x1c3RlciA9PSAxIH4gIkluc2VndXJpZGFkIE11eSBBbHRhIiwKICAgIGNsdXN0ZXIgPT0gNCB+ICJJbnNlZ3VyaWRhZCBBbHRhIiwKICAgIGNsdXN0ZXIgPT0gNSB+ICJJbnNlZ3VyaWRhZCBNZWRpYSIsCiAgICBjbHVzdGVyID09IDMgfiAiSW5zZWd1cmlkYWQgQmFqYSIsCiAgICBjbHVzdGVyID09IDIgfiAiSW5zZWd1cmlkYWQgTXV5IEJhamEiCiAgKSkKaGVhZChkZjJfY2x1c3RlcnMpIApgYGAKCiMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gRWplcmNpY2lvIDM6IFNlZ21lbnRhY2nDs24gZGUgY2xpZW50ZXMgPC9zcGFuPgoKIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjogeWVsbG93Ij4gQ29udGV4dG8gPC9zcGFuPgpMYSBiYXNlIGRlIGRhdG9zICoqdmVudGFzKiogdGllbmUgbG9zIHJlZ2lzdHJvcyBlbnRyZSBlbCAxIGRlIGRpY2llbWJyZSBkZSAyMDEwIHkgZWwgOSBkZSBkaWNpZW1icmUgZGUgMjAxMSBkZSBsYXMgdmVudGFzIGRlIHVuYSBlbXByZXNhIG1pbm9yaXN0YSBlbiBsw61uZWEgc2luIHRpZW5kYSBmw61zaWNhLCBiYXNhZGEgZW4gUmVpbm8gVW5pZG8uIApMYSBlbXByZXNhIHZlbmRlIHByaW5jaXBhbG1lbnRlIHJlZ2Fsb3Mgw7puaWNvcyBwYXJhIHRvZGEgb2Nhc2nDs24sIHkgbXVjaG9zIGRlIHN1cyBjbGllbnRlcyBzb24gbWF5b3Jpc3Rhcy4gCk9iamV0aXZvOiBTZWdtZW50YXIgY2xpZW50ZXMsIGFzaWduYXJsZXMgbm9tYnJlIHkgY2FyYWN0ZXLDrXN0aWNhcyBkZSBjb21wb3J0YW1pZW50bywgeSBwcm9wb25lciBzdWdlcmVuY2lhcyBhIGxhIGVtcHJlc2EgcGFyYSBhdW1lbnRhciB2ZW50YXMuIAoK