Teoría

Agrupamiento o clustering es una técncica de aprendizaje automatico 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 librerías

#install.packages("culster")
library(cluster)
#install.packages("ggplot2")
library(ggplot2)
#install.packages("data.table")
library(data.table)
#install.packages("factoextra")
library(factoextra)
#install.packages("tidyverse")
library(tidyverse)

Ejercicio 1. Puntos

Contexto

Agrupa los siguientes 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
## 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 número de grupos

set.seed(123)
optimizacion1 <- clusGap(df1, FUN=kmeans, nstart=1, K.max=7)

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

Agrupar los puntos

fviz_cluster(clusters1,data=df1)

Agrupar los puntos

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 .

Ejercico 2. USArrests

Contexto

La base de datos USArrests contiene estadísticas en arrestos por cada 100,000 residentes por agresion, asesinato, vioalcion en cada uno de los 50 estados de EE.UU en 1973.

Obtener datos

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

Entender los 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_escalados <- scale(df2)
summary(df2_escalados)
##      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 los puntos

grupos2 <- 5

Asignar número de grupos

set.seed(123)
clusters2 <- kmeans(df2_escalados,grupos2)

set.seed(123)
optimizacion2 <- clusGap(df2_escalados, FUN=kmeans, nstart=1, K.max=10)
plot(optimizacion2, xlab="Número de clusters", main="Optimización de Clusters")

Graficar los grupos

fviz_cluster(clusters2,data=df2_escalados)

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_inseguridad=Murder+Assault+Rape)
## # A tibble: 5 × 5
##   cluster Murder Assault  Rape indicador_inseguridad
##     <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

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 empresa minorista en línea sin tienda física, basada en Reino Unido. La empresa vende principalmente regalos unicos para toda ocasion, y muchos de sus clientes mayositas.

Objetivo: Segmentar clientes, asignarles nombre y craacteristicas de comportamiento, y proponer sugerencias a la empresa para aumentar ventas.

Cargar DataFrame

#file.choose()

df_ventas <- read.csv(
  "C:/Users/eduar/OneDrive/Datos adjuntos/Documentos/TEC clases/Sexto Semestre/Modulo 2/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,
  nstart = 25,
  K.max = 10
)
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 214800)
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 214800)
## Warning: did not converge in 10 iterations
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 214800)
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 214800)
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 214800)
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: did not converge in 10 iterations
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 214800)
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 214800)
## Warning: did not converge in 10 iterations
## Warning: Quick-TRANSfer stage steps exceeded maximum (= 214800)
## Warning: did not converge in 10 iterations
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>
LS0tDQp0aXRsZTogIkNsdXN0ZXJzIg0KYXV0aG9yOiAiRWR1YXJkbyBSb2Ryw61ndWV6Ig0KZGF0ZTogIjIwMjYtMDgtMjUiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCiAgICB0aGVtZTogZGFya2x5DQogICAgDQotLS0NCg0KIVtdKGh0dHBzOi8vdGguYmluZy5jb20vdGgvaWQvUi5hYjNiMjI3NjRkZTZmZTJmMmYwMWMzMmVkZDg0YTZhOD9yaWs9YzdrVm82TkhYYmdWbncmcml1PWh0dHAlM2ElMmYlMmZwcmFjdGljYWxjcnlwdG9ncmFwaHkuY29tJTJmbWVkaWElMmZtaXNjZWxsYW5lb3VzJTJmZmlsZXMlMmZrX21lYW5fc2VuZC5naWYmZWhrPWN5U0oyTGxiUDFRTG9za0VVQUFqbmNXUm1vSmZjemlINzRjeU1UU1pZN2clM2QmcmlzbD0mcGlkPUltZ1JhdyZyPTApDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBUZW9yw61hIA0KPC9zcGFuPg0KKipBZ3J1cGFtaWVudG8qKiBvICoqY2x1c3RlcmluZyoqIGVzIHVuYSB0w6ljbmNpY2EgZGUgYXByZW5kaXphamUgYXV0b21hdGljbyBubyBzdXBlcnZpc2FkbyBxdWUgYWdydXBhIGRhdG9zIGVuIGZ1bmNpw7NuIGRlIHN1IHNpbWlsaXR1ZC4NCg0KQWxndW5vcyB1c29zIHTDrXBpY29zIGRlIGVzdGEgdMOpY25pY2Egc29uOiAgDQoqIFNlZ21lbnRhY2nDs24gZGUgY2xpZW50ZXMgIA0KKiBEZXRlY2Npw7NuIGRlIGFub3JtYWxpZGFkZXMgIA0KKiBDYXRlZ29yaXphY2nDs24gZGUgZG9jdW1lbnRvcyAgDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBJbnN0YWxhciBwYXF1ZXRlcyB5IGxsYW1hciBsaWJyZXLDrWFzIA0KPC9zcGFuPg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNpbnN0YWxsLnBhY2thZ2VzKCJjdWxzdGVyIikNCmxpYnJhcnkoY2x1c3RlcikNCiNpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmxpYnJhcnkoZ2dwbG90MikNCiNpbnN0YWxsLnBhY2thZ2VzKCJkYXRhLnRhYmxlIikNCmxpYnJhcnkoZGF0YS50YWJsZSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJmYWN0b2V4dHJhIikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IEVqZXJjaWNpbyAxLiBQdW50b3MgDQo8L3NwYW4+DQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IENvbnRleHRvIA0KPC9zcGFuPg0KQWdydXBhIGxvcyBzaWd1aWVudGVzIHB1bnRvczoNCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gT2J0ZW5lciBkYXRvcyANCjwvc3Bhbj4NCmBgYHtyfQ0KZGYxIDwtIGRhdGEuZnJhbWUoeD1jKDIsMiw4LDUsNyw2LDEsNCksIHk9YygxMCw1LDQsOCw1LDQsMiw5KSkNCmBgYA0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBFbnRlbmRlciBkYXRvcyANCjwvc3Bhbj4NCmBgYHtyfQ0Kc3VtbWFyeShkZjEpDQpzdHIoZGYxKQ0KDQpwbG90KGRmMSR4LGRmMSR5KQ0KYGBgDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IEVzY2FsYXIgZGF0b3MgDQo8L3NwYW4+DQpgYGB7cn0NCiMgZGF0b3NfZXNjYWxhZG9zIDwtIHNjYWxlKGRhdG9zX29yaWdpbmFsZXMpDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gQXNpZ25hciBuw7ptZXJvIGRlIGdydXBvcyANCjwvc3Bhbj4NCmBgYHtyfQ0KZ3J1cG9zMSA8LSAzDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gQWdydXBhciBsb3MgcHVudG9zIA0KPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpjbHVzdGVyczEgPC0ga21lYW5zKGRmMSxncnVwb3MxKQ0KY2x1c3RlcnMxDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gT3B0aW1pemFyIG7Dum1lcm8gZGUgZ3J1cG9zIA0KPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpvcHRpbWl6YWNpb24xIDwtIGNsdXNHYXAoZGYxLCBGVU49a21lYW5zLCBuc3RhcnQ9MSwgSy5tYXg9NykNCg0KcGxvdChvcHRpbWl6YWNpb24xLCB4bGFiPSJOw7ptZXJvIGRlIENsdXN0ZXJzIGsiLCBtYWluPSJPcHRpbWl6YWNpw7NuIGRlIENsdXN0ZXJzIikNCmBgYA0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBBZ3J1cGFyIGxvcyBwdW50b3MgDQo8L3NwYW4+DQpgYGB7cn0NCmZ2aXpfY2x1c3RlcihjbHVzdGVyczEsZGF0YT1kZjEpDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gQWdydXBhciBsb3MgcHVudG9zIA0KPC9zcGFuPg0KYGBge3J9DQpkZjFfY2x1c3RlcnMgPC0gY2JpbmQoZGYxLCBjbHVzdGVyID0gY2x1c3RlcnMxJGNsdXN0ZXIpDQpoZWFkKGRmMV9jbHVzdGVycykNCmBgYA0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBDb25jbHVzaW9uZXMgDQo8L3NwYW4+DQpMYSB0w6ljbmljYSBkZSAqY2x1c3RlcmluZyogcGVybWl0ZSBpZGVudGlmaWNhciBwYXRyb25lcyBvIGdydXBvcyBuYXR1cmFsZXMgZW4gbG9zIGRhdG9zIHNpbiBuZWNlc2lkYWQgZGUgZXRpcXVldGFzIC4NCg0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gRWplcmNpY28gMi4gVVNBcnJlc3RzDQo8L3NwYW4+DQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IENvbnRleHRvIA0KPC9zcGFuPg0KTGEgYmFzZSBkZSBkYXRvcyAqKlVTQXJyZXN0cyoqIGNvbnRpZW5lIGVzdGFkw61zdGljYXMgZW4gYXJyZXN0b3MgcG9yIGNhZGEgMTAwLDAwMCByZXNpZGVudGVzIHBvciBhZ3Jlc2lvbiwgYXNlc2luYXRvLCB2aW9hbGNpb24gZW4gY2FkYSB1bm8gZGUgbG9zIDUwIGVzdGFkb3MgZGUgRUUuVVUgZW4gMTk3My4NCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gT2J0ZW5lciBkYXRvcyANCjwvc3Bhbj4NCmBgYHtyfQ0KZGYyIDwtIFVTQXJyZXN0cw0KZGYyIDwtIGRmMiAlPiUgc2VsZWN0ICgtVXJiYW5Qb3ApDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gRW50ZW5kZXIgbG9zIGRhdG9zDQo8L3NwYW4+DQpgYGB7cn0NCnN1bW1hcnkoZGYyKQ0Kc3RyKGRmMikNCg0KYGBgDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IEVzY2FsYXIgZGF0b3MgDQo8L3NwYW4+DQpgYGB7cn0NCmRmMl9lc2NhbGFkb3MgPC0gc2NhbGUoZGYyKQ0Kc3VtbWFyeShkZjJfZXNjYWxhZG9zKQ0KYGBgDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IEFzaWduYXIgbG9zIHB1bnRvcyANCjwvc3Bhbj4NCmBgYHtyfQ0KZ3J1cG9zMiA8LSA1DQpgYGANCg0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBBc2lnbmFyIG7Dum1lcm8gZGUgZ3J1cG9zDQo8L3NwYW4+DQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCmNsdXN0ZXJzMiA8LSBrbWVhbnMoZGYyX2VzY2FsYWRvcyxncnVwb3MyKQ0KYGBgDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IA0KPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpvcHRpbWl6YWNpb24yIDwtIGNsdXNHYXAoZGYyX2VzY2FsYWRvcywgRlVOPWttZWFucywgbnN0YXJ0PTEsIEsubWF4PTEwKQ0KcGxvdChvcHRpbWl6YWNpb24yLCB4bGFiPSJOw7ptZXJvIGRlIGNsdXN0ZXJzIiwgbWFpbj0iT3B0aW1pemFjacOzbiBkZSBDbHVzdGVycyIpDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gR3JhZmljYXIgbG9zIGdydXBvcyANCjwvc3Bhbj4NCmBgYHtyfQ0KZnZpel9jbHVzdGVyKGNsdXN0ZXJzMixkYXRhPWRmMl9lc2NhbGFkb3MpDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gQWdyZWdhciBncnVwb3MgYSBsYSBiYXNlIGRlIGRhdG9zIA0KPC9zcGFuPg0KYGBge3J9DQpkZjJfY2x1c3RlcnMgPC0gY2JpbmQoZGYyLCBjbHVzdGVyID0gY2x1c3RlcnMyJGNsdXN0ZXIpDQpoZWFkKGRmMl9jbHVzdGVycykNCg0KZGYyX2NsdXN0ZXJzICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgc3VtbWFyaXNlX2FsbChtZWFuKSAlPiUNCiAgbXV0YXRlKGluZGljYWRvcl9pbnNlZ3VyaWRhZD1NdXJkZXIrQXNzYXVsdCtSYXBlKQ0KDQpkZjJfY2x1c3RlcnMgPC0gZGYyX2NsdXN0ZXJzICAlPiUNCiAgbXV0YXRlKGNsdXN0ZXI9IGNhc2Vfd2hlbigNCiAgICBjbHVzdGVyID09IDEgfiAiSW5zZWd1cmlkYWQgTXV5IEFsdGEiLA0KICAgIGNsdXN0ZXIgPT0gNCB+ICJJbnNlZ3VyaWRhZCBBbHRhIiwNCiAgICBjbHVzdGVyID09IDUgfiAiSW5zZWd1cmlkYWQgTWVkaWEiLA0KICAgIGNsdXN0ZXIgPT0gMyB+ICJJbnNlZ3VyaWRhZCBCYWphIiwNCiAgICBjbHVzdGVyID09IDIgfiAiSW5zZWd1cmlkYWQgTXV5IEJhamEiDQogICkpDQpoZWFkKGRmMl9jbHVzdGVycykNCmBgYA0KDQoNCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IEVqZXJjaWNpbyAzLiBTZWdtZW50YWNpw7NuIGRlIGNsaWVudGVzIA0KPC9zcGFuPg0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBDb250ZXh0byANCjwvc3Bhbj4NCkxhIGJhc2UgZGUgZGF0b3MgKip2ZW50YXMqKiB0aWVuZSBsb3MgcmVnaXN0cm9zIGVudHJlIGVsIDEgZGUgZGljaWVtYnJlIGRlIDIwMTAgeSBlbCA5IGRlIGRpY2llbWJyZSBkZSAyMDExIGRlIGxhcyB2ZW50YXMgZGUgdW5hIGVtcHJlc2EgbWlub3Jpc3RhIGVuIGzDrW5lYSBzaW4gdGllbmRhIGbDrXNpY2EsIGJhc2FkYSBlbiBSZWlubyBVbmlkby4NCkxhIGVtcHJlc2EgdmVuZGUgcHJpbmNpcGFsbWVudGUgcmVnYWxvcyB1bmljb3MgcGFyYSB0b2RhIG9jYXNpb24sIHkgbXVjaG9zIGRlIHN1cyBjbGllbnRlcyBtYXlvc2l0YXMuDQoNCk9iamV0aXZvOiBTZWdtZW50YXIgY2xpZW50ZXMsIGFzaWduYXJsZXMgbm9tYnJlIHkgY3JhYWN0ZXJpc3RpY2FzIGRlIGNvbXBvcnRhbWllbnRvLCB5IHByb3BvbmVyIHN1Z2VyZW5jaWFzIGEgbGEgZW1wcmVzYSBwYXJhIGF1bWVudGFyIHZlbnRhcy4NCg0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBDYXJnYXIgRGF0YUZyYW1lDQo8L3NwYW4+DQpgYGB7cn0NCiNmaWxlLmNob29zZSgpDQoNCmRmX3ZlbnRhcyA8LSByZWFkLmNzdigNCiAgIkM6L1VzZXJzL2VkdWFyL09uZURyaXZlL0RhdG9zIGFkanVudG9zL0RvY3VtZW50b3MvVEVDIGNsYXNlcy9TZXh0byBTZW1lc3RyZS9Nb2R1bG8gMi92ZW50YXMuY3N2IiwNCiAgZmlsZUVuY29kaW5nID0gImxhdGluMSIsDQogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KKQ0KDQpoZWFkKGRmX3ZlbnRhcykNCmBgYA0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBFbnRlbmRlciBsb3MgZGF0b3MNCjwvc3Bhbj4NCmBgYHtyfQ0Kc3VtbWFyeShkZl92ZW50YXMpDQpzdHIoZGZfdmVudGFzKQ0KbmFtZXMoZGZfdmVudGFzKQ0KDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gQ2FtYmlhciBub21icmVzIA0KPC9zcGFuPg0KYGBge3J9DQpuYW1lcyhkZl92ZW50YXMpIDwtIGMoDQogICJUaWNrZXQiLA0KICAiUHJvZHVjdG8iLA0KICAiQ2FudGlkYWQiLA0KICAiRmVjaGEiLA0KICAiSG9yYSIsDQogICJQcmVjaW8iLA0KICAiQ2xpZW50ZSIsDQogICJQYWlzIg0KKQ0KbmFtZXMoZGZfdmVudGFzKQ0KYGBgDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IExpbXBpYXIgZGF0b3MNCmBgYHtyfQ0KZGZfdmVudGFzJEZlY2hhIDwtIGFzLkRhdGUoDQogIGRmX3ZlbnRhcyRGZWNoYSwNCiAgZm9ybWF0ID0gIiVkLyVtLyVZIg0KKQ0KDQpkZl92ZW50YXMkQ2FudGlkYWQgPC0gYXMubnVtZXJpYyhkZl92ZW50YXMkQ2FudGlkYWQpDQoNCmRmX3ZlbnRhcyRQcmVjaW8gPC0gYXMubnVtZXJpYyhkZl92ZW50YXMkUHJlY2lvKQ0KDQpkZl92ZW50YXMkQ2xpZW50ZSA8LSBhcy5udW1lcmljKGRmX3ZlbnRhcyRDbGllbnRlKQ0KYGBgDQoNCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gQ3JlYXIgZWwgdmFsb3IgZGUgY2FkYSB2ZW50YQ0KPC9zcGFuPg0KYGBge3J9DQpkZl92ZW50YXMgPC0gZGZfdmVudGFzICU+JQ0KICBtdXRhdGUoDQogICAgVmVudGEgPSBDYW50aWRhZCAqIFByZWNpbw0KICApIA0KYGBgDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IEVsaW1pbmFyIGRhdG9zIHF1ZSBubyBwb2RlbW9zIHVzYXINCjwvc3Bhbj4NCmBgYHtyfQ0KZGZfdmVudGFzX2xpbXBpYSA8LSBkZl92ZW50YXMgJT4lDQogIGZpbHRlcigNCiAgICAhaXMubmEoQ2xpZW50ZSksDQogICAgQ2FudGlkYWQgPiAwLA0KICAgIFByZWNpbyA+IDANCiAgKQ0KYGBgDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IENyZWFyIGJhc2UgcG9yIGNsaWVudGUNCjwvc3Bhbj4NCmBgYHtyfQ0KZmVjaGFfcmVmZXJlbmNpYSA8LSBtYXgoZGZfdmVudGFzX2xpbXBpYSRGZWNoYSkgKyAxDQoNCmRmX2NsaWVudGVzIDwtIGRmX3ZlbnRhc19saW1waWEgJT4lDQogIGdyb3VwX2J5KENsaWVudGUpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgcmVjZW5jaWEgPSBhcy5udW1lcmljKA0KICAgICAgZmVjaGFfcmVmZXJlbmNpYSAtIG1heChGZWNoYSkNCiAgICApLA0KICAgIA0KICAgIGZyZWN1ZW5jaWEgPSBuX2Rpc3RpbmN0KFRpY2tldCksDQogICAgDQogICAgZ2FzdG9fdG90YWwgPSBzdW0oVmVudGEpLA0KICAgIA0KICAgIGNhbnRpZGFkX3Byb2R1Y3RvcyA9IHN1bShDYW50aWRhZCksDQogICAgDQogICAgdGlja2V0X3Byb21lZGlvID0gZ2FzdG9fdG90YWwgLyBmcmVjdWVuY2lhLA0KICAgIA0KICAgIHByb2R1Y3Rvc19kaXN0aW50b3MgPSBuX2Rpc3RpbmN0KFByb2R1Y3RvKQ0KICApDQoNCmBgYA0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBFbnRlbmRlciBsb3MgZGF0b3MgZGUgbG9zIGNsaWVudGVzDQo8L3NwYW4+DQpgYGB7cn0NCnN1bW1hcnkoZGZfY2xpZW50ZXMpDQpzdHIoZGZfY2xpZW50ZXMpDQpoZWFkKGRmX2NsaWVudGVzKQ0KDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gRXNjYWxhciBkYXRvcw0KPC9zcGFuPg0KYGBge3J9DQpkZl9jbGllbnRlc19sb2cgPC0gZGZfY2xpZW50ZXMgJT4lDQogIHNlbGVjdCgNCiAgICByZWNlbmNpYSwNCiAgICBmcmVjdWVuY2lhLA0KICAgIGdhc3RvX3RvdGFsLA0KICAgIGNhbnRpZGFkX3Byb2R1Y3RvcywNCiAgICB0aWNrZXRfcHJvbWVkaW8sDQogICAgcHJvZHVjdG9zX2Rpc3RpbnRvcw0KICApICU+JQ0KICBtdXRhdGUoDQogICAgYWNyb3NzKA0KICAgICAgZXZlcnl0aGluZygpLA0KICAgICAgbG9nMXANCiAgICApDQogICkNCg0KZGZfY2xpZW50ZXNfZXNjYWxhZG9zIDwtIHNjYWxlKGRmX2NsaWVudGVzX2xvZykNCg0Kc3VtbWFyeShkZl9jbGllbnRlc19lc2NhbGFkb3MpDQoNCmBgYA0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBBc2lnbmFyIGxvcyBwdW50b3MNCjwvc3Bhbj4NCmBgYHtyfQ0KZ3J1cG9zIDwtIDEwDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gQXNpZ25hciBuw7ptZXJvIGRlIGdydXBvcw0KPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQoNCmNsdXN0ZXJzIDwtIGttZWFucygNCiAgZGZfY2xpZW50ZXNfZXNjYWxhZG9zLA0KICBncnVwb3MsDQogIG5zdGFydCA9IDI1DQopDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gT3B0aW1pemFjacOzbiBkZSBjbHVzdGVycw0KPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQoNCm9wdGltaXphY2lvbiA8LSBjbHVzR2FwKA0KICBkZl9jbGllbnRlc19lc2NhbGFkb3MsDQogIEZVTiA9IGttZWFucywNCiAgbnN0YXJ0ID0gMjUsDQogIEsubWF4ID0gMTANCikNCg0KcGxvdCgNCiAgb3B0aW1pemFjaW9uLA0KICB4bGFiID0gIk7Dum1lcm8gZGUgY2x1c3RlcnMiLA0KICBtYWluID0gIk9wdGltaXphY2nDs24gZGUgQ2x1c3RlcnMiDQopDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gR3JhZmljYXIgbG9zIGdydXBvcw0KPC9zcGFuPg0KYGBge3J9DQpmdml6X2NsdXN0ZXIoDQogIGNsdXN0ZXJzLA0KICBkYXRhID0gZGZfY2xpZW50ZXNfZXNjYWxhZG9zLA0KICBtYWluID0gIlNlZ21lbnRhY2nDs24gZGUgQ2xpZW50ZXMiLA0KICB4bGFiID0gIkRpbWVuc2nDs24gMSIsDQogIHlsYWIgPSAiRGltZW5zacOzbiAyIiwNCiAgbGVnZW5kLnRpdGxlID0gIlNlZ21lbnRvIg0KKQ0KDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gQWdyZWdhciBncnVwb3MgYSBsYSBiYXNlIGRlIGRhdG9zDQo8L3NwYW4+DQpgYGB7cn0NCmRmX2NsaWVudGVzX2NsdXN0ZXJzIDwtIGNiaW5kKA0KICBkZl9jbGllbnRlcywNCiAgY2x1c3RlciA9IGNsdXN0ZXJzJGNsdXN0ZXINCikNCg0KaGVhZChkZl9jbGllbnRlc19jbHVzdGVycykNCmBgYA0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBBbmFsaXphciBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZSBjYWRhIGNsdXN0ZXINCjwvc3Bhbj4NCmBgYHtyfQ0KZGZfY2xpZW50ZXNfY2x1c3RlcnMgJT4lDQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgY2xpZW50ZXMgPSBuKCksDQogICAgcmVjZW5jaWEgPSBtZWFuKHJlY2VuY2lhKSwNCiAgICBmcmVjdWVuY2lhID0gbWVhbihmcmVjdWVuY2lhKSwNCiAgICBnYXN0b190b3RhbCA9IG1lYW4oZ2FzdG9fdG90YWwpLA0KICAgIGNhbnRpZGFkX3Byb2R1Y3RvcyA9IG1lYW4oY2FudGlkYWRfcHJvZHVjdG9zKSwNCiAgICB0aWNrZXRfcHJvbWVkaW8gPSBtZWFuKHRpY2tldF9wcm9tZWRpbyksDQogICAgcHJvZHVjdG9zX2Rpc3RpbnRvcyA9IG1lYW4ocHJvZHVjdG9zX2Rpc3RpbnRvcykNCiAgKQ0KYGBgDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+IENyZWFyIGluZGljYWRvciBkZSB2YWxvciBkZWwgY2xpZW50ZQ0KPC9zcGFuPg0KYGBge3J9DQpkZl9jbGllbnRlc19jbHVzdGVycyA8LSBkZl9jbGllbnRlc19jbHVzdGVycyAlPiUNCiAgbXV0YXRlKA0KICAgIGluZGljYWRvcl92YWxvciA9DQogICAgICBnYXN0b190b3RhbCArDQogICAgICBmcmVjdWVuY2lhICogMTAwICsNCiAgICAgIHRpY2tldF9wcm9tZWRpbw0KICApDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gQ29tcGFyYXIgZWwgdmFsb3IgZGUgY2FkYSBjbHVzdGVyDQo8L3NwYW4+DQpgYGB7cn0NCmRmX2NsaWVudGVzX2NsdXN0ZXJzICU+JQ0KICBncm91cF9ieShjbHVzdGVyKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIGNsaWVudGVzID0gbigpLA0KICAgIHJlY2VuY2lhID0gbWVhbihyZWNlbmNpYSksDQogICAgZnJlY3VlbmNpYSA9IG1lYW4oZnJlY3VlbmNpYSksDQogICAgZ2FzdG9fdG90YWwgPSBtZWFuKGdhc3RvX3RvdGFsKSwNCiAgICB0aWNrZXRfcHJvbWVkaW8gPSBtZWFuKHRpY2tldF9wcm9tZWRpbyksDQogICAgaW5kaWNhZG9yX3ZhbG9yID0gbWVhbihpbmRpY2Fkb3JfdmFsb3IpDQogICkNCmBgYA0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBBc2lnbmFyIG5vbWJyZSBhIGxvcyBncnVwb3MNCjwvc3Bhbj4NCmBgYHtyfQ0KZGZfY2xpZW50ZXNfY2x1c3RlcnMgPC0gZGZfY2xpZW50ZXNfY2x1c3RlcnMgJT4lDQogIG11dGF0ZSgNCiAgICBzZWdtZW50byA9IGNhc2Vfd2hlbigNCiAgICAgIGNsdXN0ZXIgPT0gMSB+ICJDbGllbnRlcyBJbmFjdGl2b3MiLA0KICAgICAgY2x1c3RlciA9PSAyIH4gIkNsaWVudGVzIGRlIE11eSBCYWpvIFZhbG9yIiwNCiAgICAgIGNsdXN0ZXIgPT0gMyB+ICJDbGllbnRlcyBWSVAiLA0KICAgICAgY2x1c3RlciA9PSA0IH4gIkNsaWVudGVzIGRlIEFsdG8gVGlja2V0IiwNCiAgICAgIGNsdXN0ZXIgPT0gNSB+ICJDbGllbnRlcyBQcmVtaXVtIEZyZWN1ZW50ZXMiLA0KICAgICAgY2x1c3RlciA9PSA2IH4gIkNsaWVudGVzIEZyZWN1ZW50ZXMiLA0KICAgICAgY2x1c3RlciA9PSA3IH4gIkNsaWVudGVzIE9jYXNpb25hbGVzIiwNCiAgICAgIGNsdXN0ZXIgPT0gOCB+ICJDbGllbnRlcyBBY3Rpdm9zIiwNCiAgICAgIGNsdXN0ZXIgPT0gOSB+ICJDbGllbnRlcyBPY2FzaW9uYWxlcyBSZWNpZW50ZXMiLA0KICAgICAgY2x1c3RlciA9PSAxMCB+ICJDbGllbnRlcyBJbmFjdGl2b3MgZGUgQmFqbyBWYWxvciINCiAgICApDQogICkNCmBgYA0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBWZXIgbG9zIHNlZ21lbnRvcyBmaW5hbGVzDQo8L3NwYW4+DQpgYGB7cn0NCmhlYWQoZGZfY2xpZW50ZXNfY2x1c3RlcnMpDQpgYGANCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4gQ2FudGlkYWQgZGUgY2xpZW50ZXMgcG9yIHNlZ21lbnRvDQo8L3NwYW4+DQpgYGB7cn0NCmRmX2NsaWVudGVzX2NsdXN0ZXJzICU+JQ0KICBjb3VudChzZWdtZW50bykNCmBgYA0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPiBQZXJmaWwgZGUgY2FkYSBzZWdtZW50bw0KPC9zcGFuPg0KYGBge3J9DQpkZl9jbGllbnRlc19jbHVzdGVycyAlPiUNCiAgZ3JvdXBfYnkoc2VnbWVudG8pICU+JQ0KICBzdW1tYXJpc2UoDQogICAgY2xpZW50ZXMgPSBuKCksDQogICAgcmVjZW5jaWFfcHJvbWVkaW8gPSBtZWFuKHJlY2VuY2lhKSwNCiAgICBmcmVjdWVuY2lhX3Byb21lZGlvID0gbWVhbihmcmVjdWVuY2lhKSwNCiAgICBnYXN0b19wcm9tZWRpbyA9IG1lYW4oZ2FzdG9fdG90YWwpLA0KICAgIHRpY2tldF9wcm9tZWRpbyA9IG1lYW4odGlja2V0X3Byb21lZGlvKSwNCiAgICBwcm9kdWN0b3NfcHJvbWVkaW8gPSBtZWFuKHByb2R1Y3Rvc19kaXN0aW50b3MpDQogICkNCmBgYA0KDQoNCg0KDQoNCg0KDQo=