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 anormalidades
  • Categorización de documentos

Instalar paquetes y llamar librerias

#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)
## 
## Adjuntando el paquete: 'data.table'
## The following object is masked from 'package:base':
## 
##     %notin%
#install.packages("factoextra")  #Gráfica de optimización del número de clusters
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("datasets")
library(datasets)
#install.packages("tidyverse")
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.1     ✔ readr     2.2.0
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ lubridate 1.9.5     ✔ tibble    3.3.1
## ✔ purrr     1.2.2     ✔ tidyr     1.3.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::between()     masks data.table::between()
## ✖ dplyr::filter()      masks stats::filter()
## ✖ dplyr::first()       masks data.table::first()
## ✖ lubridate::hour()    masks data.table::hour()
## ✖ lubridate::isoweek() masks data.table::isoweek()
## ✖ lubridate::isoyear() masks data.table::isoyear()
## ✖ dplyr::lag()         masks stats::lag()
## ✖ dplyr::last()        masks data.table::last()
## ✖ lubridate::mday()    masks data.table::mday()
## ✖ lubridate::minute()  masks data.table::minute()
## ✖ lubridate::month()   masks data.table::month()
## ✖ lubridate::quarter() masks data.table::quarter()
## ✖ lubridate::second()  masks data.table::second()
## ✖ purrr::transpose()   masks data.table::transpose()
## ✖ lubridate::wday()    masks data.table::wday()
## ✖ lubridate::week()    masks data.table::week()
## ✖ lubridate::yday()    masks data.table::yday()
## ✖ lubridate::year()    masks data.table::year()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

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

grupos1 <- 3

Agrupar los puntos

set.seed(123)
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 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 ejercicio al ser 8 datos se dejó en 7,
plot(optimizacion1, xlab="Número de clustersk", main="Optimización de Clusters")

#S 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 een arrestos por cada 100,000 residentes por agresión, asesinato 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

grupos2 <- 5

Agrupar los puntos

set.seed(123)
clusters2 <- kmeans(df2_escalados, grupos2)

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 ejercicio al ser 8 datos se dejó en 7,
plot(optimizacion2, xlab="Número de clusters k", main="Optimización de Clusters")

#S 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

Conclusiones

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

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.

LS0tDQp0aXRsZTogIkNsdXN0ZXJzIC0gUHVudG9zICwgVVNBcnJlc3RzIHkgQ2xpZW50ZXMiDQphdXRob3I6ICJTYXJhaGkgTWFjaWFzIFJ1YmlvIEEwMTc0Mjc5MSINCmRhdGU6ICIyMDI2LTA4LTI1Ig0KDQoNCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KICAgIHRoZW1lOiBkYXJrbHkNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPlRlb3LDrWE8L3NwYW4+DQoqKkFncnVwYW1pZW50byoqIG8gKkNsdXN0ZXJpbmcqIGVzIHVuYSB0w6ljbmljYSBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyANCm5vIHN1cGVydmlzYWRvIHF1ZSBhZ3J1cGEgZGF0b3MgZW4gZnVuY2nDs24gZGUgc3Ugc2ltaWxpdHVkLg0KDQpBbGd1bm9zIHVzb3MgdMOtcGljb3MgZGUgZXN0YSB0w6ljbmljYSBzb246DQoNCiogU2VnbWVudGFjacOzbiBkZSBjbGllbnRlcw0KKiBEZXRlY2Npw7NuIGRlIGFub3JtYWxpZGFkZXMNCiogQ2F0ZWdvcml6YWNpw7NuIGRlIGRvY3VtZW50b3MNCg0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPkluc3RhbGFyIHBhcXVldGVzIHkgbGxhbWFyIGxpYnJlcmlhczwvc3Bhbj4NCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQojaW5zdGFsbC5wYWNrYWdlcygiY2x1c3RlciIpICNBbsOhbGlzaXMgZGUgQWdydXBhbWllbnRvDQpsaWJyYXJ5KGNsdXN0ZXIpDQojaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpICNHcmFmaWNhcg0KbGlicmFyeShnZ3Bsb3QyKQ0KI2luc3RhbGwucGFja2FnZXMoImRhdGEudGFibGUiKSAgI01hbmVqbyBkZSBtdWNob3MgZGF0b3MNCmxpYnJhcnkoZGF0YS50YWJsZSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJmYWN0b2V4dHJhIikgICNHcsOhZmljYSBkZSBvcHRpbWl6YWNpw7NuIGRlbCBuw7ptZXJvIGRlIGNsdXN0ZXJzDQpsaWJyYXJ5KGZhY3RvZXh0cmEpIA0KI2luc3RhbGwucGFja2FnZXMoImRhdGFzZXRzIikNCmxpYnJhcnkoZGF0YXNldHMpDQojaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQojIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPkVqZXJjaWNpbyAxLiBQdW50b3M8L3NwYW4+DQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBDb250ZXh0byA8L3NwYW4+DQpBZ3J1cGEgbG9zIHNpZ3VpZW50ZXMgOCBwdW50b3MuDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBPYnRlbmVyIGRhdG9zIDwvc3Bhbj4NCmBgYHtyfQ0KZGYxIDwtIGRhdGEuZnJhbWUoeD1jKDIsMiw4LDUsNyw2LDEsNCksIHk9YygxMCw1LDQsOCw1LDQsMiw5KSkNCmBgYA0KDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBFbnRlbmRlciBkYXRvcyA8L3NwYW4+DQpgYGB7cn0NCnN1bW1hcnkoZGYxKQ0Kc3RyKGRmMSkNCnBsb3QoZGYxJHgsIGRmMSR5ICkNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gRXNjYWxhciBkYXRvcyA8L3NwYW4+DQpgYGB7cn0NCiNkYXRvc19lc2NhbGFkb3MgPC0gc2NhbGUoRGF0b3Nfb3JpZ2luYWxlcykNCmBgYA0KDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBBc2lnbmFyIG7Dum1lcm8gZGUgZ3J1cG9zIDwvc3Bhbj4NCmBgYHtyfQ0KZ3J1cG9zMSA8LSAzDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEFncnVwYXIgbG9zIHB1bnRvczwvc3Bhbj4NCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KY2x1c3RlcnMxIDwtIGttZWFucyhkZjEsIGdydXBvczEpDQpjbHVzdGVyczENCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gT3B0aW1pemFyIG7Dum1lcm8gZGUgZ3J1cG9zPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpvcHRpbWl6YWNpb24xIDwtIGNsdXNHYXAoZGYxLCBGVU49a21lYW5zLCBuc3RhcnQ9MSwgSy5tYXg9NykNCiNFbCBLLm1heCBub3JtYWxtZW50ZSBlcyAxMCwgZW4gZXN0ZSBlamVyY2ljaW8gYWwgc2VyIDggZGF0b3Mgc2UgZGVqw7MgZW4gNywNCnBsb3Qob3B0aW1pemFjaW9uMSwgeGxhYj0iTsO6bWVybyBkZSBjbHVzdGVyc2siLCBtYWluPSJPcHRpbWl6YWNpw7NuIGRlIENsdXN0ZXJzIikNCiNTIHNlbGVjY2lvbmEgY29tbyDDs3B0aW1vIGVsIHByaW1lciBwdW50byBtw6FzIGFsdG8uDQojU2kgZXMgZGlmZXJlbnRlIGFsIG9yaWdpbmFsLCByZWdyZXNhciB5IGFqdXN0YXIuDQpgYGANCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBHcmFmaWNhciBsb3MgZ3J1cG9zPC9zcGFuPg0KYGBge3J9DQpmdml6X2NsdXN0ZXIoY2x1c3RlcnMxLCBkYXRhPWRmMSkNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gQWdyZWdhciBncnVwb3MgYSBsYSBiYXNlIGRlIGRhdG9zIDwvc3Bhbj4NCmBgYHtyfQ0KZGYxX2NsdXN0ZXJzIDwtIGNiaW5kKGRmMSwgY2x1c3RlciA9IGNsdXN0ZXJzMSRjbHVzdGVyKQ0KaGVhZChkZjFfY2x1c3RlcnMpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IENvbmNsdXNpb25lcyAgPC9zcGFuPg0KTGEgdMOpY25pY2EgZGUgKmNsdXN0ZXJpbmcqIHBlcm1pdGUgaWRlbnRpZmljYXIgcGF0cm9uZXMgbyBncnVwb3MgbmF0dXJhbGVzIGVuIGxvcyANCmRhdG9zIHNpbiBuZWNlc2lkYWQgZGUgZXRpcXVldGFzIHByZXZpYXMNCg0KDQoNCg0KDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+RWplcmNpY2lvIDIuIFVTQXJyZXN0czwvc3Bhbj4NCg0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gQ29udGV4dG8gPC9zcGFuPg0KTGEgYmFzZSBkZSBkYXRvcyAqKlVTQXJyZXN0cyoqIGNvbnRpZW5lIGVzdGFkw61zdGljYXMgZWVuIGFycmVzdG9zIHBvciBjYWRhIDEwMCwwMDANCnJlc2lkZW50ZXMgcG9yIGFncmVzacOzbiwgYXNlc2luYXRvIHkgdmlvbGFjacOzbiBlbiBjYWRhIHVubyBkZSBsb3MgDQo1MCBlc3RhZG9zIGRlIEVFLlVVLiBlbiAxOTczLg0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gT2J0ZW5lciBkYXRvcyA8L3NwYW4+DQpgYGB7cn0NCmRmMiA8LSBVU0FycmVzdHMNCmRmMiA8LSBkZjIgJT4lIHNlbGVjdCgtVXJiYW5Qb3ApDQpgYGANCg0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gRW50ZW5kZXIgZGF0b3MgPC9zcGFuPg0KYGBge3J9DQpzdW1tYXJ5KGRmMikNCnN0cihkZjIpDQoNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gRXNjYWxhciBkYXRvcyA8L3NwYW4+DQpgYGB7cn0NCmRmMl9lc2NhbGFkb3MgPC0gc2NhbGUoZGYyKQ0Kc3VtbWFyeShkZjJfZXNjYWxhZG9zKQ0KYGBgDQoNCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEFzaWduYXIgbsO6bWVybyBkZSBncnVwb3MgPC9zcGFuPg0KYGBge3J9DQpncnVwb3MyIDwtIDUNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gQWdydXBhciBsb3MgcHVudG9zPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpjbHVzdGVyczIgPC0ga21lYW5zKGRmMl9lc2NhbGFkb3MsIGdydXBvczIpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IE9wdGltaXphciBuw7ptZXJvIGRlIGdydXBvczwvc3Bhbj4NCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0Kb3B0aW1pemFjaW9uMiA8LSBjbHVzR2FwKGRmMl9lc2NhbGFkb3MsIEZVTj1rbWVhbnMsIG5zdGFydD0xLCBLLm1heD0xMCkNCiNFbCBLLm1heCBub3JtYWxtZW50ZSBlcyAxMCwgZW4gZXN0ZSBlamVyY2ljaW8gYWwgc2VyIDggZGF0b3Mgc2UgZGVqw7MgZW4gNywNCnBsb3Qob3B0aW1pemFjaW9uMiwgeGxhYj0iTsO6bWVybyBkZSBjbHVzdGVycyBrIiwgbWFpbj0iT3B0aW1pemFjacOzbiBkZSBDbHVzdGVycyIpDQojUyBzZWxlY2Npb25hIGNvbW8gw7NwdGltbyBlbCBwcmltZXIgcHVudG8gbcOhcyBhbHRvLg0KI1NpIGVzIGRpZmVyZW50ZSBhbCBvcmlnaW5hbCwgcmVncmVzYXIgeSBhanVzdGFyLg0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBHcmFmaWNhciBsb3MgZ3J1cG9zPC9zcGFuPg0KYGBge3J9DQpmdml6X2NsdXN0ZXIoY2x1c3RlcnMyLCBkYXRhPWRmMl9lc2NhbGFkb3MpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEFncmVnYXIgZ3J1cG9zIGEgbGEgYmFzZSBkZSBkYXRvcyA8L3NwYW4+DQpgYGB7cn0NCmRmMl9jbHVzdGVycyA8LSBjYmluZChkZjIsIGNsdXN0ZXIgPSBjbHVzdGVyczIkY2x1c3RlcikNCmhlYWQoZGYyX2NsdXN0ZXJzKQ0KZGYyX2NsdXN0ZXJzICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgc3VtbWFyaXNlX2FsbChtZWFuKSAlPiUgbXV0YXRlKGluZGljYWRvcl9pbnNlZ3VyaWRhZD1NdXJkZXIrQXNzYXVsdCtSYXBlKQ0KDQpkZjJfY2x1c3RlcnMgPC0gZGYyX2NsdXN0ZXJzICU+JQ0KICBtdXRhdGUoY2x1c3Rlcj1jYXNlX3doZW4oDQogICAgY2x1c3RlciA9PSAxIH4gIkluc2VndXJpZGFkIE11eSBBbHRhIiwgDQogICAgY2x1c3RlciA9PSA0IH4gIkluc2VndXJpZGFkIEFsdGEiLA0KICAgIGNsdXN0ZXIgPT0gNSB+ICJJbnNlZ3VyaWRhZCBNZWRpYSIsDQogICAgY2x1c3RlciA9PSAzIH4gIkluc2VndXJpZGFkIEJhamEiLA0KICAgIGNsdXN0ZXIgPT0gMiB+ICJJbnNlZ3VyaWRhZCBNdXkgQmFqYSIsDQogICAgDQogICkpDQpoZWFkKGRmMl9jbHVzdGVycykNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gQ29uY2x1c2lvbmVzICA8L3NwYW4+DQpMYSB0w6ljbmljYSBkZSAqY2x1c3RlcmluZyogcGVybWl0ZSBpZGVudGlmaWNhciBwYXRyb25lcyBvIGdydXBvcyBuYXR1cmFsZXMgZW4gbG9zIA0KZGF0b3Mgc2luIG5lY2VzaWRhZCBkZSBldGlxdWV0YXMgcHJldmlhcw0KDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+RWplcmNpY2lvIDMuIFNlZ21lbnRhY2nDs24gZGUgQ2xpZW50ZXM8L3NwYW4+DQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBDb250ZXh0byA8L3NwYW4+DQpMYSBiYXNlIGRlIGRhdG9zICoqdmVudGFzKiogdGllbmUgbG9zIHJlZ2lzdHJvcyBlbnRyZSBlbCAxIGRlIGRpY2llbWJyZQ0KZGUgMjAxMCB5IGVsIDkgZGUgZGljaWVtYnJlIGRlIDIwMTEgZGUgbGFzIHZlbnRhcyBkZSB1bmEgZW1wcmVzYQ0KbWlub3Jpc3RhIGVuIGzDrW5lYSBzaW4gdGllbmRhIGbDrXNpY2EsIGJhc2FkYSBlbiBSZWlubyBVbmlkby4NCkxhIGVtcHJlc2EgdmVuZGUgcHJpbmNpcGFsbWVudGUgcmVnYWxvcyDDum5pY29zIHBhcmEgdG9kYSBvY2FzacOzbiwgeQ0KbXVjaG9zIGRlIHN1cyBjbGllbnRlcyBzb24gbWF5b3Jpc3Rhcy4NCk9iamV0aXZvOiBTZWdtZW50YXIgY2xpZW50ZXMsIGFzaWduYXJsZXMgbm9tYnJlIHkgY2FyYWN0ZXLDrXN0aWNhcyBkZSANCmNvbXBvcnRhbWllbnRvLCB5IHByb3BvbmVyIHN1Z2VyZW5jaWFzIGEgbGEgZW1wcmVzYSBwYXJhIGF1bWVudGFyIHZlbnRhcy4NCg0KDQo=