Según wikipedia.org, “RFM es un método utilizado para analizar el valor del cliente”.
RFM representa las tres dimensiones:
- Actividad reciente: ¿Qué tan reciente fue la compra del cliente?
- Frecuencia: ¿con qué frecuencia compran?
- Valor monetario - ¿Cuánto gastan?
Los segmentos resultantes se pueden ordenar desde los más valiosos (mayor antigüedad, frecuencia y valor) hasta los menos valiosos (menor antigüedad, frecuencia y valor). La identificación de los segmentos de RFM más valiosos puede aprovechar las relaciones fortuitas en los datos utilizados para este análisis.
library(tidyverse)
library(skimr)
library(data.table)
library(knitr)
library(rmarkdown)
Lectura de datos
df <-
readr::read_csv(file = 'C:/Users/HUAWEI/Downloads/data.csv/data.csv',
locale = readr::locale(encoding = 'UTF-8'))
dplyr::glimpse(df)
## Rows: 541,909
## Columns: 8
## $ InvoiceNo <chr> "536365", "536365", "536365", "536365", "536365", "536365"~
## $ StockCode <chr> "85123A", "71053", "84406B", "84029G", "84029E", "22752", ~
## $ Description <chr> "WHITE HANGING HEART T-LIGHT HOLDER", "WHITE METAL LANTERN~
## $ Quantity <dbl> 6, 6, 8, 6, 6, 2, 6, 6, 6, 32, 6, 6, 8, 6, 6, 3, 2, 3, 3, ~
## $ InvoiceDate <chr> "12/1/2010 8:26", "12/1/2010 8:26", "12/1/2010 8:26", "12/~
## $ UnitPrice <dbl> 2.55, 3.39, 2.75, 3.39, 3.39, 7.65, 4.25, 1.85, 1.85, 1.69~
## $ CustomerID <dbl> 17850, 17850, 17850, 17850, 17850, 17850, 17850, 17850, 17~
## $ Country <chr> "United Kingdom", "United Kingdom", "United Kingdom", "Uni~
Tratamiento de los datos
Se remueven las cantiades negativas y consumidores con NA asignados.
# Remocion de negativos y NA's
df1 <-
df %>%
dplyr::filter(Quantity > 0 ,
UnitPrice > 0) %>%
tidyr::drop_na()
head(df1, n = 10)
Recodificacion de variables
df2 <-
df1 %>%
dplyr::mutate(InvoiceDate = as.Date(x = InvoiceDate, format = '%m/%d/%Y')) %>%
dplyr::mutate_if(is.character, as.factor) %>%
dplyr::mutate(total_dolar = Quantity*UnitPrice)
skimr::skim(df2)
Data summary
Name |
df2 |
Number of rows |
397884 |
Number of columns |
9 |
_______________________ |
|
Column type frequency: |
|
Date |
1 |
factor |
4 |
numeric |
4 |
________________________ |
|
Group variables |
None |
Variable type: Date
InvoiceDate |
0 |
1 |
2010-12-01 |
2011-12-09 |
2011-07-31 |
305 |
Variable type: factor
InvoiceNo |
0 |
1 |
FALSE |
18532 |
576: 542, 579: 533, 580: 529, 578: 442 |
StockCode |
0 |
1 |
FALSE |
3665 |
851: 2035, 224: 1723, 850: 1618, 848: 1408 |
Description |
0 |
1 |
FALSE |
3866 |
WHI: 2028, REG: 1723, JUM: 1618, ASS: 1408 |
Country |
0 |
1 |
FALSE |
37 |
Uni: 354321, Ger: 9040, Fra: 8341, EIR: 7236 |
Variable type: numeric
Quantity |
0 |
1 |
12.99 |
179.33 |
1 |
2.00 |
6.00 |
12.00 |
80995.00 |
▇▁▁▁▁ |
UnitPrice |
0 |
1 |
3.12 |
22.10 |
0 |
1.25 |
1.95 |
3.75 |
8142.75 |
▇▁▁▁▁ |
CustomerID |
0 |
1 |
15294.42 |
1713.14 |
12346 |
13969.00 |
15159.00 |
16795.00 |
18287.00 |
▇▇▇▇▇ |
total_dolar |
0 |
1 |
22.40 |
309.07 |
0 |
4.68 |
11.80 |
19.80 |
168469.60 |
▇▁▁▁▁ |
Calcular el conjunto de datos de RFM
Para implementar el análisis de RFM, necesitamos seguir procesando el conjunto de datos mediante los siguientes pasos:
- Busque la fecha más reciente para cada ID y calcule los días hasta la fecha actual o alguna otra para obtener los datos de antigüedad.
- Calcule la cantidad de traducciones de un cliente para obtener los datos de frecuencia.
- Sume la cantidad de dinero que gastó un cliente y divídala por Frecuencia, para obtener la cantidad por transacción en promedio, que son los datos monetarios.
df_RFM <-
df2 %>%
dplyr::group_by(CustomerID) %>%
dplyr::summarise(Recency = Sys.Date() - max(InvoiceDate),
Recency = as.numeric(Recency),
frequenci = dplyr::n_distinct(InvoiceNo),
monitery = sum(total_dolar)/frequenci) %>%
dplyr::ungroup()
summary(df_RFM)
kableExtra::kable(head(df_RFM))
## CustomerID Recency frequenci monitery
## Min. :12346 Min. :3423 Min. : 1.000 Min. : 3.45
## 1st Qu.:13813 1st Qu.:3440 1st Qu.: 1.000 1st Qu.: 178.62
## Median :15300 Median :3473 Median : 2.000 Median : 293.90
## Mean :15300 Mean :3515 Mean : 4.272 Mean : 419.17
## 3rd Qu.:16779 3rd Qu.:3565 3rd Qu.: 5.000 3rd Qu.: 430.11
## Max. :18287 Max. :3796 Max. :209.000 Max. :84236.25
CustomerID
|
Recency
|
frequenci
|
monitery
|
12346
|
3748
|
1
|
77183.6000
|
12347
|
3425
|
7
|
615.7143
|
12348
|
3498
|
4
|
449.3100
|
12349
|
3441
|
1
|
1757.5500
|
12350
|
3733
|
1
|
334.4000
|
12352
|
3459
|
8
|
313.2550
|
Antigüedad
Experiencia reciente: ¿Qué tan reciente fue la compra del cliente?
df_RFM %>%
dplyr::filter(monitery < 7000,
frequenci < 200) %>%
ggplot2::ggplot() +
ggplot2::aes(Recency) +
ggplot2::geom_histogram(col = 'red', fill = 'skyblue', bins = 50) +
ggplot2::theme_minimal() +
ggplot2::theme(text = ggplot2::element_text(family = 'serif'))

Frecuencia
Frecuencia: ¿con qué frecuencia compran?
df_RFM %>%
dplyr::filter(monitery < 7000,
frequenci < 200) %>%
ggplot2::ggplot() +
ggplot2::aes(frequenci) +
ggplot2::geom_histogram(col = 'red', fill = 'skyblue', bins = 50) +
ggplot2::theme_minimal() +
ggplot2::theme(text = ggplot2::element_text(family = 'serif'))

Monetario
Valor monetario: ¿cuánto gastan?
df_RFM %>%
dplyr::filter(monitery < 7000,
frequenci < 200) %>%
ggplot2::ggplot() +
ggplot2::aes(monitery) +
ggplot2::geom_histogram(col = 'red', fill = 'skyblue', bins = 50) +
ggplot2::theme_minimal() +
ggplot2::theme(text = ggplot2::element_text(family = 'serif'))

Debido a que los datos están realmente sesgados, usamos la escala logarítmica para normalizar.
df_RFM$monitery <- log(df_RFM$monitery)
df_RFM %>%
dplyr::filter(monitery < 7000,
frequenci < 200) %>%
ggplot2::ggplot() +
ggplot2::aes(monitery) +
ggplot2::geom_histogram(col = 'red', fill = 'skyblue', bins = 50) +
ggplot2::theme_minimal() +
ggplot2::theme(text = ggplot2::element_text(family = 'serif'))

RFM Analisis
rfm_result <-
rfm::rfm_table_customer(df_RFM,
customer_id = CustomerID,
recency_days = Recency,
n_transactions = frequenci,
total_revenue = monitery)
Heat Map
rfm::rfm_heatmap(rfm_result)

Bar Chart
rfm::rfm_bar_chart(rfm_result)

Histogram
rfm::rfm_histograms(rfm_result)

Customers by Orders
rfm::rfm_order_dist(rfm_result)

Refinamiento del analisis
Segmentacion del cliente:
ggplot(data = BDF) + aes(x = cus_seg, fill = cus_seg)+ geom_bar() +
geom_text(aes(label=scales::percent(..count../sum(..count..))),
stat='count',position=position_dodge(1))+
labs(title = "Segmentacion del Cliente", x = "Segmento", y = "Total de clientes") + coord_flip()+ theme_minimal()

LS0tDQp0aXRsZTogIlNlZ21lbnRhY2lvbiBkZSBjb25zdW1pZG9yZXMgdXNhbmRvIFJGTSINCmF1dGhvcjogJ0dydXBvIDUnDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiDQpvdXRwdXQ6IA0KICAgIGh0bWxfZG9jdW1lbnQ6DQogICAgICAgIGRmX3ByaW50OiBwYWdlZA0KICAgICAgICBmaWdfaGVpZ2h0OiA0DQogICAgICAgIGZpZ193aWR0aDogNw0KICAgICAgICB0b2M6IHRydWUNCiAgICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgICAgICBjb2RlX2ZvbGRpbmc6ICJzaG93Ig0KLS0tDQoNClNlZ8O6biB3aWtpcGVkaWEub3JnLCAiUkZNIGVzIHVuIG3DqXRvZG8gdXRpbGl6YWRvIHBhcmEgYW5hbGl6YXIgZWwgdmFsb3IgZGVsIGNsaWVudGUiLg0KDQpSRk0gcmVwcmVzZW50YSBsYXMgdHJlcyBkaW1lbnNpb25lczoNCg0KKiBBY3RpdmlkYWQgcmVjaWVudGU6IMK/UXXDqSB0YW4gcmVjaWVudGUgZnVlIGxhIGNvbXByYSBkZWwgY2xpZW50ZT8NCiogRnJlY3VlbmNpYTogwr9jb24gcXXDqSBmcmVjdWVuY2lhIGNvbXByYW4/DQoqIFZhbG9yIG1vbmV0YXJpbyAtIMK/Q3XDoW50byBnYXN0YW4/DQoNCkxvcyBzZWdtZW50b3MgcmVzdWx0YW50ZXMgc2UgcHVlZGVuIG9yZGVuYXIgZGVzZGUgbG9zIG3DoXMgdmFsaW9zb3MgKG1heW9yIGFudGlnw7xlZGFkLCBmcmVjdWVuY2lhIHkgdmFsb3IpIGhhc3RhIGxvcyBtZW5vcyB2YWxpb3NvcyAobWVub3IgYW50aWfDvGVkYWQsIGZyZWN1ZW5jaWEgeSB2YWxvcikuIExhIGlkZW50aWZpY2FjacOzbiBkZSBsb3Mgc2VnbWVudG9zIGRlIFJGTSBtw6FzIHZhbGlvc29zIHB1ZWRlIGFwcm92ZWNoYXIgbGFzIHJlbGFjaW9uZXMgZm9ydHVpdGFzIGVuIGxvcyBkYXRvcyB1dGlsaXphZG9zIHBhcmEgZXN0ZSBhbsOhbGlzaXMuDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0UsIG1lc3NhZ2U9RiwgZXJyb3I9Riwgd2FybmluZz1GfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShza2ltcikNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KHJtYXJrZG93bikNCg0KYGBgDQoNCiMjIExlY3R1cmEgZGUgZGF0b3MNCg0KYGBge3IsIHJlc3VsdHM9J21hcmt1cCcsIG1lc3NhZ2U9RkFMU0V9DQoNCmRmIDwtIA0KICByZWFkcjo6cmVhZF9jc3YoZmlsZSA9ICdDOi9Vc2Vycy9IVUFXRUkvRG93bmxvYWRzL2RhdGEuY3N2L2RhdGEuY3N2JywgDQogICAgICAgICAgICAgICAgICBsb2NhbGUgPSByZWFkcjo6bG9jYWxlKGVuY29kaW5nID0gJ1VURi04JykpDQoNCmRwbHlyOjpnbGltcHNlKGRmKQ0KDQpgYGANCg0KIyMgVHJhdGFtaWVudG8gZGUgbG9zIGRhdG9zDQpTZSByZW11ZXZlbiBsYXMgY2FudGlhZGVzIG5lZ2F0aXZhcyB5IGNvbnN1bWlkb3JlcyBjb24gTkEgYXNpZ25hZG9zLg0KDQpgYGB7cn0NCiMgUmVtb2Npb24gZGUgbmVnYXRpdm9zIHkgTkEncw0KZGYxIDwtIA0KICBkZiAlPiUgDQogIGRwbHlyOjpmaWx0ZXIoUXVhbnRpdHkgPiAwICwgDQogICAgICAgICAgICAgICAgVW5pdFByaWNlID4gMCkgJT4lIA0KICB0aWR5cjo6ZHJvcF9uYSgpDQoNCmhlYWQoZGYxLCBuID0gMTApDQoNCmBgYA0KDQojIyBSZWNvZGlmaWNhY2lvbiBkZSB2YXJpYWJsZXMNCg0KYGBge3J9DQpkZjIgPC0gDQogIGRmMSAlPiUgDQogIGRwbHlyOjptdXRhdGUoSW52b2ljZURhdGUgPSBhcy5EYXRlKHggPSBJbnZvaWNlRGF0ZSwgZm9ybWF0ID0gJyVtLyVkLyVZJykpICU+JSANCiAgZHBseXI6Om11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikgJT4lIA0KICBkcGx5cjo6bXV0YXRlKHRvdGFsX2RvbGFyID0gUXVhbnRpdHkqVW5pdFByaWNlKQ0KDQpza2ltcjo6c2tpbShkZjIpDQoNCmBgYA0KDQojIyBDYWxjdWxhciBlbCBjb25qdW50byBkZSBkYXRvcyBkZSBSRk0gey50YWJzZXR9DQpQYXJhIGltcGxlbWVudGFyIGVsIGFuw6FsaXNpcyBkZSBSRk0sIG5lY2VzaXRhbW9zIHNlZ3VpciBwcm9jZXNhbmRvIGVsIGNvbmp1bnRvIGRlIGRhdG9zIG1lZGlhbnRlIGxvcyBzaWd1aWVudGVzIHBhc29zOg0KDQoxLiBCdXNxdWUgbGEgZmVjaGEgbcOhcyByZWNpZW50ZSBwYXJhIGNhZGEgSUQgeSBjYWxjdWxlIGxvcyBkw61hcyBoYXN0YSBsYSBmZWNoYSBhY3R1YWwgbyBhbGd1bmEgb3RyYSBwYXJhIG9idGVuZXIgbG9zIGRhdG9zIGRlIGFudGlnw7xlZGFkLg0KMi4gQ2FsY3VsZSBsYSBjYW50aWRhZCBkZSB0cmFkdWNjaW9uZXMgZGUgdW4gY2xpZW50ZSBwYXJhIG9idGVuZXIgbG9zIGRhdG9zIGRlIGZyZWN1ZW5jaWEuDQozLiBTdW1lIGxhIGNhbnRpZGFkIGRlIGRpbmVybyBxdWUgZ2FzdMOzIHVuIGNsaWVudGUgeSBkaXbDrWRhbGEgcG9yIEZyZWN1ZW5jaWEsIHBhcmEgb2J0ZW5lciBsYSBjYW50aWRhZCBwb3IgdHJhbnNhY2Npw7NuIGVuIHByb21lZGlvLCBxdWUgc29uIGxvcyBkYXRvcyBtb25ldGFyaW9zLg0KDQpgYGB7ciByZXN1bHRzPSdob2xkJ30NCg0KZGZfUkZNIDwtIA0KICBkZjIgJT4lIA0KICBkcGx5cjo6Z3JvdXBfYnkoQ3VzdG9tZXJJRCkgJT4lIA0KICBkcGx5cjo6c3VtbWFyaXNlKFJlY2VuY3kgPSBTeXMuRGF0ZSgpIC0gbWF4KEludm9pY2VEYXRlKSwgDQogICAgICAgICAgICAgICAgICAgUmVjZW5jeSA9IGFzLm51bWVyaWMoUmVjZW5jeSksIA0KICAgICAgICAgICAgICAgICAgIGZyZXF1ZW5jaSA9IGRwbHlyOjpuX2Rpc3RpbmN0KEludm9pY2VObyksIA0KICAgICAgICAgICAgICAgICAgIG1vbml0ZXJ5ID0gc3VtKHRvdGFsX2RvbGFyKS9mcmVxdWVuY2kpICU+JSANCiAgZHBseXI6OnVuZ3JvdXAoKQ0KDQpzdW1tYXJ5KGRmX1JGTSkNCg0Ka2FibGVFeHRyYTo6a2FibGUoaGVhZChkZl9SRk0pKQ0KDQpgYGANCg0KIyMjIEFudGlnw7xlZGFkDQpFeHBlcmllbmNpYSByZWNpZW50ZTogwr9RdcOpIHRhbiByZWNpZW50ZSBmdWUgbGEgY29tcHJhIGRlbCBjbGllbnRlPw0KDQpgYGB7cn0NCg0KZGZfUkZNICU+JSANCiAgZHBseXI6OmZpbHRlcihtb25pdGVyeSA8IDcwMDAsIA0KICAgICAgICAgICAgICAgIGZyZXF1ZW5jaSA8IDIwMCkgJT4lIA0KICBnZ3Bsb3QyOjpnZ3Bsb3QoKSArIA0KICBnZ3Bsb3QyOjphZXMoUmVjZW5jeSkgKyANCiAgZ2dwbG90Mjo6Z2VvbV9oaXN0b2dyYW0oY29sID0gJ3JlZCcsIGZpbGwgPSAnc2t5Ymx1ZScsIGJpbnMgPSA1MCkgKyANCiAgZ2dwbG90Mjo6dGhlbWVfbWluaW1hbCgpICsgDQogIGdncGxvdDI6OnRoZW1lKHRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoZmFtaWx5ID0gJ3NlcmlmJykpDQoNCmBgYA0KDQojIyMgRnJlY3VlbmNpYQ0KRnJlY3VlbmNpYTogwr9jb24gcXXDqSBmcmVjdWVuY2lhIGNvbXByYW4/DQoNCmBgYHtyfSANCg0KZGZfUkZNICU+JSANCiAgZHBseXI6OmZpbHRlcihtb25pdGVyeSA8IDcwMDAsIA0KICAgICAgICAgICAgICAgIGZyZXF1ZW5jaSA8IDIwMCkgJT4lIA0KICBnZ3Bsb3QyOjpnZ3Bsb3QoKSArIA0KICBnZ3Bsb3QyOjphZXMoZnJlcXVlbmNpKSArIA0KICBnZ3Bsb3QyOjpnZW9tX2hpc3RvZ3JhbShjb2wgPSAncmVkJywgZmlsbCA9ICdza3libHVlJywgYmlucyA9IDUwKSArIA0KICBnZ3Bsb3QyOjp0aGVtZV9taW5pbWFsKCkgKyANCiAgZ2dwbG90Mjo6dGhlbWUodGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChmYW1pbHkgPSAnc2VyaWYnKSkNCg0KYGBgDQoNCiMjIyBNb25ldGFyaW8NClZhbG9yIG1vbmV0YXJpbzogwr9jdcOhbnRvIGdhc3Rhbj8NCg0KYGBge3J9IA0KDQpkZl9SRk0gJT4lIA0KICBkcGx5cjo6ZmlsdGVyKG1vbml0ZXJ5IDwgNzAwMCwgDQogICAgICAgICAgICAgICAgZnJlcXVlbmNpIDwgMjAwKSAlPiUgDQogIGdncGxvdDI6OmdncGxvdCgpICsgDQogIGdncGxvdDI6OmFlcyhtb25pdGVyeSkgKyANCiAgZ2dwbG90Mjo6Z2VvbV9oaXN0b2dyYW0oY29sID0gJ3JlZCcsIGZpbGwgPSAnc2t5Ymx1ZScsIGJpbnMgPSA1MCkgKyANCiAgZ2dwbG90Mjo6dGhlbWVfbWluaW1hbCgpICsgDQogIGdncGxvdDI6OnRoZW1lKHRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoZmFtaWx5ID0gJ3NlcmlmJykpDQoNCmBgYA0KDQpEZWJpZG8gYSBxdWUgbG9zIGRhdG9zIGVzdMOhbiByZWFsbWVudGUgc2VzZ2Fkb3MsIHVzYW1vcyBsYSBlc2NhbGEgbG9nYXLDrXRtaWNhIHBhcmEgbm9ybWFsaXphci4NCg0KYGBge3J9DQoNCmRmX1JGTSRtb25pdGVyeSA8LSBsb2coZGZfUkZNJG1vbml0ZXJ5KQ0KDQpkZl9SRk0gJT4lIA0KICBkcGx5cjo6ZmlsdGVyKG1vbml0ZXJ5IDwgNzAwMCwgDQogICAgICAgICAgICAgICAgZnJlcXVlbmNpIDwgMjAwKSAlPiUgDQogIGdncGxvdDI6OmdncGxvdCgpICsgDQogIGdncGxvdDI6OmFlcyhtb25pdGVyeSkgKyANCiAgZ2dwbG90Mjo6Z2VvbV9oaXN0b2dyYW0oY29sID0gJ3JlZCcsIGZpbGwgPSAnc2t5Ymx1ZScsIGJpbnMgPSA1MCkgKyANCiAgZ2dwbG90Mjo6dGhlbWVfbWluaW1hbCgpICsgDQogIGdncGxvdDI6OnRoZW1lKHRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoZmFtaWx5ID0gJ3NlcmlmJykpDQpgYGANCg0KIyMgUkZNIEFuYWxpc2lzIHsudGFic2V0fQ0KDQpgYGB7cn0NCnJmbV9yZXN1bHQgPC0gDQogIHJmbTo6cmZtX3RhYmxlX2N1c3RvbWVyKGRmX1JGTSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGN1c3RvbWVyX2lkID0gQ3VzdG9tZXJJRCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjZW5jeV9kYXlzID0gUmVjZW5jeSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbl90cmFuc2FjdGlvbnMgPSBmcmVxdWVuY2ksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX3JldmVudWUgPSBtb25pdGVyeSkNCg0KYGBgDQoNCiMjIyBIZWF0IE1hcA0KYGBge3J9DQpyZm06OnJmbV9oZWF0bWFwKHJmbV9yZXN1bHQpDQpgYGANCg0KIyMjIEJhciBDaGFydA0KYGBge3J9DQpyZm06OnJmbV9iYXJfY2hhcnQocmZtX3Jlc3VsdCkNCmBgYA0KDQojIyMgSGlzdG9ncmFtDQpgYGB7cn0NCnJmbTo6cmZtX2hpc3RvZ3JhbXMocmZtX3Jlc3VsdCkNCmBgYA0KDQojIyMgQ3VzdG9tZXJzIGJ5IE9yZGVycw0KYGBge3J9DQpyZm06OnJmbV9vcmRlcl9kaXN0KHJmbV9yZXN1bHQpDQoNCmBgYA0KDQojIyBSZWZpbmFtaWVudG8gZGVsIGFuYWxpc2lzDQoNCmBgYHtyIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KHNxbGRmKQ0KDQpCREYgPC0gYXMuZGF0YS5mcmFtZShyZm1fcmVzdWx0JHJmbSkNCkJERiA8LSANCiAgcmVzaGFwZTo6cmVuYW1lKEJERiwgDQogICAgICAgICAgICAgICAgICBjKHJlY2VuY3lfc2NvcmUgPSAicmZtX3JlY2VuY3kiLCANCiAgICAgICAgICAgICAgICAgICAgZnJlcXVlbmN5X3Njb3JlID0gInJmbV9mcmVxIiwgDQogICAgICAgICAgICAgICAgICAgIG1vbmV0YXJ5X3Njb3JlID0gInJmbV9tb25ldGFyeSIpKQ0KDQpCREYgPC0gc3FsZGYoInNlbGVjdA0KY3VzdG9tZXJfaWQscmVjZW5jeV9kYXlzLHRyYW5zYWN0aW9uX2NvdW50LGFtb3VudCxyZm1fcmVjZW5jeSxyZm1fZnJlcSxyZm1fbW9uZXRhcnkscmZtX3Njb3JlLA0KY2FzZSANCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gNCBhbmQgNSkgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDQgYW5kIDUgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDQgYW5kIDUpIHRoZW4gJ0NhbXBlb25lcycNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMiBhbmQgNSkgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDIgYW5kIDUgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDIgYW5kIDUpIHRoZW4gJ0NsaWVudGVzIGxlYWxlcycNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMyBhbmQgNSkgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDEgYW5kIDMgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDEgYW5kIDMpIHRoZW4gJ0xlYWwgcG90ZW5jaWFsJw0Kd2hlbiAocmZtX3JlY2VuY3kgYmV0d2VlbiA0IGFuZCA1KSBhbmQgKHJmbV9mcmVxIGJldHdlZW4gMCBhbmQgMSBhbmQgcmZtX21vbmV0YXJ5IGJldHdlZW4gMCBhbmQgMSkgdGhlbiAnQ2xpZW50ZXMgcmVjaWVudGVzJw0Kd2hlbiAocmZtX3JlY2VuY3kgYmV0d2VlbiAzIGFuZCA0KSBhbmQgKHJmbV9mcmVxIGJldHdlZW4gMCBhbmQgMSBhbmQgcmZtX21vbmV0YXJ5IGJldHdlZW4gMCBhbmQgMSkgdGhlbiAnUHJvbWV0ZWRvcicNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMiBhbmQgMykgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDIgYW5kIDMgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDIgYW5kIDMpIHRoZW4gJ0NsaWVudGVzIHF1ZSBuZWNlc2l0YW4gYXRlbmNpb24nDQp3aGVuIChyZm1fcmVjZW5jeSBiZXR3ZWVuIDIgYW5kIDMpIGFuZCAocmZtX2ZyZXEgYmV0d2VlbiAwIGFuZCAyIGFuZCByZm1fbW9uZXRhcnkgYmV0d2VlbiAwIGFuZCAyKSB0aGVuICdBIHB1bnRvIGRlIGRvcm1pcicNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMCBhbmQgMikgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDIgYW5kIDUgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDIgYW5kIDUpIHRoZW4gJ0VuIHJpZXNnbycNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMCBhbmQgMSkgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDQgYW5kIDUgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDQgYW5kIDUpIHRoZW4gJ05vIHB1ZWRvIFBlcmRlcmxvcycNCndoZW4gKHJmbV9yZWNlbmN5IGJldHdlZW4gMSBhbmQgMikgYW5kIChyZm1fZnJlcSBiZXR3ZWVuIDEgYW5kIDIgYW5kIHJmbV9tb25ldGFyeSBiZXR3ZWVuIDEgYW5kIDIpIHRoZW4gJ0hpYmVybmFuZG8nDQp3aGVuIChyZm1fcmVjZW5jeSBiZXR3ZWVuIDAgYW5kIDIpIGFuZCAocmZtX2ZyZXEgYmV0d2VlbiAwIGFuZCAyIGFuZCByZm1fbW9uZXRhcnkgYmV0d2VlbiAwIGFuZCAyKSB0aGVuICdQZXJkaWRvJw0KZWxzZSAnb3RoZXInIGVuZCBhcyBjdXNfc2VnDQpmcm9tIEJERiIpDQpgYGANCg0KDQoNCiMjIyBTZWdtZW50YWNpb24gZGVsIGNsaWVudGU6IA0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gQkRGKSArIGFlcyh4ID0gY3VzX3NlZywgZmlsbCA9IGN1c19zZWcpKyBnZW9tX2JhcigpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1zY2FsZXM6OnBlcmNlbnQoLi5jb3VudC4uL3N1bSguLmNvdW50Li4pKSksDQogICAgICAgICAgICBzdGF0PSdjb3VudCcscG9zaXRpb249cG9zaXRpb25fZG9kZ2UoMSkpKw0KICBsYWJzKHRpdGxlID0gIlNlZ21lbnRhY2lvbiBkZWwgQ2xpZW50ZSIsIHggPSAiU2VnbWVudG8iLCB5ID0gIlRvdGFsIGRlIGNsaWVudGVzIikgKyBjb29yZF9mbGlwKCkrIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg==