主要議題:依顧客屬性做市場區隔

學習重點:

集群分析與預測分析有很大的差異

rm(list=ls(all=T))
Sys.setlocale("LC_ALL","C")
[1] "C/C/C/C/C/en_US.UTF-8"
options(digits=4, scipen=12)
library(dplyr)
library(caret)



1. 資料常態化

1.1 資料摘要

Read the dataset AirlinesCluster.csv into R and call it “airlines”. Looking at the summary of airlines,

A = read.csv("AirlinesCluster.csv")
summary(A)
    Balance          QualMiles       BonusMiles       BonusTrans    FlightMiles   
 Min.   :      0   Min.   :    0   Min.   :     0   Min.   : 0.0   Min.   :    0  
 1st Qu.:  18528   1st Qu.:    0   1st Qu.:  1250   1st Qu.: 3.0   1st Qu.:    0  
 Median :  43097   Median :    0   Median :  7171   Median :12.0   Median :    0  
 Mean   :  73601   Mean   :  144   Mean   : 17145   Mean   :11.6   Mean   :  460  
 3rd Qu.:  92404   3rd Qu.:    0   3rd Qu.: 23800   3rd Qu.:17.0   3rd Qu.:  311  
 Max.   :1704838   Max.   :11148   Max.   :263685   Max.   :86.0   Max.   :30817  
  FlightTrans    DaysSinceEnroll
 Min.   : 0.00   Min.   :   2   
 1st Qu.: 0.00   1st Qu.:2330   
 Median : 0.00   Median :4096   
 Mean   : 1.37   Mean   :4119   
 3rd Qu.: 1.00   3rd Qu.:5790   
 Max.   :53.00   Max.   :8296   
colMeans(A) %>% sort
    FlightTrans      BonusTrans       QualMiles     FlightMiles DaysSinceEnroll 
          1.374          11.602         144.115         460.056        4118.559 
     BonusMiles         Balance 
      17144.846       73601.328 

colMeans()計算逐行平均值

which TWO variables have (on average) the smallest values?

  • BonusTrans
  • FlightTrans

Which TWO variables have (on average) the largest values?

  • Balance
  • BonusMiles
1.2 為甚麼要做資料常態化

In this problem, we will normalize our data before we run the clustering algorithms.

Why is it important to normalize the data before clustering?

  • If we don’t nor malize the data, the clustering will be dominated by the variables that are on a larger scale
  • 因為不對數據做標準化動作,集群將受到更大規模的變量影響。
1.3 使用caret套件做資料常態化

Let’s go ahead and normalize our data. You can normalize the variables in a data frame by using the preProcess function in the “caret” package. You should already have this package installed from Week 4, but if not, go ahead and install it with install.packages(“caret”). Then load the package with library(caret).

Now, create a normalized data frame called “airlinesNorm” by running the following commands:

preproc = preProcess(airlines)

airlinesNorm = predict(preproc, airlines)

The first command pre-processes the data, and the second command performs the normalization. If you look at the summary of airlinesNorm, you should see that all of the variables now have mean zero. You can also see that each of the variables has standard deviation 1 by using the sd() function.

library(caret)
preproc = preProcess(A)
AN = predict(preproc, A)
summary(AN)
    Balance         QualMiles        BonusMiles       BonusTrans      FlightMiles    
 Min.   :-0.730   Min.   :-0.186   Min.   :-0.710   Min.   :-1.208   Min.   :-0.329  
 1st Qu.:-0.546   1st Qu.:-0.186   1st Qu.:-0.658   1st Qu.:-0.896   1st Qu.:-0.329  
 Median :-0.303   Median :-0.186   Median :-0.413   Median : 0.041   Median :-0.329  
 Mean   : 0.000   Mean   : 0.000   Mean   : 0.000   Mean   : 0.000   Mean   : 0.000  
 3rd Qu.: 0.187   3rd Qu.:-0.186   3rd Qu.: 0.276   3rd Qu.: 0.562   3rd Qu.:-0.106  
 Max.   :16.187   Max.   :14.223   Max.   :10.208   Max.   : 7.747   Max.   :21.680  
  FlightTrans     DaysSinceEnroll  
 Min.   :-0.362   Min.   :-1.9934  
 1st Qu.:-0.362   1st Qu.:-0.8661  
 Median :-0.362   Median :-0.0109  
 Mean   : 0.000   Mean   : 0.0000  
 3rd Qu.:-0.098   3rd Qu.: 0.8096  
 Max.   :13.610   Max.   : 2.0228  
apply(AN, 2 ,mean) %>% round(3)
        Balance       QualMiles      BonusMiles      BonusTrans     FlightMiles 
              0               0               0               0               0 
    FlightTrans DaysSinceEnroll 
              0               0 
apply(AN, 2 ,sd) %>% round(3)
        Balance       QualMiles      BonusMiles      BonusTrans     FlightMiles 
              1               1               1               1               1 
    FlightTrans DaysSinceEnroll 
              1               1 

apply()將矩陣或資料框架逐列,逐行(MA=1 . 2…..)計算

apply(AN, 2, max) %>% sort
DaysSinceEnroll      BonusTrans      BonusMiles     FlightTrans       QualMiles 
          2.023           7.747          10.208          13.610          14.223 
        Balance     FlightMiles 
         16.187          21.680 

In the normalized data, which variable has the largest maximum value?

  • FlightMiles
apply(AN, 2, min) %>% sort
DaysSinceEnroll      BonusTrans         Balance      BonusMiles     FlightTrans 
        -1.9934         -1.2081         -0.7303         -0.7099         -0.3621 
    FlightMiles       QualMiles 
        -0.3286         -0.1863 

In the normalized data, which variable has the smallest minimum value?

  • DaysSinceEnroll



2. 層級式集群分析

2.1 依據樹狀圖和應用需求決定群數

Compute the distances between data points (using euclidean distance) and then run the Hierarchical clustering algorithm (using method=“ward.D”) on the normalized data. It may take a few minutes for the commands to finish since the dataset has a large number of observations for hierarchical clustering.

Then, plot the dendrogram of the hierarchical clustering process. Suppose the airline is looking for somewhere between 2 and 10 clusters.

d = dist(AN,method="euclidean")
hc = hclust(d, method='ward.D')
plot(hc)

euclidean distance 歐式距離 dist()距離函數,可作為距離矩陣 hclust()做階層式分群 method=’ward.D’華德法 method=“euclidean”歐式距離

集群中點的距離,底下每一個點,到族群中心點的距離遠

According to the dendrogram, which of the following is NOT a good choice for the number of clusters?

  • 6
2.2 分割群組

Suppose that after looking at the dendrogram and discussing with the marketing department, the airline decides to proceed with 5 clusters. Divide the data points into 5 clusters by using the cutree function. 看區隔變數

CG = cutree(hc, k=5)
table(CG)
CG
   1    2    3    4    5 
 776  519  494  868 1342 

分群:就是把近似的放在同一個群裡面,群內的差異變小,方便管理 分割族群就是在看區隔變數 cutree()可以讓整個階層的結構縮減,K為最佳的分群數目

How many data points are in Cluster 1?

  • 776
2.3 從區隔變數的平均值推論族群特性

Now, use tapply to compare the average values in each of the variables for the 5 clusters (the centroids of the clusters). You may want to compute the average values of the unnormalized data so that it is easier to interpret. You can do this for the variable “Balance” with the following command:

tapply(airlines$Balance, clusterGroups, mean)

sapply(split(A,CG), colMeans) %>% round(2) 
                       1         2         3        4        5
Balance         57866.90 110669.27 198191.57 52335.91 36255.91
QualMiles           0.64   1065.98     30.35     4.85     2.51
BonusMiles      10360.12  22881.76  55795.86 20788.77  2264.79
BonusTrans         10.82     18.23     19.66    17.09     2.97
FlightMiles        83.18   2613.42    327.68   111.57   119.32
FlightTrans         0.30      7.40      1.07     0.34     0.44
DaysSinceEnroll  6235.36   4402.41   5615.71  2840.82  3060.08
tapply(A$Balance , CG ,mean)
     1      2      3      4      5 
 57867 110669 198192  52336  36256 
tapply(A$QualMiles , CG ,mean)
        1         2         3         4         5 
   0.6443 1065.9827   30.3462    4.8479    2.5112 
tapply(A$BonusMiles , CG ,mean)
    1     2     3     4     5 
10360 22882 55796 20789  2265 
tapply(A$BonusTrans , CG ,mean)
     1      2      3      4      5 
10.823 18.229 19.664 17.088  2.973 
tapply(A$FlightMiles , CG ,mean)
      1       2       3       4       5 
  83.18 2613.42  327.68  111.57  119.32 
tapply(A$FlightTrans , CG ,mean)
     1      2      3      4      5 
0.3028 7.4027 1.0688 0.3445 0.4389 
tapply(A$DaysSinceEnroll , CG ,mean)
   1    2    3    4    5 
6235 4402 5616 2841 3060 

Compared to the other clusters, Cluster 1 has the largest average values in which variables (if any)? Select all that apply.

  • DaysSinceEnroll

How would you describe the customers in Cluster 1?

  • 不常搭飛機但具有顧客忠誠度
2.4 Cluster 2
split(AN,CG) %>% sapply(colMeans) %>% round(2)
                    1    2     3     4     5
Balance         -0.16 0.37  1.24 -0.21 -0.37
QualMiles       -0.19 1.19 -0.15 -0.18 -0.18
BonusMiles      -0.28 0.24  1.60  0.15 -0.62
BonusTrans      -0.08 0.69  0.84  0.57 -0.90
FlightMiles     -0.27 1.54 -0.09 -0.25 -0.24
FlightTrans     -0.28 1.59 -0.08 -0.27 -0.25
DaysSinceEnroll  1.03 0.14  0.72 -0.62 -0.51

split:把要处理的数据分割成小片断; apply:对每个小片断独立进行操作; combine:把片断重新组合。 sapply()(代表simplified [l]apply)可以將結果整理以矢量,矩陣,列表 的形式輸出。

par(cex=0.8)
split(AN,CG) %>% sapply(colMeans) %>% barplot(beside=T,col=rainbow(7))
legend('topright',legend=colnames(A),fill=rainbow(7))

barplot()劃出直條圖 legend()圖解 par函數用於設定或詢問繪圖參數。參數設定可通過par(參數名=取值)或par(賦值參數列表)的形式進行。

Compared to the other clusters, Cluster 2 has the largest average values in which variables (if any)? Select all that apply.

  • QualMiles
  • FlightMiles
  • FlightTrans

How would you describe the customers in Cluster 2?

  • 經常搭飛機且累積大量里程數的客戶
2.5 Cluster 3

Compared to the other clusters, Cluster 3 has the largest average values in which variables (if any)? Select all that apply.

  • Balance
  • BonusMiles
  • BonusTrans

How would you describe the customers in Cluster 3?

  • 非經常搭飛機且得到大量里程數的客戶
2.6 Cluster 4

Compared to the other clusters, Cluster 4 has the largest average values in which variables (if any)? Select all that apply.

  • None

How would you describe the customers in Cluster 4?

  • 非經常搭飛機但有累積里程數的相對較新的客戶
2.7 Cluster 5

Compared to the other clusters, Cluster 5 has the largest average values in which variables (if any)? Select all that apply.

  • None

How would you describe the customers in Cluster 5?

  • 不常使用飛機的新客戶

3. K-Means集群分析

3.1 K-Means集群分析

Now run the k-means clustering algorithm on the normalized data, again creating 5 clusters. Set the seed to 88 right before running the clustering algorithm, and set the argument iter.max to 1000.

set.seed(88)
km = kmeans(AN, 5, iter.max = 1000)
kg2 = km$cluster
table(kg2)
kg2
   1    2    3    4    5 
 408  141  993 1182 1275 

set.seed()設定隨機數種子 kmeans()分群

How many clusters have more than 1,000 observations?

  • 2
par(cex=0.8)
  km$centers %>% round(2) %>% t %>% barplot(beside=T,col=rainbow(7))
legend('topright',legend=colnames(A),fill=rainbow(7))

3.2 Hierarchical和K-Means集群的對應關係

Now, compare the cluster centroids to each other either by dividing the data points into groups and then using tapply, or by looking at the output of kmeansClust\(centers, where "kmeansClust" is the name of the output of the kmeans function. (Note that the output of kmeansClust\)centers will be for the normalized data. If you want to look at the average values for the unnormalized data, you need to use tapply like we did for hierarchical clustering.)

table(Hierarchical=CG, KMeans=kg2)
            KMeans
Hierarchical    1    2    3    4    5
           1    4    0   98  673    1
           2   92  137  105   92   93
           3  300    4  132   58    0
           4   12    0  653   30  173
           5    0    0    5  329 1008

$ center的輸出將用於規範化數據 Hierarchical階層式分群

Do you expect Cluster 1 of the K-Means clustering output to necessarily be similar to Cluster 1 of the Hierarchical clustering output?

  • 不,因為群集排序在k均值聚類或層次聚類中沒有意義。


【討論問題】

請你們為這五個族群各起一個名稱

  • 第一群:品牌支持者
  • 第二群:搭乘飛機往返的商務客
  • 第三群:注意性價比的客戶
  • 第四群:協力廠商的導流客戶
  • 第五群:未搭乘飛機的新客戶

請你們為這五個族群各設計一個行銷策略

  • 第一群:開發周邊商品增加品牌黏著度
  • 第二群:提供尊榮服務、精緻飛航體驗
  • 第三群:提供CP值高的套裝飛航選擇
  • 第四群:提供自由行或紅眼廉價航空促銷機票
  • 第五群:主打形象廣告,加深客戶印象

統計上最好的分群也是實務上最好的分群嗎?

  • 依照商業情境而定,實際分群可能與市場需求有關,因此統計分群的最佳解不一定是實際分群的最佳解。

除了考慮群間和群間距離之外,實務上的分群通常還需要考慮那些因數?

  • 群體大小及群內密集度,群體大小與市場選擇有關,群體愈大、重視程度愈高,因此必須特別關注該群體,此外群內密集度與目標客群行銷精準度有關,代表市場策略可更加精準,使行銷效果更為顯著,而使用分群作為市場區隔,接著選擇目標市場,並依據該目標市場特性,來考慮市場定位(STP分析)。






LS0tCnRpdGxlOiAiQVM2LTIg6Iiq56m65YWs5Y+455qE5biC5aC05Y2A6ZqUIgphdXRob3I6ICJHUk9VUDXigJTigJTmlr3ph4flvaPjgIHpmbPmgKHlronjgIHmpYrlh7HlgKvjgIHllJDmgJ3nkKrjgIHlh4zlgYnoqqAiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCjxicj4KCioq5Li76KaB6K2w6aGM77ya5L6d6aGn5a6i5bGs5oCn5YGa5biC5aC05Y2A6ZqUKioKCioq5a2457+S6YeN6bue77yaKioKCisg5Yip55So6ZuG576k5YiG5p6Q5YGa5biC5aC05Y2A6ZqUCisg6LOH5paZ5bi45oWL5YyWCisg6LOH5paZ6KaW6Ka65YyWCisg5peP576k54m55oCn6IiH6KGM6Yq3562W55WlCisg6KGM6Yq35bel5YW3dnPooYzpirflsI3osaEKCumbhue+pOWIhuaekOiIh+mgkOa4rOWIhuaekOacieW+iOWkp+eahOW3rueVsAoKYGBge3IgZWNobz1ULCBtZXNzYWdlPUYsIGNhY2hlPUYsIHdhcm5pbmc9Rn0Kcm0obGlzdD1scyhhbGw9VCkpClN5cy5zZXRsb2NhbGUoIkxDX0FMTCIsIkMiKQpvcHRpb25zKGRpZ2l0cz00LCBzY2lwZW49MTIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoY2FyZXQpCmBgYAo8YnI+CgotIC0gLQoKIyMjIDEuIOizh+aWmeW4uOaFi+WMlgoKIyMjIyMgMS4xIOizh+aWmeaRmOimgQpSZWFkIHRoZSBkYXRhc2V0IEFpcmxpbmVzQ2x1c3Rlci5jc3YgaW50byBSIGFuZCBjYWxsIGl0ICJhaXJsaW5lcyIuIExvb2tpbmcgYXQgdGhlIHN1bW1hcnkgb2YgYWlybGluZXMsIAoKYGBge3J9CkEgPSByZWFkLmNzdigiQWlybGluZXNDbHVzdGVyLmNzdiIpCnN1bW1hcnkoQSkKYGBgCgoKYGBge3J9CmNvbE1lYW5zKEEpICU+JSBzb3J0CmBgYApjb2xNZWFucygp6KiI566X6YCQ6KGM5bmz5Z2H5YC8Cgpfd2hpY2ggVFdPIHZhcmlhYmxlcyBoYXZlIChvbiBhdmVyYWdlKSB0aGUgc21hbGxlc3QgdmFsdWVzP18KCisgQm9udXNUcmFucworIEZsaWdodFRyYW5zICAKCl9XaGljaCBUV08gdmFyaWFibGVzIGhhdmUgKG9uIGF2ZXJhZ2UpIHRoZSBsYXJnZXN0IHZhbHVlcz9fCgorIEJhbGFuY2UKKyBCb251c01pbGVzCgoKIyMjIyMgMS4yIOeCuueUmum6vOimgeWBmuizh+aWmeW4uOaFi+WMlgpJbiB0aGlzIHByb2JsZW0sIHdlIHdpbGwgbm9ybWFsaXplIG91ciBkYXRhIGJlZm9yZSB3ZSBydW4gdGhlIGNsdXN0ZXJpbmcgYWxnb3JpdGhtcy4gCgpfV2h5IGlzIGl0IGltcG9ydGFudCB0byBub3JtYWxpemUgdGhlIGRhdGEgYmVmb3JlIGNsdXN0ZXJpbmc/XwoKKyBJZiB3ZSBkb24ndCBub3IgbWFsaXplIHRoZSBkYXRhLCB0aGUgY2x1c3RlcmluZyB3aWxsIGJlIGRvbWluYXRlZCBieSB0aGUgdmFyaWFibGVzIHRoYXQgYXJlIG9uIGEgbGFyZ2VyIHNjYWxlCisg5Zug54K65LiN5bCN5pW45pOa5YGa5qiZ5rqW5YyW5YuV5L2c77yM6ZuG576k5bCH5Y+X5Yiw5pu05aSn6KaP5qih55qE6K6K6YeP5b2x6Z+/44CCCgojIyMjIyAxLjMg5L2/55SoYGNhcmV0YOWll+S7tuWBmuizh+aWmeW4uOaFi+WMlgpMZXQncyBnbyBhaGVhZCBhbmQgbm9ybWFsaXplIG91ciBkYXRhLiBZb3UgY2FuIG5vcm1hbGl6ZSB0aGUgdmFyaWFibGVzIGluIGEgZGF0YSBmcmFtZSBieSB1c2luZyB0aGUgcHJlUHJvY2VzcyBmdW5jdGlvbiBpbiB0aGUgImNhcmV0IiBwYWNrYWdlLiBZb3Ugc2hvdWxkIGFscmVhZHkgaGF2ZSB0aGlzIHBhY2thZ2UgaW5zdGFsbGVkIGZyb20gV2VlayA0LCBidXQgaWYgbm90LCBnbyBhaGVhZCBhbmQgaW5zdGFsbCBpdCB3aXRoIGluc3RhbGwucGFja2FnZXMoImNhcmV0IikuIFRoZW4gbG9hZCB0aGUgcGFja2FnZSB3aXRoIGxpYnJhcnkoY2FyZXQpLgoKTm93LCBjcmVhdGUgYSBub3JtYWxpemVkIGRhdGEgZnJhbWUgY2FsbGVkICJhaXJsaW5lc05vcm0iIGJ5IHJ1bm5pbmcgdGhlIGZvbGxvd2luZyBjb21tYW5kczoKCnByZXByb2MgPSBwcmVQcm9jZXNzKGFpcmxpbmVzKQoKYWlybGluZXNOb3JtID0gcHJlZGljdChwcmVwcm9jLCBhaXJsaW5lcykKClRoZSBmaXJzdCBjb21tYW5kIHByZS1wcm9jZXNzZXMgdGhlIGRhdGEsIGFuZCB0aGUgc2Vjb25kIGNvbW1hbmQgcGVyZm9ybXMgdGhlIG5vcm1hbGl6YXRpb24uIElmIHlvdSBsb29rIGF0IHRoZSBzdW1tYXJ5IG9mIGFpcmxpbmVzTm9ybSwgeW91IHNob3VsZCBzZWUgdGhhdCBhbGwgb2YgdGhlIHZhcmlhYmxlcyBub3cgaGF2ZSBtZWFuIHplcm8uIFlvdSBjYW4gYWxzbyBzZWUgdGhhdCBlYWNoIG9mIHRoZSB2YXJpYWJsZXMgaGFzIHN0YW5kYXJkIGRldmlhdGlvbiAxIGJ5IHVzaW5nIHRoZSBzZCgpIGZ1bmN0aW9uLgpgYGB7cn0KbGlicmFyeShjYXJldCkKcHJlcHJvYyA9IHByZVByb2Nlc3MoQSkKQU4gPSBwcmVkaWN0KHByZXByb2MsIEEpCnN1bW1hcnkoQU4pCmFwcGx5KEFOLCAyICxtZWFuKSAlPiUgcm91bmQoMykKYXBwbHkoQU4sIDIgLHNkKSAlPiUgcm91bmQoMykKYGBgCmFwcGx5KCnlsIfnn6npmaPmiJbos4fmlpnmoYbmnrbpgJDliJfvvIzpgJDooYwoTUE9MSAuIDIuLi4uLinoqIjnrpcKCmBgYHtyfQphcHBseShBTiwgMiwgbWF4KSAlPiUgc29ydApgYGAKCkluIHRoZSBub3JtYWxpemVkIGRhdGEsIF93aGljaCB2YXJpYWJsZSBoYXMgdGhlIGxhcmdlc3QgbWF4aW11bSB2YWx1ZT9fCgorIEZsaWdodE1pbGVzIAoKYGBge3J9CmFwcGx5KEFOLCAyLCBtaW4pICU+JSBzb3J0CmBgYAoKSW4gdGhlIG5vcm1hbGl6ZWQgZGF0YSwgX3doaWNoIHZhcmlhYmxlIGhhcyB0aGUgc21hbGxlc3QgbWluaW11bSB2YWx1ZT9fCgorIERheXNTaW5jZUVucm9sbAoKPGJyPgoKLSAtIC0KCiMjIyAyLiDlsaTntJrlvI/pm4bnvqTliIbmnpAKCiMjIyMjIDIuMSDkvp3mk5rmqLnni4DlnJblkozmh4nnlKjpnIDmsYLmsbrlrprnvqTmlbgKQ29tcHV0ZSB0aGUgZGlzdGFuY2VzIGJldHdlZW4gZGF0YSBwb2ludHMgKHVzaW5nIGV1Y2xpZGVhbiBkaXN0YW5jZSkgYW5kIHRoZW4gcnVuIHRoZSBIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBhbGdvcml0aG0gKHVzaW5nIG1ldGhvZD0id2FyZC5EIikgb24gdGhlIG5vcm1hbGl6ZWQgZGF0YS4gSXQgbWF5IHRha2UgYSBmZXcgbWludXRlcyBmb3IgdGhlIGNvbW1hbmRzIHRvIGZpbmlzaCBzaW5jZSB0aGUgZGF0YXNldCBoYXMgYSBsYXJnZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGZvciBoaWVyYXJjaGljYWwgY2x1c3RlcmluZy4KClRoZW4sIHBsb3QgdGhlIGRlbmRyb2dyYW0gb2YgdGhlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIHByb2Nlc3MuIFN1cHBvc2UgdGhlIGFpcmxpbmUgaXMgbG9va2luZyBmb3Igc29tZXdoZXJlIGJldHdlZW4gMiBhbmQgMTAgY2x1c3RlcnMuIAoKYGBge3J9CmQgPSBkaXN0KEFOLG1ldGhvZD0iZXVjbGlkZWFuIikKaGMgPSBoY2x1c3QoZCwgbWV0aG9kPSd3YXJkLkQnKQpwbG90KGhjKQpgYGAKZXVjbGlkZWFuIGRpc3RhbmNlIOatkOW8j+i3nembogpkaXN0KCnot53pm6Llh73mlbjvvIzlj6/kvZzngrrot53pm6Lnn6npmaMKaGNsdXN0KCnlgZrpmo7lsaTlvI/liIbnvqQKbWV0aG9kPSd3YXJkLkQn6I+v5b635rOVCm1ldGhvZD0iZXVjbGlkZWFuIuatkOW8j+i3nembogoK6ZuG576k5Lit6bue55qE6Led6Zui77yM5bqV5LiL5q+P5LiA5YCL6bue77yM5Yiw5peP576k5Lit5b+D6bue55qE6Led6Zui6YGgCgoKQWNjb3JkaW5nIHRvIHRoZSBkZW5kcm9ncmFtLCBfd2hpY2ggb2YgdGhlIGZvbGxvd2luZyBpcyBOT1QgYSBnb29kIGNob2ljZSBmb3IgdGhlIG51bWJlciBvZiBjbHVzdGVycz9fCgorIDYKCiMjIyMjIDIuMiDliIblibLnvqTntYQKU3VwcG9zZSB0aGF0IGFmdGVyIGxvb2tpbmcgYXQgdGhlIGRlbmRyb2dyYW0gYW5kIGRpc2N1c3Npbmcgd2l0aCB0aGUgbWFya2V0aW5nIGRlcGFydG1lbnQsIHRoZSBhaXJsaW5lIGRlY2lkZXMgdG8gcHJvY2VlZCB3aXRoIDUgY2x1c3RlcnMuIERpdmlkZSB0aGUgZGF0YSBwb2ludHMgaW50byA1IGNsdXN0ZXJzIGJ5IHVzaW5nIHRoZSBjdXRyZWUgZnVuY3Rpb24uIArnnIvljYDpmpTorormlbgKCmBgYHtyfQpDRyA9IGN1dHJlZShoYywgaz01KQp0YWJsZShDRykKYGBgCuWIhue+pDrlsLHmmK/miorov5HkvLznmoTmlL7lnKjlkIzkuIDlgIvnvqToo6HpnaLvvIznvqTlhafnmoTlt67nlbDororlsI/vvIzmlrnkvr/nrqHnkIYK5YiG5Ymy5peP576k5bCx5piv5Zyo55yL5Y2A6ZqU6K6K5pW4CmN1dHJlZSgp5Y+v5Lul6K6T5pW05YCL6ZqO5bGk55qE57WQ5qeL57iu5rib77yMS+eCuuacgOS9s+eahOWIhue+pOaVuOebrgoKX0hvdyBtYW55IGRhdGEgcG9pbnRzIGFyZSBpbiBDbHVzdGVyIDE/XwoKKyA3NzYKCiMjIyMjIDIuMyDlvp7ljYDpmpTorormlbjnmoTlubPlnYflgLzmjqjoq5bml4/nvqTnibnmgKcKTm93LCB1c2UgdGFwcGx5IHRvIGNvbXBhcmUgdGhlIGF2ZXJhZ2UgdmFsdWVzIGluIGVhY2ggb2YgdGhlIHZhcmlhYmxlcyBmb3IgdGhlIDUgY2x1c3RlcnMgKHRoZSBjZW50cm9pZHMgb2YgdGhlIGNsdXN0ZXJzKS4gWW91IG1heSB3YW50IHRvIGNvbXB1dGUgdGhlIGF2ZXJhZ2UgdmFsdWVzIG9mIHRoZSB1bm5vcm1hbGl6ZWQgZGF0YSBzbyB0aGF0IGl0IGlzIGVhc2llciB0byBpbnRlcnByZXQuIFlvdSBjYW4gZG8gdGhpcyBmb3IgdGhlIHZhcmlhYmxlICJCYWxhbmNlIiB3aXRoIHRoZSBmb2xsb3dpbmcgY29tbWFuZDoKCnRhcHBseShhaXJsaW5lcyRCYWxhbmNlLCBjbHVzdGVyR3JvdXBzLCBtZWFuKQoKYGBge3J9CnNhcHBseShzcGxpdChBLENHKSwgY29sTWVhbnMpICU+JSByb3VuZCgyKSAKYGBgCgpgYGB7cn0KdGFwcGx5KEEkQmFsYW5jZSAsIENHICxtZWFuKQp0YXBwbHkoQSRRdWFsTWlsZXMgLCBDRyAsbWVhbikKdGFwcGx5KEEkQm9udXNNaWxlcyAsIENHICxtZWFuKQp0YXBwbHkoQSRCb251c1RyYW5zICwgQ0cgLG1lYW4pCnRhcHBseShBJEZsaWdodE1pbGVzICwgQ0cgLG1lYW4pCnRhcHBseShBJEZsaWdodFRyYW5zICwgQ0cgLG1lYW4pCnRhcHBseShBJERheXNTaW5jZUVucm9sbCAsIENHICxtZWFuKQpgYGAKCkNvbXBhcmVkIHRvIHRoZSBvdGhlciBjbHVzdGVycywgX0NsdXN0ZXIgMSBoYXMgdGhlIGxhcmdlc3QgYXZlcmFnZSB2YWx1ZXMgaW4gd2hpY2ggdmFyaWFibGVzIChpZiBhbnkpPyBTZWxlY3QgYWxsIHRoYXQgYXBwbHkuXwoKKyBEYXlzU2luY2VFbnJvbGwgCgpfSG93IHdvdWxkIHlvdSBkZXNjcmliZSB0aGUgY3VzdG9tZXJzIGluIENsdXN0ZXIgMT9fCgorIOS4jeW4uOaQremjm+apn+S9huWFt+aciemhp+WuouW/oOiqoOW6pgoKIyMjIyMgMi40IENsdXN0ZXIgMgpgYGB7cn0Kc3BsaXQoQU4sQ0cpICU+JSBzYXBwbHkoY29sTWVhbnMpICU+JSByb3VuZCgyKQpgYGAKc3BsaXTvvJrmioropoHlpITnkIbnmoTmlbDmja7liIblibLmiJDlsI/niYfmlq3vvJsKYXBwbHnvvJrlr7nmr4/kuKrlsI/niYfmlq3ni6znq4vov5vooYzmk43kvZzvvJsKY29tYmluZe+8muaKiueJh+aWremHjeaWsOe7hOWQiOOAggpzYXBwbHnvvIjvvInvvIjku6PooahzaW1wbGlmaWVkIFtsXWFwcGx577yJ5Y+v5Lul5bCH57WQ5p6c5pW055CG5Lul55+i6YeP77yM55+p6Zmj77yM5YiX6KGoIOeahOW9ouW8j+i8uOWHuuOAggoKYGBge3J9CnBhcihjZXg9MC44KQpzcGxpdChBTixDRykgJT4lIHNhcHBseShjb2xNZWFucykgJT4lIGJhcnBsb3QoYmVzaWRlPVQsY29sPXJhaW5ib3coNykpCmxlZ2VuZCgndG9wcmlnaHQnLGxlZ2VuZD1jb2xuYW1lcyhBKSxmaWxsPXJhaW5ib3coNykpCmBgYApiYXJwbG90KCnlioPlh7rnm7Tmop3lnJYKbGVnZW5kKCnlnJbop6MKcGFy5Ye95pW455So5pa86Kit5a6a5oiW6Kmi5ZWP57mq5ZyW5Y+D5pW444CC5Y+D5pW46Kit5a6a5Y+v6YCa6YGOcGFyKOWPg+aVuOWQjT3lj5blgLwp5oiWcGFyKOizpuWAvOWPg+aVuOWIl+ihqCnnmoTlvaLlvI/pgLLooYzjgIIKCkNvbXBhcmVkIHRvIHRoZSBvdGhlciBjbHVzdGVycywgX0NsdXN0ZXIgMiBoYXMgdGhlIGxhcmdlc3QgYXZlcmFnZSB2YWx1ZXMgaW4gd2hpY2ggdmFyaWFibGVzIChpZiBhbnkpPyBTZWxlY3QgYWxsIHRoYXQgYXBwbHkuXwoKKyBRdWFsTWlsZXMgICAKKyBGbGlnaHRNaWxlcworIEZsaWdodFRyYW5zCgpfSG93IHdvdWxkIHlvdSBkZXNjcmliZSB0aGUgY3VzdG9tZXJzIGluIENsdXN0ZXIgMj9fCgorIOe2k+W4uOaQremjm+apn+S4lOe0r+epjeWkp+mHj+mHjOeoi+aVuOeahOWuouaItiAKCiMjIyMjIDIuNSBDbHVzdGVyIDMKQ29tcGFyZWQgdG8gdGhlIG90aGVyIGNsdXN0ZXJzLCBfQ2x1c3RlciAzIGhhcyB0aGUgbGFyZ2VzdCBhdmVyYWdlIHZhbHVlcyBpbiB3aGljaCB2YXJpYWJsZXMgKGlmIGFueSk/IFNlbGVjdCBhbGwgdGhhdCBhcHBseS5fCgorIEJhbGFuY2UKKyBCb251c01pbGVzCisgQm9udXNUcmFucwoKX0hvdyB3b3VsZCB5b3UgZGVzY3JpYmUgdGhlIGN1c3RvbWVycyBpbiBDbHVzdGVyIDM/XwoKKyDpnZ7ntpPluLjmkK3po5vmqZ/kuJTlvpfliLDlpKfph4/ph4znqIvmlbjnmoTlrqLmiLYKCiMjIyMjIDIuNiBDbHVzdGVyIDQKQ29tcGFyZWQgdG8gdGhlIG90aGVyIGNsdXN0ZXJzLCBfQ2x1c3RlciA0IGhhcyB0aGUgbGFyZ2VzdCBhdmVyYWdlIHZhbHVlcyBpbiB3aGljaCB2YXJpYWJsZXMgKGlmIGFueSk/IFNlbGVjdCBhbGwgdGhhdCBhcHBseS5fCgorIE5vbmUKCl9Ib3cgd291bGQgeW91IGRlc2NyaWJlIHRoZSBjdXN0b21lcnMgaW4gQ2x1c3RlciA0P18KCisg6Z2e57aT5bi45pCt6aOb5qmf5L2G5pyJ57Sv56mN6YeM56iL5pW455qE55u45bCN6LyD5paw55qE5a6i5oi2CgojIyMjIyAyLjcgQ2x1c3RlciA1CkNvbXBhcmVkIHRvIHRoZSBvdGhlciBjbHVzdGVycywgX0NsdXN0ZXIgNSBoYXMgdGhlIGxhcmdlc3QgYXZlcmFnZSB2YWx1ZXMgaW4gd2hpY2ggdmFyaWFibGVzIChpZiBhbnkpPyBTZWxlY3QgYWxsIHRoYXQgYXBwbHkuXwoKKyBOb25lCgpfSG93IHdvdWxkIHlvdSBkZXNjcmliZSB0aGUgY3VzdG9tZXJzIGluIENsdXN0ZXIgNT9fCgorIOS4jeW4uOS9v+eUqOmjm+apn+eahOaWsOWuouaItgoKLSAtIC0KCiMjIyAzLiBLLU1lYW5z6ZuG576k5YiG5p6QCgojIyMjIyAzLjEgSy1NZWFuc+mbhue+pOWIhuaekApOb3cgcnVuIHRoZSBrLW1lYW5zIGNsdXN0ZXJpbmcgYWxnb3JpdGhtIG9uIHRoZSBub3JtYWxpemVkIGRhdGEsIGFnYWluIGNyZWF0aW5nIDUgY2x1c3RlcnMuIFNldCB0aGUgc2VlZCB0byA4OCByaWdodCBiZWZvcmUgcnVubmluZyB0aGUgY2x1c3RlcmluZyBhbGdvcml0aG0sIGFuZCBzZXQgdGhlIGFyZ3VtZW50IGl0ZXIubWF4IHRvIDEwMDAuCgpgYGB7cn0Kc2V0LnNlZWQoODgpCmttID0ga21lYW5zKEFOLCA1LCBpdGVyLm1heCA9IDEwMDApCmtnMiA9IGttJGNsdXN0ZXIKdGFibGUoa2cyKQpgYGAKc2V0LnNlZWQoKeioreWumumaqOapn+aVuOeoruWtkAprbWVhbnMoKeWIhue+pAoKX0hvdyBtYW55IGNsdXN0ZXJzIGhhdmUgbW9yZSB0aGFuIDEsMDAwIG9ic2VydmF0aW9ucz9fCgorIDIKCmBgYHtyfQpwYXIoY2V4PTAuOCkKICBrbSRjZW50ZXJzICU+JSByb3VuZCgyKSAlPiUgdCAlPiUgYmFycGxvdChiZXNpZGU9VCxjb2w9cmFpbmJvdyg3KSkKbGVnZW5kKCd0b3ByaWdodCcsbGVnZW5kPWNvbG5hbWVzKEEpLGZpbGw9cmFpbmJvdyg3KSkKYGBgCgojIyMjIyAzLjIgSGllcmFyY2hpY2Fs5ZKMSy1NZWFuc+mbhue+pOeahOWwjeaHiemXnOS/ggpOb3csIGNvbXBhcmUgdGhlIGNsdXN0ZXIgY2VudHJvaWRzIHRvIGVhY2ggb3RoZXIgZWl0aGVyIGJ5IGRpdmlkaW5nIHRoZSBkYXRhIHBvaW50cyBpbnRvIGdyb3VwcyBhbmQgdGhlbiB1c2luZyB0YXBwbHksIG9yIGJ5IGxvb2tpbmcgYXQgdGhlIG91dHB1dCBvZiBrbWVhbnNDbHVzdCRjZW50ZXJzLCB3aGVyZSAia21lYW5zQ2x1c3QiIGlzIHRoZSBuYW1lIG9mIHRoZSBvdXRwdXQgb2YgdGhlIGttZWFucyBmdW5jdGlvbi4gKE5vdGUgdGhhdCB0aGUgb3V0cHV0IG9mIGttZWFuc0NsdXN0JGNlbnRlcnMgd2lsbCBiZSBmb3IgdGhlIG5vcm1hbGl6ZWQgZGF0YS4gSWYgeW91IHdhbnQgdG8gbG9vayBhdCB0aGUgYXZlcmFnZSB2YWx1ZXMgZm9yIHRoZSB1bm5vcm1hbGl6ZWQgZGF0YSwgeW91IG5lZWQgdG8gdXNlIHRhcHBseSBsaWtlIHdlIGRpZCBmb3IgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcuKQoKYGBge3J9CnRhYmxlKEhpZXJhcmNoaWNhbD1DRywgS01lYW5zPWtnMikKYGBgCiQgY2VudGVy55qE6Ly45Ye65bCH55So5pa86KaP56+E5YyW5pW45pOaCkhpZXJhcmNoaWNhbOmajuWxpOW8j+WIhue+pAoKX0RvIHlvdSBleHBlY3QgQ2x1c3RlciAxIG9mIHRoZSBLLU1lYW5zIGNsdXN0ZXJpbmcgb3V0cHV0IHRvIG5lY2Vzc2FyaWx5IGJlIHNpbWlsYXIgdG8gQ2x1c3RlciAxIG9mIHRoZSBIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBvdXRwdXQ/XwoKKyDkuI3vvIzlm6DngrrnvqTpm4bmjpLluo/lnKhr5Z2H5YC86IGa6aGe5oiW5bGk5qyh6IGa6aGe5Lit5rKS5pyJ5oSP576p44CCCgoKPGJyPgoKIyMjIyMg44CQ6KiO6KuW5ZWP6aGM44CRCgroq4vkvaDlgJHngrrpgJnkupTlgIvml4/nvqTlkITotbfkuIDlgIvlkI3nqLEKCisg56ys5LiA576kOuWTgeeJjOaUr+aMgeiAhQorIOesrOS6jOe+pDrmkK3kuZjpo5vmqZ/lvoDov5TnmoTllYbli5nlrqIKKyDnrKzkuInnvqQ65rOo5oSP5oCn5YO55q+U55qE5a6i5oi2Cisg56ys5Zub576kOuWNlOWKm+W7oOWVhueahOWwjua1geWuouaItgorIOesrOS6lOe+pDrmnKrmkK3kuZjpo5vmqZ/nmoTmlrDlrqLmiLYKCuiri+S9oOWAkeeCuumAmeS6lOWAi+aXj+e+pOWQhOioreioiOS4gOWAi+ihjOmKt+etlueVpQoKKyDnrKzkuIDnvqQ66ZaL55m85ZGo6YKK5ZWG5ZOB5aKe5Yqg5ZOB54mM6buP6JGX5bqmCisg56ys5LqM576kOuaPkOS+m+WwiuamruacjeWLmeOAgeeyvue3u+mjm+iIqumrlOmplworIOesrOS4iee+pDrmj5DkvptDUOWAvOmrmOeahOWll+ijnemjm+iIqumBuOaThworIOesrOWbm+e+pDrmj5Dkvpvoh6rnlLHooYzmiJbntIXnnLzlu4nlg7noiKrnqbrkv4PpirfmqZ/npagKKyDnrKzkupTnvqQ65Li75omT5b2i6LGh5buj5ZGK77yM5Yqg5rex5a6i5oi25Y2w6LGhCgrntbHoqIjkuIrmnIDlpb3nmoTliIbnvqTkuZ/mmK/lr6bli5nkuIrmnIDlpb3nmoTliIbnvqTll47vvJ8gCgorIOS+neeFp+WVhualreaDheWig+iAjOWumu+8jOWvpumam+WIhue+pOWPr+iDveiIh+W4guWgtOmcgOaxguaciemXnO+8jOWboOatpOe1seioiOWIhue+pOeahOacgOS9s+ino+S4jeS4gOWumuaYr+Wvpumam+WIhue+pOeahOacgOS9s+ino+OAgiAgCgrpmaTkuobogIPmha7nvqTplpPlkoznvqTplpPot53pm6LkuYvlpJbvvIzlr6bli5nkuIrnmoTliIbnvqTpgJrluLjpgoTpnIDopoHogIPmha7pgqPkupvlm6DmlbjvvJ8gCgorIOe+pOmrlOWkp+Wwj+WPiue+pOWFp+WvhumbhuW6pu+8jOe+pOmrlOWkp+Wwj+iIh+W4guWgtOmBuOaTh+aciemXnO+8jOe+pOmrlOaEiOWkp+OAgemHjeimlueoi+W6puaEiOmrmO+8jOWboOatpOW/hemgiOeJueWIpemXnOazqOipsue+pOmrlO+8jOatpOWklue+pOWFp+WvhumbhuW6puiIh+ebruaomeWuoue+pOihjOmKt+eyvua6luW6puaciemXnO+8jOS7o+ihqOW4guWgtOetlueVpeWPr+abtOWKoOeyvua6lu+8jOS9v+ihjOmKt+aViOaenOabtOeCuumhr+iRl++8jOiAjOS9v+eUqOWIhue+pOS9nOeCuuW4guWgtOWNgOmalO+8jOaOpeiRl+mBuOaTh+ebruaomeW4guWgtO+8jOS4puS+neaTmuipsuebruaomeW4guWgtOeJueaAp++8jOS+huiAg+aFruW4guWgtOWumuS9jShTVFDliIbmnpAp44CCCgoKLSAtIC0KCjxicj48YnI+PGJyPjxicj48YnI+Cgo8c3R5bGU+Ci5jYXB0aW9uIHsKICBjb2xvcjogIzc3NzsKICBtYXJnaW4tdG9wOiAxMHB4Owp9CnAgY29kZSB7CiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7Cn0KcHJlIHsKICB3b3JkLWJyZWFrOiBub3JtYWw7CiAgd29yZC13cmFwOiBub3JtYWw7CiAgbGluZS1oZWlnaHQ6IDE7Cn0KcHJlIGNvZGUgewogIHdoaXRlLXNwYWNlOiBpbmhlcml0Owp9CnAsbGkgewogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOwp9CgoucnsKICBsaW5lLWhlaWdodDogMS4yOwp9Cgp0aXRsZXsKICBjb2xvcjogI2NjMDAwMDsKICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsKfQoKYm9keXsKICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsKfQoKaDEsaDIsaDMsaDQsaDV7CiAgY29sb3I6ICMwMDg4MDA7CiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7Cn0KCmgzewogIGNvbG9yOiAjYjM2YjAwOwogIGJhY2tncm91bmQ6ICNmZmUwYjM7CiAgbGluZS1oZWlnaHQ6IDI7CiAgZm9udC13ZWlnaHQ6IGJvbGQ7Cn0KCmg1ewogIGNvbG9yOiAjMDA2MDAwOwogIGJhY2tncm91bmQ6ICNmZmZmZTA7CiAgbGluZS1oZWlnaHQ6IDI7CiAgZm9udC13ZWlnaHQ6IGJvbGQ7Cn0KCmVtewogIGNvbG9yOiAjMDAwMGMwOwogIGJhY2tncm91bmQ6ICNmMGYwZjA7CiAgfQo8L3N0eWxlPgoK