I. LECTURA DE DATOS
Emilio Gondar en su libro ” Analisis de Conglomerados”(Editorial :
Data Mining Institute. 2004) (Nores, 2014)
presenta un ejemplo donde a un grupo de 21 personas se le midío una
serie de variables de tipo métrico, y segun estos atributos se van a
clasificar a estas personas en grupos o categorias de tal forma que
dentro de cada grupo las unidades muestrales sean lo mas homogonea
posible, y entre los grupos estas unidades, comparativamente, sean lo
mas heterogenea posible
La información que se recolectó de un grupo de 21 personas (usando
una escala de Likert del 1 al 7, donde 1 es desacuerdo y 7 de acuerdo),
fue su grado de conformidad a las siguientes afirmaciones cuando visita
un centro comercial:
Salir de compras es divertido
Salir de compras afecta el presupuesto
Al salir de compras aprovecho de comer fuera
Al salir a comprar trato de hacer las mejores
No me importa salir de compras
Al salir de compra voy a ahorrar si comparo precios
Se desarrollará el caso con el lenguaje de programacion R (R Core Team,
2022a)
datosc <- read.table(file = "compras-cluster.csv", header = TRUE,
sep = ",", stringsAsFactors = TRUE)
str(datosc)
## 'data.frame': 21 obs. of 7 variables:
## $ caso : int 1 2 3 4 5 6 7 8 9 10 ...
## $ divertid: int 6 2 7 4 1 6 5 7 2 3 ...
## $ presupu : int 4 3 2 6 3 4 3 3 4 3 ...
## $ aprovech: int 7 1 6 4 2 6 6 7 3 3 ...
## $ buenacom: int 3 4 4 5 2 3 3 4 3 6 ...
## $ noimport: int 2 5 1 3 6 3 3 1 6 4 ...
## $ ahorro : int 3 4 3 6 4 4 4 4 3 6 ...
attr(datosc,"variable.labels") <- NULL
datosc$caso <- NULL#Se elimino la variable "caso"
II ANÁLISIS EXPLORATORIO
Usamos la libreria DataExplorer(Cui, 2020) para
obtener una grafica que detecte los % de datos perdidos
summary(datosc)
## divertid presupu aprovech buenacom noimport
## Min. :1.0 Min. :2 Min. :1.00 Min. :2.0 Min. :1.00
## 1st Qu.:2.0 1st Qu.:3 1st Qu.:2.00 1st Qu.:3.0 1st Qu.:2.00
## Median :4.0 Median :4 Median :4.00 Median :4.0 Median :3.00
## Mean :3.9 Mean :4 Mean :4.05 Mean :4.1 Mean :3.43
## 3rd Qu.:5.0 3rd Qu.:5 3rd Qu.:6.00 3rd Qu.:5.0 3rd Qu.:4.00
## Max. :7.0 Max. :7 Max. :7.00 Max. :7.0 Max. :7.00
## ahorro
## Min. :2.00
## 1st Qu.:3.00
## Median :4.00
## Mean :4.38
## 3rd Qu.:5.00
## Max. :7.00
#Analisis Exploratorio con DataExplorer
# Detectando y graficando los % de datos perdidos
plot_missing(datosc, ggtheme=theme_bw())+
labs(tittle="Datos perdidos por variable",
y="Datos periddos",
x="Variables")


Usamos la libreria funModeling(Casas, 2020) (el
cual usa ggplot2) para obtener una grafica de las variables
#Analisis Exploratorio con la librería funModeling
# Gráfico de variables numéricas
plot_num(datosc) + theme_bw()


III.USANDO MEDIDAS DE DISTANCIA
1. Distancia euclidiana
Usamos la libreria factoextra(Kassambara & Mundt,
2020) para hallar la matriz de distancia y una grafica de la
matriz
#Calculando la matriz de distancia euclidiana con la
#funcion get_dist()
res.dist <- get_dist(datosc, stand = FALSE,
method = "euclidean")
# Visualizando un subconjunto de la matriz de distancia
round(as.matrix(res.dist)[1:6, 1:6], 1)
## 1 2 3 4 5 6
## 1 0.0 8.0 2.8 5.6 8.3 1.7
## 2 8.0 0.0 8.2 5.6 2.6 6.9
## 3 2.8 8.2 0.0 6.6 9.1 3.3
## 4 5.6 5.6 6.6 0.0 6.6 4.5
## 5 8.3 2.6 9.1 6.6 0.0 7.2
## 6 1.7 6.9 3.3 4.5 7.2 0.0
2. Gráfico matriz de distancia
#Visualizando la matriz de distancia con fviz_dist()
fviz_dist(res.dist) #ESTAN ORDENADOS, el problema es cuando hay miles de datos

fviz_dist(res.dist,
gradient = list(low = "#00AFBB", mid = "white",
high = "#FC4E07"))

IV.CLUSTER DE PARTICIÓN - NO JERÁRQUICOS
1 Criterios para hallar el número de clusters
1.1 Usando el criterio del Gráfico de Silueta
Usamos la libreria factoextra(Kassambara & Mundt,
2020) para el criterio del “grafico de la silueta”
set.seed(123)
fviz_nbclust(datosc, kmeans, method = "silhouette") +
labs(subtitle = "Silhouette method")

1.2 Usando el criterio de Suma de Cuadrados dentro de clusters
Usamos la libreria factoextra(Kassambara & Mundt,
2020) para el criterio del “Suma de cuadrados dentro de
clusters”
set.seed(123)
wss <- numeric()
for(h in 1:10){
b<-kmeans(datosc,h)
wss[h]<-b$tot.withinss #scintra
}
wss
## [1] 334.7 177.6 89.9 73.7 67.5 55.3 40.8 35.6 30.3 24.5
wss1 <- data.frame(cluster=c(1:10),wss)
wss1
## cluster wss
## 1 1 334.7
## 2 2 177.6
## 3 3 89.9
## 4 4 73.7
## 5 5 67.5
## 6 6 55.3
## 7 7 40.8
## 8 8 35.6
## 9 9 30.3
## 10 10 24.5
Usamos la libreria ggplot2(Wickham, 2016) para
el grafico de los cluster segun la suma de cuadrados dentro de
cluster
# Gráficamos el cluster con la S.C.
ggplot(wss1) + aes(cluster,wss) + geom_line(color="blue") +
geom_point(color="blue") +
geom_vline(xintercept = 3, linetype = 2, col="red") +
labs(title = "Método Elbow") +
scale_x_continuous(breaks=1:10) +
theme_classic()

1.3 NbClust: 30 Indices para determinar el número de clusters
Usamos la libreria NbClust(Charrad et al.,
2014) el cual usa 30 indices para determinar el tamaño optimo
de clusters ademas te indica cuantos criterios han elegido cada clusters
y la decision final
set.seed(123)
res.nbclust <- NbClust(datosc, distance = "euclidean",
min.nc = 2, max.nc = 5,
method = "average", index ="all")
Usamos la libreria factoextra(Kassambara & Mundt,
2020) para una grafica de barrras el cual indique cuantos
indices han elegido el “k” cluster
factoextra::fviz_nbclust(res.nbclust) + theme_minimal()
## Among all indices:
## ===================
## * 2 proposed 0 as the best number of clusters
## * 1 proposed 1 as the best number of clusters
## * 2 proposed 2 as the best number of clusters
## * 14 proposed 3 as the best number of clusters
## * 4 proposed 4 as the best number of clusters
## * 3 proposed 5 as the best number of clusters
##
## Conclusion
## =========================
## * According to the majority rule, the best number of clusters is 3 .

2.Método de particion: K-means
Usamos la libreria stats(R Core Team, 2022b) el cual es una
libreria predeterminada de R con el cual usaremos la funcion kmeans para
hallar los clusters
set.seed(123)
km <- kmeans(datosc,
centers=3, # Número de Cluster
iter.max = 100, # Número de iteraciones máxima
nstart = 1, # Número de puntos iniciales
algorithm = "Lloyd")
#nstart=25, significa que se probaran 25 puntos iniciales
#aleatorios y luego eligirá aquel donde la variación dentro
#(intra) de cluster sea minima.
#El valor por defecto es 1
A continuacion toda la informacion que da usar la funcion kmeans:
# Mostrar los clusters que pertenece cada individuo
km$cluster
## [1] 1 3 1 2 3 1 1 1 3 2 3 1 3 2 1 2 1 3 2 3 1
clusters1 = km$cluster
# Tamaño de cada cluster
km$size
## [1] 9 5 7
# Promedios de cada cluster
km$centers
## divertid presupu aprovech buenacom noimport ahorro
## 1 5.67 3.67 6.00 3.22 2.00 4.00
## 2 3.60 5.20 3.60 6.00 3.40 6.60
## 3 1.86 3.57 1.86 3.86 5.29 3.29
# Número de interaciones
km$iter
## [1] 4
# Sumas de cuadrados
km$withinss #Suma de cuadrados dentro de cada cluster
## [1] 37.6 15.6 43.1
km$tot.withinss #Suma de cuadrados Total dentro de cada cluster
## [1] 96.3
km$betweenss #Suma de cuadrados entre cluster
## [1] 238
#Se obtiene por diferencia
Usamos la libreria factoextra(Kassambara & Mundt,
2020) para una grafica de los clusters usando ACP
# Visualización de las soluciones usando ACP
fviz_cluster(km,data=datosc,ellipse.type = "convex") +
theme_classic()

V.CARACTERIZANDO A LOS CLUSTERS
Consiste en analizar los centros de gravedad de cada grupo
(promedios)
datos.j <- cbind(datosc,clusters1)
datos.j$clusters1 <- factor(datos.j$clusters1) #los clusters
Usamos la libreria dplyr(Wickham et al., 2022)
#Diagrama de líneas de promedio por cluster
datos.j %>%
group_by(clusters1) %>%
summarise_all(list(mean)) -> medias #promedios por grupo por cada variable
medias
## # A tibble: 3 x 7
## clusters1 divertid presupu aprovech buenacom noimport ahorro
## <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 5.67 3.67 6 3.22 2 4
## 2 2 3.6 5.2 3.6 6 3.4 6.6
## 3 3 1.86 3.57 1.86 3.86 5.29 3.29
#tapply(datos.j$divertid,grp,mean)
datos.j %>% summarise_if(is.numeric,mean) %>%
round(4) -> general #promedio general
general
## divertid presupu aprovech buenacom noimport ahorro
## 1 3.9 4 4.05 4.1 3.43 4.38
general <- cbind(clusters1="general",general)
general #para tener dos resultados con los mismos campos(numero de columnas)
## clusters1 divertid presupu aprovech buenacom noimport ahorro
## 1 general 3.9 4 4.05 4.1 3.43 4.38
medias <- as.data.frame(rbind(medias,general))
medias
## clusters1 divertid presupu aprovech buenacom noimport ahorro
## 1 1 5.67 3.67 6.00 3.22 2.00 4.00
## 2 2 3.60 5.20 3.60 6.00 3.40 6.60
## 3 3 1.86 3.57 1.86 3.86 5.29 3.29
## 4 general 3.90 4.00 4.05 4.10 3.43 4.38
Usamos la libreria tidyr(Wickham et al., 2022) para convertir la
data a formato tidy con pivot_longer(pasar de filas a columnas)
gathered_datos.j <- pivot_longer(data=medias,
-clusters1,
names_to="variable",
values_to = "valor")
head(gathered_datos.j)
## # A tibble: 6 x 3
## clusters1 variable valor
## <fct> <chr> <dbl>
## 1 1 divertid 5.67
## 2 1 presupu 3.67
## 3 1 aprovech 6
## 4 1 buenacom 3.22
## 5 1 noimport 2
## 6 1 ahorro 4
A continuacion finalmente la grafica de caracterizacion de los
clusters:
ggplot(gathered_datos.j) + aes(x=variable,y=valor,color=clusters1) +
geom_point() +
geom_line(aes(group = clusters1)) +
theme_bw() +
theme(legend.position = "bottom",legend.title=element_blank()) +
labs(title="Diagrama de líneas de Cluster por Variable",
x="Variable",y="") + ylim(0,8)+
scale_colour_discrete("Cluster") #+ coord_flip()

Casas, P. (2020).
funModeling: Exploratory data analysis and data
preparation tool-box.
https://CRAN.R-project.org/package=funModeling
Charrad, M., Ghazzali, N., Boiteau, V., & Niknafs, A. (2014).
NbClust: An
R package for determining the
relevant number of clusters in a data set.
Journal of Statistical
Software,
61(6), 1–36.
http://www.jstatsoft.org/v61/i06/
Cui, B. (2020).
DataExplorer: Automate data exploration and
treatment.
https://CRAN.R-project.org/package=DataExplorer
Nores, J. E. G. (2014). Análisis de conglomerados (cluster
analysis). R Foundation for Statistical Computing; Data Mining
Institute.
R Core Team. (2022a).
R: A language and environment for statistical
computing. R Foundation for Statistical Computing.
https://www.R-project.org/
R Core Team. (2022b).
R: A language and environment for statistical
computing. R Foundation for Statistical Computing.
https://www.R-project.org/
Wickham, H. (2016).
ggplot2: Elegant graphics for data
analysis. Springer-Verlag New York.
https://ggplot2.tidyverse.org
Wickham, H., François, R., Henry, L., & Müller, K. (2022).
Dplyr: A grammar of data manipulation.
https://CRAN.R-project.org/package=dplyr
LS0tDQp0aXRsZTogIkFuYWxpc2lzIENsdXN0ZXIgLSBNZXRvZG8gay1NZWFucyINCmF1dGhvcjogIldhbGRvIEfDs21leiINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCmVtYWlsOiAid2FsZG9nMjc3QGdtYWlsLmNvbSINCm91dHB1dDogDQogIHJtZGZvcm1hdHM6OmRvd25jdXRlOg0KICAgIHNlbGZfY29udGFpbmVkOiB0cnVlDQogICAgZGVmYXVsdF9zdHlsZTogImxpZ2h0Ig0KICAgIGRvd25jdXRlX3RoZW1lOiAiY2hhb3MiDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KYmlibGlvZ3JhcGh5OiBiaWJsaW9ncmFmaWEuYmliDQpjc2w6IGFwYS5jc2wNCmxpbmstY2l0YXRpb25zOiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCm9wdGlvbnMoc2NpcGVuID0gOTk5KSAgICAgICMgRWxpbWluYXIgbGEgbm90YWNpw7NuIGNpZW50w61maWNhDQpvcHRpb25zKGRpZ2l0cyA9IDMpICAgICAgICAjIE7Dum1lcm8gZGUgZGVjaW1hbGVzDQoNCiMgUGFxdWV0ZXMNCmxpYnJhcnkocGFjbWFuKQ0KcF9sb2FkKGZhY3RvZXh0cmEsTmJDbHVzdCxEYXRhRXhwbG9yZXIsDQogICAgICAgZnVuTW9kZWxpbmcsdGlkeXIsZ2dwbG90MixkcGx5cikNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojIyBJLiBMRUNUVVJBIERFIERBVE9TDQoNCkVtaWxpbyBHb25kYXIgZW4gc3UgbGlicm8gIiBBbmFsaXNpcyBkZSBDb25nbG9tZXJhZG9zIihFZGl0b3JpYWwgOiBEYXRhIE1pbmluZyBJbnN0aXR1dGUuIDIwMDQpICBbQENsdXN0ZXJBbmFseXNpc10gcHJlc2VudGEgdW4gZWplbXBsbyBkb25kZSBhIHVuIGdydXBvIGRlIDIxDQpwZXJzb25hcyBzZSBsZSBtaWTDrW8gdW5hIHNlcmllIGRlIHZhcmlhYmxlcyBkZSB0aXBvIG3DqXRyaWNvLCB5IHNlZ3VuDQplc3RvcyBhdHJpYnV0b3Mgc2UgdmFuIGEgY2xhc2lmaWNhciBhIGVzdGFzIHBlcnNvbmFzIGVuIGdydXBvcyBvDQpjYXRlZ29yaWFzIGRlIHRhbCBmb3JtYSBxdWUgZGVudHJvIGRlIGNhZGEgZ3J1cG8gbGFzIHVuaWRhZGVzIG11ZXN0cmFsZXMNCnNlYW4gbG8gbWFzIGhvbW9nb25lYSBwb3NpYmxlLCB5IGVudHJlIGxvcyBncnVwb3MgZXN0YXMgdW5pZGFkZXMsDQpjb21wYXJhdGl2YW1lbnRlLCBzZWFuIGxvIG1hcyBoZXRlcm9nZW5lYSBwb3NpYmxlDQoNCkxhIGluZm9ybWFjacOzbiBxdWUgc2UgcmVjb2xlY3TDsyBkZSB1biBncnVwbyBkZSAyMSBwZXJzb25hcyAodXNhbmRvIHVuYQ0KZXNjYWxhIGRlIExpa2VydCBkZWwgMSBhbCA3LCBkb25kZSAxIGVzIGRlc2FjdWVyZG8geSA3IGRlIGFjdWVyZG8pLCBmdWUNCnN1IGdyYWRvIGRlIGNvbmZvcm1pZGFkIGEgbGFzIHNpZ3VpZW50ZXMgYWZpcm1hY2lvbmVzIGN1YW5kbyB2aXNpdGEgdW4NCmNlbnRybyBjb21lcmNpYWw6DQoNCi0gICBTYWxpciBkZSBjb21wcmFzIGVzIGRpdmVydGlkbw0KDQotICAgU2FsaXIgZGUgY29tcHJhcyBhZmVjdGEgZWwgcHJlc3VwdWVzdG8NCg0KLSAgIEFsIHNhbGlyIGRlIGNvbXByYXMgYXByb3ZlY2hvIGRlIGNvbWVyIGZ1ZXJhDQoNCi0gICBBbCBzYWxpciBhIGNvbXByYXIgdHJhdG8gZGUgaGFjZXIgbGFzIG1lam9yZXMNCg0KLSAgIE5vIG1lIGltcG9ydGEgc2FsaXIgZGUgY29tcHJhcw0KDQotICAgQWwgc2FsaXIgZGUgY29tcHJhIHZveSBhIGFob3JyYXIgc2kgY29tcGFybyBwcmVjaW9zDQoNClNlIGRlc2Fycm9sbGFyw6EgZWwgY2FzbyBjb24gZWwgbGVuZ3VhamUgZGUgcHJvZ3JhbWFjaW9uIFIgW0BSXQ0KDQpgYGB7cn0NCmRhdG9zYyA8LSByZWFkLnRhYmxlKGZpbGUgPSAiY29tcHJhcy1jbHVzdGVyLmNzdiIsIGhlYWRlciA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiLCIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBUUlVFKQ0Kc3RyKGRhdG9zYykNCg0KYXR0cihkYXRvc2MsInZhcmlhYmxlLmxhYmVscyIpIDwtIE5VTEwNCmRhdG9zYyRjYXNvIDwtIE5VTEwjU2UgZWxpbWlubyBsYSB2YXJpYWJsZSAiY2FzbyINCmBgYA0KDQoNCiMjIElJIEFOw4FMSVNJUyBFWFBMT1JBVE9SSU8NClVzYW1vcyBsYSBsaWJyZXJpYSBEYXRhRXhwbG9yZXJbQGRhdGFleHBsb3Jlcl0gcGFyYSBvYnRlbmVyIHVuYSBncmFmaWNhIHF1ZSBkZXRlY3RlIGxvcyAlIGRlIGRhdG9zIHBlcmRpZG9zDQoNCmBgYHtyIGV4cGxvLGZpZy5hbGlnbj0nY2VudGVyJ30NCg0Kc3VtbWFyeShkYXRvc2MpDQoNCiNBbmFsaXNpcyBFeHBsb3JhdG9yaW8gY29uIERhdGFFeHBsb3Jlcg0KIyBEZXRlY3RhbmRvIHkgZ3JhZmljYW5kbyBsb3MgJSBkZSBkYXRvcyBwZXJkaWRvcw0KcGxvdF9taXNzaW5nKGRhdG9zYywgZ2d0aGVtZT10aGVtZV9idygpKSsNCiAgICAgICAgICAgICAgIGxhYnModGl0dGxlPSJEYXRvcyBwZXJkaWRvcyBwb3IgdmFyaWFibGUiLA0KICAgICAgICAgICAgICAgICAgICB5PSJEYXRvcyBwZXJpZGRvcyIsDQogICAgICAgICAgICAgICAgICAgIHg9IlZhcmlhYmxlcyIpIA0KYGBgDQoNClVzYW1vcyBsYSBsaWJyZXJpYSBmdW5Nb2RlbGluZ1tAZnVuTW9kZWxpbmddIChlbCBjdWFsIHVzYSBnZ3Bsb3QyKSBwYXJhIG9idGVuZXIgdW5hIGdyYWZpY2EgZGUgbGFzIHZhcmlhYmxlcyANCmBgYHtyIGV4cGxvX2dyYWYsd2FybmluZz1GQUxTRX0NCiNBbmFsaXNpcyBFeHBsb3JhdG9yaW8gY29uIGxhIGxpYnJlcsOtYSBmdW5Nb2RlbGluZw0KIyBHcsOhZmljbyBkZSB2YXJpYWJsZXMgbnVtw6lyaWNhcw0KcGxvdF9udW0oZGF0b3NjKSArIHRoZW1lX2J3KCkNCmBgYA0KDQojIyBJSUkuVVNBTkRPIE1FRElEQVMgREUgRElTVEFOQ0lBDQoNCiMjIyAxLiBEaXN0YW5jaWEgZXVjbGlkaWFuYQ0KDQpVc2Ftb3MgbGEgbGlicmVyaWEgZmFjdG9leHRyYVtAZmFjdG9leHRyYV0gcGFyYSBoYWxsYXIgbGEgbWF0cml6IGRlIGRpc3RhbmNpYSB5IHVuYSBncmFmaWNhIGRlIGxhIG1hdHJpeg0KYGBge3IgZXVjbGksIGZpZy5hbGlnbj0nY2VudGVyJ30NCiNDYWxjdWxhbmRvIGxhIG1hdHJpeiBkZSBkaXN0YW5jaWEgZXVjbGlkaWFuYSBjb24gbGENCiNmdW5jaW9uIGdldF9kaXN0KCkNCnJlcy5kaXN0IDwtIGdldF9kaXN0KGRhdG9zYywgc3RhbmQgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZXVjbGlkZWFuIikNCg0KIyBWaXN1YWxpemFuZG8gdW4gc3ViY29uanVudG8gZGUgbGEgbWF0cml6IGRlIGRpc3RhbmNpYQ0Kcm91bmQoYXMubWF0cml4KHJlcy5kaXN0KVsxOjYsIDE6Nl0sIDEpDQpgYGANCg0KIyMjIDIuIEdyw6FmaWNvIG1hdHJpeiBkZSBkaXN0YW5jaWENCg0KYGBge3IgbWF0cml6LCBmaWcuYWxpZ249J2NlbnRlcid9DQojVmlzdWFsaXphbmRvIGxhIG1hdHJpeiBkZSBkaXN0YW5jaWEgY29uIGZ2aXpfZGlzdCgpDQpmdml6X2Rpc3QocmVzLmRpc3QpICAjRVNUQU4gT1JERU5BRE9TLCBlbCBwcm9ibGVtYSBlcyBjdWFuZG8gaGF5IG1pbGVzIGRlIGRhdG9zDQoNCmZ2aXpfZGlzdChyZXMuZGlzdCwgDQogICAgICAgICAgZ3JhZGllbnQgPSBsaXN0KGxvdyA9ICIjMDBBRkJCIiwgbWlkID0gIndoaXRlIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSAiI0ZDNEUwNyIpKQ0KYGBgDQoNCg0KIyMgSVYuQ0xVU1RFUiBERSBQQVJUSUNJw5NOIC0gTk8gSkVSw4FSUVVJQ09TDQoNCjxjZW50ZXI+IA0KIVtdKGttZWFucy5wbmcpe3dpZHRoPSI4MDAifSA8IS0tIFdpZHRoIHBhcmEgcmVkdWNpciBlbCB0YW1hw7FvIGRlIGxhIGltYWdlbiAtLT4NCjwvY2VudGVyPg0KDQojIyMgMSBDcml0ZXJpb3MgcGFyYSBoYWxsYXIgZWwgbsO6bWVybyBkZSBjbHVzdGVycw0KDQojIyMjIDEuMSBVc2FuZG8gZWwgY3JpdGVyaW8gZGVsIEdyw6FmaWNvIGRlIFNpbHVldGENCg0KVXNhbW9zIGxhIGxpYnJlcmlhIGZhY3RvZXh0cmFbQGZhY3RvZXh0cmFdIHBhcmEgZWwgY3JpdGVyaW8gZGVsICJncmFmaWNvIGRlIGxhIHNpbHVldGEiDQpgYGB7ciBzaWx1ZXRhLCBmaWcuYWxpZ249J2NlbnRlcid9DQpzZXQuc2VlZCgxMjMpDQpmdml6X25iY2x1c3QoZGF0b3NjLCBrbWVhbnMsIG1ldGhvZCA9ICJzaWxob3VldHRlIikgKw0KICBsYWJzKHN1YnRpdGxlID0gIlNpbGhvdWV0dGUgbWV0aG9kIikNCmBgYA0KDQojIyMjIDEuMiBVc2FuZG8gZWwgY3JpdGVyaW8gZGUgU3VtYSBkZSBDdWFkcmFkb3MgZGVudHJvIGRlIGNsdXN0ZXJzDQoNClVzYW1vcyBsYSBsaWJyZXJpYSBmYWN0b2V4dHJhW0BmYWN0b2V4dHJhXSBwYXJhIGVsIGNyaXRlcmlvIGRlbCAiU3VtYSBkZSBjdWFkcmFkb3MgZGVudHJvIGRlIGNsdXN0ZXJzIg0KYGBge3Igc3VtYWN1YWRyYWludHJhLCBmaWcuYWxpZ249J2NlbnRlcid9DQpzZXQuc2VlZCgxMjMpDQp3c3MgPC0gbnVtZXJpYygpDQpmb3IoaCBpbiAxOjEwKXsNCiAgYjwta21lYW5zKGRhdG9zYyxoKQ0KICB3c3NbaF08LWIkdG90LndpdGhpbnNzICNzY2ludHJhDQp9DQp3c3MgDQoNCndzczEgPC0gZGF0YS5mcmFtZShjbHVzdGVyPWMoMToxMCksd3NzKQ0Kd3NzMQ0KYGBgDQoNClVzYW1vcyBsYSBsaWJyZXJpYSBnZ3Bsb3QyW0BnZ3Bsb3QyXSBwYXJhIGVsIGdyYWZpY28gZGUgbG9zIGNsdXN0ZXIgc2VndW4gbGEgc3VtYSBkZSBjdWFkcmFkb3MgZGVudHJvIGRlIGNsdXN0ZXINCg0KYGBge3Igc2MsZmlnLmFsaWduPSdjZW50ZXInfQ0KIyBHcsOhZmljYW1vcyBlbCBjbHVzdGVyIGNvbiBsYSBTLkMuDQpnZ3Bsb3Qod3NzMSkgKyBhZXMoY2x1c3Rlcix3c3MpICsgZ2VvbV9saW5lKGNvbG9yPSJibHVlIikgKyANCiAgZ2VvbV9wb2ludChjb2xvcj0iYmx1ZSIpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMywgbGluZXR5cGUgPSAyLCBjb2w9InJlZCIpICsNCiAgbGFicyh0aXRsZSA9ICJNw6l0b2RvIEVsYm93IikgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz0xOjEwKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCg0KIyMjIyAxLjMgTmJDbHVzdDogMzAgSW5kaWNlcyBwYXJhIGRldGVybWluYXIgZWwgbsO6bWVybyBkZSBjbHVzdGVycw0KDQpVc2Ftb3MgbGEgbGlicmVyaWEgTmJDbHVzdFtATmJDbHVzdF0gZWwgY3VhbCB1c2EgMzAgaW5kaWNlcyBwYXJhIGRldGVybWluYXIgZWwgdGFtYcOxbyBvcHRpbW8gZGUgY2x1c3RlcnMgYWRlbWFzIHRlIGluZGljYSBjdWFudG9zIGNyaXRlcmlvcyBoYW4gZWxlZ2lkbyBjYWRhIGNsdXN0ZXJzIHkgbGEgZGVjaXNpb24gZmluYWwNCmBgYHtyIG5iY2x1c3QscmVzdWx0cz0naGlkZScsZmlnLnNob3c9J2hpZGUnfQ0Kc2V0LnNlZWQoMTIzKQ0KcmVzLm5iY2x1c3QgPC0gTmJDbHVzdChkYXRvc2MsIGRpc3RhbmNlID0gImV1Y2xpZGVhbiIsDQogICAgICAgICAgICAgICAgICAgICAgIG1pbi5uYyA9IDIsIG1heC5uYyA9IDUsIA0KICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiYXZlcmFnZSIsIGluZGV4ID0iYWxsIikgDQpgYGANCg0KVXNhbW9zIGxhIGxpYnJlcmlhIGZhY3RvZXh0cmFbQGZhY3RvZXh0cmFdIHBhcmEgdW5hIGdyYWZpY2EgZGUgYmFycnJhcyBlbCBjdWFsIGluZGlxdWUgY3VhbnRvcyBpbmRpY2VzIGhhbiBlbGVnaWRvIGVsICJrIiBjbHVzdGVyDQoNCmBgYHtyIG5iY2x1c3RfZ3JhZixmaWcuYWxpZ249J2NlbnRlcicsd2FybmluZz1GQUxTRX0NCmZhY3RvZXh0cmE6OmZ2aXpfbmJjbHVzdChyZXMubmJjbHVzdCkgKyB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoNCiMjIyAyLk3DqXRvZG8gZGUgcGFydGljaW9uOiBLLW1lYW5zDQoNClVzYW1vcyBsYSBsaWJyZXJpYSBzdGF0c1tAc3RhdHNdIGVsIGN1YWwgZXMgdW5hIGxpYnJlcmlhIHByZWRldGVybWluYWRhIGRlIFIgY29uIGVsIGN1YWwgdXNhcmVtb3MgbGEgZnVuY2lvbiBrbWVhbnMgcGFyYSBoYWxsYXIgbG9zIGNsdXN0ZXJzDQpgYGB7ciBrbWVhbnN9DQpzZXQuc2VlZCgxMjMpDQprbSA8LSBrbWVhbnMoZGF0b3NjLCANCiAgICAgICAgICAgICBjZW50ZXJzPTMsICAgICAgIyBOw7ptZXJvIGRlIENsdXN0ZXINCiAgICAgICAgICAgICBpdGVyLm1heCA9IDEwMCwgIyBOw7ptZXJvIGRlIGl0ZXJhY2lvbmVzIG3DoXhpbWENCiAgICAgICAgICAgICBuc3RhcnQgPSAxLCAgICAgIyBOw7ptZXJvIGRlIHB1bnRvcyBpbmljaWFsZXMgDQogICAgICAgICAgICAgYWxnb3JpdGhtID0gIkxsb3lkIikgICAgDQojbnN0YXJ0PTI1LCBzaWduaWZpY2EgcXVlIHNlIHByb2JhcmFuIDI1IHB1bnRvcyBpbmljaWFsZXMNCiNhbGVhdG9yaW9zIHkgbHVlZ28gZWxpZ2lyw6EgYXF1ZWwgZG9uZGUgbGEgdmFyaWFjacOzbiBkZW50cm8NCiMoaW50cmEpIGRlIGNsdXN0ZXIgc2VhIG1pbmltYS4NCiNFbCB2YWxvciBwb3IgZGVmZWN0byBlcyAxDQpgYGANCg0KQSBjb250aW51YWNpb24gdG9kYSBsYSBpbmZvcm1hY2lvbiBxdWUgZGEgdXNhciBsYSBmdW5jaW9uIGttZWFuczoNCg0KYGBge3IgaW5mb19jbHVzdGVyc30NCiMgTW9zdHJhciBsb3MgY2x1c3RlcnMgcXVlIHBlcnRlbmVjZSBjYWRhIGluZGl2aWR1byANCmttJGNsdXN0ZXINCmNsdXN0ZXJzMSA9IGttJGNsdXN0ZXINCg0KIyBUYW1hw7FvIGRlIGNhZGEgY2x1c3Rlcg0Ka20kc2l6ZQ0KDQojIFByb21lZGlvcyBkZSBjYWRhIGNsdXN0ZXINCmttJGNlbnRlcnMNCg0KIyBOw7ptZXJvIGRlIGludGVyYWNpb25lcw0Ka20kaXRlcg0KDQojIFN1bWFzIGRlIGN1YWRyYWRvcw0Ka20kd2l0aGluc3MgICAgICNTdW1hIGRlIGN1YWRyYWRvcyBkZW50cm8gZGUgY2FkYSBjbHVzdGVyDQprbSR0b3Qud2l0aGluc3MgI1N1bWEgZGUgY3VhZHJhZG9zIFRvdGFsIGRlbnRybyBkZSBjYWRhIGNsdXN0ZXINCmttJGJldHdlZW5zcyAgICAjU3VtYSBkZSBjdWFkcmFkb3MgZW50cmUgY2x1c3Rlcg0KICAgICAgICAgICAgICAgICNTZSBvYnRpZW5lIHBvciBkaWZlcmVuY2lhDQpgYGANCg0KVXNhbW9zIGxhIGxpYnJlcmlhIGZhY3RvZXh0cmFbQGZhY3RvZXh0cmFdIHBhcmEgdW5hIGdyYWZpY2EgZGUgbG9zIGNsdXN0ZXJzIHVzYW5kbyBBQ1ANCmBgYHtyIGdyYWZfYWNwLCBmaWcuYWxpZ249J2NlbnRlcid9DQojIFZpc3VhbGl6YWNpw7NuIGRlIGxhcyBzb2x1Y2lvbmVzIHVzYW5kbyBBQ1ANCmZ2aXpfY2x1c3RlcihrbSxkYXRhPWRhdG9zYyxlbGxpcHNlLnR5cGUgPSAiY29udmV4IikgKyANCiAgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KIyMgVi5DQVJBQ1RFUklaQU5ETyBBIExPUyBDTFVTVEVSUw0KDQpDb25zaXN0ZSBlbiBhbmFsaXphciBsb3MgY2VudHJvcyBkZSBncmF2ZWRhZCBkZSBjYWRhIGdydXBvIChwcm9tZWRpb3MpDQoNCmBgYHtyIGNiaW5kX2NsdXN0fQ0KZGF0b3MuaiA8LSBjYmluZChkYXRvc2MsY2x1c3RlcnMxKQ0KZGF0b3MuaiRjbHVzdGVyczEgPC0gZmFjdG9yKGRhdG9zLmokY2x1c3RlcnMxKSAjbG9zIGNsdXN0ZXJzDQoNCmBgYA0KDQpVc2Ftb3MgbGEgbGlicmVyaWEgZHBseXJbQGRwbHlyXSANCmBgYHtyIGRwbHlyfQ0KI0RpYWdyYW1hIGRlIGzDrW5lYXMgZGUgcHJvbWVkaW8gcG9yIGNsdXN0ZXINCmRhdG9zLmogJT4lICAgDQogIGdyb3VwX2J5KGNsdXN0ZXJzMSkgJT4lDQogIHN1bW1hcmlzZV9hbGwobGlzdChtZWFuKSkgLT4gbWVkaWFzICNwcm9tZWRpb3MgcG9yIGdydXBvIHBvciBjYWRhIHZhcmlhYmxlDQptZWRpYXMNCiN0YXBwbHkoZGF0b3MuaiRkaXZlcnRpZCxncnAsbWVhbikNCg0KZGF0b3MuaiAlPiUgIHN1bW1hcmlzZV9pZihpcy5udW1lcmljLG1lYW4pICU+JQ0KICByb3VuZCg0KSAtPiBnZW5lcmFsICNwcm9tZWRpbyBnZW5lcmFsDQoNCmdlbmVyYWwNCg0KZ2VuZXJhbCA8LSBjYmluZChjbHVzdGVyczE9ImdlbmVyYWwiLGdlbmVyYWwpDQpnZW5lcmFsICNwYXJhIHRlbmVyIGRvcyByZXN1bHRhZG9zIGNvbiBsb3MgbWlzbW9zIGNhbXBvcyhudW1lcm8gZGUgY29sdW1uYXMpDQoNCm1lZGlhcyAgPC0gYXMuZGF0YS5mcmFtZShyYmluZChtZWRpYXMsZ2VuZXJhbCkpDQptZWRpYXMNCmBgYA0KDQpVc2Ftb3MgbGEgbGlicmVyaWEgdGlkeXJbQGRwbHlyXSBwYXJhIGNvbnZlcnRpciBsYSBkYXRhIGEgZm9ybWF0byB0aWR5IGNvbiBwaXZvdF9sb25nZXIocGFzYXIgZGUgZmlsYXMgYSBjb2x1bW5hcykNCmBgYHtyIHBpdm90fQ0KZ2F0aGVyZWRfZGF0b3MuaiA8LSBwaXZvdF9sb25nZXIoZGF0YT1tZWRpYXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtY2x1c3RlcnMxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG89InZhcmlhYmxlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWxvciIpDQpoZWFkKGdhdGhlcmVkX2RhdG9zLmopDQpgYGANCg0KQSBjb250aW51YWNpb24gZmluYWxtZW50ZSBsYSBncmFmaWNhIGRlIGNhcmFjdGVyaXphY2lvbiBkZSBsb3MgY2x1c3RlcnM6DQpgYGB7ciBncmFmaWNvX2NhcmFjdCxmaWcuYWxpZ249J2NlbnRlcid9DQpnZ3Bsb3QoZ2F0aGVyZWRfZGF0b3MuaikgKyBhZXMoeD12YXJpYWJsZSx5PXZhbG9yLGNvbG9yPWNsdXN0ZXJzMSkgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBjbHVzdGVyczEpKSArDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIixsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKSArDQogIGxhYnModGl0bGU9IkRpYWdyYW1hIGRlIGzDrW5lYXMgZGUgQ2x1c3RlciBwb3IgVmFyaWFibGUiLA0KICAgICAgIHg9IlZhcmlhYmxlIix5PSIiKSArIHlsaW0oMCw4KSsNCiAgc2NhbGVfY29sb3VyX2Rpc2NyZXRlKCJDbHVzdGVyIikgIysgY29vcmRfZmxpcCgpIA0KYGBgDQoNCjxkaXYgY2xhc3M9InRvY2lmeS1leHRlbmQtcGFnZSIgZGF0YS11bmlxdWU9InRvY2lmeS1leHRlbmQtcGFnZSIgc3R5bGU9ImhlaWdodDogMDoiPjwvZGl2Pg0K