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.5
IQR
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=