這篇筆記示範,結合了互動式的資料視覺化之後, 傳統的主成份分析技術運用在大數據上, 也可以有很不錯的效果。

library(magrittr)
library(FactoMineR)
library(factoextra)
library(dplyr)
library(highcharter)
library(RColorBrewer)            #
Set3 <- brewer.pal(12, "Set3")   # define the Set3 palette  
load('data/yelp1.rdata')         # loading yelp data
load('data/empath.rdata')        # 



1. 資料整理

利用我們上周用過程式,先將資料整理一下。

# number of biz per category
CA = biz$cat %>% strsplit('|',T) %>% unlist %>% table %>% 
  data.frame %>% 'names<-'(c('name','nbiz'))
# number of review per category
CA$nrev = CA$name %>% sapply(function(z){sum(
  review$bid %in% biz$bid[grep(z,biz$cat,fixed=T)] )})
# average number of reviews per business
CA$avg.rev = CA$nrev / CA$nbiz    
CA = CA[order(-CA$nrev),]  # order CA by no. review
rownames(CA)= CA$name 
CA$name = NULL
#View(CA)
# category-business matrix
mxBC = rownames(CA) %>% sapply(function(z)
  grepl(z,biz$cat,fixed=T)); dim(mxBC) 
[1] 11537   508
rownames(mxBC) = biz$bid
# class weights: the total weight of a class in the corpus
# order the score matrix by class weights
scores = scores[,order(-colSums(scores))]
wClass = colSums(scores)  # class weights


然後將 評論情緒評分(10項)評論內容評分(194項)商業類別(508類) 平均起來,分別放在:

這兩個矩陣裡面。

# Avg. Sentiment Scores by Category [508 x 194]
sx = apply(mxBC,2,function(v){
  i = review$bid %in% rownames(mxBC)[v]
  colMeans(senti[i,]) }) %>% t
dim(sx)
[1] 508  10
# Avg. Class Weights by Category    [508 x 194]
wx = apply(mxBC,2,function(v){
  i = review$bid %in% rownames(mxBC)[v]
  colMeans(scores[i,]) }) %>% t
dim(wx)
[1] 508 194



2. 主成份分析

先對情緒矩陣(sx)做主成份分析

ncp=10  # number of components to keep
pcx = PCA(sx,ncp=ncp,graph=F) 
barplot(pcx$eig[1:ncp,3],names=1:ncp,main="Accumulated Variance",
        xlab="No. Components", ylab="% of Variance")
abline(h=seq(0,100,10),col='lightgray')

跟據上圖,前兩個主成份就涵蓋了將近80%的變異量。
但是當我們想要將商業類別標示在前兩個主成份的平面上的時候 …

fviz_pca_biplot(pcx)

由於類別太多(共508類),大部分的類別都幾乎無法辨識。

3. 資料視覺化

近兩年來R的畫圖套件幾乎都具備了輸出互動網頁的能力,以下我們先寫一個helper function,來幫助我們檢視主成份分析的結果。

# a helper function that generates Interactive PCA charts
bipcx = function(pcx, d1, d2, nvar, nobs, t1="", t2="",
                 main="Principle Component Anaylysis", 
                 obs='obs.', col.o='gold', ratio=0.7) {
  dfvar = pcx$var$coord %>% 
    {data.frame(name=rownames(.),x=.[,d1],y=.[,d2] )}
  dfvar = head(dfvar[order(-rowSums(pcx$var$cos2[,c(d1,d2)])),], nvar)
  dfobs = pcx$ind$coord %>% 
    {data.frame(name=rownames(.),x=.[,d1],y=.[,d2])}
  dfobs = head(dfobs[order(-rowSums(pcx$ind$cos2[,c(d1,d2)])),], nobs)
  dfvar[-1] = ratio*dfvar[-1]*max(abs(dfobs[,-1]))/max(abs(dfvar[-1])) 
  lsvar = dfvar %>% group_by_("name") %>%
    do(data = list(c(0, 0), c(.$x, .$y))) %>% list_parse()
  highchart() %>%
    hc_colors(substr(Set3, 0, 7)) %>% 
    hc_plotOptions( 
      line = list(
        marker=list(enabled=F),
        tooltip=list(pointFormat="{series.name}")),
      scatter = list(marker=list(radius=4, symbol="circle"))
      ) %>%
    hc_tooltip(headerFormat = "",valueDecimals=1,borderWidth=2) %>%
    hc_add_series_list(lsvar) %>%
    hc_add_series(data = list_parse(dfobs), 
      name = obs, type = "scatter", color = hex_to_rgba(col.o, 0.65),
      tooltip = list(headerFormat="",pointFormat="{point.name}")) %>%
    hc_chart(zoomType = "xy") %>%
    hc_add_theme(hc_theme_flatdark()) %>% 
    hc_title(text=main) %>% 
    hc_xAxis(title=list(
      text=sprintf("dim%d(%.2f%%) %s",d1,pcx$eig[d1,2],t1),
      style=list(color="white")))%>% 
    hc_yAxis(title=list(
      text=sprintf("dim%d(%.2f%%) %s",d2,pcx$eig[d2,2],t2),
      style=list(color="white"))) %>% 
    hc_legend(align="right", verticalAlign="top",layout="vertical")
  }



4. 情緒矩陣 的 主成份分析

使用上面bipcx()這個function,我們可以清楚的看到商業類別(由於後面的商業類別評論數不多,我們只畫前300個類別)在第一、二主成份 …

bipcx(pcx,1,2,10,300,t1="Strength",t2="Valence",obs='Biz Category',
      main="PCA on Sentiment Scores",ratio=0.5)


和第二、三主成份平面上的分布狀況。

bipcx(pcx,3,2,10,300,t1="Arousal",t2="Valence",obs='Biz Category',
      main="PCA on Sentiment Scores")


從以上的圖形我們可以辨識出來,第一、二、三主成份正好分別代表情緒的:



5. 內容矩陣 的 主成份分析

內容矩陣的尺度(194)比情緒矩陣(10)大很多, 即使我們只挑前250個商業類別和前100個內容項目 …

ncp=30
# only take large categories and large classes
pcx = PCA(wx[1:250,1:100],ncp=ncp,graph=F) 
par(cex=0.8)
barplot(pcx$eig[1:ncp,3],names=1:ncp,main="Accumulated Variance",
        xlab="No. Components", ylab="% of Variance")
abline(h=seq(0,100,10),col='lightgray')  # 12 PC's cover ~75% of variance


做完主成份分析之後,前12個主成份也只涵蓋75%的變異量。 在這種資料點和尺度都很多的狀況之下,互動式的圖表更能幫助我們觀察到 原始尺度和資料點之間的關係。 以下我們將前12個主成份,以兩兩成對的方式, 分別畫出在該平面上變異最大的12個內容項目和100個商業類別。 在這些平面上,我們可以看到一些不容易從簡單的敘事統計看出來的關係。

bipcx(pcx,1,2,12,100,obs='Biz Category',
      main="PCA on LIWC Classes, Dim. 1 & 2",ratio=0.5)
bipcx(pcx,3,4,12,100,obs='Biz Category',main="PCA on LIWC Classes, Dim. 3 & 4")
bipcx(pcx,5,6,12,100,obs='Biz Category',main="PCA on LIWC Classes, Dim. 5 & 6")
bipcx(pcx,7,8,12,100,obs='Biz Category',main="PCA on LIWC Classes, Dim. 7 & 8")
bipcx(pcx,9,10,12,100,obs='Biz Category',main="PCA on LIWC Classes, Dim. 9 & 10")
bipcx(pcx,11,12,12,100,obs='Biz Category',main="PCA on LIWC Classes, Dim. 11 & 12")


如果我們重新組合這些主成份, 我們也許還可以發現更多隱藏在資料裡面的有趣現象。



LS0tDQp0aXRsZTogIuS4u+aIkOS7veWIhuaekOeahOizh+aWmeimluimuuWMliIgDQpzdWJ0aXRsZTogIlllbHAgS2FnZ2xlLCBQcmluY2lwbGUgQ29tcG9uZW50IEFuYWx5c2lzIg0KYXV0aG9yOiAiVG9ueSBDaHVvIg0KZGF0ZTogIjIwMTflubQ35pyIMzHml6UiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIGhpZ2hsaWdodDogdGV4dG1hdGUNCiAgICB0aGVtZTogbHVtZW4NCi0tLQ0KDQo8YnI+DQoNCi0gLSAtDQoNCjxicj4NCumAmeevh+ethuiomOekuuevhO+8jOe1kOWQiOS6huS6kuWLleW8j+eahOizh+aWmeimluimuuWMluS5i+W+jO+8jA0K5YKz57Wx55qE5Li75oiQ5Lu95YiG5p6Q5oqA6KGT6YGL55So5Zyo5aSn5pW45pOa5LiK77yMDQrkuZ/lj6/ku6XmnInlvojkuI3pjK/nmoTmlYjmnpzjgIINCg0KYGBge3Igc2V0LW9wdGlvbnMsIGVjaG89RkFMU0UsIGNhY2hlPUZBTFNFfQ0KbGlicmFyeShrbml0cikNCm9wdGlvbnMod2lkdGg9MTAwKQ0Kb3B0c19jaHVuayRzZXQoY29tbWVudCA9IE5BKQ0KYGBgDQoNCmBgYHtyIHdhcm5pbmc9RiwgbWVzc2FnZT1GLCBjYWNoZT1GfQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkoRmFjdG9NaW5lUikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGhpZ2hjaGFydGVyKQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpICAgICAgICAgICAgIw0KU2V0MyA8LSBicmV3ZXIucGFsKDEyLCAiU2V0MyIpICAgIyBkZWZpbmUgdGhlIFNldDMgcGFsZXR0ZSAgDQpsb2FkKCdkYXRhL3llbHAxLnJkYXRhJykgICAgICAgICAjIGxvYWRpbmcgeWVscCBkYXRhDQpsb2FkKCdkYXRhL2VtcGF0aC5yZGF0YScpICAgICAgICAjIA0KYGBgDQo8YnI+DQo8YnI+DQoNCiMjIDEuIOizh+aWmeaVtOeQhiANCuWIqeeUqOaIkeWAkeS4iuWRqOeUqOmBjueoi+W8j++8jOWFiOWwh+izh+aWmeaVtOeQhuS4gOS4i+OAgg0KYGBge3J9DQojIG51bWJlciBvZiBiaXogcGVyIGNhdGVnb3J5DQpDQSA9IGJpeiRjYXQgJT4lIHN0cnNwbGl0KCd8JyxUKSAlPiUgdW5saXN0ICU+JSB0YWJsZSAlPiUgDQogIGRhdGEuZnJhbWUgJT4lICduYW1lczwtJyhjKCduYW1lJywnbmJpeicpKQ0KIyBudW1iZXIgb2YgcmV2aWV3IHBlciBjYXRlZ29yeQ0KQ0EkbnJldiA9IENBJG5hbWUgJT4lIHNhcHBseShmdW5jdGlvbih6KXtzdW0oDQogIHJldmlldyRiaWQgJWluJSBiaXokYmlkW2dyZXAoeixiaXokY2F0LGZpeGVkPVQpXSApfSkNCiMgYXZlcmFnZSBudW1iZXIgb2YgcmV2aWV3cyBwZXIgYnVzaW5lc3MNCkNBJGF2Zy5yZXYgPSBDQSRucmV2IC8gQ0EkbmJpeiAgICANCkNBID0gQ0Fbb3JkZXIoLUNBJG5yZXYpLF0gICMgb3JkZXIgQ0EgYnkgbm8uIHJldmlldw0Kcm93bmFtZXMoQ0EpPSBDQSRuYW1lIA0KQ0EkbmFtZSA9IE5VTEwNCiNWaWV3KENBKQ0KDQojIGNhdGVnb3J5LWJ1c2luZXNzIG1hdHJpeA0KbXhCQyA9IHJvd25hbWVzKENBKSAlPiUgc2FwcGx5KGZ1bmN0aW9uKHopDQogIGdyZXBsKHosYml6JGNhdCxmaXhlZD1UKSk7IGRpbShteEJDKSANCnJvd25hbWVzKG14QkMpID0gYml6JGJpZA0KDQojIGNsYXNzIHdlaWdodHM6IHRoZSB0b3RhbCB3ZWlnaHQgb2YgYSBjbGFzcyBpbiB0aGUgY29ycHVzDQojIG9yZGVyIHRoZSBzY29yZSBtYXRyaXggYnkgY2xhc3Mgd2VpZ2h0cw0Kc2NvcmVzID0gc2NvcmVzWyxvcmRlcigtY29sU3VtcyhzY29yZXMpKV0NCndDbGFzcyA9IGNvbFN1bXMoc2NvcmVzKSAgIyBjbGFzcyB3ZWlnaHRzDQpgYGANCjxicj4NCg0K54S25b6M5bCHICoq6KmV6KuW5oOF57eS6KmV5YiGKDEw6aCFKSoqIOWSjCAqKuipleirluWFp+WuueipleWIhigxOTTpoIUpKiog5L6dICoq5ZWG5qWt6aGe5YilKDUwOOmhnikqKiDlubPlnYfotbfkvobvvIzliIbliKXmlL7lnKjvvJoNCg0KKyBgc3ggWzUwOCB4IDEwXWAgOiAxMCBBdmVyYWdlIFNlbnRpbWVudCBTY29yZXMgcGVyIGJ1c2luZXNzIGNhdGVnb3J5ICAgDQorIGB3eCBbNTA4IHggMTk0XWAgOiAxOTQgQXZlcmFnZSBDbGFzcyBXZWlnaHRzIHBlciBidXNpbmVzcyBjYXRlZ29yeQ0KDQrpgJnlhanlgIvnn6npmaPoo6HpnaLjgIINCmBgYHtyfQ0KIyBBdmcuIFNlbnRpbWVudCBTY29yZXMgYnkgQ2F0ZWdvcnkgWzUwOCB4IDE5NF0NCnN4ID0gYXBwbHkobXhCQywyLGZ1bmN0aW9uKHYpew0KICBpID0gcmV2aWV3JGJpZCAlaW4lIHJvd25hbWVzKG14QkMpW3ZdDQogIGNvbE1lYW5zKHNlbnRpW2ksXSkgfSkgJT4lIHQNCmRpbShzeCkNCmBgYA0KDQpgYGB7cn0NCiMgQXZnLiBDbGFzcyBXZWlnaHRzIGJ5IENhdGVnb3J5ICAgIFs1MDggeCAxOTRdDQp3eCA9IGFwcGx5KG14QkMsMixmdW5jdGlvbih2KXsNCiAgaSA9IHJldmlldyRiaWQgJWluJSByb3duYW1lcyhteEJDKVt2XQ0KICBjb2xNZWFucyhzY29yZXNbaSxdKSB9KSAlPiUgdA0KZGltKHd4KQ0KYGBgDQo8YnI+DQo8YnI+DQoNCg0KIyMgMi4g5Li75oiQ5Lu95YiG5p6QDQrlhYjlsI3mg4Xnt5Lnn6npmaMoYHN4YCnlgZrkuLvmiJDku73liIbmnpANCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9DQpuY3A9MTAgICMgbnVtYmVyIG9mIGNvbXBvbmVudHMgdG8ga2VlcA0KcGN4ID0gUENBKHN4LG5jcD1uY3AsZ3JhcGg9RikgDQpiYXJwbG90KHBjeCRlaWdbMTpuY3AsM10sbmFtZXM9MTpuY3AsbWFpbj0iQWNjdW11bGF0ZWQgVmFyaWFuY2UiLA0KICAgICAgICB4bGFiPSJOby4gQ29tcG9uZW50cyIsIHlsYWI9IiUgb2YgVmFyaWFuY2UiKQ0KYWJsaW5lKGg9c2VxKDAsMTAwLDEwKSxjb2w9J2xpZ2h0Z3JheScpDQpgYGANCui3n+aTmuS4iuWclu+8jOWJjeWFqeWAi+S4u+aIkOS7veWwsea2teiTi+S6huWwh+i/kTgwJeeahOiuiueVsOmHj+OAgjxicj4NCuS9huaYr+eVtuaIkeWAkeaDs+imgeWwh+WVhualremhnuWIpeaomeekuuWcqOWJjeWFqeWAi+S4u+aIkOS7veeahOW5s+mdouS4iueahOaZguWAmSAuLi4NCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTV9DQpmdml6X3BjYV9iaXBsb3QocGN4KQ0KYGBgDQrnlLHmlrzpoZ7liKXlpKrlpJoo5YWxNTA46aGeKe+8jOWkp+mDqOWIhueahOmhnuWIpemDveW5vuS5jueEoeazlei+qOitmOOAgjxicj4NCjxicj4NCg0KDQojIyAzLiDos4fmlpnoppboprrljJYNCui/keWFqeW5tOS+hlLnmoTnlavlnJblpZfku7blub7kuY7pg73lhbflgpnkuobovLjlh7rkupLli5XntrLpoIHnmoTog73lipvvvIzku6XkuIvmiJHlgJHlhYjlr6vkuIDlgItoZWxwZXIgZnVuY3Rpb27vvIzkvobluavliqnmiJHlgJHmqqLoppbkuLvmiJDku73liIbmnpDnmoTntZDmnpzjgIINCmBgYHtyfQ0KIyBhIGhlbHBlciBmdW5jdGlvbiB0aGF0IGdlbmVyYXRlcyBJbnRlcmFjdGl2ZSBQQ0EgY2hhcnRzDQpiaXBjeCA9IGZ1bmN0aW9uKHBjeCwgZDEsIGQyLCBudmFyLCBub2JzLCB0MT0iIiwgdDI9IiIsDQogICAgICAgICAgICAgICAgIG1haW49IlByaW5jaXBsZSBDb21wb25lbnQgQW5heWx5c2lzIiwgDQogICAgICAgICAgICAgICAgIG9icz0nb2JzLicsIGNvbC5vPSdnb2xkJywgcmF0aW89MC43KSB7DQogIGRmdmFyID0gcGN4JHZhciRjb29yZCAlPiUgDQogICAge2RhdGEuZnJhbWUobmFtZT1yb3duYW1lcyguKSx4PS5bLGQxXSx5PS5bLGQyXSApfQ0KICBkZnZhciA9IGhlYWQoZGZ2YXJbb3JkZXIoLXJvd1N1bXMocGN4JHZhciRjb3MyWyxjKGQxLGQyKV0pKSxdLCBudmFyKQ0KICBkZm9icyA9IHBjeCRpbmQkY29vcmQgJT4lIA0KICAgIHtkYXRhLmZyYW1lKG5hbWU9cm93bmFtZXMoLikseD0uWyxkMV0seT0uWyxkMl0pfQ0KICBkZm9icyA9IGhlYWQoZGZvYnNbb3JkZXIoLXJvd1N1bXMocGN4JGluZCRjb3MyWyxjKGQxLGQyKV0pKSxdLCBub2JzKQ0KICBkZnZhclstMV0gPSByYXRpbypkZnZhclstMV0qbWF4KGFicyhkZm9ic1ssLTFdKSkvbWF4KGFicyhkZnZhclstMV0pKSANCiAgbHN2YXIgPSBkZnZhciAlPiUgZ3JvdXBfYnlfKCJuYW1lIikgJT4lDQogICAgZG8oZGF0YSA9IGxpc3QoYygwLCAwKSwgYyguJHgsIC4keSkpKSAlPiUgbGlzdF9wYXJzZSgpDQogIGhpZ2hjaGFydCgpICU+JQ0KICAgIGhjX2NvbG9ycyhzdWJzdHIoU2V0MywgMCwgNykpICU+JSANCiAgICBoY19wbG90T3B0aW9ucyggDQogICAgICBsaW5lID0gbGlzdCgNCiAgICAgICAgbWFya2VyPWxpc3QoZW5hYmxlZD1GKSwNCiAgICAgICAgdG9vbHRpcD1saXN0KHBvaW50Rm9ybWF0PSJ7c2VyaWVzLm5hbWV9IikpLA0KICAgICAgc2NhdHRlciA9IGxpc3QobWFya2VyPWxpc3QocmFkaXVzPTQsIHN5bWJvbD0iY2lyY2xlIikpDQogICAgICApICU+JQ0KICAgIGhjX3Rvb2x0aXAoaGVhZGVyRm9ybWF0ID0gIiIsdmFsdWVEZWNpbWFscz0xLGJvcmRlcldpZHRoPTIpICU+JQ0KICAgIGhjX2FkZF9zZXJpZXNfbGlzdChsc3ZhcikgJT4lDQogICAgaGNfYWRkX3NlcmllcyhkYXRhID0gbGlzdF9wYXJzZShkZm9icyksIA0KICAgICAgbmFtZSA9IG9icywgdHlwZSA9ICJzY2F0dGVyIiwgY29sb3IgPSBoZXhfdG9fcmdiYShjb2wubywgMC42NSksDQogICAgICB0b29sdGlwID0gbGlzdChoZWFkZXJGb3JtYXQ9IiIscG9pbnRGb3JtYXQ9Intwb2ludC5uYW1lfSIpKSAlPiUNCiAgICBoY19jaGFydCh6b29tVHlwZSA9ICJ4eSIpICU+JQ0KICAgIGhjX2FkZF90aGVtZShoY190aGVtZV9mbGF0ZGFyaygpKSAlPiUgDQogICAgaGNfdGl0bGUodGV4dD1tYWluKSAlPiUgDQogICAgaGNfeEF4aXModGl0bGU9bGlzdCgNCiAgICAgIHRleHQ9c3ByaW50ZigiZGltJWQoJS4yZiUlKSAlcyIsZDEscGN4JGVpZ1tkMSwyXSx0MSksDQogICAgICBzdHlsZT1saXN0KGNvbG9yPSJ3aGl0ZSIpKSklPiUgDQogICAgaGNfeUF4aXModGl0bGU9bGlzdCgNCiAgICAgIHRleHQ9c3ByaW50ZigiZGltJWQoJS4yZiUlKSAlcyIsZDIscGN4JGVpZ1tkMiwyXSx0MiksDQogICAgICBzdHlsZT1saXN0KGNvbG9yPSJ3aGl0ZSIpKSkgJT4lIA0KICAgIGhjX2xlZ2VuZChhbGlnbj0icmlnaHQiLCB2ZXJ0aWNhbEFsaWduPSJ0b3AiLGxheW91dD0idmVydGljYWwiKQ0KICB9DQpgYGANCjxicj4NCjxicj4NCg0KIyMgNC4g5oOF57eS55+p6ZmjIOeahCDkuLvmiJDku73liIbmnpANCuS9v+eUqOS4iumdomBiaXBjeCgpYOmAmeWAi2Z1bmN0aW9u77yM5oiR5YCR5Y+v5Lul5riF5qWa55qE55yL5Yiw5ZWG5qWt6aGe5YilKOeUseaWvOW+jOmdoueahOWVhualremhnuWIpeipleirluaVuOS4jeWkmu+8jOaIkeWAkeWPqueVq+WJjTMwMOWAi+mhnuWIpSnlnKjnrKzkuIDjgIHkuozkuLvmiJDku70gLi4uDQpgYGB7ciBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD05fQ0KYmlwY3gocGN4LDEsMiwxMCwzMDAsdDE9IlN0cmVuZ3RoIix0Mj0iVmFsZW5jZSIsb2JzPSdCaXogQ2F0ZWdvcnknLA0KICAgICAgbWFpbj0iUENBIG9uIFNlbnRpbWVudCBTY29yZXMiLHJhdGlvPTAuNSkNCmBgYA0KPGJyPg0K5ZKM56ys5LqM44CB5LiJ5Li75oiQ5Lu95bmz6Z2i5LiK55qE5YiG5biD54uA5rOB44CCDQpgYGB7ciBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD05fQ0KYmlwY3gocGN4LDMsMiwxMCwzMDAsdDE9IkFyb3VzYWwiLHQyPSJWYWxlbmNlIixvYnM9J0JpeiBDYXRlZ29yeScsDQogICAgICBtYWluPSJQQ0Egb24gU2VudGltZW50IFNjb3JlcyIpDQpgYGANCjxicj4NCuW+nuS7peS4iueahOWcluW9ouaIkeWAkeWPr+S7pei+qOitmOWHuuS+hu+8jOesrOS4gOOAgeS6jOOAgeS4ieS4u+aIkOS7veato+WlveWIhuWIpeS7o+ihqOaDhee3kueahO+8mg0KDQorIOW8t+W6piAoU3RyZW5ndGgpDQorIOato+iyoOWAvCAoVmFsZW5jZSkNCisg5r+A55m856iL5bqmIChBcm91c2FsKQ0KDQo8YnI+DQo8YnI+DQoNCiMjIDUuIOWFp+WuueefqemZoyDnmoQg5Li75oiQ5Lu95YiG5p6QDQrlhaflrrnnn6npmaPnmoTlsLrluqYoMTk0KeavlOaDhee3kuefqemZoygxMCnlpKflvojlpJrvvIwNCuWNs+S9v+aIkeWAkeWPquaMkeWJjTI1MOWAi+WVhualremhnuWIpeWSjOWJjTEwMOWAi+WFp+WuuemgheebriAuLi4NCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTV9DQpuY3A9MzANCiMgb25seSB0YWtlIGxhcmdlIGNhdGVnb3JpZXMgYW5kIGxhcmdlIGNsYXNzZXMNCnBjeCA9IFBDQSh3eFsxOjI1MCwxOjEwMF0sbmNwPW5jcCxncmFwaD1GKSANCnBhcihjZXg9MC44KQ0KYmFycGxvdChwY3gkZWlnWzE6bmNwLDNdLG5hbWVzPTE6bmNwLG1haW49IkFjY3VtdWxhdGVkIFZhcmlhbmNlIiwNCiAgICAgICAgeGxhYj0iTm8uIENvbXBvbmVudHMiLCB5bGFiPSIlIG9mIFZhcmlhbmNlIikNCmFibGluZShoPXNlcSgwLDEwMCwxMCksY29sPSdsaWdodGdyYXknKSAgIyAxMiBQQydzIGNvdmVyIH43NSUgb2YgdmFyaWFuY2UNCmBgYA0KDQo8YnI+DQrlgZrlrozkuLvmiJDku73liIbmnpDkuYvlvozvvIzliY0xMuWAi+S4u+aIkOS7veS5n+WPqua2teiTizc1JeeahOiuiueVsOmHj+OAgg0K5Zyo6YCZ56iu6LOH5paZ6bue5ZKM5bC65bqm6YO95b6I5aSa55qE54uA5rOB5LmL5LiL77yM5LqS5YuV5byP55qE5ZyW6KGo5pu06IO95bmr5Yqp5oiR5YCR6KeA5a+f5YiwDQrljp/lp4vlsLrluqblkozos4fmlpnpu57kuYvplpPnmoTpl5zkv4LjgIINCuS7peS4i+aIkeWAkeWwh+WJjTEy5YCL5Li75oiQ5Lu977yM5Lul5YWp5YWp5oiQ5bCN55qE5pa55byP77yMDQrliIbliKXnlavlh7rlnKjoqbLlubPpnaLkuIrorornlbDmnIDlpKfnmoQxMuWAi+WFp+WuuemgheebruWSjDEwMOWAi+WVhualremhnuWIpeOAgg0K5Zyo6YCZ5Lqb5bmz6Z2i5LiK77yM5oiR5YCR5Y+v5Lul55yL5Yiw5LiA5Lqb5LiN5a655piT5b6e57Ch5Zau55qE5pWY5LqL57Wx6KiI55yL5Ye65L6G55qE6Zec5L+C44CCDQoNCmBgYHtyIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTl9DQpiaXBjeChwY3gsMSwyLDEyLDEwMCxvYnM9J0JpeiBDYXRlZ29yeScsDQogICAgICBtYWluPSJQQ0Egb24gTElXQyBDbGFzc2VzLCBEaW0uIDEgJiAyIixyYXRpbz0wLjUpDQpgYGANCg0KYGBge3IgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9OX0NCmJpcGN4KHBjeCwzLDQsMTIsMTAwLG9icz0nQml6IENhdGVnb3J5JyxtYWluPSJQQ0Egb24gTElXQyBDbGFzc2VzLCBEaW0uIDMgJiA0IikNCmBgYA0KDQpgYGB7ciBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD05fQ0KYmlwY3gocGN4LDUsNiwxMiwxMDAsb2JzPSdCaXogQ2F0ZWdvcnknLG1haW49IlBDQSBvbiBMSVdDIENsYXNzZXMsIERpbS4gNSAmIDYiKQ0KYGBgDQoNCmBgYHtyIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTl9DQpiaXBjeChwY3gsNyw4LDEyLDEwMCxvYnM9J0JpeiBDYXRlZ29yeScsbWFpbj0iUENBIG9uIExJV0MgQ2xhc3NlcywgRGltLiA3ICYgOCIpDQpgYGANCg0KYGBge3IgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9OX0NCmJpcGN4KHBjeCw5LDEwLDEyLDEwMCxvYnM9J0JpeiBDYXRlZ29yeScsbWFpbj0iUENBIG9uIExJV0MgQ2xhc3NlcywgRGltLiA5ICYgMTAiKQ0KYGBgDQoNCmBgYHtyIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTl9DQpiaXBjeChwY3gsMTEsMTIsMTIsMTAwLG9icz0nQml6IENhdGVnb3J5JyxtYWluPSJQQ0Egb24gTElXQyBDbGFzc2VzLCBEaW0uIDExICYgMTIiKQ0KYGBgDQoNCjxicj4NCuWmguaenOaIkeWAkemHjeaWsOe1hOWQiOmAmeS6m+S4u+aIkOS7ve+8jA0K5oiR5YCR5Lmf6Kix6YKE5Y+v5Lul55m854++5pu05aSa6Zqx6JeP5Zyo6LOH5paZ6KOh6Z2i55qE5pyJ6Laj54++6LGh44CCDQoNCjxicj4NCjxicj4NCg==