Teoría

Agrupamiento o clustering es una técnica de aprendizaje automático no supervisado que agrupa datos en función de su similitud.

Algunos usos típicos de esta técnica son:

  • Segmentación de Clientes
  • Detección de anormalidades
  • Categorización de documentos

Instalar paquetes y llamar librerias

#install.packages("cluster") # Análisis de Agrupamiento
library(cluster)
#install.packages("ggplot2") # Para graficar
library(ggplot2)
#install.packages("data.table") # Manejo de muchos datos
library(data.table)
#install.packages("factoextra") # Gráfica de optimización del número de clusters
library(factoextra)
#install.packages("datasets")
library(datasets)
#install.packages("tidyverse")
library(tidyverse)

Ejercicio 1. Puntos

Contexto

Agrupa los siguientes 8 puntos.

Obtener datos

df1 <- data.frame(x=c(2,2,8,5,7,6,1,4),y=c(10,5,4,8,5,4,2,9))

Entender datos

summary(df1)
##        x               y         
##  Min.   :1.000   Min.   : 2.000  
##  1st Qu.:2.000   1st Qu.: 4.000  
##  Median :4.500   Median : 5.000  
##  Mean   :4.375   Mean   : 5.875  
##  3rd Qu.:6.250   3rd Qu.: 8.250  
##  Max.   :8.000   Max.   :10.000
str(df1)
## 'data.frame':    8 obs. of  2 variables:
##  $ x: num  2 2 8 5 7 6 1 4
##  $ y: num  10 5 4 8 5 4 2 9
plot(df1$x,df1$y)

## Escalar datos

# datos_escalados <- scale(datos_originales)

Asignar número de grupos

grupos1 <- 3

Agrupar los puntos

set.seed(123)
clusters1 <- kmeans(df1,grupos1)
clusters1 # porque son pocos
## K-means clustering with 3 clusters of sizes 2, 3, 3
## 
## Cluster means:
##          x        y
## 1 1.500000 3.500000
## 2 3.666667 9.000000
## 3 7.000000 4.333333
## 
## Clustering vector:
## [1] 2 1 3 2 3 3 1 2
## 
## Within cluster sum of squares by cluster:
## [1] 5.000000 6.666667 2.666667
##  (between_SS / total_SS =  85.8 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

Optimizar numero de grupos

set.seed(123)
optimizacion1 <- clusGap(df1, FUN=kmeans,nstart=1,K.max=7)
# El K.max normalmente es 10, en este ejercicio al ser 8 datos se dejo en 7
plot(optimizacion1,xlab = "Número de Clusters k",main= "Optimización de Clusters")

# Se selecciona como óptimo el primer punto más alto
# Si es diferente al original, regresar y ajustar

Graficar los grupos

fviz_cluster(clusters1,data = df1)

Agregar grupos a la base de datos

df1_clusters <- cbind(df1,cluster= clusters1$cluster)
head(df1_clusters)
##   x  y cluster
## 1 2 10       2
## 2 2  5       1
## 3 8  4       3
## 4 5  8       2
## 5 7  5       3
## 6 6  4       3

Conclusiones

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

Ejercicio 2. USArrests

Contexto

La base de datos USArrests contiene estadísticas enn arretros por cada 100,000 residentes por agresión, asesinato y violación en cada uno de los 50 Estados de EE.UU en 1973.

Obtener datos

df2 <- USArrests
df2 <- df2 %>% select(-UrbanPop)

Entender datos

summary(df2)
##      Murder          Assault           Rape      
##  Min.   : 0.800   Min.   : 45.0   Min.   : 7.30  
##  1st Qu.: 4.075   1st Qu.:109.0   1st Qu.:15.07  
##  Median : 7.250   Median :159.0   Median :20.10  
##  Mean   : 7.788   Mean   :170.8   Mean   :21.23  
##  3rd Qu.:11.250   3rd Qu.:249.0   3rd Qu.:26.18  
##  Max.   :17.400   Max.   :337.0   Max.   :46.00
str(df2)
## 'data.frame':    50 obs. of  3 variables:
##  $ Murder : num  13.2 10 8.1 8.8 9 7.9 3.3 5.9 15.4 17.4 ...
##  $ Assault: int  236 263 294 190 276 204 110 238 335 211 ...
##  $ Rape   : num  21.2 44.5 31 19.5 40.6 38.7 11.1 15.8 31.9 25.8 ...

Escalar datos

df2_escalado <- scale(df2)
summary(df2_escalado)
##      Murder           Assault             Rape        
##  Min.   :-1.6044   Min.   :-1.5090   Min.   :-1.4874  
##  1st Qu.:-0.8525   1st Qu.:-0.7411   1st Qu.:-0.6574  
##  Median :-0.1235   Median :-0.1411   Median :-0.1209  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000  
##  3rd Qu.: 0.7949   3rd Qu.: 0.9388   3rd Qu.: 0.5277  
##  Max.   : 2.2069   Max.   : 1.9948   Max.   : 2.6444

Asignar número de grupos

grupos2 <- 5

Agrupar los puntos

set.seed(123)
clusters2 <- kmeans(df2_escalado,grupos2)
clusters2 # porque son pocos
## K-means clustering with 5 clusters of sizes 7, 14, 17, 4, 8
## 
## Cluster means:
##       Murder    Assault        Rape
## 1  0.8292944  1.3313823  0.90560938
## 2 -1.0812577 -1.0779212 -1.00700542
## 3 -0.2754591 -0.2999280 -0.12336985
## 4  0.4562038  0.9358314  2.26533514
## 5  1.5238170  0.8908337  0.09934463
## 
## Clustering vector:
##        Alabama         Alaska        Arizona       Arkansas     California 
##              5              4              1              3              4 
##       Colorado    Connecticut       Delaware        Florida        Georgia 
##              4              2              3              1              5 
##         Hawaii          Idaho       Illinois        Indiana           Iowa 
##              2              2              1              3              2 
##         Kansas       Kentucky      Louisiana          Maine       Maryland 
##              3              3              5              2              1 
##  Massachusetts       Michigan      Minnesota    Mississippi       Missouri 
##              3              1              2              5              3 
##        Montana       Nebraska         Nevada  New Hampshire     New Jersey 
##              3              2              4              2              3 
##     New Mexico       New York North Carolina   North Dakota           Ohio 
##              1              1              5              2              3 
##       Oklahoma         Oregon   Pennsylvania   Rhode Island South Carolina 
##              3              3              3              2              5 
##   South Dakota      Tennessee          Texas           Utah        Vermont 
##              2              5              5              3              2 
##       Virginia     Washington  West Virginia      Wisconsin        Wyoming 
##              3              3              2              2              3 
## 
## Within cluster sum of squares by cluster:
## [1] 3.380664 5.645542 9.205038 1.346517 4.683603
##  (between_SS / total_SS =  83.5 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

Optimizar numero de grupos

set.seed(123)
optimizacion2 <- clusGap(df2_escalado, FUN=kmeans,nstart=1,K.max=10)
# El K.max normalmente es 10, en este ejercicio al ser 8 datos se dejo en 7
plot(optimizacion2,xlab = "Número de Clusters k",main= "Optimización de Clusters")

# Se selecciona como óptimo el primer punto más alto
# Si es diferente al original, regresar y ajustar

Graficar los grupos

fviz_cluster(clusters2,data = df2_escalado)

Agregar grupos a la base de datos

df2_clusters <- cbind(df2,cluster= clusters2$cluster)
head(df2_clusters)
##            Murder Assault Rape cluster
## Alabama      13.2     236 21.2       5
## Alaska       10.0     263 44.5       4
## Arizona       8.1     294 31.0       1
## Arkansas      8.8     190 19.5       3
## California    9.0     276 40.6       4
## Colorado      7.9     204 38.7       4
df2_clusters %>% group_by(cluster) %>% summarise_all(mean) %>% mutate(indicador_seguridad=Murder+Assault+Rape)
## # A tibble: 5 × 5
##   cluster Murder Assault  Rape indicador_seguridad
##     <int>  <dbl>   <dbl> <dbl>               <dbl>
## 1       1  11.4    282.   29.7               323. 
## 2       2   3.08    80.9  11.8                95.8
## 3       3   6.59   146.   20.1               172. 
## 4       4   9.78   249.   42.4               301. 
## 5       5  14.4    245    22.2               282.
df2_clusters <- df2_clusters %>% 
  mutate(cluster = case_when(
    cluster == 1 ~ "Inseguridad Muy Alta",
    cluster == 4 ~ "Inseguridad Alta",
    cluster == 5 ~ "Inseguridad Media",
    cluster == 3 ~ "Inseguridad Baja",
    cluster == 2 ~ "Inseguridad Muy Baja",
  ))

head(df2_clusters)
##            Murder Assault Rape              cluster
## Alabama      13.2     236 21.2    Inseguridad Media
## Alaska       10.0     263 44.5     Inseguridad Alta
## Arizona       8.1     294 31.0 Inseguridad Muy Alta
## Arkansas      8.8     190 19.5     Inseguridad Baja
## California    9.0     276 40.6     Inseguridad Alta
## Colorado      7.9     204 38.7     Inseguridad Alta

Conclusiones

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

Ejercicio 3. Segmentación de Clientes

Contexto

La base de datos ventas tiene los registros entre el 1 de diciembre de 2010 y el 9 de diciembre de 2011 de las ventas de una emoresa minorista en línea sin tienda física, basada en Reino Unido. La empresa vende principalmente regalos únicos para toda ocasión, y muchos de sus clientes son mayoristas.
Objetivo: Segmentar clientes, asignarles nombres y características de comportamiento, y proponer sugerencias a la empresa para aumentar ventas

Cargar DataFrame

# file.choose()
df_ventas <- read.csv("C:\\Users\\LuisG\\Downloads\\ventas.csv",fileEncoding = "latin1",stringsAsFactors = FALSE)

head(df_ventas)
##   ï..Ticket                            Producto Cantidad      Fecha     Hora
## 1    536365  WHITE HANGING HEART T-LIGHT HOLDER        6 01/12/2010 08:26:00
## 2    536365                 WHITE METAL LANTERN        6 01/12/2010 08:26:00
## 3    536365      CREAM CUPID HEARTS COAT HANGER        8 01/12/2010 08:26:00
## 4    536365 KNITTED UNION FLAG HOT WATER BOTTLE        6 01/12/2010 08:26:00
## 5    536365      RED WOOLLY HOTTIE WHITE HEART         6 01/12/2010 08:26:00
## 6    536365        SET 7 BABUSHKA NESTING BOXES        2 01/12/2010 08:26:00
##   Precio Cliente          PaÃ.s
## 1   2.55   17850 United Kingdom
## 2   3.39   17850 United Kingdom
## 3   2.75   17850 United Kingdom
## 4   3.39   17850 United Kingdom
## 5   3.39   17850 United Kingdom
## 6   7.65   17850 United Kingdom

Entender los datos

summary(df_ventas)
##      ï..Ticket           Producto         Cantidad              Fecha       
##  Length   :522064   Length   :522064   Min.   :-9600.00   Length   :522064  
##  N.unique : 21663   N.unique :  4183   1st Qu.:    1.00   N.unique :   305  
##  N.blank  :     0   N.blank  :  1455   Median :    3.00   N.blank  :     0  
##  Min.nchar:     6   Min.nchar:     0   Mean   :   10.09   Min.nchar:    10  
##  Max.nchar:     7   Max.nchar:    38   3rd Qu.:   10.00   Max.nchar:    10  
##                                        Max.   :80995.00                     
##                                                                             
##         Hora            Precio              Cliente             PaÃ.s       
##  Length   :522064   Min.   :-11062.060   Min.   :12346    Length   :522064  
##  N.unique :   739   1st Qu.:     1.250   1st Qu.:13950    N.unique :    30  
##  N.blank  :     0   Median :     2.080   Median :15265    N.blank  :     0  
##  Min.nchar:     8   Mean   :     3.827   Mean   :15317    Min.nchar:     3  
##  Max.nchar:     8   3rd Qu.:     4.130   3rd Qu.:16837    Max.nchar:    20  
##                     Max.   : 13541.330   Max.   :18287                      
##                                          NAs    :134041
str(df_ventas)
## 'data.frame':    522064 obs. of  8 variables:
##  $ ï..Ticket: chr  "536365" "536365" "536365" "536365" ...
##  $ Producto : chr  "WHITE HANGING HEART T-LIGHT HOLDER" "WHITE METAL LANTERN" "CREAM CUPID HEARTS COAT HANGER" "KNITTED UNION FLAG HOT WATER BOTTLE" ...
##  $ Cantidad : int  6 6 8 6 6 2 6 6 6 32 ...
##  $ Fecha    : chr  "01/12/2010" "01/12/2010" "01/12/2010" "01/12/2010" ...
##  $ Hora     : chr  "08:26:00" "08:26:00" "08:26:00" "08:26:00" ...
##  $ Precio   : num  2.55 3.39 2.75 3.39 3.39 7.65 4.25 1.85 1.85 1.69 ...
##  $ Cliente  : int  17850 17850 17850 17850 17850 17850 17850 17850 17850 13047 ...
##  $ PaÃ.s    : chr  "United Kingdom" "United Kingdom" "United Kingdom" "United Kingdom" ...
names(df_ventas)
## [1] "ï..Ticket" "Producto"  "Cantidad"  "Fecha"     "Hora"      "Precio"   
## [7] "Cliente"   "PaÃ.s"

Cambiar nombres

names(df_ventas) <- c(
  "Ticket",
  "Producto",
  "Cantidad",
  "Fecha",
  "Hora",
  "Precio",
  "Cliente",
  "Pais"
)
names(df_ventas)
## [1] "Ticket"   "Producto" "Cantidad" "Fecha"    "Hora"     "Precio"   "Cliente" 
## [8] "Pais"

Limpiar datos

df_ventas$Fecha <- as.Date(df_ventas$Fecha,format = "%d/%m/%Y")
df_ventas$Cantidad <- as.numeric(df_ventas$Cantidad)
df_ventas$Precio <- as.numeric(df_ventas$Precio)
df_ventas$Cliente <- as.numeric(df_ventas$Cliente)

Crear el valor de cada venta

df_ventas <- df_ventas %>%
  mutate(
    Venta = Cantidad * Precio
  ) 

Eliminar datos que no podemos usar

df_ventas_limpia <- df_ventas %>% filter(
    !is.na(Cliente),
    Cantidad > 0,
    Precio > 0
  )

Crear base por cliente

fecha_referencia <- max(df_ventas_limpia$Fecha) + 1

df_clientes <- df_ventas_limpia %>% group_by(Cliente) %>% summarise(
    recencia = as.numeric(
      fecha_referencia - max(Fecha)
    ),
    
    frecuencia = n_distinct(Ticket),
    
    gasto_total = sum(Venta),
    
    cantidad_productos = sum(Cantidad),
    
    ticket_promedio = gasto_total / frecuencia,
    
    productos_distintos = n_distinct(Producto)
  )

Entender los datos de los clientes

summary(df_clientes)
##     Cliente         recencia        frecuencia       gasto_total       
##  Min.   :12346   Min.   :  1.00   Min.   :  1.000   Min.   :     3.75  
##  1st Qu.:13832   1st Qu.: 18.00   1st Qu.:  1.000   1st Qu.:   306.72  
##  Median :15322   Median : 51.00   Median :  2.000   Median :   668.85  
##  Mean   :15316   Mean   : 93.16   Mean   :  4.227   Mean   :  1993.61  
##  3rd Qu.:16790   3rd Qu.:143.00   3rd Qu.:  5.000   3rd Qu.:  1652.79  
##  Max.   :18287   Max.   :374.00   Max.   :209.000   Max.   :280206.02  
##  cantidad_productos ticket_promedio    productos_distintos
##  Min.   :     1.0   Min.   :    3.45   Min.   :   1.00    
##  1st Qu.:   160.0   1st Qu.:  178.30   1st Qu.:  16.00    
##  Median :   376.0   Median :  292.00   Median :  35.50    
##  Mean   :  1161.3   Mean   :  415.62   Mean   :  61.27    
##  3rd Qu.:   983.5   3rd Qu.:  426.63   3rd Qu.:  78.00    
##  Max.   :196915.0   Max.   :84236.25   Max.   :1774.00
str(df_clientes)
## tibble [4,296 × 7] (S3: tbl_df/tbl/data.frame)
##  $ Cliente            : num [1:4296] 12346 12347 12349 12350 12352 ...
##  $ recencia           : num [1:4296] 326 3 19 311 37 205 233 215 23 34 ...
##  $ frecuencia         : int [1:4296] 1 7 1 1 8 1 1 1 3 1 ...
##  $ gasto_total        : num [1:4296] 77184 4310 1758 334 2506 ...
##  $ cantidad_productos : num [1:4296] 74215 2458 631 197 536 ...
##  $ ticket_promedio    : num [1:4296] 77184 616 1758 334 313 ...
##  $ productos_distintos: int [1:4296] 1 103 73 17 59 4 58 13 53 131 ...
head(df_clientes)
## # A tibble: 6 × 7
##   Cliente recencia frecuencia gasto_total cantidad_productos ticket_promedio
##     <dbl>    <dbl>      <int>       <dbl>              <dbl>           <dbl>
## 1   12346      326          1      77184.              74215          77184.
## 2   12347        3          7       4310                2458            616.
## 3   12349       19          1       1758.                631           1758.
## 4   12350      311          1        334.                197            334.
## 5   12352       37          8       2506.                536            313.
## 6   12353      205          1         89                  20             89 
## # ℹ 1 more variable: productos_distintos <int>

Escalar datos

df_clientes_log <- df_clientes %>%
  select(recencia,frecuencia,gasto_total,cantidad_productos,ticket_promedio,productos_distintos) %>% mutate(
    across(
      everything(),
      log1p
    )
  )

df_clientes_escalados <- scale(df_clientes_log)

summary(df_clientes_escalados)
##     recencia         frecuencia       gasto_total       cantidad_productos
##  Min.   :-2.4185   Min.   :-0.9578   Min.   :-4.01493   Min.   :-3.86824  
##  1st Qu.:-0.6976   1st Qu.:-0.9578   1st Qu.:-0.68455   1st Qu.:-0.65755  
##  Median : 0.0720   Median :-0.3622   Median :-0.06347   Median :-0.03503  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.00000  
##  3rd Qu.: 0.8506   3rd Qu.: 0.6561   3rd Qu.: 0.65816   3rd Qu.: 0.66728  
##  Max.   : 1.5822   Max.   : 5.8791   Max.   : 4.75618   Max.   : 4.54388  
##  ticket_promedio    productos_distintos
##  Min.   :-5.60522   Min.   :-2.52890   
##  1st Qu.:-0.61486   1st Qu.:-0.63640   
##  Median : 0.04819   Median : 0.03922   
##  Mean   : 0.00000   Mean   : 0.00000   
##  3rd Qu.: 0.55869   3rd Qu.: 0.72211   
##  Max.   : 7.69169   Max.   : 3.47421

Asignar los puntos

grupos <- 10

Asignar número de grupos

set.seed(123)

clusters <- kmeans(
  df_clientes_escalados,
  grupos,
  nstart = 25
)
## Warning: did not converge in 10 iterations

Optimización de clusters

set.seed(123)

optimizacion <- clusGap(
  df_clientes_escalados,
  FUN = kmeans,
  K.max = 10,
  B = 10,
  nstart = 5,
  verbose = interactive()
)

plot(
  optimizacion,
  xlab = "Número de clusters",
  main = "Optimización de Clusters"
)

Graficar los grupos

fviz_cluster(
  clusters,
  data = df_clientes_escalados,
  main = "Segmentación de Clientes",
  xlab = "Dimensión 1",
  ylab = "Dimensión 2",
  legend.title = "Segmento"
)

Agregar grupos a la base de datos

df_clientes_clusters <- cbind(
  df_clientes,
  cluster = clusters$cluster
)

head(df_clientes_clusters)
##   Cliente recencia frecuencia gasto_total cantidad_productos ticket_promedio
## 1   12346      326          1    77183.60              74215      77183.6000
## 2   12347        3          7     4310.00               2458        615.7143
## 3   12349       19          1     1757.55                631       1757.5500
## 4   12350      311          1      334.40                197        334.4000
## 5   12352       37          8     2506.04                536        313.2550
## 6   12353      205          1       89.00                 20         89.0000
##   productos_distintos cluster
## 1                   1       4
## 2                 103       5
## 3                  73       4
## 4                  17       1
## 5                  59       6
## 6                   4      10

Analizar las características de cada cluster

df_clientes_clusters %>%
  group_by(cluster) %>%
  summarise(
    clientes = n(),
    recencia = mean(recencia),
    frecuencia = mean(frecuencia),
    gasto_total = mean(gasto_total),
    cantidad_productos = mean(cantidad_productos),
    ticket_promedio = mean(ticket_promedio),
    productos_distintos = mean(productos_distintos)
  )
## # A tibble: 10 × 8
##    cluster clientes recencia frecuencia gasto_total cantidad_productos
##      <int>    <int>    <dbl>      <dbl>       <dbl>              <dbl>
##  1       1      624   180.         1.16       426.               240. 
##  2       2      520   214.         1.38       200.               103. 
##  3       3      116     9.72      28.4      29005.             16186. 
##  4       4      310   115.         1.72      2260.              1376. 
##  5       5      414    10.7       12.1       3828.              2263. 
##  6       6      527    49.2        5.54      2662.              1627. 
##  7       7      591   101.         3.08       755.               465. 
##  8       8      539    14.2        3.93      1008.               602. 
##  9       9      474    27.8        1.71       290.               169. 
## 10      10      181   159.         1.28        80.6               43.6
## # ℹ 2 more variables: ticket_promedio <dbl>, productos_distintos <dbl>

Crear indicador de valor del cliente

df_clientes_clusters <- df_clientes_clusters %>%
  mutate(
    indicador_valor =
      gasto_total +
      frecuencia * 100 +
      ticket_promedio
  )

Comparar el valor de cada cluster

df_clientes_clusters %>%
  group_by(cluster) %>%
  summarise(
    clientes = n(),
    recencia = mean(recencia),
    frecuencia = mean(frecuencia),
    gasto_total = mean(gasto_total),
    ticket_promedio = mean(ticket_promedio),
    indicador_valor = mean(indicador_valor)
  )
## # A tibble: 10 × 7
##    cluster clientes recencia frecuencia gasto_total ticket_promedio
##      <int>    <int>    <dbl>      <dbl>       <dbl>           <dbl>
##  1       1      624   180.         1.16       426.            377. 
##  2       2      520   214.         1.38       200.            155. 
##  3       3      116     9.72      28.4      29005.           1814. 
##  4       4      310   115.         1.72      2260.           1399. 
##  5       5      414    10.7       12.1       3828.            346. 
##  6       6      527    49.2        5.54      2662.            515. 
##  7       7      591   101.         3.08       755.            265. 
##  8       8      539    14.2        3.93      1008.            284. 
##  9       9      474    27.8        1.71       290.            188. 
## 10      10      181   159.         1.28        80.6            68.7
## # ℹ 1 more variable: indicador_valor <dbl>

Asignar nombre a los grupos

df_clientes_clusters <- df_clientes_clusters %>%
  mutate(
    segmento = case_when(
      cluster == 1 ~ "Clientes Inactivos",
      cluster == 2 ~ "Clientes de Muy Bajo Valor",
      cluster == 3 ~ "Clientes VIP",
      cluster == 4 ~ "Clientes de Alto Ticket",
      cluster == 5 ~ "Clientes Premium Frecuentes",
      cluster == 6 ~ "Clientes Frecuentes",
      cluster == 7 ~ "Clientes Ocasionales",
      cluster == 8 ~ "Clientes Activos",
      cluster == 9 ~ "Clientes Ocasionales Recientes",
      cluster == 10 ~ "Clientes Inactivos de Bajo Valor"
    )
  )

Ver los segmentos finales

head(df_clientes_clusters)
##   Cliente recencia frecuencia gasto_total cantidad_productos ticket_promedio
## 1   12346      326          1    77183.60              74215      77183.6000
## 2   12347        3          7     4310.00               2458        615.7143
## 3   12349       19          1     1757.55                631       1757.5500
## 4   12350      311          1      334.40                197        334.4000
## 5   12352       37          8     2506.04                536        313.2550
## 6   12353      205          1       89.00                 20         89.0000
##   productos_distintos cluster indicador_valor                         segmento
## 1                   1       4      154467.200          Clientes de Alto Ticket
## 2                 103       5        5625.714      Clientes Premium Frecuentes
## 3                  73       4        3615.100          Clientes de Alto Ticket
## 4                  17       1         768.800               Clientes Inactivos
## 5                  59       6        3619.295              Clientes Frecuentes
## 6                   4      10         278.000 Clientes Inactivos de Bajo Valor

Cantidad de clientes por segmento

df_clientes_clusters %>%
  count(segmento)
##                            segmento   n
## 1                  Clientes Activos 539
## 2               Clientes Frecuentes 527
## 3                Clientes Inactivos 624
## 4  Clientes Inactivos de Bajo Valor 181
## 5              Clientes Ocasionales 591
## 6    Clientes Ocasionales Recientes 474
## 7       Clientes Premium Frecuentes 414
## 8                      Clientes VIP 116
## 9           Clientes de Alto Ticket 310
## 10       Clientes de Muy Bajo Valor 520

Perfil de cada segmento

df_clientes_clusters %>%
  group_by(segmento) %>%
  summarise(
    clientes = n(),
    recencia_promedio = mean(recencia),
    frecuencia_promedio = mean(frecuencia),
    gasto_promedio = mean(gasto_total),
    ticket_promedio = mean(ticket_promedio),
    productos_promedio = mean(productos_distintos)
  )
## # A tibble: 10 × 7
##    segmento        clientes recencia_promedio frecuencia_promedio gasto_promedio
##    <chr>              <int>             <dbl>               <dbl>          <dbl>
##  1 Clientes Activ…      539             14.2                 3.93         1008. 
##  2 Clientes Frecu…      527             49.2                 5.54         2662. 
##  3 Clientes Inact…      624            180.                  1.16          426. 
##  4 Clientes Inact…      181            159.                  1.28           80.6
##  5 Clientes Ocasi…      591            101.                  3.08          755. 
##  6 Clientes Ocasi…      474             27.8                 1.71          290. 
##  7 Clientes Premi…      414             10.7                12.1          3828. 
##  8 Clientes VIP         116              9.72               28.4         29005. 
##  9 Clientes de Al…      310            115.                  1.72         2260. 
## 10 Clientes de Mu…      520            214.                  1.38          200. 
## # ℹ 2 more variables: ticket_promedio <dbl>, productos_promedio <dbl>
LS0tDQp0aXRsZTogIkNsdXN0ZXJzIC0gUHVudG9zLCBVU0FycmVzdHMgeSBDbGllbnRlcyINCmF1dGhvcjogIkx1aXMgR2FsbGFyZG8gQTAwODM4OTM0Ig0KZGF0ZTogIjIwMjYtMDgtMjUiDQpvdXRwdXQ6ICAgDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KICAgIHRoZW1lOiBkYXJrbHkNCi0tLQ0KDQohW10oaHR0cHM6Ly9taXJvLm1lZGl1bS5jb20vdjIvcmVzaXplOmZpdDoxMTAwL2Zvcm1hdDp3ZWJwLzEqYjJzTzJmLS15ZlppSmF6YzVyWVNwZy5naWYpICANCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gVGVvcsOtYSA8L3NwYW4+ICANCg0KKipBZ3J1cGFtaWVudG8qKiBvICpjbHVzdGVyaW5nKiAgZXMgdW5hIHTDqWNuaWNhIGRlIGFwcmVuZGl6YWplIGF1dG9tw6F0aWNvIG5vIHN1cGVydmlzYWRvIHF1ZSBhZ3J1cGEgZGF0b3MgZW4gZnVuY2nDs24gZGUgc3Ugc2ltaWxpdHVkLiAgDQoNCkFsZ3Vub3MgdXNvcyB0w61waWNvcyBkZSBlc3RhIHTDqWNuaWNhIHNvbjogIA0KDQoqIFNlZ21lbnRhY2nDs24gZGUgQ2xpZW50ZXMNCiogRGV0ZWNjacOzbiBkZSBhbm9ybWFsaWRhZGVzDQoqIENhdGVnb3JpemFjacOzbiBkZSBkb2N1bWVudG9zDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEluc3RhbGFyIHBhcXVldGVzIHkgbGxhbWFyIGxpYnJlcmlhcyA8L3NwYW4+DQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KI2luc3RhbGwucGFja2FnZXMoImNsdXN0ZXIiKSAjIEFuw6FsaXNpcyBkZSBBZ3J1cGFtaWVudG8NCmxpYnJhcnkoY2x1c3RlcikNCiNpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikgIyBQYXJhIGdyYWZpY2FyDQpsaWJyYXJ5KGdncGxvdDIpDQojaW5zdGFsbC5wYWNrYWdlcygiZGF0YS50YWJsZSIpICMgTWFuZWpvIGRlIG11Y2hvcyBkYXRvcw0KbGlicmFyeShkYXRhLnRhYmxlKQ0KI2luc3RhbGwucGFja2FnZXMoImZhY3RvZXh0cmEiKSAjIEdyw6FmaWNhIGRlIG9wdGltaXphY2nDs24gZGVsIG7Dum1lcm8gZGUgY2x1c3RlcnMNCmxpYnJhcnkoZmFjdG9leHRyYSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJkYXRhc2V0cyIpDQpsaWJyYXJ5KGRhdGFzZXRzKQ0KI2luc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEVqZXJjaWNpbyAxLiBQdW50b3MgPC9zcGFuPg0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gQ29udGV4dG8gPC9zcGFuPg0KQWdydXBhIGxvcyBzaWd1aWVudGVzIDggcHVudG9zLg0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gT2J0ZW5lciBkYXRvcyAgPC9zcGFuPg0KYGBge3J9DQpkZjEgPC0gZGF0YS5mcmFtZSh4PWMoMiwyLDgsNSw3LDYsMSw0KSx5PWMoMTAsNSw0LDgsNSw0LDIsOSkpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEVudGVuZGVyIGRhdG9zICA8L3NwYW4+DQpgYGB7cn0NCnN1bW1hcnkoZGYxKQ0Kc3RyKGRmMSkNCnBsb3QoZGYxJHgsZGYxJHkpDQpgYGANCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBFc2NhbGFyIGRhdG9zICA8L3NwYW4+DQpgYGB7cn0NCiMgZGF0b3NfZXNjYWxhZG9zIDwtIHNjYWxlKGRhdG9zX29yaWdpbmFsZXMpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+QXNpZ25hciBuw7ptZXJvIGRlIGdydXBvcyA8L3NwYW4+DQpgYGB7cn0NCmdydXBvczEgPC0gMw0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBBZ3J1cGFyIGxvcyBwdW50b3MgPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpjbHVzdGVyczEgPC0ga21lYW5zKGRmMSxncnVwb3MxKQ0KY2x1c3RlcnMxICMgcG9ycXVlIHNvbiBwb2Nvcw0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBPcHRpbWl6YXIgbnVtZXJvIGRlIGdydXBvcyA8L3NwYW4+DQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCm9wdGltaXphY2lvbjEgPC0gY2x1c0dhcChkZjEsIEZVTj1rbWVhbnMsbnN0YXJ0PTEsSy5tYXg9NykNCiMgRWwgSy5tYXggbm9ybWFsbWVudGUgZXMgMTAsIGVuIGVzdGUgZWplcmNpY2lvIGFsIHNlciA4IGRhdG9zIHNlIGRlam8gZW4gNw0KcGxvdChvcHRpbWl6YWNpb24xLHhsYWIgPSAiTsO6bWVybyBkZSBDbHVzdGVycyBrIixtYWluPSAiT3B0aW1pemFjacOzbiBkZSBDbHVzdGVycyIpDQoNCiMgU2Ugc2VsZWNjaW9uYSBjb21vIMOzcHRpbW8gZWwgcHJpbWVyIHB1bnRvIG3DoXMgYWx0bw0KIyBTaSBlcyBkaWZlcmVudGUgYWwgb3JpZ2luYWwsIHJlZ3Jlc2FyIHkgYWp1c3Rhcg0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBHcmFmaWNhciBsb3MgZ3J1cG9zIDwvc3Bhbj4NCmBgYHtyfQ0KZnZpel9jbHVzdGVyKGNsdXN0ZXJzMSxkYXRhID0gZGYxKQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBBZ3JlZ2FyIGdydXBvcyBhIGxhIGJhc2UgZGUgZGF0b3MgPC9zcGFuPg0KYGBge3J9DQpkZjFfY2x1c3RlcnMgPC0gY2JpbmQoZGYxLGNsdXN0ZXI9IGNsdXN0ZXJzMSRjbHVzdGVyKQ0KaGVhZChkZjFfY2x1c3RlcnMpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IENvbmNsdXNpb25lcyA8L3NwYW4+DQoNCkxhIHTDqWNuaWNhIGRlICpjbHVzdGVyaW5nKiBwZXJtaXRlIGlkZW50aWZpY2FyIHBhdHJvbmVzIG8gZ3J1cG9zIG5hdHVyYWxlcyBlbiBsb3MgZGF0b3Mgc2luIG5lY2VzaWRhZCBkZSBldGlxdWV0YXMgcHJldmlhcy4NCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gRWplcmNpY2lvIDIuIFVTQXJyZXN0cyA8L3NwYW4+DQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBDb250ZXh0byA8L3NwYW4+DQpMYSBiYXNlIGRlIGRhdG9zICoqVVNBcnJlc3RzKiogY29udGllbmUgZXN0YWTDrXN0aWNhcyBlbm4gYXJyZXRyb3MgcG9yIGNhZGEgMTAwLDAwMCByZXNpZGVudGVzIHBvciBhZ3Jlc2nDs24sIGFzZXNpbmF0byB5IHZpb2xhY2nDs24gZW4gY2FkYSB1bm8gZGUgbG9zIDUwIEVzdGFkb3MgZGUgRUUuVVUgZW4gMTk3My4NCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IE9idGVuZXIgZGF0b3MgIDwvc3Bhbj4NCmBgYHtyfQ0KZGYyIDwtIFVTQXJyZXN0cw0KZGYyIDwtIGRmMiAlPiUgc2VsZWN0KC1VcmJhblBvcCkNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gRW50ZW5kZXIgZGF0b3MgIDwvc3Bhbj4NCmBgYHtyfQ0Kc3VtbWFyeShkZjIpDQpzdHIoZGYyKQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBFc2NhbGFyIGRhdG9zICA8L3NwYW4+DQpgYGB7cn0NCmRmMl9lc2NhbGFkbyA8LSBzY2FsZShkZjIpDQpzdW1tYXJ5KGRmMl9lc2NhbGFkbykNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij5Bc2lnbmFyIG7Dum1lcm8gZGUgZ3J1cG9zIDwvc3Bhbj4NCmBgYHtyfQ0KZ3J1cG9zMiA8LSA1DQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEFncnVwYXIgbG9zIHB1bnRvcyA8L3NwYW4+DQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCmNsdXN0ZXJzMiA8LSBrbWVhbnMoZGYyX2VzY2FsYWRvLGdydXBvczIpDQpjbHVzdGVyczIgIyBwb3JxdWUgc29uIHBvY29zDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IE9wdGltaXphciBudW1lcm8gZGUgZ3J1cG9zIDwvc3Bhbj4NCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0Kb3B0aW1pemFjaW9uMiA8LSBjbHVzR2FwKGRmMl9lc2NhbGFkbywgRlVOPWttZWFucyxuc3RhcnQ9MSxLLm1heD0xMCkNCiMgRWwgSy5tYXggbm9ybWFsbWVudGUgZXMgMTAsIGVuIGVzdGUgZWplcmNpY2lvIGFsIHNlciA4IGRhdG9zIHNlIGRlam8gZW4gNw0KcGxvdChvcHRpbWl6YWNpb24yLHhsYWIgPSAiTsO6bWVybyBkZSBDbHVzdGVycyBrIixtYWluPSAiT3B0aW1pemFjacOzbiBkZSBDbHVzdGVycyIpDQoNCiMgU2Ugc2VsZWNjaW9uYSBjb21vIMOzcHRpbW8gZWwgcHJpbWVyIHB1bnRvIG3DoXMgYWx0bw0KIyBTaSBlcyBkaWZlcmVudGUgYWwgb3JpZ2luYWwsIHJlZ3Jlc2FyIHkgYWp1c3Rhcg0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBHcmFmaWNhciBsb3MgZ3J1cG9zIDwvc3Bhbj4NCmBgYHtyfQ0KZnZpel9jbHVzdGVyKGNsdXN0ZXJzMixkYXRhID0gZGYyX2VzY2FsYWRvKQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBBZ3JlZ2FyIGdydXBvcyBhIGxhIGJhc2UgZGUgZGF0b3MgPC9zcGFuPg0KYGBge3J9DQpkZjJfY2x1c3RlcnMgPC0gY2JpbmQoZGYyLGNsdXN0ZXI9IGNsdXN0ZXJzMiRjbHVzdGVyKQ0KaGVhZChkZjJfY2x1c3RlcnMpDQoNCmRmMl9jbHVzdGVycyAlPiUgZ3JvdXBfYnkoY2x1c3RlcikgJT4lIHN1bW1hcmlzZV9hbGwobWVhbikgJT4lIG11dGF0ZShpbmRpY2Fkb3Jfc2VndXJpZGFkPU11cmRlcitBc3NhdWx0K1JhcGUpDQoNCmRmMl9jbHVzdGVycyA8LSBkZjJfY2x1c3RlcnMgJT4lIA0KICBtdXRhdGUoY2x1c3RlciA9IGNhc2Vfd2hlbigNCiAgICBjbHVzdGVyID09IDEgfiAiSW5zZWd1cmlkYWQgTXV5IEFsdGEiLA0KICAgIGNsdXN0ZXIgPT0gNCB+ICJJbnNlZ3VyaWRhZCBBbHRhIiwNCiAgICBjbHVzdGVyID09IDUgfiAiSW5zZWd1cmlkYWQgTWVkaWEiLA0KICAgIGNsdXN0ZXIgPT0gMyB+ICJJbnNlZ3VyaWRhZCBCYWphIiwNCiAgICBjbHVzdGVyID09IDIgfiAiSW5zZWd1cmlkYWQgTXV5IEJhamEiLA0KICApKQ0KDQpoZWFkKGRmMl9jbHVzdGVycykNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gQ29uY2x1c2lvbmVzIDwvc3Bhbj4NCg0KTGEgdMOpY25pY2EgZGUgKmNsdXN0ZXJpbmcqIHBlcm1pdGUgaWRlbnRpZmljYXIgcGF0cm9uZXMgbyBncnVwb3MgbmF0dXJhbGVzIGVuIGxvcyBkYXRvcyBzaW4gbmVjZXNpZGFkIGRlIGV0aXF1ZXRhcyBwcmV2aWFzLg0KDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEVqZXJjaWNpbyAzLiBTZWdtZW50YWNpw7NuIGRlIENsaWVudGVzIDwvc3Bhbj4NCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IENvbnRleHRvIDwvc3Bhbj4NCg0KTGEgYmFzZSBkZSBkYXRvcyAqKnZlbnRhcyoqIHRpZW5lIGxvcyByZWdpc3Ryb3MgZW50cmUgZWwgMSBkZSBkaWNpZW1icmUgZGUgMjAxMCB5IGVsIDkgZGUgZGljaWVtYnJlIGRlIDIwMTEgZGUgbGFzIHZlbnRhcyBkZSB1bmEgZW1vcmVzYSBtaW5vcmlzdGEgZW4gbMOtbmVhIHNpbiB0aWVuZGEgZsOtc2ljYSwgYmFzYWRhIGVuIFJlaW5vIFVuaWRvLiBMYSBlbXByZXNhIHZlbmRlIHByaW5jaXBhbG1lbnRlIHJlZ2Fsb3Mgw7puaWNvcyBwYXJhIHRvZGEgb2Nhc2nDs24sIHkgbXVjaG9zIGRlIHN1cyBjbGllbnRlcyBzb24gbWF5b3Jpc3Rhcy4gIA0KT2JqZXRpdm86IFNlZ21lbnRhciBjbGllbnRlcywgYXNpZ25hcmxlcyBub21icmVzIHkgY2FyYWN0ZXLDrXN0aWNhcyBkZSBjb21wb3J0YW1pZW50bywgeSBwcm9wb25lciBzdWdlcmVuY2lhcyBhIGxhIGVtcHJlc2EgcGFyYSBhdW1lbnRhciB2ZW50YXMNCg0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gQ2FyZ2FyIERhdGFGcmFtZTwvc3Bhbj4NCmBgYHtyfQ0KDQojIGZpbGUuY2hvb3NlKCkNCmRmX3ZlbnRhcyA8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxMdWlzR1xcRG93bmxvYWRzXFx2ZW50YXMuY3N2IixmaWxlRW5jb2RpbmcgPSAibGF0aW4xIixzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQoNCmhlYWQoZGZfdmVudGFzKQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBFbnRlbmRlciBsb3MgZGF0b3M8L3NwYW4+DQpgYGB7cn0NCnN1bW1hcnkoZGZfdmVudGFzKQ0Kc3RyKGRmX3ZlbnRhcykNCm5hbWVzKGRmX3ZlbnRhcykNCg0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBDYW1iaWFyIG5vbWJyZXMgPC9zcGFuPg0KYGBge3J9DQpuYW1lcyhkZl92ZW50YXMpIDwtIGMoDQogICJUaWNrZXQiLA0KICAiUHJvZHVjdG8iLA0KICAiQ2FudGlkYWQiLA0KICAiRmVjaGEiLA0KICAiSG9yYSIsDQogICJQcmVjaW8iLA0KICAiQ2xpZW50ZSIsDQogICJQYWlzIg0KKQ0KbmFtZXMoZGZfdmVudGFzKQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBMaW1waWFyIGRhdG9zPC9zcGFuPg0KYGBge3J9DQpkZl92ZW50YXMkRmVjaGEgPC0gYXMuRGF0ZShkZl92ZW50YXMkRmVjaGEsZm9ybWF0ID0gIiVkLyVtLyVZIikNCmRmX3ZlbnRhcyRDYW50aWRhZCA8LSBhcy5udW1lcmljKGRmX3ZlbnRhcyRDYW50aWRhZCkNCmRmX3ZlbnRhcyRQcmVjaW8gPC0gYXMubnVtZXJpYyhkZl92ZW50YXMkUHJlY2lvKQ0KZGZfdmVudGFzJENsaWVudGUgPC0gYXMubnVtZXJpYyhkZl92ZW50YXMkQ2xpZW50ZSkNCmBgYA0KDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBDcmVhciBlbCB2YWxvciBkZSBjYWRhIHZlbnRhPC9zcGFuPg0KYGBge3J9DQpkZl92ZW50YXMgPC0gZGZfdmVudGFzICU+JQ0KICBtdXRhdGUoDQogICAgVmVudGEgPSBDYW50aWRhZCAqIFByZWNpbw0KICApIA0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBFbGltaW5hciBkYXRvcyBxdWUgbm8gcG9kZW1vcyB1c2FyPC9zcGFuPg0KYGBge3J9DQpkZl92ZW50YXNfbGltcGlhIDwtIGRmX3ZlbnRhcyAlPiUgZmlsdGVyKA0KICAgICFpcy5uYShDbGllbnRlKSwNCiAgICBDYW50aWRhZCA+IDAsDQogICAgUHJlY2lvID4gMA0KICApDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IENyZWFyIGJhc2UgcG9yIGNsaWVudGU8L3NwYW4+DQpgYGB7cn0NCmZlY2hhX3JlZmVyZW5jaWEgPC0gbWF4KGRmX3ZlbnRhc19saW1waWEkRmVjaGEpICsgMQ0KDQpkZl9jbGllbnRlcyA8LSBkZl92ZW50YXNfbGltcGlhICU+JSBncm91cF9ieShDbGllbnRlKSAlPiUgc3VtbWFyaXNlKA0KICAgIHJlY2VuY2lhID0gYXMubnVtZXJpYygNCiAgICAgIGZlY2hhX3JlZmVyZW5jaWEgLSBtYXgoRmVjaGEpDQogICAgKSwNCiAgICANCiAgICBmcmVjdWVuY2lhID0gbl9kaXN0aW5jdChUaWNrZXQpLA0KICAgIA0KICAgIGdhc3RvX3RvdGFsID0gc3VtKFZlbnRhKSwNCiAgICANCiAgICBjYW50aWRhZF9wcm9kdWN0b3MgPSBzdW0oQ2FudGlkYWQpLA0KICAgIA0KICAgIHRpY2tldF9wcm9tZWRpbyA9IGdhc3RvX3RvdGFsIC8gZnJlY3VlbmNpYSwNCiAgICANCiAgICBwcm9kdWN0b3NfZGlzdGludG9zID0gbl9kaXN0aW5jdChQcm9kdWN0bykNCiAgKQ0KDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEVudGVuZGVyIGxvcyBkYXRvcyBkZSBsb3MgY2xpZW50ZXM8L3NwYW4+DQpgYGB7cn0NCnN1bW1hcnkoZGZfY2xpZW50ZXMpDQpzdHIoZGZfY2xpZW50ZXMpDQpoZWFkKGRmX2NsaWVudGVzKQ0KDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEVzY2FsYXIgZGF0b3M8L3NwYW4+DQpgYGB7cn0NCmRmX2NsaWVudGVzX2xvZyA8LSBkZl9jbGllbnRlcyAlPiUNCiAgc2VsZWN0KHJlY2VuY2lhLGZyZWN1ZW5jaWEsZ2FzdG9fdG90YWwsY2FudGlkYWRfcHJvZHVjdG9zLHRpY2tldF9wcm9tZWRpbyxwcm9kdWN0b3NfZGlzdGludG9zKSAlPiUgbXV0YXRlKA0KICAgIGFjcm9zcygNCiAgICAgIGV2ZXJ5dGhpbmcoKSwNCiAgICAgIGxvZzFwDQogICAgKQ0KICApDQoNCmRmX2NsaWVudGVzX2VzY2FsYWRvcyA8LSBzY2FsZShkZl9jbGllbnRlc19sb2cpDQoNCnN1bW1hcnkoZGZfY2xpZW50ZXNfZXNjYWxhZG9zKQ0KDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEFzaWduYXIgbG9zIHB1bnRvczwvc3Bhbj4NCmBgYHtyfQ0KZ3J1cG9zIDwtIDEwDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEFzaWduYXIgbsO6bWVybyBkZSBncnVwb3M8L3NwYW4+DQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCg0KY2x1c3RlcnMgPC0ga21lYW5zKA0KICBkZl9jbGllbnRlc19lc2NhbGFkb3MsDQogIGdydXBvcywNCiAgbnN0YXJ0ID0gMjUNCikNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gT3B0aW1pemFjacOzbiBkZSBjbHVzdGVyczwvc3Bhbj4NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzZXQuc2VlZCgxMjMpDQoNCm9wdGltaXphY2lvbiA8LSBjbHVzR2FwKAogIGRmX2NsaWVudGVzX2VzY2FsYWRvcywKICBGVU4gPSBrbWVhbnMsCiAgSy5tYXggPSAxMCwKICBCID0gMTAsCiAgbnN0YXJ0ID0gNSwKICB2ZXJib3NlID0gaW50ZXJhY3RpdmUoKQopCg0KcGxvdCgNCiAgb3B0aW1pemFjaW9uLA0KICB4bGFiID0gIk7Dum1lcm8gZGUgY2x1c3RlcnMiLA0KICBtYWluID0gIk9wdGltaXphY2nDs24gZGUgQ2x1c3RlcnMiDQopDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEdyYWZpY2FyIGxvcyBncnVwb3M8L3NwYW4+DQpgYGB7cn0NCmZ2aXpfY2x1c3RlcigNCiAgY2x1c3RlcnMsDQogIGRhdGEgPSBkZl9jbGllbnRlc19lc2NhbGFkb3MsDQogIG1haW4gPSAiU2VnbWVudGFjacOzbiBkZSBDbGllbnRlcyIsDQogIHhsYWIgPSAiRGltZW5zacOzbiAxIiwNCiAgeWxhYiA9ICJEaW1lbnNpw7NuIDIiLA0KICBsZWdlbmQudGl0bGUgPSAiU2VnbWVudG8iDQopDQoNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gQWdyZWdhciBncnVwb3MgYSBsYSBiYXNlIGRlIGRhdG9zPC9zcGFuPg0KYGBge3J9DQpkZl9jbGllbnRlc19jbHVzdGVycyA8LSBjYmluZCgNCiAgZGZfY2xpZW50ZXMsDQogIGNsdXN0ZXIgPSBjbHVzdGVycyRjbHVzdGVyDQopDQoNCmhlYWQoZGZfY2xpZW50ZXNfY2x1c3RlcnMpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEFuYWxpemFyIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGRlIGNhZGEgY2x1c3Rlcjwvc3Bhbj4NCmBgYHtyfQ0KZGZfY2xpZW50ZXNfY2x1c3RlcnMgJT4lDQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgY2xpZW50ZXMgPSBuKCksDQogICAgcmVjZW5jaWEgPSBtZWFuKHJlY2VuY2lhKSwNCiAgICBmcmVjdWVuY2lhID0gbWVhbihmcmVjdWVuY2lhKSwNCiAgICBnYXN0b190b3RhbCA9IG1lYW4oZ2FzdG9fdG90YWwpLA0KICAgIGNhbnRpZGFkX3Byb2R1Y3RvcyA9IG1lYW4oY2FudGlkYWRfcHJvZHVjdG9zKSwNCiAgICB0aWNrZXRfcHJvbWVkaW8gPSBtZWFuKHRpY2tldF9wcm9tZWRpbyksDQogICAgcHJvZHVjdG9zX2Rpc3RpbnRvcyA9IG1lYW4ocHJvZHVjdG9zX2Rpc3RpbnRvcykNCiAgKQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjp5ZWxsb3ciPiBDcmVhciBpbmRpY2Fkb3IgZGUgdmFsb3IgZGVsIGNsaWVudGU8L3NwYW4+DQpgYGB7cn0NCmRmX2NsaWVudGVzX2NsdXN0ZXJzIDwtIGRmX2NsaWVudGVzX2NsdXN0ZXJzICU+JQ0KICBtdXRhdGUoDQogICAgaW5kaWNhZG9yX3ZhbG9yID0NCiAgICAgIGdhc3RvX3RvdGFsICsNCiAgICAgIGZyZWN1ZW5jaWEgKiAxMDAgKw0KICAgICAgdGlja2V0X3Byb21lZGlvDQogICkNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gQ29tcGFyYXIgZWwgdmFsb3IgZGUgY2FkYSBjbHVzdGVyPC9zcGFuPg0KYGBge3J9DQpkZl9jbGllbnRlc19jbHVzdGVycyAlPiUNCiAgZ3JvdXBfYnkoY2x1c3RlcikgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBjbGllbnRlcyA9IG4oKSwNCiAgICByZWNlbmNpYSA9IG1lYW4ocmVjZW5jaWEpLA0KICAgIGZyZWN1ZW5jaWEgPSBtZWFuKGZyZWN1ZW5jaWEpLA0KICAgIGdhc3RvX3RvdGFsID0gbWVhbihnYXN0b190b3RhbCksDQogICAgdGlja2V0X3Byb21lZGlvID0gbWVhbih0aWNrZXRfcHJvbWVkaW8pLA0KICAgIGluZGljYWRvcl92YWxvciA9IG1lYW4oaW5kaWNhZG9yX3ZhbG9yKQ0KICApDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IEFzaWduYXIgbm9tYnJlIGEgbG9zIGdydXBvczwvc3Bhbj4NCmBgYHtyfQ0KZGZfY2xpZW50ZXNfY2x1c3RlcnMgPC0gZGZfY2xpZW50ZXNfY2x1c3RlcnMgJT4lDQogIG11dGF0ZSgNCiAgICBzZWdtZW50byA9IGNhc2Vfd2hlbigNCiAgICAgIGNsdXN0ZXIgPT0gMSB+ICJDbGllbnRlcyBJbmFjdGl2b3MiLA0KICAgICAgY2x1c3RlciA9PSAyIH4gIkNsaWVudGVzIGRlIE11eSBCYWpvIFZhbG9yIiwNCiAgICAgIGNsdXN0ZXIgPT0gMyB+ICJDbGllbnRlcyBWSVAiLA0KICAgICAgY2x1c3RlciA9PSA0IH4gIkNsaWVudGVzIGRlIEFsdG8gVGlja2V0IiwNCiAgICAgIGNsdXN0ZXIgPT0gNSB+ICJDbGllbnRlcyBQcmVtaXVtIEZyZWN1ZW50ZXMiLA0KICAgICAgY2x1c3RlciA9PSA2IH4gIkNsaWVudGVzIEZyZWN1ZW50ZXMiLA0KICAgICAgY2x1c3RlciA9PSA3IH4gIkNsaWVudGVzIE9jYXNpb25hbGVzIiwNCiAgICAgIGNsdXN0ZXIgPT0gOCB+ICJDbGllbnRlcyBBY3Rpdm9zIiwNCiAgICAgIGNsdXN0ZXIgPT0gOSB+ICJDbGllbnRlcyBPY2FzaW9uYWxlcyBSZWNpZW50ZXMiLA0KICAgICAgY2x1c3RlciA9PSAxMCB+ICJDbGllbnRlcyBJbmFjdGl2b3MgZGUgQmFqbyBWYWxvciINCiAgICApDQogICkNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gVmVyIGxvcyBzZWdtZW50b3MgZmluYWxlczwvc3Bhbj4NCmBgYHtyfQ0KaGVhZChkZl9jbGllbnRlc19jbHVzdGVycykNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6eWVsbG93Ij4gQ2FudGlkYWQgZGUgY2xpZW50ZXMgcG9yIHNlZ21lbnRvPC9zcGFuPg0KYGBge3J9DQpkZl9jbGllbnRlc19jbHVzdGVycyAlPiUNCiAgY291bnQoc2VnbWVudG8pDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnllbGxvdyI+IFBlcmZpbCBkZSBjYWRhIHNlZ21lbnRvPC9zcGFuPg0KYGBge3J9DQpkZl9jbGllbnRlc19jbHVzdGVycyAlPiUNCiAgZ3JvdXBfYnkoc2VnbWVudG8pICU+JQ0KICBzdW1tYXJpc2UoDQogICAgY2xpZW50ZXMgPSBuKCksDQogICAgcmVjZW5jaWFfcHJvbWVkaW8gPSBtZWFuKHJlY2VuY2lhKSwNCiAgICBmcmVjdWVuY2lhX3Byb21lZGlvID0gbWVhbihmcmVjdWVuY2lhKSwNCiAgICBnYXN0b19wcm9tZWRpbyA9IG1lYW4oZ2FzdG9fdG90YWwpLA0KICAgIHRpY2tldF9wcm9tZWRpbyA9IG1lYW4odGlja2V0X3Byb21lZGlvKSwNCiAgICBwcm9kdWN0b3NfcHJvbWVkaW8gPSBtZWFuKHByb2R1Y3Rvc19kaXN0aW50b3MpDQogICkNCmBgYA0KDQoNCg0KDQoNCg==