Librerías
library(dplyr)
library(factoextra)
library(ggplot2)
library(cluster)
library(data.table)
Ventas
Importar base de datos
Como observación, en este análisis se omite el paso de entender base
de datos ya que esta base de datos es la exportada al final deel
ejercicio Herramientas para la Limpieza de Datos.
setwd("C:\\Users\\javaw\\OneDrive - Instituto Tecnologico y de Estudios Superiores de Monterrey\\7mo Semestre\\Modulo 3")
ventas<-read.csv("ventas_bd_limpia.csv")
Herramienta “El Generador de Valor de Datos”
Paso 1. Definir el área de negocio a impactar y su
KPI
El departamento de mercadotecnia, específicamente en el indicador de
ticket promedio por cliente y las visitas a la tienda.
Paso 2. Seleccionar plantilla(s) para crear valor a partir
de los datos de los clientes.
Visión Segmentación Personalización Contextualización
Paso 3. Generar ideas o conceptos específicos.
Elaborar un modelo de clusters para identificar los segmentos de
mercado y analizar su comportamiento de visitas a la tienda.
Paso 4.Reunir los datos requeridos.
Elaborar una base de datos con la variables para realizar los
clusters: ticket promedio, número de cliente y visitas por cliente.
Paso 5. Plan de ejecución.
Mecadotecnia elaborará un plan para desarrollar estrategias de
mercado específicas para cada segmento identificado.
De igual manera, mercadotecnia puede complementar esto con un market
basket analysis.
Segmentación de mercado
Ticket Promedio
Para obtener el ticket promedio es necesario primero hacer una
columna que sea el precio por la cantidad comprada.
ventas1<-cbind(ventas,Totalcompra=(ventas$Price*ventas$Quantity))
Posteriormente, hay que usar la funcion aggregate para saber cuanto
fue el total de compra por cada ticket y cuantos tickets hay por
cliente.
ticket_por_cliente<-aggregate(Totalcompra ~ CustomerID + BillNo, data = ventas1, sum)
head(ticket_por_cliente)
## CustomerID BillNo Totalcompra
## 1 17850 536365 139.12
## 2 17850 536366 22.20
## 3 13047 536367 278.73
## 4 13047 536368 70.05
## 5 13047 536369 17.85
## 6 12583 536370 855.86
ticket_promedio<-group_by(ticket_por_cliente,CustomerID) %>% summarise(TicketsPromedio = mean(Totalcompra))
head(ticket_promedio)
## # A tibble: 6 x 2
## CustomerID TicketsPromedio
## <int> <dbl>
## 1 12346 77184.
## 2 12347 613.
## 3 12349 1758.
## 4 12350 334.
## 5 12352 313.
## 6 12353 89
Una vez que tenemos el ticket promedio por cliente es necesario saber
lo siguiente.
Visitas a la tienda
visitas<-group_by(ticket_por_cliente,CustomerID) %>% summarise(Visitas = n_distinct(BillNo))
visitas
## # A tibble: 4,291 x 2
## CustomerID Visitas
## <int> <int>
## 1 12346 1
## 2 12347 7
## 3 12349 1
## 4 12350 1
## 5 12352 8
## 6 12353 1
## 7 12354 1
## 8 12355 1
## 9 12356 3
## 10 12357 1
## # ... with 4,281 more rows
Ya que tenemos las visitas por cliente hay que unir las bases de
datos para tener nuestra segmentación.
segmentacion<-merge(ticket_promedio,visitas,by="CustomerID")
head(segmentacion)
## CustomerID TicketsPromedio Visitas
## 1 12346 77183.6000 1
## 2 12347 613.3143 7
## 3 12349 1757.5500 1
## 4 12350 334.4000 1
## 5 12352 313.2550 8
## 6 12353 89.0000 1
Cluster Analysis: Pasos Previos
Agregar el customerID como nombre a los renglones
df<-segmentacion
rownames(df)<-df$CustomerID
Eliminar la columna de Customer ID
df2<-df
df2<-subset(df2, select=-c(CustomerID))
head(df2)
## TicketsPromedio Visitas
## 12346 77183.6000 1
## 12347 613.3143 7
## 12349 1757.5500 1
## 12350 334.4000 1
## 12352 313.2550 8
## 12353 89.0000 1
Revisar la presencia de datos anormales
summary(df2)
## TicketsPromedio Visitas
## Min. : 3.45 Min. : 1.000
## 1st Qu.: 176.14 1st Qu.: 1.000
## Median : 286.72 Median : 2.000
## Mean : 412.33 Mean : 4.223
## 3rd Qu.: 423.24 3rd Qu.: 5.000
## Max. :84236.25 Max. :207.000
plot(df2$TicketsPromedio, df2$Visitas)

Podemos definir que los datos fuera de lo normal están fuera de los
siguientes límites:
Límite Inferior = Q1 - 1.5IQR
Límite Superior = Q3 + 1.5IQR
Q1: Cuartil 1, Q3: Cuartil 3, IQR: Rango Intercuartil = Q3-Q1
Calcular el límite superior del total de tickets promedio
iqr_total<-IQR(df2$TicketsPromedio)
limite_superior_total <-423.24 + 1.5*iqr_total
limite_superior_total
## [1] 793.8898
Calcular el limite superior del total de visitas
iqr_visitas<-IQR(df2$Visitas)
limite_superior_visitas<-5+1.5*iqr_visitas
limite_superior_visitas
## [1] 11
Conservar solo los tickets promedios menores a 794
df3<-df2
df3<-df3[df3$TicketsPromedio<794,]
summary(df3)
## TicketsPromedio Visitas
## Min. : 3.45 Min. : 1.00
## 1st Qu.:169.96 1st Qu.: 1.00
## Median :269.00 Median : 2.00
## Mean :294.98 Mean : 4.12
## 3rd Qu.:386.99 3rd Qu.: 5.00
## Max. :791.15 Max. :207.00
Conservar solo las visitas inferiores a 12
df4<-df3
df4<-df4[df4$Visitas<12,]
summary(df4)
## TicketsPromedio Visitas
## Min. : 3.45 Min. : 1.000
## 1st Qu.:166.06 1st Qu.: 1.000
## Median :263.24 Median : 2.000
## Mean :290.86 Mean : 2.967
## 3rd Qu.:381.55 3rd Qu.: 4.000
## Max. :791.15 Max. :11.000
Boxplot con tickets inferiores a 794 y visitas inferiores a 12
plot(df4$TicketsPromedio,df4$Visitas)

Cluster Analysis: Pasos Definitivos
Para hacer en analisis por clusters es necesario primero llevar a
cabo el siguiente proceso.
Paso1. Normalizar variables
df5<-df4
df5<-as.data.frame(scale(df5))
plot(df5$TicketsPromedio, df5$Visitas)

Paso 2. K-means Clustering
4 clusters
segmentos<-kmeans(df5,4)
asignacion<-cbind(df4,Cluster=segmentos$cluster)
head(asignacion,10)
## TicketsPromedio Visitas Cluster
## 12347 613.3143 7 1
## 12350 334.4000 1 4
## 12352 313.2550 8 3
## 12353 89.0000 1 2
## 12355 459.4000 1 4
## 12358 584.0300 2 1
## 12361 189.9000 1 2
## 12362 519.5990 10 3
## 12363 276.0000 2 4
## 12364 328.2750 4 4
3 clusters
segmentos2<-kmeans(df5,3)
asignacion2<-cbind(df4,Cluster=segmentos2$cluster)
head(asignacion2,10)
## TicketsPromedio Visitas Cluster
## 12347 613.3143 7 2
## 12350 334.4000 1 1
## 12352 313.2550 8 2
## 12353 89.0000 1 1
## 12355 459.4000 1 3
## 12358 584.0300 2 3
## 12361 189.9000 1 1
## 12362 519.5990 10 2
## 12363 276.0000 2 1
## 12364 328.2750 4 3
Exportar CSV
write.csv(asignacion,"clientes_segmentados.csv")
Visualizar Segmentos 4 clusters
fviz_cluster(segmentos, data=df5,
palette=c("red","blue","black","darkgreen"),
ellipse.type="euclid",
star.plot=T,
repel=T,
ggtheme=theme())
## Warning: ggrepel: 3728 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps

Visualizar Segmentos 3 clusters
fviz_cluster(segmentos2, data=df5,
palette=c("red","blue","black","darkgreen"),
ellipse.type="euclid",
star.plot=T,
repel=T,
ggtheme=theme())
## Warning: ggrepel: 3728 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps

Optimizar K
set.seed(123)
optimizacion<-clusGap(df5, FUN= kmeans, nstart=25,K.max=10,B=50)
plot(optimizacion,xlab="Numero de clusters K")

Gráfico Interactivo
#selectInput("opciones",label="x",choices = names(ticket_por_cliente),selected = "BillNo")
#renderPlot(plot(ticket_por_cliente$Totalcompra,ticket_por_cliente[,input$opciones])
#)
Us Arrests
Importar Base de Datos
setwd("C:\\Users\\javaw\\OneDrive - Instituto Tecnologico y de Estudios Superiores de Monterrey\\7mo Semestre\\Modulo 3")
us<-read.csv("USArrests.csv")
Herramienta “El Generador de Valor de Datos”
Paso 1. Definir el área de negocio a impactar y su
KPI
El departamento de seguridad nacional, o departamento que sea
responsable, específicamente en el indicador de índices de
criminalidad.
Paso 2. Seleccionar plantilla(s) para crear valor a partir
de los datos de los clientes.
Visión Segmentación Personalización Contextualización
Paso 3. Generar ideas o conceptos específicos.
Elaborar un modelo de clusters para identificar los estados que
poseen índices de criminalidad similares, además de una población
similar.
Paso 4.Reunir los datos requeridos.
Los datos necesarios ya están reunidos, solo hay que realizar el
análisis de clusters y normalizar.
Paso 5. Plan de ejecución.
El departamento de seguridad desarrollará una estrategia a
implementarse para reducir los índices de criminalidad. Esta estrategia
será por fases.
1. Implementarla en estados con menores índices (California).
2. Replicar en estados similares a california.
3. Replicar en el resto del país.
Cluster Analysis: Pasos Previos
Convertir estados a los renglones
us1<-us
rownames(us1)<-us1$ï..
Eliminar la columna de Estado
us2<-us1
us2<-subset(us2, select=-c(ï..))
Cluster Analysis: Pasos Definitivos
Para hacer en analisis por clusters es necesario primero llevar a
cabo el siguiente proceso.
Paso1. Normalizar variables
us3<-us2
us3<-as.data.frame(scale(us3))
Paso 2. K-means Clustering
4 clusters
clusters<-kmeans(us3,4)
asignacion<-cbind(us2,Cluster=clusters$cluster)
head(asignacion,10)
## Murder Assault UrbanPop Rape Cluster
## Alabama 13.2 236 58 21.2 4
## Alaska 10.0 263 48 44.5 3
## Arizona 8.1 294 80 31.0 3
## Arkansas 8.8 190 50 19.5 4
## California 9.0 276 91 40.6 3
## Colorado 7.9 204 78 38.7 3
## Connecticut 3.3 110 77 11.1 2
## Delaware 5.9 238 72 15.8 2
## Florida 15.4 335 80 31.9 3
## Georgia 17.4 211 60 25.8 4
3 clusters
clusters2<-kmeans(us3,3)
asignacion2<-cbind(us2,Cluster=clusters2$cluster)
head(asignacion2,10)
## Murder Assault UrbanPop Rape Cluster
## Alabama 13.2 236 58 21.2 2
## Alaska 10.0 263 48 44.5 2
## Arizona 8.1 294 80 31.0 2
## Arkansas 8.8 190 50 19.5 1
## California 9.0 276 91 40.6 2
## Colorado 7.9 204 78 38.7 2
## Connecticut 3.3 110 77 11.1 1
## Delaware 5.9 238 72 15.8 1
## Florida 15.4 335 80 31.9 2
## Georgia 17.4 211 60 25.8 2
Exportar CSV
write.csv(asignacion,"datos_con_cluster_usarrests.csv")
Visualizar Segmentos 4 clusters
fviz_cluster(clusters, data=us3,
palette=c("red","blue","black","darkgreen"),
ellipse.type="euclid",
star.plot=T,
repel=T,
ggtheme=theme())

Visualizar Segmentos 3 clusters
fviz_cluster(clusters2, data=us3,
palette=c("red","blue","black","darkgreen"),
ellipse.type="euclid",
star.plot=T,
repel=T,
ggtheme=theme())

Gráfico interactivo
#selectInput("variables",label="x",choices = names(us2),selected = #"Murder")
#renderPlot(plot(us2$Assault,us2[,input$variables])
#)
Conclusiones
Me parece que el analisis de clusters aporta bastante valor,
especialmente en ámbitos de negocios como en el caso de ventas. En el
caso de US Arrests, los clusters nos sirven para identificar aquellos
estados que tienen menores incidencias en asesinatos, violaciones, entre
otros. Además de poder agrupas aquellos estados que tienen índices de
criminalidad parecidos y poblaciones parecidas.
Los clusters permite que visualicemos los grupos o conjuntos de datos
que poseen características similares y pueden ser segmentados o
catalogados bajo estas características. Estos son de gran valor para
analísis donde se requiere hacer estrategias por grupos, como es el caso
de los supermercados o cuando se quiere conocer grupos con
características similares.
LS0tDQp0aXRsZTogIlNlZ21lbnRhY2nDs24gZGUgTWVyY2FkbyINCmF1dGhvcjogIkphdmllciBBeWFsYS1BMDA4MjM5NTgiDQpkYXRlOiAnMjAyMi0wOS0xMCcNCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCjxjZW50ZXI+DQo8aW1nIHNyYz0iQzovL1VzZXJzLy9qYXZhdy8vT25lRHJpdmUgLSBJbnN0aXR1dG8gVGVjbm9sb2dpY28geSBkZSBFc3R1ZGlvcyBTdXBlcmlvcmVzIGRlIE1vbnRlcnJleS8vTG9nbyBJVEVTTSBzbWFsbC5wbmciPg0KPC9jZW50ZXI+IA0KDQpgYGB7Y3NzLCBlY2hvPUZBTFNFfQ0KaDEsIGg0IHsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KDQpoMSwgaDIsIGgzIHsNCiAgY29sb3I6ICMwNjFGNkI7DQp9DQpgYGANCg0KIyMgTGlicmVyw61hcyAgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoY2x1c3RlcikNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmBgYA0KDQojIFZlbnRhcyAgDQoNCiMjIEltcG9ydGFyIGJhc2UgZGUgZGF0b3MgIA0KQ29tbyBvYnNlcnZhY2nDs24sIGVuIGVzdGUgYW7DoWxpc2lzIHNlIG9taXRlIGVsIHBhc28gZGUgZW50ZW5kZXIgYmFzZSBkZSBkYXRvcyB5YSBxdWUgZXN0YSBiYXNlIGRlIGRhdG9zIGVzIGxhIGV4cG9ydGFkYSBhbCBmaW5hbCBkZWVsIGVqZXJjaWNpbyAqKkhlcnJhbWllbnRhcyBwYXJhIGxhIExpbXBpZXphIGRlIERhdG9zKiouICANCmBgYHtyfQ0Kc2V0d2QoIkM6XFxVc2Vyc1xcamF2YXdcXE9uZURyaXZlIC0gSW5zdGl0dXRvIFRlY25vbG9naWNvIHkgZGUgRXN0dWRpb3MgU3VwZXJpb3JlcyBkZSBNb250ZXJyZXlcXDdtbyBTZW1lc3RyZVxcTW9kdWxvIDMiKQ0KdmVudGFzPC1yZWFkLmNzdigidmVudGFzX2JkX2xpbXBpYS5jc3YiKQ0KYGBgDQoNCiMjIEhlcnJhbWllbnRhICJFbCBHZW5lcmFkb3IgZGUgVmFsb3IgZGUgRGF0b3MiICANCiMjIyMjICoqUGFzbyAxLiBEZWZpbmlyIGVsIMOhcmVhIGRlIG5lZ29jaW8gYSBpbXBhY3RhciB5IHN1IEtQSSoqDQpFbCBkZXBhcnRhbWVudG8gZGUgbWVyY2Fkb3RlY25pYSwgZXNwZWPDrWZpY2FtZW50ZSBlbiBlbCBpbmRpY2Fkb3IgZGUgdGlja2V0IHByb21lZGlvIHBvciBjbGllbnRlIHkgbGFzIHZpc2l0YXMgYSBsYSB0aWVuZGEuDQoNCiMjIyMjICoqUGFzbyAyLiBTZWxlY2Npb25hciBwbGFudGlsbGEocykgcGFyYSBjcmVhciB2YWxvciBhIHBhcnRpciBkZSBsb3MgZGF0b3MgZGUgbG9zIGNsaWVudGVzLioqDQo8Y2VudGVyPg0KVmlzacOzbiAgKipTZWdtZW50YWNpw7NuKiogIFBlcnNvbmFsaXphY2nDs24gIENvbnRleHR1YWxpemFjacOzbg0KPC9jZW50ZXI+DQoNCiMjIyMjICoqUGFzbyAzLiBHZW5lcmFyIGlkZWFzIG8gY29uY2VwdG9zIGVzcGVjw61maWNvcy4qKg0KRWxhYm9yYXIgdW4gbW9kZWxvIGRlIGNsdXN0ZXJzIHBhcmEgaWRlbnRpZmljYXIgbG9zIHNlZ21lbnRvcyBkZSBtZXJjYWRvIHkgYW5hbGl6YXIgc3UgY29tcG9ydGFtaWVudG8gZGUgdmlzaXRhcyBhIGxhIHRpZW5kYS4gIA0KDQojIyMjIyAqKlBhc28gNC5SZXVuaXIgbG9zIGRhdG9zIHJlcXVlcmlkb3MuKioNCkVsYWJvcmFyIHVuYSBiYXNlIGRlIGRhdG9zIGNvbiBsYSB2YXJpYWJsZXMgcGFyYSByZWFsaXphciBsb3MgY2x1c3RlcnM6IHRpY2tldCBwcm9tZWRpbywgbsO6bWVybyBkZSBjbGllbnRlIHkgdmlzaXRhcyBwb3IgY2xpZW50ZS4NCg0KIyMjIyMgKipQYXNvIDUuIFBsYW4gZGUgZWplY3VjacOzbi4qKg0KTWVjYWRvdGVjbmlhIGVsYWJvcmFyw6EgdW4gcGxhbiBwYXJhIGRlc2Fycm9sbGFyIGVzdHJhdGVnaWFzIGRlIG1lcmNhZG8gZXNwZWPDrWZpY2FzIHBhcmEgY2FkYSBzZWdtZW50byBpZGVudGlmaWNhZG8uIA0KDQpEZSBpZ3VhbCBtYW5lcmEsIG1lcmNhZG90ZWNuaWEgcHVlZGUgY29tcGxlbWVudGFyIGVzdG8gY29uIHVuIG1hcmtldCBiYXNrZXQgYW5hbHlzaXMuDQoNCiMjIFNlZ21lbnRhY2nDs24gZGUgbWVyY2FkbyAgDQoNCg0KIyMjIFRpY2tldCBQcm9tZWRpbyAgIA0KUGFyYSBvYnRlbmVyIGVsIHRpY2tldCBwcm9tZWRpbyBlcyBuZWNlc2FyaW8gcHJpbWVybyBoYWNlciB1bmEgY29sdW1uYSBxdWUgc2VhIGVsIHByZWNpbyBwb3IgbGEgY2FudGlkYWQgY29tcHJhZGEuDQoNCmBgYHtyfQ0KdmVudGFzMTwtY2JpbmQodmVudGFzLFRvdGFsY29tcHJhPSh2ZW50YXMkUHJpY2UqdmVudGFzJFF1YW50aXR5KSkNCmBgYA0KDQpQb3N0ZXJpb3JtZW50ZSwgaGF5IHF1ZSB1c2FyIGxhIGZ1bmNpb24gYWdncmVnYXRlIHBhcmEgc2FiZXIgY3VhbnRvIGZ1ZSBlbCB0b3RhbCBkZSBjb21wcmEgcG9yIGNhZGEgdGlja2V0IHkgY3VhbnRvcyB0aWNrZXRzIGhheSBwb3IgY2xpZW50ZS4gDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0aWNrZXRfcG9yX2NsaWVudGU8LWFnZ3JlZ2F0ZShUb3RhbGNvbXByYSB+IEN1c3RvbWVySUQgKyBCaWxsTm8sIGRhdGEgPSB2ZW50YXMxLCBzdW0pDQpoZWFkKHRpY2tldF9wb3JfY2xpZW50ZSkNCg0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0aWNrZXRfcHJvbWVkaW88LWdyb3VwX2J5KHRpY2tldF9wb3JfY2xpZW50ZSxDdXN0b21lcklEKSAlPiUgc3VtbWFyaXNlKFRpY2tldHNQcm9tZWRpbyA9IG1lYW4oVG90YWxjb21wcmEpKQ0KaGVhZCh0aWNrZXRfcHJvbWVkaW8pDQpgYGANCg0KVW5hIHZleiBxdWUgdGVuZW1vcyBlbCB0aWNrZXQgcHJvbWVkaW8gcG9yIGNsaWVudGUgZXMgbmVjZXNhcmlvIHNhYmVyIGxvIHNpZ3VpZW50ZS4NCg0KIyMjIFZpc2l0YXMgYSBsYSB0aWVuZGEgIA0KDQpgYGB7cn0NCnZpc2l0YXM8LWdyb3VwX2J5KHRpY2tldF9wb3JfY2xpZW50ZSxDdXN0b21lcklEKSAlPiUgc3VtbWFyaXNlKFZpc2l0YXMgPSBuX2Rpc3RpbmN0KEJpbGxObykpDQp2aXNpdGFzDQpgYGANCg0KWWEgcXVlIHRlbmVtb3MgbGFzIHZpc2l0YXMgcG9yIGNsaWVudGUgaGF5IHF1ZSB1bmlyIGxhcyBiYXNlcyBkZSBkYXRvcyBwYXJhIHRlbmVyIG51ZXN0cmEgc2VnbWVudGFjacOzbi4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNlZ21lbnRhY2lvbjwtbWVyZ2UodGlja2V0X3Byb21lZGlvLHZpc2l0YXMsYnk9IkN1c3RvbWVySUQiKQ0KaGVhZChzZWdtZW50YWNpb24pDQpgYGANCg0KIyMgQ2x1c3RlciBBbmFseXNpczogUGFzb3MgUHJldmlvcyAgDQoNCiMjIyBBZ3JlZ2FyIGVsIGN1c3RvbWVySUQgY29tbyBub21icmUgYSBsb3MgcmVuZ2xvbmVzICANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmRmPC1zZWdtZW50YWNpb24NCnJvd25hbWVzKGRmKTwtZGYkQ3VzdG9tZXJJRA0KYGBgDQoNCg0KIyMjIEVsaW1pbmFyIGxhIGNvbHVtbmEgZGUgQ3VzdG9tZXIgIElEICANCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkZjI8LWRmDQpkZjI8LXN1YnNldChkZjIsIHNlbGVjdD0tYyhDdXN0b21lcklEKSkNCmhlYWQoZGYyKQ0KYGBgDQoNCg0KIyMjIFJldmlzYXIgbGEgcHJlc2VuY2lhIGRlIGRhdG9zIGFub3JtYWxlcyAgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc3VtbWFyeShkZjIpDQpwbG90KGRmMiRUaWNrZXRzUHJvbWVkaW8sIGRmMiRWaXNpdGFzKQ0KYGBgDQoNClBvZGVtb3MgZGVmaW5pciBxdWUgbG9zIGRhdG9zIGZ1ZXJhIGRlIGxvIG5vcm1hbCBlc3TDoW4gZnVlcmEgZGUgbG9zIHNpZ3VpZW50ZXMgbMOtbWl0ZXM6ICANCkzDrW1pdGUgSW5mZXJpb3IgPSBRMSAtIDEuNSpJUVIgIA0KTMOtbWl0ZSBTdXBlcmlvciA9IFEzICsgMS41KklRUiAgDQpRMTogQ3VhcnRpbCAxLCBRMzogQ3VhcnRpbCAzLCBJUVI6IFJhbmdvIEludGVyY3VhcnRpbCA9IFEzLVExDQoNCiMjIyBDYWxjdWxhciBlbCBsw61taXRlIHN1cGVyaW9yIGRlbCB0b3RhbCBkZSB0aWNrZXRzIHByb21lZGlvICANCmBgYHtyfQ0KaXFyX3RvdGFsPC1JUVIoZGYyJFRpY2tldHNQcm9tZWRpbykNCmxpbWl0ZV9zdXBlcmlvcl90b3RhbCA8LTQyMy4yNCArIDEuNSppcXJfdG90YWwNCmxpbWl0ZV9zdXBlcmlvcl90b3RhbA0KDQpgYGANCg0KIyMjIENhbGN1bGFyIGVsIGxpbWl0ZSBzdXBlcmlvciBkZWwgdG90YWwgZGUgdmlzaXRhcyAgDQpgYGB7cn0NCmlxcl92aXNpdGFzPC1JUVIoZGYyJFZpc2l0YXMpDQpsaW1pdGVfc3VwZXJpb3JfdmlzaXRhczwtNSsxLjUqaXFyX3Zpc2l0YXMNCmxpbWl0ZV9zdXBlcmlvcl92aXNpdGFzDQpgYGANCg0KIyMjIENvbnNlcnZhciBzb2xvIGxvcyB0aWNrZXRzIHByb21lZGlvcyBtZW5vcmVzIGEgNzk0ICANCmBgYHtyfQ0KZGYzPC1kZjINCmRmMzwtZGYzW2RmMyRUaWNrZXRzUHJvbWVkaW88Nzk0LF0NCnN1bW1hcnkoZGYzKQ0KDQpgYGANCg0KIyMjIENvbnNlcnZhciBzb2xvIGxhcyB2aXNpdGFzIGluZmVyaW9yZXMgYSAxMiAgDQpgYGB7cn0NCmRmNDwtZGYzDQpkZjQ8LWRmNFtkZjQkVmlzaXRhczwxMixdDQpzdW1tYXJ5KGRmNCkNCg0KYGBgDQoNCiMjIyBCb3hwbG90IGNvbiB0aWNrZXRzIGluZmVyaW9yZXMgYSA3OTQgeSB2aXNpdGFzIGluZmVyaW9yZXMgYSAxMiAgDQpgYGB7cn0NCnBsb3QoZGY0JFRpY2tldHNQcm9tZWRpbyxkZjQkVmlzaXRhcykNCmBgYA0KDQoNCiMjIENsdXN0ZXIgQW5hbHlzaXM6IFBhc29zIERlZmluaXRpdm9zICANClBhcmEgaGFjZXIgZW4gYW5hbGlzaXMgcG9yIGNsdXN0ZXJzIGVzIG5lY2VzYXJpbyBwcmltZXJvIGxsZXZhciBhIGNhYm8gZWwgc2lndWllbnRlIHByb2Nlc28uICANCg0KIyMjIFBhc28xLiBOb3JtYWxpemFyIHZhcmlhYmxlcyAgDQpgYGB7cn0NCmRmNTwtZGY0DQpkZjU8LWFzLmRhdGEuZnJhbWUoc2NhbGUoZGY1KSkNCnBsb3QoZGY1JFRpY2tldHNQcm9tZWRpbywgZGY1JFZpc2l0YXMpDQpgYGANCg0KIyMjIFBhc28gMi4gSy1tZWFucyBDbHVzdGVyaW5nICANCiMjIyMjIDQgY2x1c3RlcnMgIA0KYGBge3J9DQpzZWdtZW50b3M8LWttZWFucyhkZjUsNCkNCg0KYXNpZ25hY2lvbjwtY2JpbmQoZGY0LENsdXN0ZXI9c2VnbWVudG9zJGNsdXN0ZXIpDQpoZWFkKGFzaWduYWNpb24sMTApDQpgYGANCg0KDQojIyMjIyAzIGNsdXN0ZXJzICANCmBgYHtyfQ0Kc2VnbWVudG9zMjwta21lYW5zKGRmNSwzKQ0KDQphc2lnbmFjaW9uMjwtY2JpbmQoZGY0LENsdXN0ZXI9c2VnbWVudG9zMiRjbHVzdGVyKQ0KaGVhZChhc2lnbmFjaW9uMiwxMCkNCmBgYA0KDQoNCiMjIyBFeHBvcnRhciBDU1YgIA0KYGBge3J9DQp3cml0ZS5jc3YoYXNpZ25hY2lvbiwiY2xpZW50ZXNfc2VnbWVudGFkb3MuY3N2IikNCg0KYGBgDQoNCg0KIyMjIFZpc3VhbGl6YXIgU2VnbWVudG9zICA0IGNsdXN0ZXJzICANCmBgYHtyfQ0KZnZpel9jbHVzdGVyKHNlZ21lbnRvcywgZGF0YT1kZjUsDQogICAgICAgICAgICAgcGFsZXR0ZT1jKCJyZWQiLCJibHVlIiwiYmxhY2siLCJkYXJrZ3JlZW4iKSwNCiAgICAgICAgICAgICBlbGxpcHNlLnR5cGU9ImV1Y2xpZCIsDQogICAgICAgICAgICAgc3Rhci5wbG90PVQsDQogICAgICAgICAgICAgcmVwZWw9VCwNCiAgICAgICAgICAgICBnZ3RoZW1lPXRoZW1lKCkpDQoNCmBgYA0KDQojIyMgVmlzdWFsaXphciBTZWdtZW50b3MgMyBjbHVzdGVycyAgDQpgYGB7cn0NCmZ2aXpfY2x1c3RlcihzZWdtZW50b3MyLCBkYXRhPWRmNSwNCiAgICAgICAgICAgICBwYWxldHRlPWMoInJlZCIsImJsdWUiLCJibGFjayIsImRhcmtncmVlbiIpLA0KICAgICAgICAgICAgIGVsbGlwc2UudHlwZT0iZXVjbGlkIiwNCiAgICAgICAgICAgICBzdGFyLnBsb3Q9VCwNCiAgICAgICAgICAgICByZXBlbD1ULA0KICAgICAgICAgICAgIGdndGhlbWU9dGhlbWUoKSkNCmBgYA0KDQoNCiMjIyBPcHRpbWl6YXIgSyAgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc2V0LnNlZWQoMTIzKQ0Kb3B0aW1pemFjaW9uPC1jbHVzR2FwKGRmNSwgRlVOPSBrbWVhbnMsIG5zdGFydD0yNSxLLm1heD0xMCxCPTUwKQ0KcGxvdChvcHRpbWl6YWNpb24seGxhYj0iTnVtZXJvIGRlIGNsdXN0ZXJzIEsiKQ0KDQpgYGANCg0KIyMgR3LDoWZpY28gSW50ZXJhY3Rpdm8NCg0KYGBge3Igc2VsZWN0LWlucHV0Mn0NCiNzZWxlY3RJbnB1dCgib3BjaW9uZXMiLGxhYmVsPSJ4IixjaG9pY2VzID0gbmFtZXModGlja2V0X3Bvcl9jbGllbnRlKSxzZWxlY3RlZCA9ICJCaWxsTm8iKQ0KYGBgDQoNCmBgYHtyfQ0KDQojcmVuZGVyUGxvdChwbG90KHRpY2tldF9wb3JfY2xpZW50ZSRUb3RhbGNvbXByYSx0aWNrZXRfcG9yX2NsaWVudGVbLGlucHV0JG9wY2lvbmVzXSkNCiAgDQojKQ0KYGBgDQoNCiMgVXMgQXJyZXN0cyAgDQoNCiMjIEltcG9ydGFyIEJhc2UgZGUgRGF0b3MgIA0KYGBge3J9DQpzZXR3ZCgiQzpcXFVzZXJzXFxqYXZhd1xcT25lRHJpdmUgLSBJbnN0aXR1dG8gVGVjbm9sb2dpY28geSBkZSBFc3R1ZGlvcyBTdXBlcmlvcmVzIGRlIE1vbnRlcnJleVxcN21vIFNlbWVzdHJlXFxNb2R1bG8gMyIpDQp1czwtcmVhZC5jc3YoIlVTQXJyZXN0cy5jc3YiKQ0KYGBgDQoNCg0KIyMgSGVycmFtaWVudGEgIkVsIEdlbmVyYWRvciBkZSBWYWxvciBkZSBEYXRvcyIgIA0KIyMjIyMgKipQYXNvIDEuIERlZmluaXIgZWwgw6FyZWEgZGUgbmVnb2NpbyBhIGltcGFjdGFyIHkgc3UgS1BJKioNCkVsIGRlcGFydGFtZW50byBkZSBzZWd1cmlkYWQgbmFjaW9uYWwsIG8gZGVwYXJ0YW1lbnRvIHF1ZSBzZWEgcmVzcG9uc2FibGUsIGVzcGVjw61maWNhbWVudGUgZW4gZWwgaW5kaWNhZG9yIGRlIMOtbmRpY2VzIGRlIGNyaW1pbmFsaWRhZC4NCg0KIyMjIyMgKipQYXNvIDIuIFNlbGVjY2lvbmFyIHBsYW50aWxsYShzKSBwYXJhIGNyZWFyIHZhbG9yIGEgcGFydGlyIGRlIGxvcyBkYXRvcyBkZSBsb3MgY2xpZW50ZXMuKioNCjxjZW50ZXI+DQpWaXNpw7NuICAqKlNlZ21lbnRhY2nDs24qKiAgUGVyc29uYWxpemFjacOzbiAgQ29udGV4dHVhbGl6YWNpw7NuDQo8L2NlbnRlcj4NCg0KIyMjIyMgKipQYXNvIDMuIEdlbmVyYXIgaWRlYXMgbyBjb25jZXB0b3MgZXNwZWPDrWZpY29zLioqDQpFbGFib3JhciB1biBtb2RlbG8gZGUgY2x1c3RlcnMgcGFyYSBpZGVudGlmaWNhciBsb3MgZXN0YWRvcyBxdWUgcG9zZWVuIMOtbmRpY2VzIGRlIGNyaW1pbmFsaWRhZCBzaW1pbGFyZXMsIGFkZW3DoXMgZGUgdW5hIHBvYmxhY2nDs24gc2ltaWxhci4NCg0KIyMjIyMgKipQYXNvIDQuUmV1bmlyIGxvcyBkYXRvcyByZXF1ZXJpZG9zLioqDQpMb3MgZGF0b3MgbmVjZXNhcmlvcyB5YSBlc3TDoW4gcmV1bmlkb3MsIHNvbG8gaGF5IHF1ZSByZWFsaXphciBlbCBhbsOhbGlzaXMgZGUgY2x1c3RlcnMgeSBub3JtYWxpemFyLg0KDQojIyMjIyAqKlBhc28gNS4gUGxhbiBkZSBlamVjdWNpw7NuLioqDQpFbCBkZXBhcnRhbWVudG8gZGUgc2VndXJpZGFkIGRlc2Fycm9sbGFyw6EgdW5hIGVzdHJhdGVnaWEgYSBpbXBsZW1lbnRhcnNlIHBhcmEgcmVkdWNpciBsb3Mgw61uZGljZXMgZGUgY3JpbWluYWxpZGFkLiBFc3RhIGVzdHJhdGVnaWEgc2Vyw6EgcG9yIGZhc2VzLiAgDQoxLiBJbXBsZW1lbnRhcmxhIGVuIGVzdGFkb3MgY29uIG1lbm9yZXMgw61uZGljZXMgKENhbGlmb3JuaWEpLiAgDQoyLiBSZXBsaWNhciBlbiBlc3RhZG9zIHNpbWlsYXJlcyBhIGNhbGlmb3JuaWEuICANCjMuIFJlcGxpY2FyIGVuIGVsIHJlc3RvIGRlbCBwYcOtcy4gIA0KDQojIyBDbHVzdGVyIEFuYWx5c2lzOiBQYXNvcyBQcmV2aW9zICANCg0KIyMjIENvbnZlcnRpciBlc3RhZG9zIGEgbG9zIHJlbmdsb25lcyAgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdXMxPC11cw0Kcm93bmFtZXModXMxKTwtdXMxJMOvLi4NCmBgYA0KDQoNCiMjIyBFbGltaW5hciBsYSBjb2x1bW5hIGRlIEVzdGFkbyAgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdXMyPC11czENCnVzMjwtc3Vic2V0KHVzMiwgc2VsZWN0PS1jKMOvLi4pKQ0KDQpgYGANCg0KIyMgQ2x1c3RlciBBbmFseXNpczogUGFzb3MgRGVmaW5pdGl2b3MgIA0KUGFyYSBoYWNlciBlbiBhbmFsaXNpcyBwb3IgY2x1c3RlcnMgZXMgbmVjZXNhcmlvIHByaW1lcm8gbGxldmFyIGEgY2FibyBlbCBzaWd1aWVudGUgcHJvY2Vzby4gDQoNCiMjIyBQYXNvMS4gTm9ybWFsaXphciB2YXJpYWJsZXMgIA0KYGBge3J9DQp1czM8LXVzMg0KdXMzPC1hcy5kYXRhLmZyYW1lKHNjYWxlKHVzMykpDQoNCmBgYA0KDQoNCiMjIyBQYXNvIDIuIEstbWVhbnMgQ2x1c3RlcmluZyAgDQojIyMjIyA0IGNsdXN0ZXJzICANCmBgYHtyfQ0KY2x1c3RlcnM8LWttZWFucyh1czMsNCkNCg0KYXNpZ25hY2lvbjwtY2JpbmQodXMyLENsdXN0ZXI9Y2x1c3RlcnMkY2x1c3RlcikNCmhlYWQoYXNpZ25hY2lvbiwxMCkNCmBgYA0KDQoNCiMjIyMjIDMgY2x1c3RlcnMgIA0KYGBge3J9DQpjbHVzdGVyczI8LWttZWFucyh1czMsMykNCg0KYXNpZ25hY2lvbjI8LWNiaW5kKHVzMixDbHVzdGVyPWNsdXN0ZXJzMiRjbHVzdGVyKQ0KaGVhZChhc2lnbmFjaW9uMiwxMCkNCmBgYA0KDQoNCiMjIyMjIEV4cG9ydGFyIENTViAgDQpgYGB7cn0NCndyaXRlLmNzdihhc2lnbmFjaW9uLCJkYXRvc19jb25fY2x1c3Rlcl91c2FycmVzdHMuY3N2IikNCg0KYGBgDQoNCiMjIyMjIFZpc3VhbGl6YXIgU2VnbWVudG9zICA0IGNsdXN0ZXJzICANCmBgYHtyfQ0KZnZpel9jbHVzdGVyKGNsdXN0ZXJzLCBkYXRhPXVzMywNCiAgICAgICAgICAgICBwYWxldHRlPWMoInJlZCIsImJsdWUiLCJibGFjayIsImRhcmtncmVlbiIpLA0KICAgICAgICAgICAgIGVsbGlwc2UudHlwZT0iZXVjbGlkIiwNCiAgICAgICAgICAgICBzdGFyLnBsb3Q9VCwNCiAgICAgICAgICAgICByZXBlbD1ULA0KICAgICAgICAgICAgIGdndGhlbWU9dGhlbWUoKSkNCg0KYGBgDQoNCiMjIyMjIFZpc3VhbGl6YXIgU2VnbWVudG9zIDMgY2x1c3RlcnMgIA0KYGBge3J9DQpmdml6X2NsdXN0ZXIoY2x1c3RlcnMyLCBkYXRhPXVzMywNCiAgICAgICAgICAgICBwYWxldHRlPWMoInJlZCIsImJsdWUiLCJibGFjayIsImRhcmtncmVlbiIpLA0KICAgICAgICAgICAgIGVsbGlwc2UudHlwZT0iZXVjbGlkIiwNCiAgICAgICAgICAgICBzdGFyLnBsb3Q9VCwNCiAgICAgICAgICAgICByZXBlbD1ULA0KICAgICAgICAgICAgIGdndGhlbWU9dGhlbWUoKSkNCmBgYA0KDQojIyBHcsOhZmljbyBpbnRlcmFjdGl2byAgDQpgYGB7ciBlY2hvID0gRkFMU0V9DQojc2VsZWN0SW5wdXQoIm5fYnJlYWtzIiwgbGFiZWwgPSAiTnVtYmVyIG9mIGJpbnM6IiwNCiAjICAgICAgICAgICAgIGNob2ljZXMgPSBjKDUsIDEwLCAyMCwgMzAsIDQwLCA1MCksIHNlbGVjdGVkID0gMjApDQoNCiNzbGlkZXJJbnB1dCgiYndfYWRqdXN0IiwgbGFiZWwgPSAiQmFuZHdpZHRoIGFkanVzdG1lbnQ6IiwNCiAjICAgICAgICAgICAgIG1pbiA9IDAuMiwgbWF4ID0gMiwgdmFsdWUgPSAxLCBzdGVwID0gMC4yKQ0KYGBgDQoNCg0KYGBge3IgZWNobyA9IEZBTFNFfQ0KI3JlbmRlclBsb3Qoew0KICAjaGlzdCh1cyRNdXJkZXIsIHByb2JhYmlsaXR5ID0gVFJVRSwNCiAgIyAgICAgYnJlYWtzID0gYXMubnVtZXJpYyhpbnB1dCRuX2JyZWFrcyksDQogICAjICAgIHhsYWIgPSAiIiwNCiAgICAjICAgbWFpbiA9ICIiKQ0KDQogICNkZW5zIDwtIGRlbnNpdHkodXMkTXVyZGVyLCBhZGp1c3QgPSBpbnB1dCRid19hZGp1c3QpDQogICNsaW5lcyhkZW5zLCBjb2wgPSAiYmx1ZSIpDQojfSkNCmBgYA0KDQoNCmBgYHtyIHNlbGVjdC1pbnB1dH0NCiNzZWxlY3RJbnB1dCgidmFyaWFibGVzIixsYWJlbD0ieCIsY2hvaWNlcyA9IG5hbWVzKHVzMiksc2VsZWN0ZWQgPSAjIk11cmRlciIpDQpgYGANCg0KYGBge3J9DQoNCiNyZW5kZXJQbG90KHBsb3QodXMyJEFzc2F1bHQsdXMyWyxpbnB1dCR2YXJpYWJsZXNdKQ0KICANCiMpDQpgYGANCg0KIyBDb25jbHVzaW9uZXMNCk1lIHBhcmVjZSBxdWUgZWwgYW5hbGlzaXMgZGUgY2x1c3RlcnMgYXBvcnRhIGJhc3RhbnRlIHZhbG9yLCBlc3BlY2lhbG1lbnRlIGVuIMOhbWJpdG9zIGRlIG5lZ29jaW9zIGNvbW8gZW4gZWwgY2FzbyBkZSB2ZW50YXMuIEVuIGVsIGNhc28gZGUgVVMgQXJyZXN0cywgbG9zIGNsdXN0ZXJzIG5vcyBzaXJ2ZW4gcGFyYSBpZGVudGlmaWNhciBhcXVlbGxvcyBlc3RhZG9zIHF1ZSB0aWVuZW4gbWVub3JlcyBpbmNpZGVuY2lhcyBlbiBhc2VzaW5hdG9zLCB2aW9sYWNpb25lcywgZW50cmUgb3Ryb3MuIEFkZW3DoXMgZGUgcG9kZXIgYWdydXBhcyBhcXVlbGxvcyBlc3RhZG9zIHF1ZSB0aWVuZW4gw61uZGljZXMgZGUgY3JpbWluYWxpZGFkIHBhcmVjaWRvcyB5IHBvYmxhY2lvbmVzIHBhcmVjaWRhcy4gIA0KDQpMb3MgY2x1c3RlcnMgcGVybWl0ZSBxdWUgdmlzdWFsaWNlbW9zIGxvcyBncnVwb3MgbyBjb25qdW50b3MgZGUgZGF0b3MgcXVlIHBvc2VlbiBjYXJhY3RlcsOtc3RpY2FzIHNpbWlsYXJlcyB5IHB1ZWRlbiBzZXIgc2VnbWVudGFkb3MgbyBjYXRhbG9nYWRvcyBiYWpvIGVzdGFzIGNhcmFjdGVyw61zdGljYXMuIEVzdG9zIHNvbiBkZSBncmFuIHZhbG9yIHBhcmEgYW5hbMOtc2lzIGRvbmRlIHNlIHJlcXVpZXJlIGhhY2VyIGVzdHJhdGVnaWFzIHBvciBncnVwb3MsIGNvbW8gZXMgZWwgY2FzbyBkZSBsb3Mgc3VwZXJtZXJjYWRvcyBvIGN1YW5kbyBzZSBxdWllcmUgY29ub2NlciBncnVwb3MgY29uIGNhcmFjdGVyw61zdGljYXMgc2ltaWxhcmVzLg0KDQo=