![](

via GIPHY

)

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écnica son:

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

Instalar paquetes y llamar librerías

#install.packages("cluster")
library(cluster)
#install.packages("ggplot2")
library(ggplot2)
#install.packages("data.table")
library(data.table)
## 
## Adjuntando el paquete: 'data.table'
## The following object is masked from 'package:base':
## 
##     %notin%
#install.packages("factoextra")
library(factoextra)
## Welcome to factoextra!
## Want to learn more? See two factoextra-related books at https://www.datanovia.com/library/principal-component-methods
#install.packages("dataset")
library(dataset)
#install.packages("dplyr)
library(dplyr)
## 
## Adjuntando el paquete: 'dplyr'
## The following object is masked from 'package:dataset':
## 
##     as_tibble
## The following objects are masked from 'package:data.table':
## 
##     between, first, last
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

Ejercicio1: Puntos

Contexto

Agrupa los siguientes 8 puntos

Cargar 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 los 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 los datos

#datos_escalados <- scale(datos_originales)
#Escalar los datos en caso de que los nuemros de las columnas sean muy diferentes

Asignar numero de grupos (n)

grupos1 <- 3

Agrupar los puntos

set.seed(123) #random state

clusters1 <- kmeans(df1,grupos1)

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 el número de grupos (n)

set.seed(123)

optimizacion1 <- clusGap(df1, FUN=kmeans, nstart=1, K.max=7)
#El k.max normalmente es 10, en este ejercicio al ser 8 datos se dejó en 7.

plot(optimizacion1, xlab="Número de clusters k", main="optimización de Clusters")

# Se selecciona como optimo el primer punto más alto

Graficar los grupos

fviz_cluster(clusters1, data=df1)

Agregar grupos a la base de datos

df1_cluster1 <- cbind(df1, cluster = clusters1$cluster)
head(df1_cluster1)
##   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 tecnica de clustering permite identificar patrones o grupos naturales en los datos sin necesidad de etiquetas previas.

Ejercicio2: USA Arrest

Cargar datos

df2 <- USArrests

#quitar la columna Urbanpop
df2 <- df2 %>% select(-UrbanPop)

Entender los 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 los 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
#Escalar los datos en caso de que los nuemeros de las columnas sean muy diferentes

Asignar numero de grupos (n)

grupos2 <- 5

Agrupar los puntos

set.seed(123) #random state

clusters2 <- kmeans(df2_escalados,grupos2)

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 el número de grupos (n)

set.seed(123)

optimizacion2 <- clusGap(df2_escalados, FUN=kmeans, nstart=1, K.max=10)
#El k.max normalmente es 10

plot(optimizacion2, xlab="Número de clusters k", main="optimización de Clusters")

# Se selecciona como optimo el primer punto más alto

Graficar los grupos

fviz_cluster(clusters2, data=df2_escalados)

Agregar grupos a la base de datos

df2_cluster2 <- cbind(df2, cluster = clusters2$cluster)
head(df2_cluster2)
##            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_cluster2 %>% group_by(cluster) %>% summarize_all(mean) %>%
  mutate(indicator_inseguridad=Murder+Assault+Rape)
## # A tibble: 5 × 5
##   cluster Murder Assault  Rape indicator_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_cluster2 %>% group_by(cluster) %>% summarize_all(mean) %>%
  mutate(cluster = case_when(
    cluster == 1 ~ "Inseguridad muy alta",
    cluster == 4 ~ "Inseguridad  alta",
    cluster == 5 ~ "Inseguridad media",
    cluster == 3 ~ "Inseguridad baja",
    cluster == 2 ~ "Otros"
  ))
## # A tibble: 5 × 4
##   cluster              Murder Assault  Rape
##   <chr>                 <dbl>   <dbl> <dbl>
## 1 Inseguridad muy alta  11.4    282.   29.7
## 2 Otros                  3.08    80.9  11.8
## 3 Inseguridad baja       6.59   146.   20.1
## 4 Inseguridad  alta      9.78   249.   42.4
## 5 Inseguridad media     14.4    245    22.2

Conclusiones

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

Ejercicio3: Segmentación de cliente

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 un empresa minorista en línea sin tienda física, basada en Reino Unido. La empresa vende prinicpalmente regalos unicos para toda ocasion, y muchos de sus clientes son mayoristas.

LS0tDQp0aXRsZTogIklBU2VzaW9uNyINCmF1dGhvcjogIlBhdWxhIENoYWlyZXogQTAxMjg2MjUxIg0KZGF0ZTogIjIwMjYtMDgtMjUiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRSAjIFRhYmxhIGRlIGNvbnRlbmlkb3MNCiAgICB0b2NfZmxvYXQ6IFRSVUUgIyBUYWJsYSBkZSBjb250ZW5pZG8gcXVlIHNlIGNvbGFwc2ENCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFICMgQm90b24gcGFyYSBkZXNjYXJnYXIgZWwgY29kaWdvDQogICAgdGhlbWU6IHBhcGVyDQotLS0NCiFbXSg8aWZyYW1lIHNyYz0iaHR0cHM6Ly9naXBoeS5jb20vZW1iZWQvMTJ2VkFHa2FxSFVxQ1EiIHdpZHRoPSI0ODAiIGhlaWdodD0iMjcxIiBzdHlsZT0iIiBmcmFtZUJvcmRlcj0iMCIgY2xhc3M9ImdpcGh5LWVtYmVkIiBhbGxvd0Z1bGxTY3JlZW4+PC9pZnJhbWU+PHA+PGEgaHJlZj0iaHR0cHM6Ly9naXBoeS5jb20vZ2lmcy9zcG90LWRhc2hlZTg3Z2l0aHViaW8tc2Npa2l0LTEydlZBR2thcUhVcUNRIj52aWEgR0lQSFk8L2E+PC9wPikNCg0KIyA8c3BhbiBzdHlsZT0gImNvbG9yOmdyZWVuIj4qKlRlb3LDrWEqKjwvc3Bhbj4NCipBZ3J1cGFtaWVudG8qIG8gKkNsdXN0ZXJpbmcqIGVzIHVuYSB0w6ljbmljYSBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyBubyBzdXBlcnZpc2FkbyBxdWUgYWdydXBhIGRhdG9zIGVuIGZ1bmNpw7NuIGRlIHN1IHNpbWlsaXR1ZC4gIA0KDQpBbGd1bm9zIHVzb3MgdMOtcGljb3MgZGUgZXN0YSB0w6ljbmljYSBzb246DQoNCiogU2VnbWVudGFjacOzbiBkZSBjbGllbnRlcy4gIA0KKiBEZXRlY2Npw7NuIGRlIGFub21hbGlkYWRlcy4gIA0KKiBDYXRlZ29yaXphY2nDs24gZGUgZG9jdW1lbnRvcy4gIA0KDQojIDxzcGFuIHN0eWxlPSAiY29sb3I6Z3JlZW4iPkluc3RhbGFyIHBhcXVldGVzIHkgbGxhbWFyIGxpYnJlcsOtYXM8L3NwYW4+DQpgYGB7cn0NCiNpbnN0YWxsLnBhY2thZ2VzKCJjbHVzdGVyIikNCmxpYnJhcnkoY2x1c3RlcikNCiNpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmxpYnJhcnkoZ2dwbG90MikNCiNpbnN0YWxsLnBhY2thZ2VzKCJkYXRhLnRhYmxlIikNCmxpYnJhcnkoZGF0YS50YWJsZSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJmYWN0b2V4dHJhIikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJkYXRhc2V0IikNCmxpYnJhcnkoZGF0YXNldCkNCiNpbnN0YWxsLnBhY2thZ2VzKCJkcGx5cikNCmxpYnJhcnkoZHBseXIpDQpgYGANCg0KIyA8c3BhbiBzdHlsZT0gImNvbG9yOmJsdWUiPioqRWplcmNpY2lvMTogUHVudG9zKio8L3NwYW4+DQoNCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6Ymx1ZSI+Q29udGV4dG88L3NwYW4+DQpBZ3J1cGEgbG9zIHNpZ3VpZW50ZXMgOCBwdW50b3MNCg0KIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjpibHVlIj5DYXJnYXIgZGF0b3M8L3NwYW4+DQpgYGB7cn0NCmRmMSA8LSBkYXRhLmZyYW1lKHg9YygyLDIsOCw1LDcsNiwxLDQpLCB5PWMoMTAsNSw0LDgsNSw0LDIsOSkpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjpibHVlIj5FbnRlbmRlciBsb3MgZGF0b3M8L3NwYW4+DQpgYGB7cn0NCnN1bW1hcnkoZGYxKQ0Kc3RyKGRmMSkNCnBsb3QoZGYxJHgsZGYxJHkpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjpibHVlIj5Fc2NhbGFyIGxvcyBkYXRvczwvc3Bhbj4NCmBgYHtyfQ0KI2RhdG9zX2VzY2FsYWRvcyA8LSBzY2FsZShkYXRvc19vcmlnaW5hbGVzKQ0KI0VzY2FsYXIgbG9zIGRhdG9zIGVuIGNhc28gZGUgcXVlIGxvcyBudWVtcm9zIGRlIGxhcyBjb2x1bW5hcyBzZWFuIG11eSBkaWZlcmVudGVzDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjpibHVlIj5Bc2lnbmFyIG51bWVybyBkZSBncnVwb3MgKG4pPC9zcGFuPg0KYGBge3J9DQpncnVwb3MxIDwtIDMNCmBgYA0KDQoNCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6Ymx1ZSI+QWdydXBhciBsb3MgcHVudG9zPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpICNyYW5kb20gc3RhdGUNCg0KY2x1c3RlcnMxIDwtIGttZWFucyhkZjEsZ3J1cG9zMSkNCg0KY2x1c3RlcnMxDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjpibHVlIj5PcHRpbWl6YXIgZWwgbsO6bWVybyBkZSBncnVwb3MgKG4pPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQoNCm9wdGltaXphY2lvbjEgPC0gY2x1c0dhcChkZjEsIEZVTj1rbWVhbnMsIG5zdGFydD0xLCBLLm1heD03KQ0KI0VsIGsubWF4IG5vcm1hbG1lbnRlIGVzIDEwLCBlbiBlc3RlIGVqZXJjaWNpbyBhbCBzZXIgOCBkYXRvcyBzZSBkZWrDsyBlbiA3Lg0KDQpwbG90KG9wdGltaXphY2lvbjEsIHhsYWI9Ik7Dum1lcm8gZGUgY2x1c3RlcnMgayIsIG1haW49Im9wdGltaXphY2nDs24gZGUgQ2x1c3RlcnMiKQ0KIyBTZSBzZWxlY2Npb25hIGNvbW8gb3B0aW1vIGVsIHByaW1lciBwdW50byBtw6FzIGFsdG8NCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0gImNvbG9yOmJsdWUiPkdyYWZpY2FyIGxvcyBncnVwb3M8L3NwYW4+DQpgYGB7cn0NCmZ2aXpfY2x1c3RlcihjbHVzdGVyczEsIGRhdGE9ZGYxKQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6Ymx1ZSI+QWdyZWdhciBncnVwb3MgYSBsYSBiYXNlIGRlIGRhdG9zPC9zcGFuPg0KYGBge3J9DQpkZjFfY2x1c3RlcjEgPC0gY2JpbmQoZGYxLCBjbHVzdGVyID0gY2x1c3RlcnMxJGNsdXN0ZXIpDQpoZWFkKGRmMV9jbHVzdGVyMSkNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0gImNvbG9yOmJsdWUiPkNvbmNsdXNpb25lczwvc3Bhbj4NCg0KTGEgdGVjbmljYSBkZSAqKmNsdXN0ZXJpbmcqKiBwZXJtaXRlIGlkZW50aWZpY2FyIHBhdHJvbmVzIG8gZ3J1cG9zIG5hdHVyYWxlcyBlbiAgbG9zIGRhdG9zIHNpbiBuZWNlc2lkYWQgZGUgZXRpcXVldGFzIHByZXZpYXMuDQoNCg0KIyA8c3BhbiBzdHlsZT0gImNvbG9yOmdyZWVuIj4qKkVqZXJjaWNpbzI6IFVTQSBBcnJlc3QqKjwvc3Bhbj4NCg0KDQojIyA8c3BhbiBzdHlsZT0gImNvbG9yOmdyZWVuIj5DYXJnYXIgZGF0b3M8L3NwYW4+DQpgYGB7cn0NCmRmMiA8LSBVU0FycmVzdHMNCg0KI3F1aXRhciBsYSBjb2x1bW5hIFVyYmFucG9wDQpkZjIgPC0gZGYyICU+JSBzZWxlY3QoLVVyYmFuUG9wKQ0KDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjpncmVlbiI+RW50ZW5kZXIgbG9zIGRhdG9zPC9zcGFuPg0KYGBge3J9DQpzdW1tYXJ5KGRmMikNCnN0cihkZjIpDQoNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0gImNvbG9yOmdyZWVuIj5Fc2NhbGFyIGxvcyBkYXRvczwvc3Bhbj4NCmBgYHtyfQ0KZGYyX2VzY2FsYWRvcyA8LSBzY2FsZShkZjIpDQpzdW1tYXJ5KGRmMl9lc2NhbGFkb3MpDQojRXNjYWxhciBsb3MgZGF0b3MgZW4gY2FzbyBkZSBxdWUgbG9zIG51ZW1lcm9zIGRlIGxhcyBjb2x1bW5hcyBzZWFuIG11eSBkaWZlcmVudGVzDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjpncmVlbiI+QXNpZ25hciBudW1lcm8gZGUgZ3J1cG9zIChuKTwvc3Bhbj4NCmBgYHtyfQ0KZ3J1cG9zMiA8LSA1DQpgYGANCg0KDQojIyA8c3BhbiBzdHlsZT0gImNvbG9yOmdyZWVuIj5BZ3J1cGFyIGxvcyBwdW50b3M8L3NwYW4+DQpgYGB7cn0NCnNldC5zZWVkKDEyMykgI3JhbmRvbSBzdGF0ZQ0KDQpjbHVzdGVyczIgPC0ga21lYW5zKGRmMl9lc2NhbGFkb3MsZ3J1cG9zMikNCg0KY2x1c3RlcnMyDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjpncmVlbiI+T3B0aW1pemFyIGVsIG7Dum1lcm8gZGUgZ3J1cG9zIChuKTwvc3Bhbj4NCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KDQpvcHRpbWl6YWNpb24yIDwtIGNsdXNHYXAoZGYyX2VzY2FsYWRvcywgRlVOPWttZWFucywgbnN0YXJ0PTEsIEsubWF4PTEwKQ0KI0VsIGsubWF4IG5vcm1hbG1lbnRlIGVzIDEwDQoNCnBsb3Qob3B0aW1pemFjaW9uMiwgeGxhYj0iTsO6bWVybyBkZSBjbHVzdGVycyBrIiwgbWFpbj0ib3B0aW1pemFjacOzbiBkZSBDbHVzdGVycyIpDQojIFNlIHNlbGVjY2lvbmEgY29tbyBvcHRpbW8gZWwgcHJpbWVyIHB1bnRvIG3DoXMgYWx0bw0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSAiY29sb3I6Z3JlZW4iPkdyYWZpY2FyIGxvcyBncnVwb3M8L3NwYW4+DQpgYGB7cn0NCmZ2aXpfY2x1c3RlcihjbHVzdGVyczIsIGRhdGE9ZGYyX2VzY2FsYWRvcykNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0gImNvbG9yOmdyZWVuIj5BZ3JlZ2FyIGdydXBvcyBhIGxhIGJhc2UgZGUgZGF0b3M8L3NwYW4+DQpgYGB7cn0NCmRmMl9jbHVzdGVyMiA8LSBjYmluZChkZjIsIGNsdXN0ZXIgPSBjbHVzdGVyczIkY2x1c3RlcikNCmhlYWQoZGYyX2NsdXN0ZXIyKQ0KDQpkZjJfY2x1c3RlcjIgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSBzdW1tYXJpemVfYWxsKG1lYW4pICU+JQ0KICBtdXRhdGUoaW5kaWNhdG9yX2luc2VndXJpZGFkPU11cmRlcitBc3NhdWx0K1JhcGUpDQoNCmRmMl9jbHVzdGVyMiAlPiUgZ3JvdXBfYnkoY2x1c3RlcikgJT4lIHN1bW1hcml6ZV9hbGwobWVhbikgJT4lDQogIG11dGF0ZShjbHVzdGVyID0gY2FzZV93aGVuKA0KICAgIGNsdXN0ZXIgPT0gMSB+ICJJbnNlZ3VyaWRhZCBtdXkgYWx0YSIsDQogICAgY2x1c3RlciA9PSA0IH4gIkluc2VndXJpZGFkICBhbHRhIiwNCiAgICBjbHVzdGVyID09IDUgfiAiSW5zZWd1cmlkYWQgbWVkaWEiLA0KICAgIGNsdXN0ZXIgPT0gMyB+ICJJbnNlZ3VyaWRhZCBiYWphIiwNCiAgICBjbHVzdGVyID09IDIgfiAiT3Ryb3MiDQogICkpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ICJjb2xvcjpncmVlbiI+Q29uY2x1c2lvbmVzPC9zcGFuPg0KDQpMYSB0ZWNuaWNhIGRlICoqY2x1c3RlcmluZyoqIHBlcm1pdGUgaWRlbnRpZmljYXIgcGF0cm9uZXMgbyBncnVwb3MgbmF0dXJhbGVzIGVuICBsb3MgZGF0b3Mgc2luIG5lY2VzaWRhZCBkZSBldGlxdWV0YXMgcHJldmlhcy4NCg0KDQojIDxzcGFuIHN0eWxlPSAiY29sb3I6cHVycGxlIj4qKkVqZXJjaWNpbzM6IFNlZ21lbnRhY2nDs24gZGUgY2xpZW50ZSoqPC9zcGFuPg0KDQojIyA8c3BhbiBzdHlsZT0gImNvbG9yOnB1cnBsZSI+Q29udGV4dG88L3NwYW4+DQpMYSBiYXNlIGRlIGRhdG9zICoqdmVudGFzKiogdGllbmUgbG9zIHJlZ2lzdHJvcyBlbnRyZSBlbCAxIGRlIGRpY2llbWJyZSBkZSAyMDEwIHkgZWwgOSBkZSBkaWNpZW1icmUgZGUgMjAxMSBkZSBsYXMgdmVudGFzIGRlIHVuIGVtcHJlc2EgbWlub3Jpc3RhIGVuIGzDrW5lYSBzaW4gdGllbmRhIGbDrXNpY2EsIGJhc2FkYSBlbiBSZWlubyBVbmlkby4gIExhIGVtcHJlc2EgdmVuZGUgcHJpbmljcGFsbWVudGUgcmVnYWxvcyB1bmljb3MgcGFyYSB0b2RhIG9jYXNpb24sIHkgbXVjaG9zIGRlIHN1cyBjbGllbnRlcyBzb24gbWF5b3Jpc3Rhcy4gDQoNCg==