getwd()
[1] "/Users/katy/Desktop/rdata"

【A】 Definitions

機率、勝率(Odd)、Logit

  • Odd = \(p/(1-p)\)

  • Logit = \(log(odd)\) = \(log(\frac{p}{1=p})\)

  • \(o = p/(1-p)\) ; \(p = o/(1+o)\) ; \(logit = log(o)\)

par(cex=0.8, mfcol=c(1,2))
curve(x/(1-x), 0.02, 0.98, col='cyan',lwd=2, main='odd')
abline(v=seq(0,1,0.1), h=seq(0,50,5), col='lightgray', lty=3)
curve(log(x/(1-x)), 0.005, 0.995, lwd=2, col='purple', main="logit")
abline(v=seq(0,1,0.1), h=seq(-5,5,1), col='lightgray', lty=3)

Logistic Function & Logistic Regression

  • Linear Model: \(y = f(x) = b_0 + b_1x_1 + b_2x_2 + ...\)

  • General Linear Model(GLM): \(y = Link(f(x))\)

  • Logistic Regression: \(logit(y) = log(\frac{p}{1-p}) = f(x) \text{ where } p = prob[y=1]\)

  • Logistic Function: \(Logistic(F_x) = \frac{1}{1+Exp(-F_x)} = \frac{Exp(F_x)}{1+Exp(F_x)}\)

par(cex=0.8)
curve(1/(1+exp(-x)), -5, 5, col='blue', lwd=2,main="Logistic Function",
      xlab="f(x): the logit of y = 1", ylab="the probability of y = 1")
abline(v=-5:5, h=seq(0,1,0.1), col='lightgray', lty=2)
abline(v=0,h=0.5,col='pink')
points(0,0.5,pch=20,cex=1.5,col='red')

Q】What are the definiion of logit & logistic function? What is the relationship between them?

#logit:一種機率的算法,先算出odd再取log #logistic function:把logit轉成機率


【B】glm(, family=binomial)

glm()的功能:在 \(\{x\}\) 的空間之中,找出區隔 \(y\) 的(類別)界線


#Q %>% head()

glm1 = glm(PoorCare~OfficeVisits+Narcotics,Q, family=binomial)
#summary(glm1)
b = coef(glm1); b   # extract the regression coef

Given OfficeVisits=3, Narcotics=4, what are the predicted logit, odd and probability?

logit = sum(b * c(1, 3, 4))
odd = exp(logit)
prob = odd/(1+odd)
c(logit=logit, odd=odd, prob=prob)

Q】What if OfficeVisits=2, Narcotics=3?

#
logit = sum(b * c(1, 2, 3))
odd = exp(logit)
prob = odd/(1+odd)
c(logit=logit, odd=odd, prob=prob)
#

We can plot the line of logit = 0 or prob = 0.5 on the plane of \(X\)

par(cex=0.8)
plot(Q$OfficeVisits, Q$Narcotics, col=1+Q$PoorCare,pch=20)
abline(-b[1]/b[3], -b[2]/b[3])

Furthermore, we can translate probability, logit and coefficents to intercept & slope …

\[f(x) = b_1 + b_2 x_2 + b_3 x_3 = g \Rightarrow x_3 = \frac{g - b_1}{b_3} - \frac{b_2}{b_3}x_2\]

p = seq(0.1,0.9,0.1)
logit = log(p/(1-p))
data.frame(prob = p, logit)

then mark the contours of proabilities into the scatter plot

par(cex=0.7)
plot(Q$OfficeVisits, Q$Narcotics, col=1+Q$PoorCare,
     pch=20, cex=1.3, xlab='X2', ylab='X3')
for(g in logit) {
  abline((g-b[1])/b[3], -b[2]/b[3], col=ifelse(g==0,'blue','cyan')) }

Q】What do the blue/cyan lines means?

等機率線(0.1~0.9) 【Q】Given any point in the figure above, how can you tell its (predicted) probability approximately? 看觀察點最接近的等機率線為何



【C】The Confusion Matrix

Figure 1 - Confusion Matrix

Figure 1 - Confusion Matrix



【D】The Distribution of Predicted Probability (DPP)

Confusion matrix is not fixed. It changes by Threshold

Figure 2 - Dist. Prediected Prob.

Figure 2 - Dist. Prediected Prob.

library(caTools)
DPP2 = function(pred,class,tvalue,breaks=0.01) {
  mx = table(class == tvalue, pred > 0.5) 
  tn = sum(class != tvalue & pred <= 0.5)
  fn = sum(class == tvalue & pred <= 0.5)
  fp = sum(class != tvalue & pred > 0.5)
  tp = sum(class == tvalue & pred > 0.5)
  acc = (tn + tp)/length(pred)
  sens = tp/(fn+tp)
  spec = tn/(tn+fp)
  auc = colAUC(pred,class)
  data.frame(pred,class) %>% 
    ggplot(aes(x=pred, fill=class)) +
    geom_histogram(col='gray',alpha=0.5,breaks=seq(0,1,breaks)) +
    xlim(0,1) + theme_bw() + xlab("predicted probability") + 
    ggtitle(
      sprintf("Distribution of Prob[class = \'%s\']", tvalue),
      sprintf("AUC=%.3f, Acc=%.3f, Sens=%.3f, Spec=%.3f",
              auc, acc, sens, spec) ) 
  }

N1 = 300; N2 = 100      #平均值#標準差
DPP2(pred = c(rnorm(N1,0.125,0.03), rnorm(N2,0.375,0.03)),
     class = c(rep('B',N1), rep('A',N2)), 
     tvalue = 'A')

Q】Is it possible to have AUC = ACC = SENS = SPEC = 1? Can you modify the code to make it happen?

# 
N1 = 300; N2 = 100      #平均值#標準差
DPP2(pred = c(rnorm(N1,0.125,0.03), rnorm(N2,0.625,0.03)),
     class = c(rep('B',N1), rep('A',N2)), 
     tvalue = 'A')
# 

Q】Is it possible to have AUC = ACC = SENS = SPEC = 0? Can you modify the code to make that happen?

# 
N1 = 300; N2 = 100      #平均值#標準差
DPP2(pred = c(rnorm(N1,0.625,0.03), rnorm(N2,0.125,0.03)),
     class = c(rep('B',N1), rep('A',N2)), 
     tvalue = 'A')
# 



【E】Modeling Expert

E1: Random Split


set.seed(88)
split = sample.split(Q$PoorCare, SplitRatio = 0.75)
table(split) %>% prop.table()
table(y = Q$PoorCare, split) %>% prop.table(2)
TR = subset(Q, split == TRUE)
TS = subset(Q, split == FALSE)

E2: Build Model

glm1 = glm(PoorCare ~ OfficeVisits + Narcotics, TR, family=binomial)
summary(glm1)

E3: Prediction & Evaluation

pred = predict(glm1, type='response')
mx = table(TR$PoorCare, pred > 0.5); mx
c(accuracy = sum(diag(mx))/sum(mx),
  sensitivity = mx[2,2]/sum(mx[2,]),
  specificity = mx[1,1]/sum(mx[1,]))

E4: ROC & AUC

library(ROCR)
ROCRpred = prediction(pred, TR$PoorCare)
ROCRperf = performance(ROCRpred, "tpr", "fpr")
par(cex=0.8)
plot(ROCRperf, colorize=TRUE, print.cutoffs.at=seq(0,1,0.1))
as.numeric(performance(ROCRpred, "auc")@y.values)
caTools::colAUC(pred, TR$PoorCare)



【F】Framingham Heart Study

getwd()
source("DPP.R")

F1: Reading & Splitting

F <- read.csv("framingham.csv")
set.seed(1000)
split = sample.split(F$TenYearCHD, SplitRatio = 0.65)
TR = subset(F, split==TRUE)
TS = subset(F, split==FALSE)

F2: Logistic Regression Model

glm2 = glm(TenYearCHD ~ ., TR, family=binomial)
summary(glm2)

F3: Prediction & Evaluation

pred = predict(glm2, TS, type="response")
y = TS$TenYearCHD[!is.na(pred)]             # remove NA
pred = pred[!is.na(pred)]

mx = table(y, pred > 0.5); mx
c(accuracy = sum(diag(mx))/sum(mx),
  sensitivity = mx[2,2]/sum(mx[2,]),
  specificity = mx[1,1]/sum(mx[1,]))

F4: AUC & DPP

par(cex=0.7)
auc = DPP(pred, y, 1, b=seq(0,1,0.02))  # 0.74211

F5: Expected Result & Optimization

                                #2*2矩陣
payoff = matrix(c(0,-100,-10,-60),2,2) 
cutoff = seq(0.02, 0.64, 0.01)
result = sapply(cutoff, function(p) sum(table(y,pred>p)*payoff) )
i = which.max(result)
par(cex=0.7)
plot(cutoff, result, type='l', col='cyan', lwd=2, main=sprintf(
  "Optomal Expected Result: $%d @ %.2f",result[i],cutoff[i]))
abline(v=seq(0,1,0.05),h=seq(-23000,-17000,500),col='lightgray',lty=3)
abline(v=cutoff[i],col='red')

Q】如果什麼都不做,期望報酬是多少? 19700 【Q】如果每位病人都做呢? 22600 【Q】以上哪一種做法期望報酬比較高? 什麼都不做 【Q】在所有的商務情境都是這種狀況嗎? 否 【Q】你可以模擬出「全做」比「全不做」還要好的狀況、並舉出一個會發生這種狀況的商務情境嗎? (0,-200,-100,500) 預測顧客偏好,-200我們預測該顧客不喜歡此產品,但實際上該顧客喜歡,因此我們會損失沒有該市場的營收。-100是我們針對錯誤的目標市場去做行銷活動的損失成本。500是我們正確預測顧客喜好並對他們做行銷活動。所以在此情況之下,對所有人做行銷活動會比完全不做還好。

#
#
#


F6: Simulation


library(manipulate)
library(ROCR)
p0 = par(mfrow=c(2,1),cex=0.8)
# manipulate({
#   Y0 = -22000; Y1 = -12000
#   mx = matrix(c(true_neg, false_neg, false_pos, true_pos),2,2) 
#   cx = seq(0.02, 0.64, 0.01)
#   rx = sapply(cx, function(p) sum(table(y, pred>p)*mx) )
#   i = which.max(rx)
#   plot(cx, rx, type='l',col='cyan',lwd=2,main=sprintf(
#     "Optomal Expected Result: $%d @ %.2f, T:%d",rx[i],cx[i],sum(pred>cx[i])),
#     ylim=c(Y0,Y1))
#   abline(v=cx[i],col='red')
#   abline(v=seq(0,1,0.1),h=seq(Y0,Y1,2000),col='lightgray',lty=3)
#   DPP(pred, y, 1, b=seq(0,1,0.02))
#   abline(v=cx[i],col='red')l
#   },
#   true_neg  = slider(-100,100,0,step=5),
#   false_neg = slider(-100,100,-100,step=5),
#   false_pos = slider(-100,100,-10,step=5),
#   true_pos  = slider(-100,100,-60,step=5)
#   ) 
par(p0)

Q】有五種成本分別為 $5, $10, $15, $20, $30 的藥,它們分別可以將風險成本從 $100 降低到 $70, $60, $50, $40, $25,哪一種藥的期望效益是最大的呢?

#$5,$10,$15,$20,$30
#-$17415,-$17500,-$17450,-$17400,-$17655
#$20的藥期望效益最大



【G】分析流程:資料、模型、預測、決策






LS0tCnRpdGxlOiAiQVMzLTAgR3JvdXAtMCIKYXV0aG9yOiAi5Y2T6ZuN54S2IEQ5OTQwMTAwMDEsIC4uLiIKb3V0cHV0OiBodG1sX25vdGVib29rCgotLS0KCmBgYHtyIGVjaG89VCwgbWVzc2FnZT1GLCBjYWNoZT1GLCB3YXJuaW5nPUZ9CnJtKGxpc3Q9bHMoYWxsPVQpKQpvcHRpb25zKGRpZ2l0cz00LCBzY2lwZW49MTIpCmxpYnJhcnkoZHBseXIpOyBsaWJyYXJ5KGdncGxvdDIpCmdldHdkKCkKcmVhZC5jc3YoJy4vVW5pdDMvbG9hbnMuY3N2JykKCgpgYGAKCgotIC0gLQoKIyMjIOOAkEHjgJEgRGVmaW5pdGlvbnMKCiMjIyMg5qmf546H44CB5Yud546HKE9kZCnjgIFMb2dpdAoKKyBPZGQgPSAgJHAvKDEtcCkkCgorIExvZ2l0ID0gJGxvZyhvZGQpJCA9ICRsb2coXGZyYWN7cH17MT1wfSkkCgorICRvID0gcC8oMS1wKSQgOyAkcCA9IG8vKDErbykkIDsgICRsb2dpdCA9IGxvZyhvKSQKCmBgYHtyIGZpZy5oZWlnaHQ9My42LCBmaWcud2lkdGg9N30KcGFyKGNleD0wLjgsIG1mY29sPWMoMSwyKSkKY3VydmUoeC8oMS14KSwgMC4wMiwgMC45OCwgY29sPSdjeWFuJyxsd2Q9MiwgbWFpbj0nb2RkJykKYWJsaW5lKHY9c2VxKDAsMSwwLjEpLCBoPXNlcSgwLDUwLDUpLCBjb2w9J2xpZ2h0Z3JheScsIGx0eT0zKQpjdXJ2ZShsb2coeC8oMS14KSksIDAuMDA1LCAwLjk5NSwgbHdkPTIsIGNvbD0ncHVycGxlJywgbWFpbj0ibG9naXQiKQphYmxpbmUodj1zZXEoMCwxLDAuMSksIGg9c2VxKC01LDUsMSksIGNvbD0nbGlnaHRncmF5JywgbHR5PTMpCgpgYGAKCiMjIyMgTG9naXN0aWMgRnVuY3Rpb24gJiBMb2dpc3RpYyBSZWdyZXNzaW9uCgorIExpbmVhciBNb2RlbDogJHkgPSBmKHgpID0gYl8wICsgYl8xeF8xICsgYl8yeF8yICsgLi4uJAoKKyBHZW5lcmFsIExpbmVhciBNb2RlbChHTE0pOiAkeSA9IExpbmsoZih4KSkkIAoKKyBMb2dpc3RpYyBSZWdyZXNzaW9uOiAkbG9naXQoeSkgPSBsb2coXGZyYWN7cH17MS1wfSkgPSBmKHgpIFx0ZXh0eyB3aGVyZSB9IHAgPSBwcm9iW3k9MV0kIAoKKyBMb2dpc3RpYyBGdW5jdGlvbjogJExvZ2lzdGljKEZfeCkgPSBcZnJhY3sxfXsxK0V4cCgtRl94KX0gPSBcZnJhY3tFeHAoRl94KX17MStFeHAoRl94KX0kCgpgYGB7ciAgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9NH0KcGFyKGNleD0wLjgpCmN1cnZlKDEvKDErZXhwKC14KSksIC01LCA1LCBjb2w9J2JsdWUnLCBsd2Q9MixtYWluPSJMb2dpc3RpYyBGdW5jdGlvbiIsCiAgICAgIHhsYWI9ImYoeCk6IHRoZSBsb2dpdCBvZiB5ID0gMSIsIHlsYWI9InRoZSBwcm9iYWJpbGl0eSBvZiB5ID0gMSIpCmFibGluZSh2PS01OjUsIGg9c2VxKDAsMSwwLjEpLCBjb2w9J2xpZ2h0Z3JheScsIGx0eT0yKQphYmxpbmUodj0wLGg9MC41LGNvbD0ncGluaycpCnBvaW50cygwLDAuNSxwY2g9MjAsY2V4PTEuNSxjb2w9J3JlZCcpCmBgYAoK44CQKipRKirjgJFXaGF0IGFyZSB0aGUgZGVmaW5paW9uIG9mIGBsb2dpdGAgJiBgbG9naXN0aWMgZnVuY3Rpb25gPyBXaGF0IGlzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGVtPwoK77yDbG9naXTvvJrkuIDnqK7mqZ/njofnmoTnrpfms5XvvIzlhYjnrpflh7pvZGTlho3lj5Zsb2cK77yDbG9naXN0aWMgZnVuY3Rpb27vvJrmiopsb2dpdOi9ieaIkOapn+eOhwo8YnI+CgotIC0gLQoKIyMjIOOAkELjgJFgZ2xtKCwgZmFtaWx5PWJpbm9taWFsKWAKCmBnbG0oKWDnmoTlip/og73vvJrlnKggJFx7eFx9JCDnmoTnqbrplpPkuYvkuK3vvIzmib7lh7rljYDpmpQgJHkkIOeahCjpoZ7liKUp55WM57eaCgpgYGB7cn0KCiNRICU+JSBoZWFkKCkKCmdsbTEgPSBnbG0oUG9vckNhcmV+T2ZmaWNlVmlzaXRzK05hcmNvdGljcyxRLCBmYW1pbHk9Ymlub21pYWwpCiNzdW1tYXJ5KGdsbTEpCmBgYAoKYGBge3J9CmIgPSBjb2VmKGdsbTEpOyBiICAgIyBleHRyYWN0IHRoZSByZWdyZXNzaW9uIGNvZWYKYGBgCgpHaXZlbiBgT2ZmaWNlVmlzaXRzPTMsIE5hcmNvdGljcz00YCwgd2hhdCBhcmUgdGhlIHByZWRpY3RlZCBsb2dpdCwgb2RkIGFuZCBwcm9iYWJpbGl0eT8KYGBge3J9CmxvZ2l0ID0gc3VtKGIgKiBjKDEsIDMsIDQpKQpvZGQgPSBleHAobG9naXQpCnByb2IgPSBvZGQvKDErb2RkKQpjKGxvZ2l0PWxvZ2l0LCBvZGQ9b2RkLCBwcm9iPXByb2IpCmBgYAoK44CQKipRKirjgJFXaGF0IGlmIGBPZmZpY2VWaXNpdHM9MiwgTmFyY290aWNzPTNgPwpgYGB7cn0KIwpsb2dpdCA9IHN1bShiICogYygxLCAyLCAzKSkKb2RkID0gZXhwKGxvZ2l0KQpwcm9iID0gb2RkLygxK29kZCkKYyhsb2dpdD1sb2dpdCwgb2RkPW9kZCwgcHJvYj1wcm9iKQojCmBgYAoKV2UgY2FuIHBsb3QgdGhlIGxpbmUgb2YgYGxvZ2l0ID0gMGAgb3IgYHByb2IgPSAwLjVgIG9uIHRoZSBwbGFuZSBvZiAkWCQKYGBge3IgZmlnLndpZHRoPTMuNiwgZmlnLmhlaWdodD0zLjZ9CnBhcihjZXg9MC44KQpwbG90KFEkT2ZmaWNlVmlzaXRzLCBRJE5hcmNvdGljcywgY29sPTErUSRQb29yQ2FyZSxwY2g9MjApCmFibGluZSgtYlsxXS9iWzNdLCAtYlsyXS9iWzNdKQpgYGAKCkZ1cnRoZXJtb3JlLCB3ZSBjYW4gdHJhbnNsYXRlIHByb2JhYmlsaXR5LCBsb2dpdCBhbmQgY29lZmZpY2VudHMgdG8gaW50ZXJjZXB0ICYgc2xvcGUgLi4uCgokJGYoeCkgPSBiXzEgKyBiXzIgeF8yICsgYl8zIHhfMyA9IGcgXFJpZ2h0YXJyb3cgIHhfMyA9IFxmcmFje2cgLSBiXzF9e2JfM30gLSBcZnJhY3tiXzJ9e2JfM314XzIkJAoKCmBgYHtyICBmaWcud2lkdGg9My42LCBmaWcuaGVpZ2h0PTMuNn0KcCA9IHNlcSgwLjEsMC45LDAuMSkKbG9naXQgPSBsb2cocC8oMS1wKSkKZGF0YS5mcmFtZShwcm9iID0gcCwgbG9naXQpCmBgYAoKdGhlbiBtYXJrIHRoZSBjb250b3VycyBvZiBwcm9hYmlsaXRpZXMgaW50byB0aGUgc2NhdHRlciBwbG90IApgYGB7ciAgZmlnLndpZHRoPTMuNiwgZmlnLmhlaWdodD0zLjZ9CnBhcihjZXg9MC43KQpwbG90KFEkT2ZmaWNlVmlzaXRzLCBRJE5hcmNvdGljcywgY29sPTErUSRQb29yQ2FyZSwKICAgICBwY2g9MjAsIGNleD0xLjMsIHhsYWI9J1gyJywgeWxhYj0nWDMnKQpmb3IoZyBpbiBsb2dpdCkgewogIGFibGluZSgoZy1iWzFdKS9iWzNdLCAtYlsyXS9iWzNdLCBjb2w9aWZlbHNlKGc9PTAsJ2JsdWUnLCdjeWFuJykpIH0KYGBgCgrjgJAqKlEqKuOAkVdoYXQgZG8gdGhlIGJsdWUvY3lhbiBsaW5lcyBtZWFucz8KCuetieapn+eOh+e3mu+8iDAuMX4wLjkpCuOAkCoqUSoq44CRR2l2ZW4gYW55IHBvaW50IGluIHRoZSBmaWd1cmUgYWJvdmUsIGhvdyBjYW4geW91IHRlbGwgaXRzIChwcmVkaWN0ZWQpIHByb2JhYmlsaXR5IGFwcHJveGltYXRlbHk/Cueci+ingOWvn+m7nuacgOaOpei/keeahOetieapn+eOh+e3mueCuuS9lQoKPGJyPgoKLSAtIC0KCiMjIyDjgJBD44CRVGhlIENvbmZ1c2lvbiBNYXRyaXgKCgohW0ZpZ3VyZSAxIC0gQ29uZnVzaW9uIE1hdHJpeF0ocmVzL2NvbmZ1c2lvbl9tYXRyaXguanBnKQoKCjxicj4KCi0gLSAtCiMjIyDjgJBE44CRVGhlIERpc3RyaWJ1dGlvbiBvZiBQcmVkaWN0ZWQgUHJvYmFiaWxpdHkgKERQUCkKCkNvbmZ1c2lvbiBtYXRyaXggaXMgbm90IGZpeGVkLiBJdCBjaGFuZ2VzIGJ5IGBUaHJlc2hvbGRgIC4uLgoKIVtGaWd1cmUgMiAtIERpc3QuIFByZWRpZWN0ZWQgUHJvYi5dKHJlcy9kcHAuanBnKQoKCgpgYGB7cn0KbGlicmFyeShjYVRvb2xzKQpEUFAyID0gZnVuY3Rpb24ocHJlZCxjbGFzcyx0dmFsdWUsYnJlYWtzPTAuMDEpIHsKICBteCA9IHRhYmxlKGNsYXNzID09IHR2YWx1ZSwgcHJlZCA+IDAuNSkgCiAgdG4gPSBzdW0oY2xhc3MgIT0gdHZhbHVlICYgcHJlZCA8PSAwLjUpCiAgZm4gPSBzdW0oY2xhc3MgPT0gdHZhbHVlICYgcHJlZCA8PSAwLjUpCiAgZnAgPSBzdW0oY2xhc3MgIT0gdHZhbHVlICYgcHJlZCA+IDAuNSkKICB0cCA9IHN1bShjbGFzcyA9PSB0dmFsdWUgJiBwcmVkID4gMC41KQogIGFjYyA9ICh0biArIHRwKS9sZW5ndGgocHJlZCkKICBzZW5zID0gdHAvKGZuK3RwKQogIHNwZWMgPSB0bi8odG4rZnApCiAgYXVjID0gY29sQVVDKHByZWQsY2xhc3MpCiAgZGF0YS5mcmFtZShwcmVkLGNsYXNzKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHg9cHJlZCwgZmlsbD1jbGFzcykpICsKICAgIGdlb21faGlzdG9ncmFtKGNvbD0nZ3JheScsYWxwaGE9MC41LGJyZWFrcz1zZXEoMCwxLGJyZWFrcykpICsKICAgIHhsaW0oMCwxKSArIHRoZW1lX2J3KCkgKyB4bGFiKCJwcmVkaWN0ZWQgcHJvYmFiaWxpdHkiKSArIAogICAgZ2d0aXRsZSgKICAgICAgc3ByaW50ZigiRGlzdHJpYnV0aW9uIG9mIFByb2JbY2xhc3MgPSBcJyVzXCddIiwgdHZhbHVlKSwKICAgICAgc3ByaW50ZigiQVVDPSUuM2YsIEFjYz0lLjNmLCBTZW5zPSUuM2YsIFNwZWM9JS4zZiIsCiAgICAgICAgICAgICAgYXVjLCBhY2MsIHNlbnMsIHNwZWMpICkgCiAgfQoKYGBgCu+8gwpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0yLjV9Ck4xID0gMzAwOyBOMiA9IDEwMCAgICAgICPlubPlnYflgLwj5qiZ5rqW5beuCkRQUDIocHJlZCA9IGMocm5vcm0oTjEsMC4xMjUsMC4wMyksIHJub3JtKE4yLDAuMzc1LDAuMDMpKSwKICAgICBjbGFzcyA9IGMocmVwKCdCJyxOMSksIHJlcCgnQScsTjIpKSwgCiAgICAgdHZhbHVlID0gJ0EnKQpgYGAKCuOAkCoqUSoq44CRSXMgaXQgcG9zc2libGUgdG8gaGF2ZSBgQVVDID0gQUNDID0gU0VOUyA9IFNQRUMgPSAxYD8gQ2FuIHlvdSBtb2RpZnkgdGhlIGNvZGUgdG8gbWFrZSBpdCBoYXBwZW4/CgpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0yLjV9CiMgCk4xID0gMzAwOyBOMiA9IDEwMCAgICAgICPlubPlnYflgLwj5qiZ5rqW5beuCkRQUDIocHJlZCA9IGMocm5vcm0oTjEsMC4xMjUsMC4wMyksIHJub3JtKE4yLDAuNjI1LDAuMDMpKSwKICAgICBjbGFzcyA9IGMocmVwKCdCJyxOMSksIHJlcCgnQScsTjIpKSwgCiAgICAgdHZhbHVlID0gJ0EnKQojIApgYGAKCgrjgJAqKlEqKuOAkUlzIGl0IHBvc3NpYmxlIHRvIGhhdmUgYEFVQyA9IEFDQyA9IFNFTlMgPSBTUEVDID0gMGA/IENhbiB5b3UgbW9kaWZ5IHRoZSBjb2RlIHRvIG1ha2UgdGhhdCBoYXBwZW4/CgpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0yLjV9CiMgCk4xID0gMzAwOyBOMiA9IDEwMCAgICAgICPlubPlnYflgLwj5qiZ5rqW5beuCkRQUDIocHJlZCA9IGMocm5vcm0oTjEsMC42MjUsMC4wMyksIHJub3JtKE4yLDAuMTI1LDAuMDMpKSwKICAgICBjbGFzcyA9IGMocmVwKCdCJyxOMSksIHJlcCgnQScsTjIpKSwgCiAgICAgdHZhbHVlID0gJ0EnKQojIApgYGAKPGJyPgoKLSAtIC0KIyMjIOOAkEXjgJFNb2RlbGluZyBFeHBlcnQKCiMjIyMgRTE6IFJhbmRvbSBTcGxpdApgYGB7cn0KCnNldC5zZWVkKDg4KQpzcGxpdCA9IHNhbXBsZS5zcGxpdChRJFBvb3JDYXJlLCBTcGxpdFJhdGlvID0gMC43NSkKdGFibGUoc3BsaXQpICU+JSBwcm9wLnRhYmxlKCkKdGFibGUoeSA9IFEkUG9vckNhcmUsIHNwbGl0KSAlPiUgcHJvcC50YWJsZSgyKQpgYGAKCmBgYHtyfQpUUiA9IHN1YnNldChRLCBzcGxpdCA9PSBUUlVFKQpUUyA9IHN1YnNldChRLCBzcGxpdCA9PSBGQUxTRSkKYGBgCgojIyMjIEUyOiBCdWlsZCBNb2RlbApgYGB7cn0KZ2xtMSA9IGdsbShQb29yQ2FyZSB+IE9mZmljZVZpc2l0cyArIE5hcmNvdGljcywgVFIsIGZhbWlseT1iaW5vbWlhbCkKc3VtbWFyeShnbG0xKQpgYGAKCiMjIyMgRTM6IFByZWRpY3Rpb24gJiBFdmFsdWF0aW9uCmBgYHtyfQpwcmVkID0gcHJlZGljdChnbG0xLCB0eXBlPSdyZXNwb25zZScpCm14ID0gdGFibGUoVFIkUG9vckNhcmUsIHByZWQgPiAwLjUpOyBteApjKGFjY3VyYWN5ID0gc3VtKGRpYWcobXgpKS9zdW0obXgpLAogIHNlbnNpdGl2aXR5ID0gbXhbMiwyXS9zdW0obXhbMixdKSwKICBzcGVjaWZpY2l0eSA9IG14WzEsMV0vc3VtKG14WzEsXSkpCmBgYAoKIyMjIyBFNDogUk9DICYgQVVDCmBgYHtyIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9CmxpYnJhcnkoUk9DUikKUk9DUnByZWQgPSBwcmVkaWN0aW9uKHByZWQsIFRSJFBvb3JDYXJlKQpST0NScGVyZiA9IHBlcmZvcm1hbmNlKFJPQ1JwcmVkLCAidHByIiwgImZwciIpCnBhcihjZXg9MC44KQpwbG90KFJPQ1JwZXJmLCBjb2xvcml6ZT1UUlVFLCBwcmludC5jdXRvZmZzLmF0PXNlcSgwLDEsMC4xKSkKYGBgCgpgYGB7cn0KYXMubnVtZXJpYyhwZXJmb3JtYW5jZShST0NScHJlZCwgImF1YyIpQHkudmFsdWVzKQpjYVRvb2xzOjpjb2xBVUMocHJlZCwgVFIkUG9vckNhcmUpCmBgYAoKPGJyPgoKLSAtIC0KIyMjIOOAkEbjgJFGcmFtaW5naGFtIEhlYXJ0IFN0dWR5CgpgYGB7cn0KZ2V0d2QoKQpzb3VyY2UoIkRQUC5SIikKYGBgCgojIyMjIEYxOiBSZWFkaW5nICYgU3BsaXR0aW5nCmBgYHtyfQpGIDwtIHJlYWQuY3N2KCJmcmFtaW5naGFtLmNzdiIpCnNldC5zZWVkKDEwMDApCnNwbGl0ID0gc2FtcGxlLnNwbGl0KEYkVGVuWWVhckNIRCwgU3BsaXRSYXRpbyA9IDAuNjUpClRSID0gc3Vic2V0KEYsIHNwbGl0PT1UUlVFKQpUUyA9IHN1YnNldChGLCBzcGxpdD09RkFMU0UpCmBgYAoKIyMjIyBGMjogTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbApgYGB7cn0KZ2xtMiA9IGdsbShUZW5ZZWFyQ0hEIH4gLiwgVFIsIGZhbWlseT1iaW5vbWlhbCkKc3VtbWFyeShnbG0yKQpgYGAKCiMjIyMgRjM6IFByZWRpY3Rpb24gJiBFdmFsdWF0aW9uCmBgYHtyfQpwcmVkID0gcHJlZGljdChnbG0yLCBUUywgdHlwZT0icmVzcG9uc2UiKQp5ID0gVFMkVGVuWWVhckNIRFshaXMubmEocHJlZCldICAgICAgICAgICAgICMgcmVtb3ZlIE5BCnByZWQgPSBwcmVkWyFpcy5uYShwcmVkKV0KCm14ID0gdGFibGUoeSwgcHJlZCA+IDAuNSk7IG14CmMoYWNjdXJhY3kgPSBzdW0oZGlhZyhteCkpL3N1bShteCksCiAgc2Vuc2l0aXZpdHkgPSBteFsyLDJdL3N1bShteFsyLF0pLAogIHNwZWNpZmljaXR5ID0gbXhbMSwxXS9zdW0obXhbMSxdKSkKYGBgCgojIyMjIEY0OiBBVUMgJiBEUFAKYGBge3IgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9Mi40fQpwYXIoY2V4PTAuNykKYXVjID0gRFBQKHByZWQsIHksIDEsIGI9c2VxKDAsMSwwLjAyKSkgICMgMC43NDIxMQpgYGAKCiMjIyMgRjU6IEV4cGVjdGVkIFJlc3VsdCAmIE9wdGltaXphdGlvbgoKCgoKCmBgYHtyIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTR9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIzIqMuefqemZowpwYXlvZmYgPSBtYXRyaXgoYygwLC0xMDAsLTEwLC02MCksMiwyKSAKY3V0b2ZmID0gc2VxKDAuMDIsIDAuNjQsIDAuMDEpCnJlc3VsdCA9IHNhcHBseShjdXRvZmYsIGZ1bmN0aW9uKHApIHN1bSh0YWJsZSh5LHByZWQ+cCkqcGF5b2ZmKSApCmkgPSB3aGljaC5tYXgocmVzdWx0KQpwYXIoY2V4PTAuNykKcGxvdChjdXRvZmYsIHJlc3VsdCwgdHlwZT0nbCcsIGNvbD0nY3lhbicsIGx3ZD0yLCBtYWluPXNwcmludGYoCiAgIk9wdG9tYWwgRXhwZWN0ZWQgUmVzdWx0OiAkJWQgQCAlLjJmIixyZXN1bHRbaV0sY3V0b2ZmW2ldKSkKYWJsaW5lKHY9c2VxKDAsMSwwLjA1KSxoPXNlcSgtMjMwMDAsLTE3MDAwLDUwMCksY29sPSdsaWdodGdyYXknLGx0eT0zKQphYmxpbmUodj1jdXRvZmZbaV0sY29sPSdyZWQnKQpgYGAKCuOAkCoqUSoq44CR5aaC5p6c5LuA6bq86YO95LiN5YGa77yM5pyf5pyb5aCx6YWs5piv5aSa5bCR77yfCjE5NzAwCuOAkCoqUSoq44CR5aaC5p6c5q+P5L2N55eF5Lq66YO95YGa5ZGi77yfCjIyNjAwCuOAkCoqUSoq44CR5Lul5LiK5ZOq5LiA56iu5YGa5rOV5pyf5pyb5aCx6YWs5q+U6LyD6auY77yfCuS7gOm6vOmDveS4jeWBmgrjgJAqKlEqKuOAkeWcqOaJgOacieeahOWVhuWLmeaDheWig+mDveaYr+mAmeeorueLgOazgeWXju+8nwrlkKYK44CQKipRKirjgJHkvaDlj6/ku6XmqKHmk6zlh7rjgIzlhajlgZrjgI3mr5TjgIzlhajkuI3lgZrjgI3pgoTopoHlpb3nmoTni4Dms4HjgIHkuKboiInlh7rkuIDlgIvmnIPnmbznlJ/pgJnnqK7ni4Dms4HnmoTllYbli5nmg4XlooPll47vvJ8KKDAsLTIwMCwtMTAwLDUwMCkg6aCQ5ris6aGn5a6i5YGP5aW977yMLTIwMOaIkeWAkemgkOa4rOipsumhp+WuouS4jeWWnOatoeatpOeUouWTge+8jOS9huWvpumam+S4iuipsumhp+WuouWWnOatoe+8jOWboOatpOaIkeWAkeacg+aQjeWkseaykuacieipsuW4guWgtOeahOeHn+aUtuOAgi0xMDDmmK/miJHlgJHph53lsI3pjK/oqqTnmoTnm67mqJnluILloLTljrvlgZrooYzpirfmtLvli5XnmoTmkI3lpLHmiJDmnKzjgII1MDDmmK/miJHlgJHmraPnorrpoJDmuKzpoaflrqLllpzlpb3kuKblsI3ku5blgJHlgZrooYzpirfmtLvli5XjgILmiYDku6XlnKjmraTmg4Xms4HkuYvkuIvvvIzlsI3miYDmnInkurrlgZrooYzpirfmtLvli5XmnIPmr5TlrozlhajkuI3lgZrpgoTlpb3jgIIKCmBgYHtyfQojCiMKIwpgYGAKCjxicj4KCiMjIyMgRjY6IFNpbXVsYXRpb24KYGBge3IgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9Nn0KCmxpYnJhcnkobWFuaXB1bGF0ZSkKbGlicmFyeShST0NSKQpwMCA9IHBhcihtZnJvdz1jKDIsMSksY2V4PTAuOCkKIyBtYW5pcHVsYXRlKHsKIyAgIFkwID0gLTIyMDAwOyBZMSA9IC0xMjAwMAojICAgbXggPSBtYXRyaXgoYyh0cnVlX25lZywgZmFsc2VfbmVnLCBmYWxzZV9wb3MsIHRydWVfcG9zKSwyLDIpIAojICAgY3ggPSBzZXEoMC4wMiwgMC42NCwgMC4wMSkKIyAgIHJ4ID0gc2FwcGx5KGN4LCBmdW5jdGlvbihwKSBzdW0odGFibGUoeSwgcHJlZD5wKSpteCkgKQojICAgaSA9IHdoaWNoLm1heChyeCkKIyAgIHBsb3QoY3gsIHJ4LCB0eXBlPSdsJyxjb2w9J2N5YW4nLGx3ZD0yLG1haW49c3ByaW50ZigKIyAgICAgIk9wdG9tYWwgRXhwZWN0ZWQgUmVzdWx0OiAkJWQgQCAlLjJmLCBUOiVkIixyeFtpXSxjeFtpXSxzdW0ocHJlZD5jeFtpXSkpLAojICAgICB5bGltPWMoWTAsWTEpKQojICAgYWJsaW5lKHY9Y3hbaV0sY29sPSdyZWQnKQojICAgYWJsaW5lKHY9c2VxKDAsMSwwLjEpLGg9c2VxKFkwLFkxLDIwMDApLGNvbD0nbGlnaHRncmF5JyxsdHk9MykKIyAgIERQUChwcmVkLCB5LCAxLCBiPXNlcSgwLDEsMC4wMikpCiMgICBhYmxpbmUodj1jeFtpXSxjb2w9J3JlZCcpbAojICAgfSwKIyAgIHRydWVfbmVnICA9IHNsaWRlcigtMTAwLDEwMCwwLHN0ZXA9NSksCiMgICBmYWxzZV9uZWcgPSBzbGlkZXIoLTEwMCwxMDAsLTEwMCxzdGVwPTUpLAojICAgZmFsc2VfcG9zID0gc2xpZGVyKC0xMDAsMTAwLC0xMCxzdGVwPTUpLAojICAgdHJ1ZV9wb3MgID0gc2xpZGVyKC0xMDAsMTAwLC02MCxzdGVwPTUpCiMgICApIApwYXIocDApCmBgYAoKCuOAkCoqUSoq44CR5pyJ5LqU56iu5oiQ5pys5YiG5Yil54K6IGAkNSwgJDEwLCAkMTUsICQyMCwgJDMwYCDnmoTol6XvvIzlroPlgJHliIbliKXlj6/ku6XlsIfpoqjpmqrmiJDmnKzlvp4gYCQxMDBgIOmZjeS9juWIsCBgJDcwLCAkNjAsICQ1MCwgJDQwLCAkMjVg77yM5ZOq5LiA56iu6Jel55qE5pyf5pyb5pWI55uK5piv5pyA5aSn55qE5ZGi77yfCgoKYGBge3J9CiMkNSwkMTAsJDE1LCQyMCwkMzAKIy0kMTc0MTUsLSQxNzUwMCwtJDE3NDUwLC0kMTc0MDAsLSQxNzY1NQojJDIw55qE6Jel5pyf5pyb5pWI55uK5pyA5aSnCmBgYAoKPGJyPgoKLSAtIC0KIyMjIOOAkEfjgJHliIbmnpDmtYHnqIvvvJros4fmlpnjgIHmqKHlnovjgIHpoJDmuKzjgIHmsbrnrZYKCgoKCgo8YnI+PGJyPjxicj48YnI+PGJyPgoKCgoKCgoKCgo=