
#Contexto La base de datos
USArrests contiene estadisticas en arrestos por cada
100,000 residentes por agresion, asesinato y violacion en cada uno de
los 50 estados de EE. UU. en 1973.
##Instalar paquetes y librerias
#install.packages("cluster") # Agrupamientos
library(cluster)
#install.packages("ggplot2") # Graficar
library(ggplot2)
#install.packages("factoextra") # Visualizar Clusters
library(factoextra)
#install.packages("data.table") # Manejo de datos grandes
library(data.table)
#install.packages("tidyverse") # Manipulación de datos
library(tidyverse)
##Importar base de datos
data("USArrests")
datos <- USArrests
##Escalar la base de datos
datos_escalados <- datos
datos_escalados <- subset(datos_escalados, select = -UrbanPop) # Quitamos la columna de nombres
datos_escalados <- scale(datos_escalados) # Normalizamos las variables
##Generar los segmentos
grupos <- 5
segmentos <- kmeans(datos_escalados, grupos)
##Graficar y asignar grupo a los
cluster
asignacion <- cbind(datos_escalados, cluster = segmentos$cluster)
fviz_cluster(segmentos, data = datos_escalados)
##Optimizar la cantidad de grupos
# Método del Codo para determinar el número óptimo de clusters
set.seed(123)
optimizacion <- clusGap(datos_escalados, FUN = kmeans, nstart = 1, K.max = 10)
plot(optimizacion, xlab = "Número de clusters")
##Comparar segmentos
# Asegurar que asignacion es un data frame
asignacion <- as.data.frame(cbind(datos, cluster = segmentos$cluster))
# Calcular los promedios por cluster
promedio <- aggregate(asignacion[, 1:4], by = list(Cluster = asignacion$cluster), FUN = mean)
print(promedio)
## Cluster Murder Assault UrbanPop Rape
## 1 1 2.791667 82.08333 56.16667 10.70833
## 2 2 10.100000 261.28571 74.57143 38.28571
## 3 3 13.633333 258.16667 64.66667 23.92500
## 4 4 6.887500 172.00000 69.00000 22.31250
## 5 5 6.045455 113.63636 68.45455 18.13636
table(asignacion$cluster)
##
## 1 2 3 4 5
## 12 7 12 8 11
##Inseguridad por promedio
# Crear una nueva columna con la suma de Murder, Assault y Rape en los promedios de los clusters
promedio$Total_Inseguridad <- promedio$Murder + promedio$Assault + promedio$Rape
# Ordenar de mayor a menor inseguridad
promedio_ordenado <- promedio[order(-promedio$Total_Inseguridad), ]
# Asignar categorías de inseguridad
promedio_ordenado$Clasificación <- factor(rank(-promedio_ordenado$Total_Inseguridad),
levels = 1:nrow(promedio_ordenado),
labels = c("Más Inseguro", "Inseguro", "Moderado", "Seguro", "Más Seguro"))
print(promedio_ordenado)
## Cluster Murder Assault UrbanPop Rape Total_Inseguridad Clasificación
## 2 2 10.100000 261.28571 74.57143 38.28571 309.67143 Más Inseguro
## 3 3 13.633333 258.16667 64.66667 23.92500 295.72500 Inseguro
## 4 4 6.887500 172.00000 69.00000 22.31250 201.20000 Moderado
## 5 5 6.045455 113.63636 68.45455 18.13636 137.81818 Seguro
## 1 1 2.791667 82.08333 56.16667 10.70833 95.58333 Más Seguro
#Machine Learning
##Instalar paquetes y librerias
#install.packages("caret") # Algoritmos de aprendizaje automático
library(caret)
#install.packages("ggplot2")
library(ggplot2)
#install.packages("rpart") # Árboles de decisión
library(rpart)
#install.packages("rpart.plot") # Visualización de árboles
library(rpart.plot)
#install.packages("kernlab") # SVM
library(kernlab)
#install.packages("nnet") # Redes neuronales
library(nnet)
#install.packages("randomForest") # Bosques Aleatorios
library(randomForest)
library(caret)
##Importar base de datos
# Cambiar el nombre de la columna "cluster" a "clasificacion" (Por estetica)
colnames(asignacion)[colnames(asignacion) == "cluster"] <- "clasificacion"
# Reemplazar los valores numéricos por etiquetas de clasificación
asignacion$clasificacion <- factor(asignacion$clasificacion,
levels = c(1, 2, 3, 4, 5),
labels = c("Más inseguro", "Más seguro", "Seguro", "Moderado", "Inseguro"))
##Partir datos 80-20
set.seed(123)
renglones_entrenamiento <- createDataPartition(asignacion$clasificacion, p=0.8, list=FALSE)
entrenamiento <- asignacion[renglones_entrenamiento, ]
prueba <- asignacion[-renglones_entrenamiento, ]
##Modelo 1: SVM Lineal
modelo1 <- train(clasificacion ~ Murder + Assault + Rape, data=entrenamiento,
method = "svmLinear",
preProcess=c("scale", "center"),
trControl = trainControl(method = "cv", number=10),
tuneGrid = data.frame(C=1))
resultado_entrenamiento1 <- predict(modelo1, entrenamiento)
resultado_prueba1 <- predict(modelo1, prueba)
mcre1 <- confusionMatrix(resultado_entrenamiento1, entrenamiento$clasificacion)
mcrp1 <- confusionMatrix(resultado_prueba1, prueba$clasificacion)
##Modelo 2: SVM Radial
modelo2 <- train(clasificacion ~ Murder + Assault + Rape, data=entrenamiento,
method = "svmRadial",
preProcess=c("scale", "center"),
trControl = trainControl(method = "cv", number=10),
tuneGrid = data.frame(sigma=1, C=1))
resultado_entrenamiento2 <- predict(modelo2, entrenamiento)
resultado_prueba2 <- predict(modelo2, prueba)
mcre2 <- confusionMatrix(resultado_entrenamiento2, entrenamiento$clasificacion)
mcrp2 <- confusionMatrix(resultado_prueba2, prueba$clasificacion)
##Modelo 3: SVM Polinómico
modelo3 <- train(clasificacion ~ Murder + Assault + Rape, data=entrenamiento,
method = "svmPoly",
preProcess=c("scale", "center"),
trControl = trainControl(method = "cv", number=10),
tuneGrid = data.frame(degree=1, scale=1, C=1))
resultado_entrenamiento3 <- predict(modelo3, entrenamiento)
resultado_prueba3 <- predict(modelo3, prueba)
mcre3 <- confusionMatrix(resultado_entrenamiento3, entrenamiento$clasificacion)
mcrp3 <- confusionMatrix(resultado_prueba3, prueba$clasificacion)
##Modelo 4: Árbol de Decisión
modelo4 <- train(clasificacion ~ Murder + Assault + Rape, data=entrenamiento,
method = "rpart",
preProcess=c("scale", "center"),
trControl = trainControl(method = "cv", number=10),
tuneLength = 20, # Aumenta la longitud para más profundidad
control = rpart.control(minsplit=2, cp=0.01, maxdepth=5) # Permite más divisiones
)
resultado_entrenamiento4 <- predict(modelo4, entrenamiento)
resultado_prueba4 <- predict(modelo4, prueba)
mcre4 <- confusionMatrix(resultado_entrenamiento4, entrenamiento$clasificacion)
mcrp4 <- confusionMatrix(resultado_prueba4, prueba$clasificacion)
rpart.plot(modelo4$finalModel, type=5, extra=104, fallen.leaves=TRUE, box.palette="Blues")
##Modelo 5: Redes Neuronales
modelo5 <- train(clasificacion ~ Murder + Assault + Rape, data=entrenamiento,
method = "nnet",
preProcess=c("scale", "center"),
trControl = trainControl(method = "cv", number=10),
trace=FALSE)
resultado_entrenamiento5 <- predict(modelo5, entrenamiento)
resultado_prueba5 <- predict(modelo5, prueba)
mcre5 <- confusionMatrix(resultado_entrenamiento5, entrenamiento$clasificacion)
mcrp5 <- confusionMatrix(resultado_prueba5, prueba$clasificacion)
##Modelo 6: Random Forest
modelo6 <- train(clasificacion ~ Murder + Assault + Rape, data=entrenamiento,
method = "rf",
preProcess=c("scale", "center"),
trControl = trainControl(method = "cv", number=10),
tuneGrid = expand.grid(mtry = c(2, 4, 6)))
resultado_entrenamiento6 <- predict(modelo6, entrenamiento)
resultado_prueba6 <- predict(modelo6, prueba)
mcre6 <- confusionMatrix(resultado_entrenamiento6, entrenamiento$clasificacion)
mcrp6 <- confusionMatrix(resultado_prueba6, prueba$clasificacion)
## Resumen de Resultados
resultados <- data.frame(
"SVM Lineal" = c(mcre1$overall["Accuracy"], mcrp1$overall["Accuracy"]),
"SVM Radial" = c(mcre2$overall["Accuracy"], mcrp2$overall["Accuracy"]),
"SVM Polinómico" = c(mcre3$overall["Accuracy"], mcrp3$overall["Accuracy"]),
"Árbol de decisión" = c(mcre4$overall["Accuracy"], mcrp4$overall["Accuracy"]),
"Redes Neuronales" = c(mcre5$overall["Accuracy"], mcrp5$overall["Accuracy"]),
"Bosques Aleatorios" = c(mcre6$overall["Accuracy"], mcrp6$overall["Accuracy"])
)
rownames(resultados) <- c("Precisión de Entrenamiento", "Precisión de Prueba")
resultados
## SVM.Lineal SVM.Radial SVM.Polinómico
## Precisión de Entrenamiento 1 1.000 1
## Precisión de Prueba 1 0.875 1
## Árbol.de.decisión Redes.Neuronales
## Precisión de Entrenamiento 0.9761905 1.000
## Precisión de Prueba 0.5000000 0.875
## Bosques.Aleatorios
## Precisión de Entrenamiento 1.00
## Precisión de Prueba 0.75
LS0tDQp0aXRsZTogIlVTQXJyZXN0cyINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KICAgIHRoZW1lOiAiam91cm5hbCINCiAgICBoaWdobGlnaHQ6ICJrYXRlIg0KZGF0ZTogIjIwMjUtMDItMjEiDQotLS0NCiFbXShDOlxcVXNlcnNcXEx1aXMgQXJ0dXJvIFJlaW5vc29cXERvd25sb2Fkc1xcaW1hZ2VzICgxKS5qcGcpDQoNCg0KIzxzcGFuIHN0eWxlPSAiY29sb3I6IGJsdWU7Ij5Db250ZXh0bzwvc3Bhbj4NCkxhIGJhc2UgZGUgZGF0b3MgKipVU0FycmVzdHMqKiBjb250aWVuZSBlc3RhZGlzdGljYXMgZW4gYXJyZXN0b3MgcG9yIGNhZGEgMTAwLDAwMCByZXNpZGVudGVzIHBvciBhZ3Jlc2lvbiwgYXNlc2luYXRvIHkgdmlvbGFjaW9uIGVuIGNhZGEgdW5vIGRlIGxvcyA1MCBlc3RhZG9zIGRlIEVFLiBVVS4gZW4gMTk3My4NCg0KIyM8c3BhbiBzdHlsZT0gImNvbG9yOiBibHVlOyI+SW5zdGFsYXIgcGFxdWV0ZXMgeSBsaWJyZXJpYXM8L3NwYW4+DQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KI2luc3RhbGwucGFja2FnZXMoImNsdXN0ZXIiKSAjIEFncnVwYW1pZW50b3MNCmxpYnJhcnkoY2x1c3RlcikNCiNpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikgIyBHcmFmaWNhcg0KbGlicmFyeShnZ3Bsb3QyKQ0KI2luc3RhbGwucGFja2FnZXMoImZhY3RvZXh0cmEiKSAjIFZpc3VhbGl6YXIgQ2x1c3RlcnMNCmxpYnJhcnkoZmFjdG9leHRyYSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJkYXRhLnRhYmxlIikgIyBNYW5lam8gZGUgZGF0b3MgZ3JhbmRlcw0KbGlicmFyeShkYXRhLnRhYmxlKQ0KI2luc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpICMgTWFuaXB1bGFjacOzbiBkZSBkYXRvcw0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KIyM8c3BhbiBzdHlsZT0gImNvbG9yOiBibHVlOyI+SW1wb3J0YXIgYmFzZSBkZSBkYXRvczwvc3Bhbj4NCmBgYHtyfQ0KZGF0YSgiVVNBcnJlc3RzIikNCg0KZGF0b3MgPC0gVVNBcnJlc3RzDQoNCmBgYA0KIyM8c3BhbiBzdHlsZT0gImNvbG9yOiBibHVlOyI+RXNjYWxhciBsYSBiYXNlIGRlIGRhdG9zPC9zcGFuPg0KDQpgYGB7cn0NCmRhdG9zX2VzY2FsYWRvcyA8LSBkYXRvcw0KZGF0b3NfZXNjYWxhZG9zIDwtIHN1YnNldChkYXRvc19lc2NhbGFkb3MsIHNlbGVjdCA9IC1VcmJhblBvcCkgICMgUXVpdGFtb3MgbGEgY29sdW1uYSBkZSBub21icmVzDQpkYXRvc19lc2NhbGFkb3MgPC0gc2NhbGUoZGF0b3NfZXNjYWxhZG9zKSAgIyBOb3JtYWxpemFtb3MgbGFzIHZhcmlhYmxlcw0KYGBgDQojIzxzcGFuIHN0eWxlPSAiY29sb3I6IGJsdWU7Ij5HZW5lcmFyIGxvcyBzZWdtZW50b3M8L3NwYW4+DQpgYGB7cn0NCmdydXBvcyA8LSA1IA0Kc2VnbWVudG9zIDwtIGttZWFucyhkYXRvc19lc2NhbGFkb3MsIGdydXBvcykNCg0KYGBgDQojIzxzcGFuIHN0eWxlPSAiY29sb3I6IGJsdWU7Ij5HcmFmaWNhciB5IGFzaWduYXIgZ3J1cG8gYSBsb3MgY2x1c3Rlcjwvc3Bhbj4NCmBgYHtyfQ0KYXNpZ25hY2lvbiA8LSBjYmluZChkYXRvc19lc2NhbGFkb3MsIGNsdXN0ZXIgPSBzZWdtZW50b3MkY2x1c3RlcikNCmZ2aXpfY2x1c3RlcihzZWdtZW50b3MsIGRhdGEgPSBkYXRvc19lc2NhbGFkb3MpDQoNCmBgYA0KIyM8c3BhbiBzdHlsZT0gImNvbG9yOiBibHVlOyI+T3B0aW1pemFyIGxhIGNhbnRpZGFkIGRlIGdydXBvczwvc3Bhbj4NCmBgYHtyfQ0KIyBNw6l0b2RvIGRlbCBDb2RvIHBhcmEgZGV0ZXJtaW5hciBlbCBuw7ptZXJvIMOzcHRpbW8gZGUgY2x1c3RlcnMNCnNldC5zZWVkKDEyMykNCm9wdGltaXphY2lvbiA8LSBjbHVzR2FwKGRhdG9zX2VzY2FsYWRvcywgRlVOID0ga21lYW5zLCBuc3RhcnQgPSAxLCBLLm1heCA9IDEwKQ0KcGxvdChvcHRpbWl6YWNpb24sIHhsYWIgPSAiTsO6bWVybyBkZSBjbHVzdGVycyIpDQoNCmBgYA0KIyM8c3BhbiBzdHlsZT0gImNvbG9yOiBibHVlOyI+Q29tcGFyYXIgc2VnbWVudG9zPC9zcGFuPg0KYGBge3J9DQojIEFzZWd1cmFyIHF1ZSBhc2lnbmFjaW9uIGVzIHVuIGRhdGEgZnJhbWUNCmFzaWduYWNpb24gPC0gYXMuZGF0YS5mcmFtZShjYmluZChkYXRvcywgY2x1c3RlciA9IHNlZ21lbnRvcyRjbHVzdGVyKSkNCg0KIyBDYWxjdWxhciBsb3MgcHJvbWVkaW9zIHBvciBjbHVzdGVyDQpwcm9tZWRpbyA8LSBhZ2dyZWdhdGUoYXNpZ25hY2lvblssIDE6NF0sIGJ5ID0gbGlzdChDbHVzdGVyID0gYXNpZ25hY2lvbiRjbHVzdGVyKSwgRlVOID0gbWVhbikNCnByaW50KHByb21lZGlvKQ0KdGFibGUoYXNpZ25hY2lvbiRjbHVzdGVyKQ0KDQoNCmBgYA0KDQojIzxzcGFuIHN0eWxlPSAiY29sb3I6IGJsdWU7Ij5JbnNlZ3VyaWRhZCBwb3IgcHJvbWVkaW88L3NwYW4+DQoNCmBgYHtyfQ0KIyBDcmVhciB1bmEgbnVldmEgY29sdW1uYSBjb24gbGEgc3VtYSBkZSBNdXJkZXIsIEFzc2F1bHQgeSBSYXBlIGVuIGxvcyBwcm9tZWRpb3MgZGUgbG9zIGNsdXN0ZXJzDQpwcm9tZWRpbyRUb3RhbF9JbnNlZ3VyaWRhZCA8LSBwcm9tZWRpbyRNdXJkZXIgKyBwcm9tZWRpbyRBc3NhdWx0ICsgcHJvbWVkaW8kUmFwZQ0KDQojIE9yZGVuYXIgZGUgbWF5b3IgYSBtZW5vciBpbnNlZ3VyaWRhZA0KcHJvbWVkaW9fb3JkZW5hZG8gPC0gcHJvbWVkaW9bb3JkZXIoLXByb21lZGlvJFRvdGFsX0luc2VndXJpZGFkKSwgXQ0KDQojIEFzaWduYXIgY2F0ZWdvcsOtYXMgZGUgaW5zZWd1cmlkYWQNCnByb21lZGlvX29yZGVuYWRvJENsYXNpZmljYWNpw7NuIDwtIGZhY3RvcihyYW5rKC1wcm9tZWRpb19vcmRlbmFkbyRUb3RhbF9JbnNlZ3VyaWRhZCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gMTpucm93KHByb21lZGlvX29yZGVuYWRvKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIk3DoXMgSW5zZWd1cm8iLCAiSW5zZWd1cm8iLCAiTW9kZXJhZG8iLCAiU2VndXJvIiwgIk3DoXMgU2VndXJvIikpDQoNCnByaW50KHByb21lZGlvX29yZGVuYWRvKQ0KDQoNCmBgYA0KIzxzcGFuIHN0eWxlPSAiY29sb3I6IGJsdWU7Ij5NYWNoaW5lIExlYXJuaW5nPC9zcGFuPg0KDQojIzxzcGFuIHN0eWxlPSAiY29sb3I6IGJsdWU7Ij5JbnN0YWxhciBwYXF1ZXRlcyB5IGxpYnJlcmlhczwvc3Bhbj4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNpbnN0YWxsLnBhY2thZ2VzKCJjYXJldCIpICMgQWxnb3JpdG1vcyBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbw0KbGlicmFyeShjYXJldCkNCiNpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmxpYnJhcnkoZ2dwbG90MikNCiNpbnN0YWxsLnBhY2thZ2VzKCJycGFydCIpICMgw4FyYm9sZXMgZGUgZGVjaXNpw7NuDQpsaWJyYXJ5KHJwYXJ0KQ0KI2luc3RhbGwucGFja2FnZXMoInJwYXJ0LnBsb3QiKSAjIFZpc3VhbGl6YWNpw7NuIGRlIMOhcmJvbGVzDQpsaWJyYXJ5KHJwYXJ0LnBsb3QpDQojaW5zdGFsbC5wYWNrYWdlcygia2VybmxhYiIpICMgU1ZNDQpsaWJyYXJ5KGtlcm5sYWIpDQojaW5zdGFsbC5wYWNrYWdlcygibm5ldCIpICMgUmVkZXMgbmV1cm9uYWxlcw0KbGlicmFyeShubmV0KQ0KI2luc3RhbGwucGFja2FnZXMoInJhbmRvbUZvcmVzdCIpICMgQm9zcXVlcyBBbGVhdG9yaW9zDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoY2FyZXQpDQoNCmBgYA0KIyM8c3BhbiBzdHlsZT0gImNvbG9yOiBibHVlOyI+SW1wb3J0YXIgYmFzZSBkZSBkYXRvczwvc3Bhbj4NCmBgYHtyfQ0KDQojIENhbWJpYXIgZWwgbm9tYnJlIGRlIGxhIGNvbHVtbmEgImNsdXN0ZXIiIGEgImNsYXNpZmljYWNpb24iIChQb3IgZXN0ZXRpY2EpDQpjb2xuYW1lcyhhc2lnbmFjaW9uKVtjb2xuYW1lcyhhc2lnbmFjaW9uKSA9PSAiY2x1c3RlciJdIDwtICJjbGFzaWZpY2FjaW9uIg0KDQojIFJlZW1wbGF6YXIgbG9zIHZhbG9yZXMgbnVtw6lyaWNvcyBwb3IgZXRpcXVldGFzIGRlIGNsYXNpZmljYWNpw7NuDQphc2lnbmFjaW9uJGNsYXNpZmljYWNpb24gPC0gZmFjdG9yKGFzaWduYWNpb24kY2xhc2lmaWNhY2lvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygxLCAyLCAzLCA0LCA1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiTcOhcyBpbnNlZ3VybyIsICJNw6FzIHNlZ3VybyIsICJTZWd1cm8iLCAiTW9kZXJhZG8iLCAiSW5zZWd1cm8iKSkNCg0KDQoNCmBgYA0KIyM8c3BhbiBzdHlsZT0gImNvbG9yOiBibHVlOyI+UGFydGlyIGRhdG9zIDgwLTIwPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpyZW5nbG9uZXNfZW50cmVuYW1pZW50byA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGFzaWduYWNpb24kY2xhc2lmaWNhY2lvbiwgcD0wLjgsIGxpc3Q9RkFMU0UpDQplbnRyZW5hbWllbnRvIDwtIGFzaWduYWNpb25bcmVuZ2xvbmVzX2VudHJlbmFtaWVudG8sIF0NCnBydWViYSA8LSBhc2lnbmFjaW9uWy1yZW5nbG9uZXNfZW50cmVuYW1pZW50bywgXQ0KDQoNCmBgYA0KIyM8c3BhbiBzdHlsZT0gImNvbG9yOiBibHVlOyI+TW9kZWxvIDE6IFNWTSBMaW5lYWw8L3NwYW4+DQpgYGB7cn0NCm1vZGVsbzEgPC0gdHJhaW4oY2xhc2lmaWNhY2lvbiB+IE11cmRlciArIEFzc2F1bHQgKyBSYXBlLCBkYXRhPWVudHJlbmFtaWVudG8sDQogICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJzdm1MaW5lYXIiLA0KICAgICAgICAgICAgICAgICBwcmVQcm9jZXNzPWMoInNjYWxlIiwgImNlbnRlciIpLA0KICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyPTEwKSwNCiAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBkYXRhLmZyYW1lKEM9MSkpDQoNCnJlc3VsdGFkb19lbnRyZW5hbWllbnRvMSA8LSBwcmVkaWN0KG1vZGVsbzEsIGVudHJlbmFtaWVudG8pDQpyZXN1bHRhZG9fcHJ1ZWJhMSA8LSBwcmVkaWN0KG1vZGVsbzEsIHBydWViYSkNCg0KbWNyZTEgPC0gY29uZnVzaW9uTWF0cml4KHJlc3VsdGFkb19lbnRyZW5hbWllbnRvMSwgZW50cmVuYW1pZW50byRjbGFzaWZpY2FjaW9uKQ0KbWNycDEgPC0gY29uZnVzaW9uTWF0cml4KHJlc3VsdGFkb19wcnVlYmExLCBwcnVlYmEkY2xhc2lmaWNhY2lvbikNCg0KDQpgYGANCiMjPHNwYW4gc3R5bGU9ICJjb2xvcjogYmx1ZTsiPk1vZGVsbyAyOiBTVk0gUmFkaWFsPC9zcGFuPg0KYGBge3J9DQptb2RlbG8yIDwtIHRyYWluKGNsYXNpZmljYWNpb24gfiBNdXJkZXIgKyBBc3NhdWx0ICsgUmFwZSwgZGF0YT1lbnRyZW5hbWllbnRvLA0KICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwNCiAgICAgICAgICAgICAgICAgcHJlUHJvY2Vzcz1jKCJzY2FsZSIsICJjZW50ZXIiKSwNCiAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlcj0xMCksDQogICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZGF0YS5mcmFtZShzaWdtYT0xLCBDPTEpKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzIgPC0gcHJlZGljdChtb2RlbG8yLCBlbnRyZW5hbWllbnRvKQ0KcmVzdWx0YWRvX3BydWViYTIgPC0gcHJlZGljdChtb2RlbG8yLCBwcnVlYmEpDQoNCm1jcmUyIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzIsIGVudHJlbmFtaWVudG8kY2xhc2lmaWNhY2lvbikNCm1jcnAyIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fcHJ1ZWJhMiwgcHJ1ZWJhJGNsYXNpZmljYWNpb24pDQoNCg0KYGBgDQojIzxzcGFuIHN0eWxlPSAiY29sb3I6IGJsdWU7Ij5Nb2RlbG8gMzogU1ZNIFBvbGluw7NtaWNvPC9zcGFuPg0KYGBge3J9DQptb2RlbG8zIDwtIHRyYWluKGNsYXNpZmljYWNpb24gfiBNdXJkZXIgKyBBc3NhdWx0ICsgUmFwZSwgZGF0YT1lbnRyZW5hbWllbnRvLA0KICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUG9seSIsDQogICAgICAgICAgICAgICAgIHByZVByb2Nlc3M9Yygic2NhbGUiLCAiY2VudGVyIiksDQogICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXI9MTApLA0KICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoZGVncmVlPTEsIHNjYWxlPTEsIEM9MSkpDQoNCnJlc3VsdGFkb19lbnRyZW5hbWllbnRvMyA8LSBwcmVkaWN0KG1vZGVsbzMsIGVudHJlbmFtaWVudG8pDQpyZXN1bHRhZG9fcHJ1ZWJhMyA8LSBwcmVkaWN0KG1vZGVsbzMsIHBydWViYSkNCg0KbWNyZTMgPC0gY29uZnVzaW9uTWF0cml4KHJlc3VsdGFkb19lbnRyZW5hbWllbnRvMywgZW50cmVuYW1pZW50byRjbGFzaWZpY2FjaW9uKQ0KbWNycDMgPC0gY29uZnVzaW9uTWF0cml4KHJlc3VsdGFkb19wcnVlYmEzLCBwcnVlYmEkY2xhc2lmaWNhY2lvbikNCg0KDQpgYGANCiMjPHNwYW4gc3R5bGU9ICJjb2xvcjogYmx1ZTsiPk1vZGVsbyA0OiDDgXJib2wgZGUgRGVjaXNpw7NuPC9zcGFuPg0KYGBge3J9DQoNCm1vZGVsbzQgPC0gdHJhaW4oY2xhc2lmaWNhY2lvbiB+IE11cmRlciArIEFzc2F1bHQgKyBSYXBlLCBkYXRhPWVudHJlbmFtaWVudG8sDQogICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJycGFydCIsDQogICAgICAgICAgICAgICAgIHByZVByb2Nlc3M9Yygic2NhbGUiLCAiY2VudGVyIiksDQogICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXI9MTApLA0KICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMjAsICAjIEF1bWVudGEgbGEgbG9uZ2l0dWQgcGFyYSBtw6FzIHByb2Z1bmRpZGFkDQogICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBycGFydC5jb250cm9sKG1pbnNwbGl0PTIsIGNwPTAuMDEsIG1heGRlcHRoPTUpICMgUGVybWl0ZSBtw6FzIGRpdmlzaW9uZXMNCikNCg0KcmVzdWx0YWRvX2VudHJlbmFtaWVudG80IDwtIHByZWRpY3QobW9kZWxvNCwgZW50cmVuYW1pZW50bykNCnJlc3VsdGFkb19wcnVlYmE0IDwtIHByZWRpY3QobW9kZWxvNCwgcHJ1ZWJhKQ0KDQptY3JlNCA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX2VudHJlbmFtaWVudG80LCBlbnRyZW5hbWllbnRvJGNsYXNpZmljYWNpb24pDQptY3JwNCA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYTQsIHBydWViYSRjbGFzaWZpY2FjaW9uKQ0KDQpycGFydC5wbG90KG1vZGVsbzQkZmluYWxNb2RlbCwgdHlwZT01LCBleHRyYT0xMDQsIGZhbGxlbi5sZWF2ZXM9VFJVRSwgYm94LnBhbGV0dGU9IkJsdWVzIikNCg0KDQpgYGANCiMjPHNwYW4gc3R5bGU9ICJjb2xvcjogYmx1ZTsiPk1vZGVsbyA1OiBSZWRlcyBOZXVyb25hbGVzPC9zcGFuPg0KYGBge3J9DQptb2RlbG81IDwtIHRyYWluKGNsYXNpZmljYWNpb24gfiBNdXJkZXIgKyBBc3NhdWx0ICsgUmFwZSwgZGF0YT1lbnRyZW5hbWllbnRvLA0KICAgICAgICAgICAgICAgICBtZXRob2QgPSAibm5ldCIsDQogICAgICAgICAgICAgICAgIHByZVByb2Nlc3M9Yygic2NhbGUiLCAiY2VudGVyIiksDQogICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXI9MTApLA0KICAgICAgICAgICAgICAgICB0cmFjZT1GQUxTRSkgDQoNCnJlc3VsdGFkb19lbnRyZW5hbWllbnRvNSA8LSBwcmVkaWN0KG1vZGVsbzUsIGVudHJlbmFtaWVudG8pDQpyZXN1bHRhZG9fcHJ1ZWJhNSA8LSBwcmVkaWN0KG1vZGVsbzUsIHBydWViYSkNCg0KbWNyZTUgPC0gY29uZnVzaW9uTWF0cml4KHJlc3VsdGFkb19lbnRyZW5hbWllbnRvNSwgZW50cmVuYW1pZW50byRjbGFzaWZpY2FjaW9uKQ0KbWNycDUgPC0gY29uZnVzaW9uTWF0cml4KHJlc3VsdGFkb19wcnVlYmE1LCBwcnVlYmEkY2xhc2lmaWNhY2lvbikNCg0KDQpgYGANCiMjPHNwYW4gc3R5bGU9ICJjb2xvcjogYmx1ZTsiPk1vZGVsbyA2OiBSYW5kb20gRm9yZXN0PC9zcGFuPg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1vZGVsbzYgPC0gdHJhaW4oY2xhc2lmaWNhY2lvbiB+IE11cmRlciArIEFzc2F1bHQgKyBSYXBlLCBkYXRhPWVudHJlbmFtaWVudG8sDQogICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZiIsDQogICAgICAgICAgICAgICAgIHByZVByb2Nlc3M9Yygic2NhbGUiLCAiY2VudGVyIiksDQogICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXI9MTApLA0KICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKG10cnkgPSBjKDIsIDQsIDYpKSkgDQoNCnJlc3VsdGFkb19lbnRyZW5hbWllbnRvNiA8LSBwcmVkaWN0KG1vZGVsbzYsIGVudHJlbmFtaWVudG8pDQpyZXN1bHRhZG9fcHJ1ZWJhNiA8LSBwcmVkaWN0KG1vZGVsbzYsIHBydWViYSkNCg0KbWNyZTYgPC0gY29uZnVzaW9uTWF0cml4KHJlc3VsdGFkb19lbnRyZW5hbWllbnRvNiwgZW50cmVuYW1pZW50byRjbGFzaWZpY2FjaW9uKQ0KbWNycDYgPC0gY29uZnVzaW9uTWF0cml4KHJlc3VsdGFkb19wcnVlYmE2LCBwcnVlYmEkY2xhc2lmaWNhY2lvbikNCg0KDQpgYGANCiMjPHNwYW4gc3R5bGU9ICJjb2xvcjogYmx1ZTsiPiBSZXN1bWVuIGRlIFJlc3VsdGFkb3M8L3NwYW4+DQpgYGB7cn0NCnJlc3VsdGFkb3MgPC0gZGF0YS5mcmFtZSgNCiAgIlNWTSBMaW5lYWwiID0gYyhtY3JlMSRvdmVyYWxsWyJBY2N1cmFjeSJdLCBtY3JwMSRvdmVyYWxsWyJBY2N1cmFjeSJdKSwNCiAgIlNWTSBSYWRpYWwiID0gYyhtY3JlMiRvdmVyYWxsWyJBY2N1cmFjeSJdLCBtY3JwMiRvdmVyYWxsWyJBY2N1cmFjeSJdKSwNCiAgIlNWTSBQb2xpbsOzbWljbyIgPSBjKG1jcmUzJG92ZXJhbGxbIkFjY3VyYWN5Il0sIG1jcnAzJG92ZXJhbGxbIkFjY3VyYWN5Il0pLA0KICAiw4FyYm9sIGRlIGRlY2lzacOzbiIgPSBjKG1jcmU0JG92ZXJhbGxbIkFjY3VyYWN5Il0sIG1jcnA0JG92ZXJhbGxbIkFjY3VyYWN5Il0pLA0KICAiUmVkZXMgTmV1cm9uYWxlcyIgPSBjKG1jcmU1JG92ZXJhbGxbIkFjY3VyYWN5Il0sIG1jcnA1JG92ZXJhbGxbIkFjY3VyYWN5Il0pLA0KICAiQm9zcXVlcyBBbGVhdG9yaW9zIiA9IGMobWNyZTYkb3ZlcmFsbFsiQWNjdXJhY3kiXSwgbWNycDYkb3ZlcmFsbFsiQWNjdXJhY3kiXSkNCikNCg0Kcm93bmFtZXMocmVzdWx0YWRvcykgPC0gYygiUHJlY2lzacOzbiBkZSBFbnRyZW5hbWllbnRvIiwgIlByZWNpc2nDs24gZGUgUHJ1ZWJhIikNCnJlc3VsdGFkb3MNCg0KDQpgYGANCg0K