1 Giới thiệu

Trong nghiên cứu y học, phần lớn các đại lượng mà ta khảo sát là biến liên tục, dương và thường có phân phối bất đối xứng. Trong khi đó, những phương pháp thống kê cơ bản mà sinh viên được dạy như t test hay ANOVA đều dựa trên giả định phân phối chuẩn (Gaussian).Đa số nghiên cứu sinh sử dụng một cách máy móc công cụ (thí dụ t test) và bỏ qua bước kiểm tra đặc tính phân bố của dữ liệu. Cách làm này có thể dẫn đến sai lầm trong suy diễn và/hoặc tiên lượng.

Ngay cả khi kết quả phản nghiệm bằng p_values có vẻ hợp lý, việc sử dụng giả định phân bố sai vẫn là nguy cơ tiềm ẩn cho sai lầm về tiên lượng hay ước tính hiệu ứng - thí dụ đơn giản nhất : phân phối chuẩn cho phép biến số ngẫu nhiên dao động tự do trên tập R,do đó một mô hình hồi quy tuyến tính có thể cho ra kết quả âm hoặc quá cao, trái với tự nhiên.

Mặt khác, nếu cẩn trọng hơn và phát hiện ra vấn đề dữ liệu phân phối lệch, các bạn cũng không có nhiều lựa chọn: những cách làm thông thường là hoán chuyển logarit hoặc dùng kiểm định phi tham số chỉ mang tính đối phó về kỹ thuật nhưng không phải là giải pháp tối ưu.

Trong bài thực hành này, Nhi muốn giới thiệu một giải pháp hiệu quả và phổ quát cho suy diễn thống kê trên dữ liệu là biến liên tục và có phân phối lệch dương, đó là mô hình hồi quy tuyến tính với phân bố gamma.

2 Bước 1: Chuẩn bị dữ liệu

Chúng ta bắt đầu câu chuyện bằng một bài toán suy diễn thống kê đơn giản : so sánh nồng độ bilirubin giữa 2 nhóm bệnh nhân mắc bệnh Viêm gan : nhóm thứ nhất bệnh nhẹ hơn và sống sót, nhóm thứ hai nặng hơn và đã tử vong; dựa theo bộ dữ liệu “hepatitis”.

Nhi tải dữ liệu từ database openml.org, sau khi loại bỏ missing values,giản lược chúng ta có 149 cases, gồm 119 trường hợp sống sót và 30 trường hợp tử vong. Biến kết quả là BILIRUBIN, phân nhóm là Class.

library(tidyverse)

df = read_csv("https://www.openml.org/data/get_csv/55/dataset_55_hepatitis.csv",na = '?')

df<-df%>%dplyr::select(Class,BILIRUBIN)%>%na.omit()

df$Class = recode(df$Class, `DIE` = "G2_Die", `LIVE` = "G1_Live")

tail(df,10)%>%knitr::kable()
Class BILIRUBIN
G1_Live 1.2
G2_Die 4.2
G2_Die 1.7
G1_Live 0.9
G1_Live 0.6
G2_Die 7.6
G1_Live 0.9
G1_Live 0.8
G1_Live 1.5
G2_Die 1.2

3 Bước 2: mô tả đặc tính phân bố:

Tiếp theo, Nhi sử dụng các hàm của package dplyr, psych và e1071 để làm một thống kê mô tả cho biến BILIRUBIN ở từng phân nhóm. Các trị số được tính bao gồm arithmetic mean, geometric mean, median, SD, Skewness, kurtosis, Min và Max:

df%>%group_by(Class)%>%
    summarise_at("BILIRUBIN",funs(n=length(.),
                                  Arith_Mean=mean,
                                  Geom_mean = psych::geometric.mean(.),
                                  Median=median,
                                  SD=sd,
                                  Skew = e1071::skewness(.),
                                  Kurt = e1071::kurtosis(.),
                                  Min=min,
                                  Max=max))
## # A tibble: 2 x 10
##   Class       n Arith_Mean Geom_mean Median    SD  Skew  Kurt   Min   Max
##   <chr>   <int>      <dbl>     <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 G1_Live   119       1.15      1.01   1    0.722  2.73  8.57   0.3   4.6
## 2 G2_Die     30       2.54      1.94   1.95 1.94   1.27  1.03   0.4   8

Như ta thấy, Bilirubin (như các đại lượng sinh học khác), có giá trị dương (>0), liên tục và phân phối bất đối xứng (lệch dương).Thang đo của bilirubin trong dữ liệu từ 0.3 đến 8 đơn vị. Hình ảnh phân bố ở mỗi phân nhóm có thể được khảo sát trực quan bằng KDE plot như sau:

df%>%ggplot()+
  geom_density(aes(x=BILIRUBIN,fill=Class),alpha=0.5)+
  scale_fill_manual(values=c("blue","red"))+
  theme_bw(10)

Một chi tiết khác là phân bố của bilirubin hoàn toàn khác biệt (không đồng dạng) giữa 2 phân nhóm, có nghĩa là kiểm định phi tham số như Mann_Whitney U không cho phép chúng ta suy diễn về khác biệt trung vị, mà chỉ có thể suy diễn sự khác biệt chung chung về loại phân phối.

Như đã trình bày ở trên, hoán chuyển logarit là một giải pháp thông dụng để đối phó với phân phối lệch dương, sau khi hoán chuyển, phân phối mới trở nên khá đối xứng và gần với phân phối Gaussian, tuy nhiên ta mất đi ý nghĩa của thang đo gốc:

df%>%ggplot()+
  geom_density(aes(x=log(BILIRUBIN),fill=Class),alpha=0.5)+
  scale_fill_manual(values=c("blue","red"))+
  theme_bw(10)

4 Bước 3: Suy diễn thống kê

Một kiểm định t trên dữ liệu hoán chuyển logarit thường đủ cho việc suy diễn thống kê về so sánh hơn/kém giữa 2 phân nhóm, tuy nhiên do thang đo gốc không còn nữa, ta chỉ có thể ước tính hiệu ứng khác biệt trên thang đo mới là log(bilirubin):

t.test(log(df$BILIRUBIN) ~ df$Class)
## 
##  Welch Two Sample t-test
## 
## data:  log(df$BILIRUBIN) by df$Class
## t = -4.4108, df = 34.413, p-value = 9.636e-05
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  -0.9487554 -0.3504332
## sample estimates:
## mean in group G1_Live  mean in group G2_Die 
##            0.01132794            0.66092224

Thay vì dùng kiểm định t, chúng ta có thể dùng mô hình hồi quy tuyến tính để suy diễn thống kê, với nhiều ưu điểm hơn như tính phổ quát và cho nhiều thông tin hơn.

Để thích nghi mô hình tuyến tính với dữ liệu biến kết quả lệch dương như hiện thời, chúng ta có nhiều giải pháp, thí dụ:

  1. Hoán chuyển logarit cho biến kết quả trước khi đưa vào mô hình
library(jtools)

lm(log(BILIRUBIN) ~ Class-1, data = df)%>%
  summ(confint = TRUE, digits = 3)
## MODEL INFO:
## Observations: 149
## Dependent Variable: log(BILIRUBIN)
## Type: OLS linear regression 
## 
## MODEL FIT:
## F(2,147) = 22.657, p = 0.000
## R² = 0.236
## Adj. R² = 0.225 
## 
## Standard errors: OLS
##               Est.   2.5% 97.5% t val.     p    
## ClassG1_Live 0.011 -0.086 0.109  0.230 0.819    
## ClassG2_Die  0.661  0.467 0.855  6.728 0.000 ***

Khi làm theo cách này, ta có 2 hướng diễn giải hiệu ứng của phân nhóm G2 (tử vong): hoặc dùng trực tiếp hệ số hồi quy (0.661) làm tăng trung bình (arithmetic mean) trên thang đo log(Y), hoặc sự thay đổi tỉ lệ thuận geometric mean của Y theo mean ratio = exp(0.661), trên thang đo nguyên thủy.

\[E[log(Y_i)] = \beta_0 + \beta_1X_1\]

Thí dụ ta có thể diễn giải: bệnh nhân ở phân nhóm tử vong có bilirubin cao hơn 1.94 lần so với phân nhóm sống sót, tức là geometric mean của bilirubin ở các bệnh nhân tử vong ước tính khoảng 1.96 đơn vị

Kết quả tiên lượng này không chính xác, thấp hơn so với thống kê mô tả mà ta đã làm bên trên.

1.011392 * exp(0.661)
## [1] 1.958791
  1. Cách làm thứ 2, đó là sử dụng giả định phân bố Gaussian cho kết quả Y trong mô hình tuyến tính, nhưng với link function (hàm liên kết) là logarit. Cách làm này có ưu điểm là chính xác hơn so với việc hoán chuyển trước khi dựng mô hình (Ta cần lưu ý: logarit của trung bình không tương đương với trung bình của logarit )
log(mean(df$BILIRUBIN)) 
## [1] 0.3559364
mean(log(df$BILIRUBIN))
## [1] 0.1421187

Mô hình này cũng cho phép diễn giải về hiệu ứng của phân nhóm tử vong theo 2 cách: trực tiếp trên thang đo log(Y) hoặc gián tiếp qua hàm exponential trên thang đo nguyên thủy. Tuy nhiên khác với mô hình 1, ở đây cả 2 suy diễn đều cho arithmetic mean.

\[E[log(Y_i)] = \beta_0 + \beta_1X_1\]

glm(formula =BILIRUBIN ~ Class,
                         family = gaussian(link = "log"),
                         data = df)%>%
  summ(confint = TRUE, digits = 3)
## MODEL INFO:
## Observations: 149
## Dependent Variable: BILIRUBIN
## Type: Generalized linear model
##   Family: gaussian 
##   Link function: log 
## 
## MODEL FIT:
## <U+03C7>²(1) = 46.768, p = 0.000
## Pseudo-R² (Cragg-Uhler) = 0.224
## Pseudo-R² (McFadden) = 0.075
## AIC = 449.093, BIC = 458.105 
## 
## Standard errors: MLE
##              Est.   2.5% 97.5% t val.     p    
## (Intercept) 0.136 -0.032 0.305  1.583 0.115    
## ClassG2_Die 0.797  0.570 1.024  6.882 0.000 ***
## 
## Estimated dispersion parameter = 1.161

Thí dụ: ta nói nhóm tử vong có log(bilirubin) tăng 0.797 đơn vị so với phân nhóm sống sót.

\[E[Y_i] = e^{(\beta_0 + \beta_1X_1)}\]

glm(formula =BILIRUBIN ~ Class,
                         family = gaussian(link = "log"),
                         data = df)%>%
  summ(confint = TRUE, digits = 3,exp=T)
## MODEL INFO:
## Observations: 149
## Dependent Variable: BILIRUBIN
## Type: Generalized linear model
##   Family: gaussian 
##   Link function: log 
## 
## MODEL FIT:
## <U+03C7>²(1) = 46.768, p = 0.000
## Pseudo-R² (Cragg-Uhler) = 0.224
## Pseudo-R² (McFadden) = 0.075
## AIC = 449.093, BIC = 458.105 
## 
## Standard errors: MLE
##             exp(Est.)  2.5% 97.5% t val.     p    
## (Intercept)     1.146 0.968 1.357  1.583 0.115    
## ClassG2_Die     2.219 1.768 2.784  6.882 0.000 ***
## 
## Estimated dispersion parameter = 1.161

Sau khi dùng hàm exponential cho intercept và ClassG2, ta có thể nhận ra: exp(b0) chính là arithmetic mean của phân nhóm G1, còn exp(b1) chính là tỉ lệ tăng của arithmetic mean nhóm G1 , cũng là arithmetic mean của nhóm G2.Như vậy phân nhóm G2 có bilirubin tăng 2.219 lần. Kết quả tiên lượng arithmetic mean của G2 cũng phù hợp hơn:

2.219 * 1.146218
## [1] 2.543458
  1. Cách thứ 3 chính là nội dung của bài thực hành này, đó là Hồi quy Gamma. Ở đây ta có thể đặt giả định là Y được ước lượng bằng 1 biến số ngẫu nhiên có phân phối Gamma, với link function là logarithm.
glm(formula =BILIRUBIN ~ Class,
    family = Gamma(link = "log"),
    data = df)%>%
  summ(confint = TRUE, digits = 3,exp=T)
## MODEL INFO:
## Observations: 149
## Dependent Variable: BILIRUBIN
## Type: Generalized linear model
##   Family: Gamma 
##   Link function: log 
## 
## MODEL FIT:
## <U+03C7>²(1) = 17.581, p = 0.000
## Pseudo-R² (Cragg-Uhler) = 0.321
## Pseudo-R² (McFadden) = 0.146
## AIC = 304.182, BIC = 313.194 
## 
## Standard errors: MLE
##             exp(Est.)  2.5% 97.5% t val.     p    
## (Intercept)     1.146 1.018 1.290  2.261 0.025   *
## ClassG2_Die     2.219 1.705 2.888  5.926 0.000 ***
## 
## Estimated dispersion parameter = 0.433

Mô hình hồi quy Gamma này cho ra kết quả tương đương như trên, với khác biệt nhỏ trong khoảng tin cậy và ý nghĩa thống kê của phân nhóm G1 (Intercept). AIC của mô hình gamma thấp hơn so với AIC của mô hình Gaussian (log).

Cách diễn giải cho mô hình hồi quy Gamma cũng giống như mô hình Gamma(log), vì chúng đều có hàm liên kết logarit.

2.219 * 1.146218
## [1] 2.543458

Bạn cũng có thể dùng mô hình hồi quy Gamma với link function là Identity thay vì log, mô hình này cho phép suy diễn trực tiếp về hiệu ứng của phân nhóm làm tăng arithmetic mean trên thang đo gốc của Y mà không cần hàm exponential, thí dụ ta có thể ước tính arithmetic của phân nhóm G2 đơn giản bằng cách cộng b0 và b1 = 1.146 + 1.397 = 2.54;

glm(formula =BILIRUBIN ~ Class,
    family = Gamma(link = "identity"),
    data = df)%>%
  summ(confint = TRUE, digits = 3,exp=F)
## MODEL INFO:
## Observations: 149
## Dependent Variable: BILIRUBIN
## Type: Generalized linear model
##   Family: Gamma 
##   Link function: identity 
## 
## MODEL FIT:
## <U+03C7>²(1) = 17.581, p = 0.000
## Pseudo-R² (Cragg-Uhler) = 0.321
## Pseudo-R² (McFadden) = 0.146
## AIC = 304.182, BIC = 313.194 
## 
## Standard errors: MLE
##              Est.  2.5% 97.5% t val.     p    
## (Intercept) 1.146 1.011 1.282 16.570 0.000 ***
## ClassG2_Die 1.397 0.783 2.011  4.458 0.000 ***
## 
## Estimated dispersion parameter = 0.433
1.397 + 1.146218
## [1] 2.543218

5 Hồi quy Gamma sử dụng gamlss

Trong phần tiếp theo, Nhi sẽ giới thiệu về mô hình hồi quy Gamma sử dụng package gamlss. Nội dung này là sự nâng cao so với hàm glm cơ bản trong R. Trước hết,package gamlss cho phép ta chứng minh: phân phối Gamma phù hợp hơn cho dữ liệu hiện thời so với phân phối Gaussian: thật vậy, mô hình hồi quy ước tính trung bình của Y sử dụng Gamma có AIC và BIC (SBC) thấp hơn.

library(gamlss)

histDist(data=df,BILIRUBIN,family='GA')

## 
## Family:  c("GA", "Gamma") 
## Fitting method: "nlminb" 
## 
## Call:  gamlssML(formula = BILIRUBIN, family = "GA", data = df) 
## 
## Mu Coefficients:
## [1]  0.3559
## Sigma Coefficients:
## [1]  -0.4566
## 
##  Degrees of Freedom for the fit: 2 Residual Deg. of Freedom   147 
## Global Deviance:     348.844 
##             AIC:     352.844 
##             SBC:     358.852
histDist(data=df,BILIRUBIN,family='NO')

## 
## Family:  c("NO", "Normal") 
## Fitting method: "nlminb" 
## 
## Call:  gamlssML(formula = BILIRUBIN, family = "NO", data = df) 
## 
## Mu Coefficients:
## [1]  1.428
## Sigma Coefficients:
## [1]  0.189
## 
##  Degrees of Freedom for the fit: 2 Residual Deg. of Freedom   147 
## Global Deviance:     479.174 
##             AIC:     483.174 
##             SBC:     489.182

Có nhiều cách định nghĩa phân phối Gamma, thông thường ta sử dụng 2 tham số là alpha (shape) và beta (scale), gói R cơ bản sử dụng cách định nghĩa này. Tuy nhiên trong gamlss, phân phối Gamma được xác định bằng cách khác, với 2 tham số là Mu và Sigma.

Hàm PDF của phân phối Gamma trong gamlss như sau:

\[f(y|\mu ,\sigma)=\frac{1}{\left ( \sigma ^{2} \mu \right )^{1/\sigma^{2}}}\frac{y^{\frac{1}{\sigma ^{2}}-1}e^{-y/(\sigma ^{2}\mu )}}{\Gamma \left ( 1/\sigma^{2} \right )}\]

gamlss cho phép dựng 2 mô hình riêng biệt cho 2 tham số: Mu và Sigma trong hàm PDF nêu trên. Liên hệ giữa Mu, Sigma và alpha, beta là như sau:

\[\mu = \alpha * \beta\] Mu có ý nghĩa như vị trí trung tâm (trung bình) của biến kết quả cần ước lượng, Mu phụ thuộc cả vào alpha và beta.

Sigma có quan hệ với alpha:

\[\sigma = \sqrt{\frac{1}{\alpha }}\]

Một khi có mu và sigma, có thể ước tính độ lệch chuẩn và phương sai của Y:

\[SD = \sigma * \mu\]

Thí nghiệm mô phỏng sau đây sẽ cho phép hình dung về hiệu ứng lên mu và sigma khi thay đổi 2 tham số scale và shape :

Khi giữ nguyên shape = 10 và thay đổi scale ;

library(gamlss.dist)

sim_gamma = function(n,a,b){
  mu=a*b
  sigma=sqrt(1/a)
  sd=sigma*mu
  x = rGA(n,mu,sigma)
  return(x)
}

sim_df = data_frame(X=NA,shape=NA,scale=NA)

for (i in c(1,2,3,4,5,10,15,20)){
  x=sim_gamma(1000,10,i)
  temp_df = data_frame(X=x,shape=rep(10,1000),scale=rep(i,1000))
  sim_df = rbind(sim_df,temp_df)%>%na.omit()
}

sim_df$mu=factor(sim_df$shape*sim_df$scale)
sim_df%>%ggplot()+geom_density(aes(x=X,fill=mu),alpha=0.5)+theme_bw()

Như ta thấy trong hình, Gamma là một phân phối rất linh hoạt và phù hợp một cách tự nhiên với quy luật phân phối của nhiều đại lượng sinh học, với 2 đặc tính: liên tục trong tập hợp R+, và có kiểu hình biến chuyển đa dạng, từ lệch dương cho đến đối xứng.Hình ảnh này phụ thuộc vào tham số alpha (shape)và beta (scale.)

Khi giữ nguyên scale = 5 và thay đổi shape

sim_df = data_frame(X=NA,shape=NA,scale=NA)

for (i in c(1,2,3,4,5,10,15,20)){
  x=sim_gamma(1000,i,5)
  temp_df = data_frame(X=x,shape=rep(i,1000),scale=rep(5,1000))
  sim_df = rbind(sim_df,temp_df)%>%na.omit()
}

sim_df$sigma=factor(sqrt(1/sim_df$shape))
sim_df%>%ggplot()+geom_density(aes(x=X,fill=sigma),alpha=0.5)+theme_bw()

sim_df = data_frame(X=NA,shape=NA,scale=NA)

for (b in c(1,2,3,4,5,10,15,20)){
  for (a in c(1,2,3,4,5,10,15,20)){
       x=sim_gamma(1000,a,b)
       temp_df = data_frame(X=x,shape=rep(a,1000),scale=rep(b,1000))
  sim_df = rbind(sim_df,temp_df)%>%na.omit()
  }
}

sim_df$mu=sim_df$shape*sim_df$scale
sim_df$sigma=sqrt(1/sim_df$shape)
sim_df$sd=sim_df$mu*sim_df$sigma

Tương tác giữa scale và shape,

sim_df%>%ggplot()+geom_path(aes(x=shape,y=mu,col=factor(scale)))+theme_bw()

sim_df%>%ggplot()+geom_path(aes(x=shape,y=sigma,col=factor(scale)))+facet_wrap(~scale)+theme_bw()

sim_df%>%ggplot()+geom_path(aes(x=shape,y=sd,col=factor(scale)))+theme_bw()

sim_df%>%ggplot()+geom_path(aes(x=scale,y=sd,col=factor(shape)))+theme_bw()

Bây giờ, Nhi dựng 2 mô hình hồi quy gamma với 2 tham số Mu và Sigma, cả 2 đều có link function (hàm liên kết) là log

library(broom)
library(sjPlot)

mod = gamlss(formula = BILIRUBIN ~ Class,
             data=df,
             sigma.fo = ~ Class,
             family = GA,
             trace=F,
       parallel="multicore",
       ncpus = nC)

summary(mod)
## ******************************************************************
## Family:  c("GA", "Gamma") 
## 
## Call:  gamlss(formula = BILIRUBIN ~ Class, sigma.formula = ~Class,  
##     family = GA, data = df, trace = F, parallel = "multicore",  
##     ncpus = nC) 
## 
## Fitting method: RS() 
## 
## ------------------------------------------------------------------
## Mu link function:  log
## Mu Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.13647    0.04497   3.034  0.00286 ** 
## ClassG2_Die  0.79701    0.13717   5.810  3.8e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## Sigma link function:  log
## Sigma Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -0.71214    0.06239 -11.414  < 2e-16 ***
## ClassG2_Die  0.36933    0.13511   2.734  0.00704 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## No. of observations in the fit:  149 
## Degrees of Freedom for the fit:  4
##       Residual Deg. of Freedom:  145 
##                       at cycle:  2 
##  
## Global Deviance:     289.6076 
##             AIC:     297.6076 
##             SBC:     309.6234 
## ******************************************************************
plot(mod)

## ******************************************************************
##        Summary of the Quantile Residuals
##                            mean   =  -0.00909365 
##                        variance   =  1.003027 
##                coef. of skewness  =  1.354485 
##                coef. of kurtosis  =  5.463048 
## Filliben correlation coefficient  =  0.9447688 
## ******************************************************************

Mô hình có AIC = 297.6 và BIC = 309.6, độ tự do sai số residual ở đây là 145; do chúng ta đã dùng 1 intercept và 1 phân nhóm G2 cho mỗi mô hình Mu và Sigma ((1+1)*2=4)

glance(mod)%>%knitr::kable()
df logLik AIC BIC deviance df.residual
0 -144.8038 297.6076 309.6234 289.6076 145

Sử dụng hàm augment của package broom , ta có thể vẽ biểu đồ khảo sát “marginal effect” của mô hình: lưu ý rằng cần áp dụng hàm exponential trên các hiệu ứng gốc trong mô hình để quy đổi về thang đo nguyên thủy.

Khi mô hình trở nên phức tạp, biểu diễn nội dung của nó bằng biểu đồ là một cách để người đọc nắm bắt được thông tin trực quan và đơn giản.

augment(mod)%>%mutate(UL= .fitted +.se.fit*1.96,
                      LL=.fitted-.se.fit*1.96)%>%
  ggplot()+
  geom_jitter(aes(x=Class,y=BILIRUBIN,col=Class),alpha=0.3)+
  geom_errorbar(aes(x=Class, ymin=exp(LL), ymax=exp(UL),col=Class),
                width=0.2, 
                size=1) + 
  geom_point(aes(x=Class, y=exp(.fitted),col=Class), 
             size=4) +
  geom_path(aes(x=Class, y=exp(.fitted)))+
  scale_color_manual(values=c("red","blue"))+
  theme_bw()  

Nội dung của mô hình có thể được diễn giải bằng các arithmetic mean ratio trên một biểu đồ, như đã bàn ở trên; lưu ý rằng mô hình gamlss cho phép suy diễn đồng thời hiệu ứng trên cả 2 tham số Mu (trung bình, vị trí trung tâm) và sigma (kiểu hình của phân bố).

Trong dữ liệu hiện thời, hiệu ứng của phân nhóm G1, G2 đều có ý nghĩa trong cả 2 tham số Mu và Sigma. Nói cách khác, khi bệnh viêm gan càng nặng (nguy cơ tử vong cao) thì bilirubin càng phân bố theo khuynh hướng: vị trí trung tâm dịch chuyển sang phải (tăng cao), nhưng sự phân tán cũng cao hơn và hình ảnh phân bố lệch nhiều hơn (skewness tăng).

tidy(mod)%>%mutate(Ratio=exp(estimate),
                   LL=exp(estimate-1.96*std.error),
                   UL=exp(estimate+1.96*std.error))%>%
  ggplot()+
  geom_errorbar(aes(x=term, ymin=LL, ymax=UL,col=parameter),
                width=0.2, 
                size=1) + 
  geom_point(aes(x=term,y=Ratio,col=parameter), 
             size=4)+
  geom_hline(yintercept=1,linetype=2,col="red4")+
  coord_flip()+
  facet_wrap(~parameter,ncol=1)+
  scale_color_manual(values=c("red","blue"))+
  theme_bw()  

6 Bootstrap mô hình gamlss

Ta sẽ nâng cao hơn 1 chút bằng một phân tích hồi quy bootstrap, Nhi dùng hàm bootstraps của package rsample để tạo ra 1000 phiên bản dữ liệu ngẫu nhiên khác nhau (có bảo tồn tỉ lệ 2 phân nhóm), sau đó dựng cùng một mô hình như trên cho mỗi phiên bản dữ liệu; trích xuất kết quả 1000 mô hình này.

library(rsample)

boot_df <- bootstraps(df, strata = "Class", times = 1000)

mu_form=as.formula(BILIRUBIN ~ Class)
sigma_form=as.formula(~ Class)

mod=df%>%gamlss(formula =.$BILIRUBIN ~ .$Class,
       sigma.fo = ~ .$Class,
       family = GA,
       trace=F,
       parallel="multicore",
       ncpus = nC)

model_dev = function(splits,...){
  mod = gamlss(..., data=analysis(splits),
               family = GA,
               trace=F,
               parallel="multicore",
               ncpus = nC)
  
  tidy_df=broom::tidy(mod,
              conf.int = TRUE, 
              conf.level = 0.975)
  
  glance_df = glance(mod)
  
  tidy_df<-tidy_df%>%mutate(BIC=glance_df$BIC,
                   AIC=glance_df$AIC,
                   R2=Rsq(mod))
  return(tidy_df)
  }

coef_list <- map(.x = boot_df$splits, 
             .f = model_dev, 
             mu_form,
             sigma_form)%>%
  map_df(~data_frame(Para=.x$parameter,
                     Term=.x$term,
                     Est=.x$estimate,
                     Ratio=exp(.x$estimate),
                     t=.x$statistic,
                     p_val=.x$p.value,
                     BIC=.x$BIC,
                     AIC=.x$AIC,
                     R2=.x$R2))

Đây là dữ liệu kết quả của 1000 mô hình gamma:

head(coef_list)%>%knitr::kable()
Para Term Est Ratio t p_val BIC AIC R2
mu (Intercept) 0.0837849 1.0873950 2.167614 0.0317979 258.6515 246.6357 0.3523451
mu ClassG2_Die 0.6692023 1.9526790 4.543393 0.0000115 258.6515 246.6357 0.3523451
sigma (Intercept) -0.8635693 0.4216544 -13.709218 0.0000000 258.6515 246.6357 0.3523451
sigma ClassG2_Die 0.6131483 1.8462348 4.573322 0.0000101 258.6515 246.6357 0.3523451
mu (Intercept) 0.0853293 1.0890756 2.007896 0.0464866 286.8091 274.7933 0.3943641
mu ClassG2_Die 0.8752973 2.3995885 6.596875 0.0000000 286.8091 274.7933 0.3943641

Phân phối của các arithmetic mean ratio từ 1000 mô hình:

library(ggridges)

coef_list%>%
  ggplot(aes(y=Term,x=Ratio))+
  geom_density_ridges(alpha=0.7,scale=0.85,
                      aes(fill=Term),
                      show.legend = F)+
  scale_y_discrete(expand = c(0.1,0.5))+
  geom_vline(xintercept = 1,linetype=2,col="red3")+
  theme_bw()+
  facet_wrap(~Para,ncol=2,scales = "free")

Phân tích bootstrap cho phép ta ước tính khoảng tin cậy 97.5% cho tỉ số arithmetic mean ratio của phân nhóm G2 cho Mu, Sigma:

coef_list%>%
  group_by(Para,Term)%>%
    summarise_at("Ratio",funs(n=length(.),
                              Mean=mean,
                              Median=median,
                              LL=quantile(.,0.025),
                              UL=quantile(.,0.975),
                              Min=min,
                              Max=max))
## # A tibble: 4 x 9
## # Groups:   Para [2]
##   Para  Term            n  Mean Median    LL    UL   Min   Max
##   <chr> <chr>       <int> <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 mu    (Intercept)  1000 1.14   1.14  1.03  1.27  0.946 1.39 
## 2 mu    ClassG2_Die  1000 2.24   2.22  1.67  2.91  1.32  3.35 
## 3 sigma (Intercept)  1000 0.485  0.483 0.403 0.565 0.342 0.604
## 4 sigma ClassG2_Die  1000 1.45   1.45  1.09  1.84  0.910 2.19

Kết quả này khép lại bài thực hành, hy vọng các bạn đã có những giây phút học tập vui vẻ. Như bạn thấy, mô hình hồi quy là một công cụ hữu ích ; hẹn gặp các bạn trong một lần khác.

LS0tDQp0aXRsZTogIk3DtCBow6xuaCBo4buTaSBxdXkgR2FtbWEiIA0KYXV0aG9yOiAiTMOqIE5n4buNYyBLaOG6oyBOaGkiDQpkYXRlOiAiMzAgVGjDoW5nIDMgbsSDbSAyMDE5Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImRlZmF1bHQiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgZGV2OiAnc3ZnJw0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIVtdKGdhbW1hLnBuZykNCg0KIyBHaeG7m2kgdGhp4buHdSANCg0KVHJvbmcgbmdoacOqbiBj4bupdSB5IGjhu41jLCBwaOG6p24gbOG7m24gY8OhYyDEkeG6oWkgbMaw4bujbmcgbcOgIHRhIGto4bqjbyBzw6F0IGzDoCBiaeG6v24gbGnDqm4gdOG7pWMsIGTGsMahbmcgdsOgIHRoxrDhu51uZyBjw7MgcGjDom4gcGjhu5FpIGLhuqV0IMSR4buRaSB44bupbmcuIFRyb25nIGtoaSDEkcOzLCBuaOG7r25nIHBoxrDGoW5nIHBow6FwIHRo4buRbmcga8OqIGPGoSBi4bqjbiBtw6Agc2luaCB2acOqbiDEkcaw4bujYyBk4bqheSBuaMawIHQgdGVzdCBoYXkgQU5PVkEgxJHhu4F1IGThu7FhIHRyw6puIGdp4bqjIMSR4buLbmggcGjDom4gcGjhu5FpIGNodeG6qW4gKEdhdXNzaWFuKS7EkGEgc+G7kSBuZ2hpw6puIGPhu6l1IHNpbmggc+G7rSBk4bulbmcgbeG7mXQgY8OhY2ggbcOheSBtw7NjIGPDtG5nIGPhu6UgKHRow60gZOG7pSB0IHRlc3QpIHbDoCBi4buPIHF1YSBixrDhu5tjIGtp4buDbSB0cmEgxJHhurdjIHTDrW5oIHBow6JuIGLhu5EgY+G7p2EgZOG7ryBsaeG7h3UuIEPDoWNoIGzDoG0gbsOgeSBjw7MgdGjhu4MgZOG6q24gxJHhur9uIHNhaSBs4bqnbSB0cm9uZyBzdXkgZGnhu4VuIHbDoC9ob+G6t2MgdGnDqm4gbMaw4bujbmcuDQoNCk5nYXkgY+G6oyBraGkga+G6v3QgcXXhuqMgcGjhuqNuIG5naGnhu4dtIGLhurFuZyBwX3ZhbHVlcyBjw7MgduG6uyBo4bujcCBsw70sIHZp4buHYyBz4butIGThu6VuZyBnaeG6oyDEkeG7i25oIHBow6JuIGLhu5Egc2FpIHbhuqtuIGzDoCBuZ3V5IGPGoSB0aeG7gW0g4bqpbiBjaG8gc2FpIGzhuqdtIHbhu4EgdGnDqm4gbMaw4bujbmcgaGF5IMaw4bubYyB0w61uaCBoaeG7h3Ug4bupbmcgLSB0aMOtIGThu6UgxJHGoW4gZ2nhuqNuIG5o4bqldCA6IHBow6JuIHBo4buRaSBjaHXhuqluIGNobyBwaMOpcCBiaeG6v24gc+G7kSBuZ+G6q3Ugbmhpw6puIGRhbyDEkeG7mW5nIHThu7EgZG8gdHLDqm4gdOG6rXAgUixkbyDEkcOzIG3hu5l0IG3DtCBow6xuaCBo4buTaSBxdXkgdHV54bq/biB0w61uaCBjw7MgdGjhu4MgY2hvIHJhIGvhur90IHF14bqjIMOibSBob+G6t2MgcXXDoSBjYW8sIHRyw6FpIHbhu5tpIHThu7Egbmhpw6puLiANCg0KTeG6t3Qga2jDoWMsIG7hur91IGPhuqluIHRy4buNbmcgaMahbiB2w6AgcGjDoXQgaGnhu4duIHJhIHbhuqVuIMSR4buBIGThu68gbGnhu4d1IHBow6JuIHBo4buRaSBs4buHY2gsIGPDoWMgYuG6oW4gY8Wpbmcga2jDtG5nIGPDsyBuaGnhu4F1IGzhu7FhIGNo4buNbjogbmjhu69uZyBjw6FjaCBsw6BtIHRow7RuZyB0aMaw4budbmcgbMOgIGhvw6FuIGNodXnhu4NuIGxvZ2FyaXQgaG/hurdjIGTDuW5nIGtp4buDbSDEkeG7i25oIHBoaSB0aGFtIHPhu5EgY2jhu4kgbWFuZyB0w61uaCDEkeG7kWkgcGjDsyB24buBIGvhu7kgdGh14bqtdCBuaMawbmcga2jDtG5nIHBo4bqjaSBsw6AgZ2nhuqNpIHBow6FwIHThu5FpIMawdS4NCg0KVHJvbmcgYsOgaSB0aOG7sWMgaMOgbmggbsOgeSwgTmhpIG114buRbiBnaeG7m2kgdGhp4buHdSBt4buZdCBnaeG6o2kgcGjDoXAgaGnhu4d1IHF14bqjIHbDoCBwaOG7lSBxdcOhdCBjaG8gc3V5IGRp4buFbiB0aOG7kW5nIGvDqiB0csOqbiBk4buvIGxp4buHdSBsw6AgYmnhur9uIGxpw6puIHThu6VjIHbDoCBjw7MgcGjDom4gcGjhu5FpIGzhu4djaCBkxrDGoW5nLCDEkcOzIGzDoCBtw7QgaMOsbmggaOG7k2kgcXV5IHR1eeG6v24gdMOtbmggduG7m2kgcGjDom4gYuG7kSBnYW1tYS4gDQoNCiMgQsaw4bubYyAxOiBDaHXhuqluIGLhu4sgZOG7ryBsaeG7h3UNCg0KQ2jDum5nIHRhIGLhuq90IMSR4bqndSBjw6J1IGNodXnhu4duIGLhurFuZyBt4buZdCBiw6BpIHRvw6FuIHN1eSBkaeG7hW4gdGjhu5FuZyBrw6ogxJHGoW4gZ2nhuqNuIDogc28gc8OhbmggbuG7k25nIMSR4buZIGJpbGlydWJpbiBnaeG7r2EgMiBuaMOzbSBi4buHbmggbmjDom4gbeG6r2MgYuG7h25oIFZpw6ptIGdhbiA6IG5ow7NtIHRo4bupIG5o4bqldCBi4buHbmggbmjhurkgaMahbiB2w6Agc+G7kW5nIHPDs3QsIG5ow7NtIHRo4bupIGhhaSBu4bq3bmcgaMahbiB2w6AgxJHDoyB04butIHZvbmc7IGThu7FhIHRoZW8gYuG7mSBk4buvIGxp4buHdSAiaGVwYXRpdGlzIi4NCg0KTmhpIHThuqNpIGThu68gbGnhu4d1IHThu6sgZGF0YWJhc2Ugb3Blbm1sLm9yZywgc2F1IGtoaSBsb+G6oWkgYuG7jyBtaXNzaW5nIHZhbHVlcyxnaeG6o24gbMaw4bujYyBjaMO6bmcgdGEgY8OzIDE0OSBjYXNlcywgZ+G7k20gMTE5IHRyxrDhu51uZyBo4bujcCBz4buRbmcgc8OzdCB2w6AgMzAgdHLGsOG7nW5nIGjhu6NwIHThu60gdm9uZy4gQmnhur9uIGvhur90IHF14bqjIGzDoCBCSUxJUlVCSU4sIHBow6JuIG5ow7NtIGzDoCBDbGFzcy4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KZGYgPSByZWFkX2NzdigiaHR0cHM6Ly93d3cub3Blbm1sLm9yZy9kYXRhL2dldF9jc3YvNTUvZGF0YXNldF81NV9oZXBhdGl0aXMuY3N2IixuYSA9ICc/JykNCg0KZGY8LWRmJT4lZHBseXI6OnNlbGVjdChDbGFzcyxCSUxJUlVCSU4pJT4lbmEub21pdCgpDQoNCmRmJENsYXNzID0gcmVjb2RlKGRmJENsYXNzLCBgRElFYCA9ICJHMl9EaWUiLCBgTElWRWAgPSAiRzFfTGl2ZSIpDQoNCnRhaWwoZGYsMTApJT4la25pdHI6OmthYmxlKCkNCmBgYA0KDQojIELGsOG7m2MgMjogbcO0IHThuqMgxJHhurdjIHTDrW5oIHBow6JuIGLhu5E6DQoNClRp4bq/cCB0aGVvLCBOaGkgc+G7rSBk4bulbmcgY8OhYyBow6BtIGPhu6dhIHBhY2thZ2UgZHBseXIsIHBzeWNoIHbDoCBlMTA3MSDEkeG7gyBsw6BtIG3hu5l0IHRo4buRbmcga8OqIG3DtCB04bqjIGNobyBiaeG6v24gQklMSVJVQklOIOG7nyB04burbmcgcGjDom4gbmjDs20uIEPDoWMgdHLhu4sgc+G7kSDEkcaw4bujYyB0w61uaCBiYW8gZ+G7k20gYXJpdGhtZXRpYyBtZWFuLCBnZW9tZXRyaWMgbWVhbiwgbWVkaWFuLCBTRCwgU2tld25lc3MsIGt1cnRvc2lzLCBNaW4gdsOgIE1heDoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZiU+JWdyb3VwX2J5KENsYXNzKSU+JQ0KICAgIHN1bW1hcmlzZV9hdCgiQklMSVJVQklOIixmdW5zKG49bGVuZ3RoKC4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFyaXRoX01lYW49bWVhbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHZW9tX21lYW4gPSBwc3ljaDo6Z2VvbWV0cmljLm1lYW4oLiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTWVkaWFuPW1lZGlhbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTRD1zZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTa2V3ID0gZTEwNzE6OnNrZXduZXNzKC4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEt1cnQgPSBlMTA3MTo6a3VydG9zaXMoLiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTWluPW1pbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNYXg9bWF4KSkNCmBgYA0KDQpOaMawIHRhIHRo4bqleSwgQmlsaXJ1YmluIChuaMawIGPDoWMgxJHhuqFpIGzGsOG7o25nIHNpbmggaOG7jWMga2jDoWMpLCBjw7MgZ2nDoSB0cuG7iyBkxrDGoW5nICg+MCksIGxpw6puIHThu6VjIHbDoCBwaMOibiBwaOG7kWkgYuG6pXQgxJHhu5FpIHjhu6luZyAobOG7h2NoIGTGsMahbmcpLlRoYW5nIMSRbyBj4bunYSBiaWxpcnViaW4gdHJvbmcgZOG7ryBsaeG7h3UgdOG7qyAwLjMgxJHhur9uIDggxJHGoW4gduG7iy4gSMOsbmgg4bqjbmggcGjDom4gYuG7kSDhu58gbeG7l2kgcGjDom4gbmjDs20gY8OzIHRo4buDIMSRxrDhu6NjIGto4bqjbyBzw6F0IHRy4buxYyBxdWFuIGLhurFuZyBLREUgcGxvdCBuaMawIHNhdToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZiU+JWdncGxvdCgpKw0KICBnZW9tX2RlbnNpdHkoYWVzKHg9QklMSVJVQklOLGZpbGw9Q2xhc3MpLGFscGhhPTAuNSkrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJibHVlIiwicmVkIikpKw0KICB0aGVtZV9idygxMCkNCmBgYA0KDQpN4buZdCBjaGkgdGnhur90IGtow6FjIGzDoCBwaMOibiBi4buRIGPhu6dhIGJpbGlydWJpbiBob8OgbiB0b8OgbiBraMOhYyBiaeG7h3QgKGtow7RuZyDEkeG7k25nIGThuqFuZykgZ2nhu69hIDIgcGjDom4gbmjDs20sIGPDsyBuZ2jEqWEgbMOgIGtp4buDbSDEkeG7i25oIHBoaSB0aGFtIHPhu5EgbmjGsCBNYW5uX1doaXRuZXkgVSBraMO0bmcgY2hvIHBow6lwIGNow7puZyB0YSBzdXkgZGnhu4VuIHbhu4Ega2jDoWMgYmnhu4d0IHRydW5nIHbhu4ssIG3DoCBjaOG7iSBjw7MgdGjhu4Mgc3V5IGRp4buFbiBz4buxIGtow6FjIGJp4buHdCBjaHVuZyBjaHVuZyB24buBIGxv4bqhaSBwaMOibiBwaOG7kWkuIA0KDQpOaMawIMSRw6MgdHLDrG5oIGLDoHkg4bufIHRyw6puLCBob8OhbiBjaHV54buDbiBsb2dhcml0IGzDoCBt4buZdCBnaeG6o2kgcGjDoXAgdGjDtG5nIGThu6VuZyDEkeG7gyDEkeG7kWkgcGjDsyB24bubaSBwaMOibiBwaOG7kWkgbOG7h2NoIGTGsMahbmcsIHNhdSBraGkgaG/DoW4gY2h1eeG7g24sIHBow6JuIHBo4buRaSBt4bubaSB0cuG7nyBuw6puIGtow6EgxJHhu5FpIHjhu6luZyB2w6AgZ+G6p24gduG7m2kgcGjDom4gcGjhu5FpIEdhdXNzaWFuLCB0dXkgbmhpw6puIHRhIG3huqV0IMSRaSDDvSBuZ2jEqWEgY+G7p2EgdGhhbmcgxJFvIGfhu5FjOiANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZiU+JWdncGxvdCgpKw0KICBnZW9tX2RlbnNpdHkoYWVzKHg9bG9nKEJJTElSVUJJTiksZmlsbD1DbGFzcyksYWxwaGE9MC41KSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoImJsdWUiLCJyZWQiKSkrDQogIHRoZW1lX2J3KDEwKQ0KYGBgDQoNCiMgQsaw4bubYyAzOiBTdXkgZGnhu4VuIHRo4buRbmcga8OqDQoNCk3hu5l0IGtp4buDbSDEkeG7i25oIHQgdHLDqm4gZOG7ryBsaeG7h3UgaG/DoW4gY2h1eeG7g24gbG9nYXJpdCB0aMaw4budbmcgxJHhu6cgY2hvIHZp4buHYyBzdXkgZGnhu4VuIHRo4buRbmcga8OqIHbhu4Egc28gc8OhbmggaMahbi9rw6ltIGdp4buvYSAyIHBow6JuIG5ow7NtLCB0dXkgbmhpw6puIGRvIHRoYW5nIMSRbyBn4buRYyBraMO0bmcgY8OybiBu4buvYSwgdGEgY2jhu4kgY8OzIHRo4buDIMaw4bubYyB0w61uaCBoaeG7h3Ug4bupbmcga2jDoWMgYmnhu4d0IHRyw6puIHRoYW5nIMSRbyBt4bubaSBsw6AgbG9nKGJpbGlydWJpbik6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdC50ZXN0KGxvZyhkZiRCSUxJUlVCSU4pIH4gZGYkQ2xhc3MpDQpgYGANCg0KVGhheSB2w6wgZMO5bmcga2nhu4NtIMSR4buLbmggdCwgY2jDum5nIHRhIGPDsyB0aOG7gyBkw7luZyBtw7QgaMOsbmggaOG7k2kgcXV5IHR1eeG6v24gdMOtbmggxJHhu4Mgc3V5IGRp4buFbiB0aOG7kW5nIGvDqiwgduG7m2kgbmhp4buBdSDGsHUgxJFp4buDbSBoxqFuIG5oxrAgdMOtbmggcGjhu5UgcXXDoXQgdsOgIGNobyBuaGnhu4F1IHRow7RuZyB0aW4gaMahbi4NCg0KxJDhu4MgdGjDrWNoIG5naGkgbcO0IGjDrG5oIHR1eeG6v24gdMOtbmggduG7m2kgZOG7ryBsaeG7h3UgYmnhur9uIGvhur90IHF14bqjIGzhu4djaCBkxrDGoW5nIG5oxrAgaGnhu4duIHRo4budaSwgY2jDum5nIHRhIGPDsyBuaGnhu4F1IGdp4bqjaSBwaMOhcCwgdGjDrSBk4bulOg0KDQoxKSBIb8OhbiBjaHV54buDbiBsb2dhcml0IGNobyBiaeG6v24ga+G6v3QgcXXhuqMgdHLGsOG7m2Mga2hpIMSRxrBhIHbDoG8gbcO0IGjDrG5oDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShqdG9vbHMpDQoNCmxtKGxvZyhCSUxJUlVCSU4pIH4gQ2xhc3MtMSwgZGF0YSA9IGRmKSU+JQ0KICBzdW1tKGNvbmZpbnQgPSBUUlVFLCBkaWdpdHMgPSAzKQ0KYGBgDQoNCktoaSBsw6BtIHRoZW8gY8OhY2ggbsOgeSwgdGEgY8OzIDIgaMaw4bubbmcgZGnhu4VuIGdp4bqjaSBoaeG7h3Ug4bupbmcgY+G7p2EgcGjDom4gbmjDs20gRzIgKHThu60gdm9uZyk6IGhv4bq3YyBkw7luZyB0cuG7sWMgdGnhur9wIGjhu4cgc+G7kSBo4buTaSBxdXkgKDAuNjYxKSBsw6BtIHTEg25nIHRydW5nIGLDrG5oIChhcml0aG1ldGljIG1lYW4pIHRyw6puIHRoYW5nIMSRbyBsb2coWSksIGhv4bq3YyBz4buxIHRoYXkgxJHhu5VpIHThu4kgbOG7hyB0aHXhuq1uIGdlb21ldHJpYyBtZWFuIGPhu6dhIFkgdGhlbyBtZWFuIHJhdGlvID0gZXhwKDAuNjYxKSwgdHLDqm4gdGhhbmcgxJFvIG5ndXnDqm4gdGjhu6d5Lg0KDQokJEVbbG9nKFlfaSldID0gXGJldGFfMCArIFxiZXRhXzFYXzEkJA0KDQpUaMOtIGThu6UgdGEgY8OzIHRo4buDIGRp4buFbiBnaeG6o2k6IGLhu4duaCBuaMOibiDhu58gcGjDom4gbmjDs20gdOG7rSB2b25nIGPDsyBiaWxpcnViaW4gY2FvIGjGoW4gMS45NCBs4bqnbiBzbyB24bubaSBwaMOibiBuaMOzbSBz4buRbmcgc8OzdCwgdOG7qWMgbMOgIGdlb21ldHJpYyBtZWFuIGPhu6dhIGJpbGlydWJpbiDhu58gY8OhYyBi4buHbmggbmjDom4gdOG7rSB2b25nIMaw4bubYyB0w61uaCBraG/huqNuZyAxLjk2IMSRxqFuIHbhu4sNCg0KS+G6v3QgcXXhuqMgdGnDqm4gbMaw4bujbmcgbsOgeSBraMO0bmcgY2jDrW5oIHjDoWMsIHRo4bqlcCBoxqFuIHNvIHbhu5tpIHRo4buRbmcga8OqIG3DtCB04bqjIG3DoCB0YSDEkcOjIGzDoG0gYsOqbiB0csOqbi4gDQoNCmBgYHtyfQ0KMS4wMTEzOTIgKiBleHAoMC42NjEpDQpgYGANCg0KMikgQ8OhY2ggbMOgbSB0aOG7qSAyLCDEkcOzIGzDoCBz4butIGThu6VuZyBnaeG6oyDEkeG7i25oIHBow6JuIGLhu5EgR2F1c3NpYW4gY2hvIGvhur90IHF14bqjIFkgdHJvbmcgbcO0IGjDrG5oIHR1eeG6v24gdMOtbmgsIG5oxrBuZyB24bubaSBsaW5rIGZ1bmN0aW9uICAoaMOgbSBsacOqbiBr4bq/dCkgbMOgIGxvZ2FyaXQuIEPDoWNoIGzDoG0gbsOgeSBjw7MgxrB1IMSRaeG7g20gbMOgIGNow61uaCB4w6FjIGjGoW4gc28gduG7m2kgdmnhu4djIGhvw6FuIGNodXnhu4NuIHRyxrDhu5tjIGtoaSBk4buxbmcgbcO0IGjDrG5oIChUYSBj4bqnbiBsxrB1IMO9OiBsb2dhcml0IGPhu6dhIHRydW5nIGLDrG5oIGtow7RuZyB0xrDGoW5nIMSRxrDGoW5nIHbhu5tpIHRydW5nIGLDrG5oIGPhu6dhIGxvZ2FyaXQgKQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxvZyhtZWFuKGRmJEJJTElSVUJJTikpIA0KbWVhbihsb2coZGYkQklMSVJVQklOKSkNCmBgYA0KDQpNw7QgaMOsbmggbsOgeSBjxaluZyBjaG8gcGjDqXAgZGnhu4VuIGdp4bqjaSB24buBIGhp4buHdSDhu6luZyBj4bunYSBwaMOibiBuaMOzbSB04butIHZvbmcgdGhlbyAyIGPDoWNoOiB0cuG7sWMgdGnhur9wIHRyw6puIHRoYW5nIMSRbyBsb2coWSkgaG/hurdjIGdpw6FuIHRp4bq/cCBxdWEgaMOgbSBleHBvbmVudGlhbCB0csOqbiB0aGFuZyDEkW8gbmd1ecOqbiB0aOG7p3kuIFR1eSBuaGnDqm4ga2jDoWMgduG7m2kgbcO0IGjDrG5oIDEsIOG7nyDEkcOieSBj4bqjIDIgc3V5IGRp4buFbiDEkeG7gXUgY2hvIGFyaXRobWV0aWMgbWVhbi4NCg0KJCRFW2xvZyhZX2kpXSA9IFxiZXRhXzAgKyBcYmV0YV8xWF8xJCQNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpnbG0oZm9ybXVsYSA9QklMSVJVQklOIH4gQ2xhc3MsDQogICAgICAgICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gZ2F1c3NpYW4obGluayA9ICJsb2ciKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGYpJT4lDQogIHN1bW0oY29uZmludCA9IFRSVUUsIGRpZ2l0cyA9IDMpDQpgYGANCg0KVGjDrSBk4bulOiB0YSBuw7NpIG5ow7NtIHThu60gdm9uZyBjw7MgbG9nKGJpbGlydWJpbikgdMSDbmcgMC43OTcgxJHGoW4gduG7iyBzbyB24bubaSBwaMOibiBuaMOzbSBz4buRbmcgc8OzdC4NCg0KJCRFW1lfaV0gPSBlXnsoXGJldGFfMCArIFxiZXRhXzFYXzEpfSQkDQoNCmBgYHtyfQ0KZ2xtKGZvcm11bGEgPUJJTElSVUJJTiB+IENsYXNzLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseSA9IGdhdXNzaWFuKGxpbmsgPSAibG9nIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRmKSU+JQ0KICBzdW1tKGNvbmZpbnQgPSBUUlVFLCBkaWdpdHMgPSAzLGV4cD1UKQ0KYGBgDQoNClNhdSBraGkgZMO5bmcgaMOgbSBleHBvbmVudGlhbCBjaG8gaW50ZXJjZXB0IHbDoCBDbGFzc0cyLCB0YSBjw7MgdGjhu4Mgbmjhuq1uIHJhOiBleHAoYjApIGNow61uaCBsw6AgYXJpdGhtZXRpYyBtZWFuIGPhu6dhIHBow6JuIG5ow7NtIEcxLCBjw7JuIGV4cChiMSkgY2jDrW5oIGzDoCB04buJIGzhu4cgdMSDbmcgY+G7p2EgYXJpdGhtZXRpYyBtZWFuIG5ow7NtIEcxICwgY8WpbmcgbMOgIGFyaXRobWV0aWMgbWVhbiBj4bunYSBuaMOzbSBHMi5OaMawIHbhuq15IHBow6JuIG5ow7NtIEcyIGPDsyBiaWxpcnViaW4gdMSDbmcgMi4yMTkgbOG6p24uIEvhur90IHF14bqjIHRpw6puIGzGsOG7o25nIGFyaXRobWV0aWMgbWVhbiBj4bunYSBHMiBjxaluZyBwaMO5IGjhu6NwIGjGoW46DQoNCmBgYHtyfQ0KMi4yMTkgKiAxLjE0NjIxOA0KYGBgDQoNCjMpIEPDoWNoIHRo4bupIDMgY2jDrW5oIGzDoCBu4buZaSBkdW5nIGPhu6dhIGLDoGkgdGjhu7FjIGjDoG5oIG7DoHksIMSRw7MgbMOgIEjhu5NpIHF1eSBHYW1tYS4g4bueIMSRw6J5IHRhIGPDsyB0aOG7gyDEkeG6t3QgZ2nhuqMgxJHhu4tuaCBsw6AgWSDEkcaw4bujYyDGsOG7m2MgbMaw4bujbmcgYuG6sW5nIDEgYmnhur9uIHPhu5Egbmfhuqt1IG5oacOqbiBjw7MgcGjDom4gcGjhu5FpIEdhbW1hLCB24bubaSBsaW5rIGZ1bmN0aW9uIGzDoCBsb2dhcml0aG0uDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZ2xtKGZvcm11bGEgPUJJTElSVUJJTiB+IENsYXNzLA0KICAgIGZhbWlseSA9IEdhbW1hKGxpbmsgPSAibG9nIiksDQogICAgZGF0YSA9IGRmKSU+JQ0KICBzdW1tKGNvbmZpbnQgPSBUUlVFLCBkaWdpdHMgPSAzLGV4cD1UKQ0KYGBgDQoNCk3DtCBow6xuaCBo4buTaSBxdXkgR2FtbWEgbsOgeSBjaG8gcmEga+G6v3QgcXXhuqMgdMawxqFuZyDEkcawxqFuZyBuaMawIHRyw6puLCB24bubaSBraMOhYyBiaeG7h3Qgbmjhu48gdHJvbmcga2hv4bqjbmcgdGluIGPhuq15IHbDoCDDvSBuZ2jEqWEgdGjhu5FuZyBrw6ogY+G7p2EgcGjDom4gbmjDs20gRzEgKEludGVyY2VwdCkuIEFJQyBj4bunYSBtw7QgaMOsbmggZ2FtbWEgdGjhuqVwIGjGoW4gc28gduG7m2kgQUlDIGPhu6dhIG3DtCBow6xuaCBHYXVzc2lhbiAobG9nKS4NCg0KQ8OhY2ggZGnhu4VuIGdp4bqjaSBjaG8gbcO0IGjDrG5oIGjhu5NpIHF1eSBHYW1tYSBjxaluZyBnaeG7kW5nIG5oxrAgbcO0IGjDrG5oIEdhbW1hKGxvZyksIHbDrCBjaMO6bmcgxJHhu4F1IGPDsyBow6BtIGxpw6puIGvhur90IGxvZ2FyaXQuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KMi4yMTkgKiAxLjE0NjIxOA0KYGBgDQoNCkLhuqFuIGPFqW5nIGPDsyB0aOG7gyBkw7luZyBtw7QgaMOsbmggaOG7k2kgcXV5IEdhbW1hIHbhu5tpIGxpbmsgZnVuY3Rpb24gbMOgIElkZW50aXR5IHRoYXkgdsOsIGxvZywgbcO0IGjDrG5oIG7DoHkgY2hvIHBow6lwIHN1eSBkaeG7hW4gdHLhu7FjIHRp4bq/cCB24buBIGhp4buHdSDhu6luZyBj4bunYSBwaMOibiBuaMOzbSBsw6BtIHTEg25nIGFyaXRobWV0aWMgbWVhbiB0csOqbiB0aGFuZyDEkW8gZ+G7kWMgY+G7p2EgWSBtw6Aga2jDtG5nIGPhuqduIGjDoG0gZXhwb25lbnRpYWwsIHRow60gZOG7pSB0YSBjw7MgdGjhu4MgxrDhu5tjIHTDrW5oIGFyaXRobWV0aWMgY+G7p2EgcGjDom4gbmjDs20gRzIgxJHGoW4gZ2nhuqNuIGLhurFuZyBjw6FjaCBj4buZbmcgYjAgdsOgIGIxID0gMS4xNDYgKyAxLjM5NyA9IDIuNTQ7DQoNCmBgYHtyfQ0KZ2xtKGZvcm11bGEgPUJJTElSVUJJTiB+IENsYXNzLA0KICAgIGZhbWlseSA9IEdhbW1hKGxpbmsgPSAiaWRlbnRpdHkiKSwNCiAgICBkYXRhID0gZGYpJT4lDQogIHN1bW0oY29uZmludCA9IFRSVUUsIGRpZ2l0cyA9IDMsZXhwPUYpDQpgYGANCg0KYGBge3J9DQoxLjM5NyArIDEuMTQ2MjE4DQpgYGANCg0KIyBI4buTaSBxdXkgR2FtbWEgc+G7rSBk4bulbmcgZ2FtbHNzDQoNClRyb25nIHBo4bqnbiB0aeG6v3AgdGhlbywgTmhpIHPhur0gZ2nhu5tpIHRoaeG7h3UgduG7gSBtw7QgaMOsbmggaOG7k2kgcXV5IEdhbW1hIHPhu60gZOG7pW5nIHBhY2thZ2UgZ2FtbHNzLiBO4buZaSBkdW5nIG7DoHkgbMOgIHPhu7EgbsOibmcgY2FvIHNvIHbhu5tpIGjDoG0gZ2xtIGPGoSBi4bqjbiB0cm9uZyBSLiBUcsaw4bubYyBo4bq/dCxwYWNrYWdlIGdhbWxzcyBjaG8gcGjDqXAgdGEgY2jhu6luZyBtaW5oOiBwaMOibiBwaOG7kWkgR2FtbWEgcGjDuSBo4bujcCBoxqFuIGNobyBk4buvIGxp4buHdSBoaeG7h24gdGjhu51pIHNvIHbhu5tpIHBow6JuIHBo4buRaSBHYXVzc2lhbjogdGjhuq10IHbhuq15LCBtw7QgaMOsbmggaOG7k2kgcXV5IMaw4bubYyB0w61uaCB0cnVuZyBiw6xuaCBj4bunYSBZIHPhu60gZOG7pW5nIEdhbW1hIGPDsyBBSUMgdsOgIEJJQyAoU0JDKSB0aOG6pXAgaMahbi4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGdhbWxzcykNCg0KaGlzdERpc3QoZGF0YT1kZixCSUxJUlVCSU4sZmFtaWx5PSdHQScpDQpoaXN0RGlzdChkYXRhPWRmLEJJTElSVUJJTixmYW1pbHk9J05PJykNCg0KYGBgDQoNCkPDsyBuaGnhu4F1IGPDoWNoIMSR4buLbmggbmdoxKlhIHBow6JuIHBo4buRaSBHYW1tYSwgdGjDtG5nIHRoxrDhu51uZyB0YSBz4butIGThu6VuZyAyIHRoYW0gc+G7kSBsw6AgYWxwaGEgKHNoYXBlKSB2w6AgYmV0YSAoc2NhbGUpLCBnw7NpIFIgY8ahIGLhuqNuIHPhu60gZOG7pW5nIGPDoWNoIMSR4buLbmggbmdoxKlhIG7DoHkuIFR1eSBuaGnDqm4gdHJvbmcgZ2FtbHNzLCBwaMOibiBwaOG7kWkgR2FtbWEgxJHGsOG7o2MgeMOhYyDEkeG7i25oIGLhurFuZyBjw6FjaCBraMOhYywgduG7m2kgMiB0aGFtIHPhu5EgbMOgIE11IHbDoCBTaWdtYS4NCg0KSMOgbSBQREYgY+G7p2EgcGjDom4gcGjhu5FpIEdhbW1hIHRyb25nIGdhbWxzcyBuaMawIHNhdToNCg0KJCRmKHl8XG11ICxcc2lnbWEpPVxmcmFjezF9e1xsZWZ0ICggXHNpZ21hIF57Mn0gXG11IFxyaWdodCApXnsxL1xzaWdtYV57Mn19fVxmcmFje3lee1xmcmFjezF9e1xzaWdtYSBeezJ9fS0xfWVeey15Lyhcc2lnbWEgXnsyfVxtdSApfX17XEdhbW1hIFxsZWZ0ICggMS9cc2lnbWFeezJ9IFxyaWdodCApfSQkDQoNCmdhbWxzcyBjaG8gcGjDqXAgZOG7sW5nIDIgbcO0IGjDrG5oIHJpw6puZyBiaeG7h3QgY2hvIDIgdGhhbSBz4buROiBNdSB2w6AgU2lnbWEgdHJvbmcgaMOgbSBQREYgbsOqdSB0csOqbi4gTGnDqm4gaOG7hyBnaeG7r2EgTXUsIFNpZ21hIHbDoCBhbHBoYSwgYmV0YSBsw6AgbmjGsCBzYXU6DQoNCiQkXG11ID0gXGFscGhhICogXGJldGEkJA0KTXUgY8OzIMO9IG5naMSpYSBuaMawIHbhu4sgdHLDrSB0cnVuZyB0w6JtICh0cnVuZyBiw6xuaCkgY+G7p2EgYmnhur9uIGvhur90IHF14bqjIGPhuqduIMaw4bubYyBsxrDhu6NuZywgTXUgcGjhu6UgdGh14buZYyBj4bqjIHbDoG8gYWxwaGEgdsOgIGJldGEuDQoNClNpZ21hIGPDsyBxdWFuIGjhu4cgduG7m2kgYWxwaGE6DQoNCiQkXHNpZ21hID0gXHNxcnR7XGZyYWN7MX17XGFscGhhIH19JCQNCg0KTeG7mXQga2hpIGPDsyBtdSB2w6Agc2lnbWEsIGPDsyB0aOG7gyDGsOG7m2MgdMOtbmggxJHhu5kgbOG7h2NoIGNodeG6qW4gdsOgIHBoxrDGoW5nIHNhaSBj4bunYSBZOg0KDQokJFNEID0gXHNpZ21hICogXG11JCQNCg0KVGjDrSBuZ2hp4buHbSBtw7QgcGjhu49uZyBzYXUgxJHDonkgc+G6vSBjaG8gcGjDqXAgaMOsbmggZHVuZyB24buBIGhp4buHdSDhu6luZyBsw6puIG11IHbDoCBzaWdtYSAga2hpIHRoYXkgxJHhu5VpIDIgdGhhbSBz4buRIHNjYWxlIHbDoCBzaGFwZSA6DQoNCktoaSBnaeG7ryBuZ3V5w6puIHNoYXBlID0gMTAgdsOgIHRoYXkgxJHhu5VpIHNjYWxlIDsNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGdhbWxzcy5kaXN0KQ0KDQpzaW1fZ2FtbWEgPSBmdW5jdGlvbihuLGEsYil7DQogIG11PWEqYg0KICBzaWdtYT1zcXJ0KDEvYSkNCiAgc2Q9c2lnbWEqbXUNCiAgeCA9IHJHQShuLG11LHNpZ21hKQ0KICByZXR1cm4oeCkNCn0NCg0Kc2ltX2RmID0gZGF0YV9mcmFtZShYPU5BLHNoYXBlPU5BLHNjYWxlPU5BKQ0KDQpmb3IgKGkgaW4gYygxLDIsMyw0LDUsMTAsMTUsMjApKXsNCiAgeD1zaW1fZ2FtbWEoMTAwMCwxMCxpKQ0KICB0ZW1wX2RmID0gZGF0YV9mcmFtZShYPXgsc2hhcGU9cmVwKDEwLDEwMDApLHNjYWxlPXJlcChpLDEwMDApKQ0KICBzaW1fZGYgPSByYmluZChzaW1fZGYsdGVtcF9kZiklPiVuYS5vbWl0KCkNCn0NCg0Kc2ltX2RmJG11PWZhY3RvcihzaW1fZGYkc2hhcGUqc2ltX2RmJHNjYWxlKQ0Kc2ltX2RmJT4lZ2dwbG90KCkrZ2VvbV9kZW5zaXR5KGFlcyh4PVgsZmlsbD1tdSksYWxwaGE9MC41KSt0aGVtZV9idygpDQpgYGANCg0KTmjGsCB0YSB0aOG6pXkgdHJvbmcgaMOsbmgsIEdhbW1hIGzDoCBt4buZdCBwaMOibiBwaOG7kWkgcuG6pXQgbGluaCBob+G6oXQgdsOgIHBow7kgaOG7o3AgbeG7mXQgY8OhY2ggdOG7sSBuaGnDqm4gduG7m2kgcXV5IGx14bqtdCBwaMOibiBwaOG7kWkgY+G7p2Egbmhp4buBdSDEkeG6oWkgbMaw4bujbmcgc2luaCBo4buNYywgduG7m2kgMiDEkeG6t2MgdMOtbmg6IGxpw6puIHThu6VjIHRyb25nIHThuq1wIGjhu6NwIFIrLCB2w6AgY8OzIGtp4buDdSBow6xuaCBiaeG6v24gY2h1eeG7g24gxJFhIGThuqFuZywgdOG7qyBs4buHY2ggZMawxqFuZyBjaG8gxJHhur9uIMSR4buRaSB44bupbmcuSMOsbmgg4bqjbmggbsOgeSBwaOG7pSB0aHXhu5ljIHbDoG8gdGhhbSBz4buRIGFscGhhIChzaGFwZSl2w6AgYmV0YSAoc2NhbGUuKQ0KDQpLaGkgZ2nhu68gbmd1ecOqbiBzY2FsZSA9IDUgdsOgIHRoYXkgxJHhu5VpIHNoYXBlDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kc2ltX2RmID0gZGF0YV9mcmFtZShYPU5BLHNoYXBlPU5BLHNjYWxlPU5BKQ0KDQpmb3IgKGkgaW4gYygxLDIsMyw0LDUsMTAsMTUsMjApKXsNCiAgeD1zaW1fZ2FtbWEoMTAwMCxpLDUpDQogIHRlbXBfZGYgPSBkYXRhX2ZyYW1lKFg9eCxzaGFwZT1yZXAoaSwxMDAwKSxzY2FsZT1yZXAoNSwxMDAwKSkNCiAgc2ltX2RmID0gcmJpbmQoc2ltX2RmLHRlbXBfZGYpJT4lbmEub21pdCgpDQp9DQoNCnNpbV9kZiRzaWdtYT1mYWN0b3Ioc3FydCgxL3NpbV9kZiRzaGFwZSkpDQpzaW1fZGYlPiVnZ3Bsb3QoKStnZW9tX2RlbnNpdHkoYWVzKHg9WCxmaWxsPXNpZ21hKSxhbHBoYT0wLjUpK3RoZW1lX2J3KCkNCg0KYGBgDQoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpzaW1fZGYgPSBkYXRhX2ZyYW1lKFg9TkEsc2hhcGU9TkEsc2NhbGU9TkEpDQoNCmZvciAoYiBpbiBjKDEsMiwzLDQsNSwxMCwxNSwyMCkpew0KICBmb3IgKGEgaW4gYygxLDIsMyw0LDUsMTAsMTUsMjApKXsNCiAgICAgICB4PXNpbV9nYW1tYSgxMDAwLGEsYikNCiAgICAgICB0ZW1wX2RmID0gZGF0YV9mcmFtZShYPXgsc2hhcGU9cmVwKGEsMTAwMCksc2NhbGU9cmVwKGIsMTAwMCkpDQogIHNpbV9kZiA9IHJiaW5kKHNpbV9kZix0ZW1wX2RmKSU+JW5hLm9taXQoKQ0KICB9DQp9DQoNCnNpbV9kZiRtdT1zaW1fZGYkc2hhcGUqc2ltX2RmJHNjYWxlDQpzaW1fZGYkc2lnbWE9c3FydCgxL3NpbV9kZiRzaGFwZSkNCnNpbV9kZiRzZD1zaW1fZGYkbXUqc2ltX2RmJHNpZ21hDQpgYGANCg0KVMawxqFuZyB0w6FjIGdp4buvYSBzY2FsZSB2w6Agc2hhcGUsDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kc2ltX2RmJT4lZ2dwbG90KCkrZ2VvbV9wYXRoKGFlcyh4PXNoYXBlLHk9bXUsY29sPWZhY3RvcihzY2FsZSkpKSt0aGVtZV9idygpDQpgYGANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpzaW1fZGYlPiVnZ3Bsb3QoKStnZW9tX3BhdGgoYWVzKHg9c2hhcGUseT1zaWdtYSxjb2w9ZmFjdG9yKHNjYWxlKSkpK2ZhY2V0X3dyYXAofnNjYWxlKSt0aGVtZV9idygpDQpgYGANCg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnNpbV9kZiU+JWdncGxvdCgpK2dlb21fcGF0aChhZXMoeD1zaGFwZSx5PXNkLGNvbD1mYWN0b3Ioc2NhbGUpKSkrdGhlbWVfYncoKQ0KYGBgDQoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpzaW1fZGYlPiVnZ3Bsb3QoKStnZW9tX3BhdGgoYWVzKHg9c2NhbGUseT1zZCxjb2w9ZmFjdG9yKHNoYXBlKSkpK3RoZW1lX2J3KCkNCg0KYGBgDQoNCkLDonkgZ2nhu50sIE5oaSBk4buxbmcgMiBtw7QgaMOsbmggaOG7k2kgcXV5IGdhbW1hIHbhu5tpIDIgdGhhbSBz4buRIE11IHbDoCBTaWdtYSwgY+G6oyAyIMSR4buBdSBjw7MgbGluayBmdW5jdGlvbiAoaMOgbSBsacOqbiBr4bq/dCkgbMOgIGxvZw0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoYnJvb20pDQpsaWJyYXJ5KHNqUGxvdCkNCg0KbW9kID0gZ2FtbHNzKGZvcm11bGEgPSBCSUxJUlVCSU4gfiBDbGFzcywNCiAgICAgICAgICAgICBkYXRhPWRmLA0KICAgICAgICAgICAgIHNpZ21hLmZvID0gfiBDbGFzcywNCiAgICAgICAgICAgICBmYW1pbHkgPSBHQSwNCiAgICAgICAgICAgICB0cmFjZT1GLA0KICAgICAgIHBhcmFsbGVsPSJtdWx0aWNvcmUiLA0KICAgICAgIG5jcHVzID0gbkMpDQoNCnN1bW1hcnkobW9kKQ0KDQpwbG90KG1vZCkNCmBgYA0KDQoNCk3DtCBow6xuaCBjw7MgQUlDID0gMjk3LjYgdsOgIEJJQyA9IDMwOS42LCDEkeG7mSB04buxIGRvIHNhaSBz4buRIHJlc2lkdWFsIOG7nyDEkcOieSBsw6AgMTQ1OyBkbyBjaMO6bmcgdGEgxJHDoyBkw7luZyAxIGludGVyY2VwdCB2w6AgMSBwaMOibiBuaMOzbSBHMiBjaG8gbeG7l2kgbcO0IGjDrG5oIE11IHbDoCBTaWdtYSAoKDErMSkqMj00KQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmdsYW5jZShtb2QpJT4la25pdHI6OmthYmxlKCkNCmBgYA0KDQpT4butIGThu6VuZyBow6BtIGF1Z21lbnQgY+G7p2EgcGFja2FnZSBicm9vbSAsIHRhIGPDsyB0aOG7gyB24bq9IGJp4buDdSDEkeG7kyBraOG6o28gc8OhdCAibWFyZ2luYWwgZWZmZWN0IiBj4bunYSBtw7QgaMOsbmg6IGzGsHUgw70gcuG6sW5nIGPhuqduIMOhcCBk4bulbmcgaMOgbSBleHBvbmVudGlhbCB0csOqbiBjw6FjIGhp4buHdSDhu6luZyBn4buRYyB0cm9uZyBtw7QgaMOsbmggxJHhu4MgcXV5IMSR4buVaSB24buBIHRoYW5nIMSRbyBuZ3V5w6puIHRo4buneS4NCg0KS2hpIG3DtCBow6xuaCB0cuG7nyBuw6puIHBo4bupYyB04bqhcCwgYmnhu4N1IGRp4buFbiBu4buZaSBkdW5nIGPhu6dhIG7DsyBi4bqxbmcgYmnhu4N1IMSR4buTIGzDoCBt4buZdCBjw6FjaCDEkeG7gyBuZ8aw4budaSDEkeG7jWMgbuG6r20gYuG6r3QgxJHGsOG7o2MgdGjDtG5nIHRpbiB0cuG7sWMgcXVhbiB2w6AgxJHGoW4gZ2nhuqNuLg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmF1Z21lbnQobW9kKSU+JW11dGF0ZShVTD0gLmZpdHRlZCArLnNlLmZpdCoxLjk2LA0KICAgICAgICAgICAgICAgICAgICAgIExMPS5maXR0ZWQtLnNlLmZpdCoxLjk2KSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9qaXR0ZXIoYWVzKHg9Q2xhc3MseT1CSUxJUlVCSU4sY29sPUNsYXNzKSxhbHBoYT0wLjMpKw0KICBnZW9tX2Vycm9yYmFyKGFlcyh4PUNsYXNzLCB5bWluPWV4cChMTCksIHltYXg9ZXhwKFVMKSxjb2w9Q2xhc3MpLA0KICAgICAgICAgICAgICAgIHdpZHRoPTAuMiwgDQogICAgICAgICAgICAgICAgc2l6ZT0xKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PUNsYXNzLCB5PWV4cCguZml0dGVkKSxjb2w9Q2xhc3MpLCANCiAgICAgICAgICAgICBzaXplPTQpICsNCiAgZ2VvbV9wYXRoKGFlcyh4PUNsYXNzLCB5PWV4cCguZml0dGVkKSkpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoInJlZCIsImJsdWUiKSkrDQogIHRoZW1lX2J3KCkgIA0KYGBgDQoNCk7hu5lpIGR1bmcgY+G7p2EgbcO0IGjDrG5oIGPDsyB0aOG7gyDEkcaw4bujYyBkaeG7hW4gZ2nhuqNpIGLhurFuZyBjw6FjIGFyaXRobWV0aWMgbWVhbiByYXRpbyB0csOqbiBt4buZdCBiaeG7g3UgxJHhu5MsIG5oxrAgxJHDoyBiw6BuIOG7nyB0csOqbjsgbMawdSDDvSBy4bqxbmcgbcO0IGjDrG5oIGdhbWxzcyBjaG8gcGjDqXAgc3V5IGRp4buFbiDEkeG7k25nIHRo4budaSBoaeG7h3Ug4bupbmcgdHLDqm4gY+G6oyAyIHRoYW0gc+G7kSBNdSAodHJ1bmcgYsOsbmgsIHbhu4sgdHLDrSB0cnVuZyB0w6JtKSB2w6Agc2lnbWEgKGtp4buDdSBow6xuaCBj4bunYSBwaMOibiBi4buRKS4NCg0KVHJvbmcgZOG7ryBsaeG7h3UgaGnhu4duIHRo4budaSwgaGnhu4d1IOG7qW5nIGPhu6dhIHBow6JuIG5ow7NtIEcxLCBHMiDEkeG7gXUgY8OzIMO9IG5naMSpYSB0cm9uZyBj4bqjIDIgdGhhbSBz4buRIE11IHbDoCBTaWdtYS4gTsOzaSBjw6FjaCBraMOhYywga2hpIGLhu4duaCB2acOqbSBnYW4gY8OgbmcgbuG6t25nIChuZ3V5IGPGoSB04butIHZvbmcgY2FvKSB0aMOsIGJpbGlydWJpbiBjw6BuZyBwaMOibiBi4buRIHRoZW8ga2h1eW5oIGjGsOG7m25nOiB24buLIHRyw60gdHJ1bmcgdMOibSBk4buLY2ggY2h1eeG7g24gc2FuZyBwaOG6o2kgKHTEg25nIGNhbyksIG5oxrBuZyBz4buxIHBow6JuIHTDoW4gY8WpbmcgY2FvIGjGoW4gdsOgIGjDrG5oIOG6o25oIHBow6JuIGLhu5EgbOG7h2NoIG5oaeG7gXUgaMahbiAoc2tld25lc3MgdMSDbmcpLg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnRpZHkobW9kKSU+JW11dGF0ZShSYXRpbz1leHAoZXN0aW1hdGUpLA0KICAgICAgICAgICAgICAgICAgIExMPWV4cChlc3RpbWF0ZS0xLjk2KnN0ZC5lcnJvciksDQogICAgICAgICAgICAgICAgICAgVUw9ZXhwKGVzdGltYXRlKzEuOTYqc3RkLmVycm9yKSklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fZXJyb3JiYXIoYWVzKHg9dGVybSwgeW1pbj1MTCwgeW1heD1VTCxjb2w9cGFyYW1ldGVyKSwNCiAgICAgICAgICAgICAgICB3aWR0aD0wLjIsIA0KICAgICAgICAgICAgICAgIHNpemU9MSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeD10ZXJtLHk9UmF0aW8sY29sPXBhcmFtZXRlciksIA0KICAgICAgICAgICAgIHNpemU9NCkrDQogIGdlb21faGxpbmUoeWludGVyY2VwdD0xLGxpbmV0eXBlPTIsY29sPSJyZWQ0IikrDQogIGNvb3JkX2ZsaXAoKSsNCiAgZmFjZXRfd3JhcCh+cGFyYW1ldGVyLG5jb2w9MSkrDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygicmVkIiwiYmx1ZSIpKSsNCiAgdGhlbWVfYncoKSAgDQpgYGANCg0KIyBCb290c3RyYXAgbcO0IGjDrG5oIGdhbWxzcw0KDQpUYSBz4bq9IG7Dom5nIGNhbyBoxqFuIDEgY2jDunQgYuG6sW5nIG3hu5l0IHBow6JuIHTDrWNoIGjhu5NpIHF1eSBib290c3RyYXAsIE5oaSBkw7luZyBow6BtIGJvb3RzdHJhcHMgY+G7p2EgcGFja2FnZSByc2FtcGxlIMSR4buDIHThuqFvIHJhIDEwMDAgcGhpw6puIGLhuqNuIGThu68gbGnhu4d1IG5n4bqrdSBuaGnDqm4ga2jDoWMgbmhhdSAoY8OzIGLhuqNvIHThu5NuIHThu4kgbOG7hyAyIHBow6JuIG5ow7NtKSwgc2F1IMSRw7MgZOG7sW5nIGPDuW5nIG3hu5l0IG3DtCBow6xuaCBuaMawIHRyw6puIGNobyBt4buXaSBwaGnDqm4gYuG6o24gZOG7ryBsaeG7h3U7IHRyw61jaCB4deG6pXQga+G6v3QgcXXhuqMgMTAwMCBtw7QgaMOsbmggbsOgeS4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHJzYW1wbGUpDQoNCmJvb3RfZGYgPC0gYm9vdHN0cmFwcyhkZiwgc3RyYXRhID0gIkNsYXNzIiwgdGltZXMgPSAxMDAwKQ0KDQptdV9mb3JtPWFzLmZvcm11bGEoQklMSVJVQklOIH4gQ2xhc3MpDQpzaWdtYV9mb3JtPWFzLmZvcm11bGEofiBDbGFzcykNCg0KbW9kPWRmJT4lZ2FtbHNzKGZvcm11bGEgPS4kQklMSVJVQklOIH4gLiRDbGFzcywNCiAgICAgICBzaWdtYS5mbyA9IH4gLiRDbGFzcywNCiAgICAgICBmYW1pbHkgPSBHQSwNCiAgICAgICB0cmFjZT1GLA0KICAgICAgIHBhcmFsbGVsPSJtdWx0aWNvcmUiLA0KICAgICAgIG5jcHVzID0gbkMpDQoNCm1vZGVsX2RldiA9IGZ1bmN0aW9uKHNwbGl0cywuLi4pew0KICBtb2QgPSBnYW1sc3MoLi4uLCBkYXRhPWFuYWx5c2lzKHNwbGl0cyksDQogICAgICAgICAgICAgICBmYW1pbHkgPSBHQSwNCiAgICAgICAgICAgICAgIHRyYWNlPUYsDQogICAgICAgICAgICAgICBwYXJhbGxlbD0ibXVsdGljb3JlIiwNCiAgICAgICAgICAgICAgIG5jcHVzID0gbkMpDQogIA0KICB0aWR5X2RmPWJyb29tOjp0aWR5KG1vZCwNCiAgICAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFLCANCiAgICAgICAgICAgICAgY29uZi5sZXZlbCA9IDAuOTc1KQ0KICANCiAgZ2xhbmNlX2RmID0gZ2xhbmNlKG1vZCkNCiAgDQogIHRpZHlfZGY8LXRpZHlfZGYlPiVtdXRhdGUoQklDPWdsYW5jZV9kZiRCSUMsDQogICAgICAgICAgICAgICAgICAgQUlDPWdsYW5jZV9kZiRBSUMsDQogICAgICAgICAgICAgICAgICAgUjI9UnNxKG1vZCkpDQogIHJldHVybih0aWR5X2RmKQ0KICB9DQoNCmNvZWZfbGlzdCA8LSBtYXAoLnggPSBib290X2RmJHNwbGl0cywgDQogICAgICAgICAgICAgLmYgPSBtb2RlbF9kZXYsIA0KICAgICAgICAgICAgIG11X2Zvcm0sDQogICAgICAgICAgICAgc2lnbWFfZm9ybSklPiUNCiAgbWFwX2RmKH5kYXRhX2ZyYW1lKFBhcmE9LngkcGFyYW1ldGVyLA0KICAgICAgICAgICAgICAgICAgICAgVGVybT0ueCR0ZXJtLA0KICAgICAgICAgICAgICAgICAgICAgRXN0PS54JGVzdGltYXRlLA0KICAgICAgICAgICAgICAgICAgICAgUmF0aW89ZXhwKC54JGVzdGltYXRlKSwNCiAgICAgICAgICAgICAgICAgICAgIHQ9Lngkc3RhdGlzdGljLA0KICAgICAgICAgICAgICAgICAgICAgcF92YWw9LngkcC52YWx1ZSwNCiAgICAgICAgICAgICAgICAgICAgIEJJQz0ueCRCSUMsDQogICAgICAgICAgICAgICAgICAgICBBSUM9LngkQUlDLA0KICAgICAgICAgICAgICAgICAgICAgUjI9LngkUjIpKQ0KYGBgDQoNCsSQw6J5IGzDoCBk4buvIGxp4buHdSBr4bq/dCBxdeG6oyBj4bunYSAxMDAwIG3DtCBow6xuaCBnYW1tYToNCg0KYGBge3J9DQpoZWFkKGNvZWZfbGlzdCklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNClBow6JuIHBo4buRaSBj4bunYSBjw6FjIGFyaXRobWV0aWMgbWVhbiByYXRpbyB04burIDEwMDAgbcO0IGjDrG5oOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZ2dyaWRnZXMpDQoNCmNvZWZfbGlzdCU+JQ0KICBnZ3Bsb3QoYWVzKHk9VGVybSx4PVJhdGlvKSkrDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoYWxwaGE9MC43LHNjYWxlPTAuODUsDQogICAgICAgICAgICAgICAgICAgICAgYWVzKGZpbGw9VGVybSksDQogICAgICAgICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGKSsNCiAgc2NhbGVfeV9kaXNjcmV0ZShleHBhbmQgPSBjKDAuMSwwLjUpKSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMSxsaW5ldHlwZT0yLGNvbD0icmVkMyIpKw0KICB0aGVtZV9idygpKw0KICBmYWNldF93cmFwKH5QYXJhLG5jb2w9MixzY2FsZXMgPSAiZnJlZSIpDQpgYGANCg0KUGjDom4gdMOtY2ggYm9vdHN0cmFwIGNobyBwaMOpcCB0YSDGsOG7m2MgdMOtbmgga2hv4bqjbmcgdGluIGPhuq15IDk3LjUlIGNobyB04buJIHPhu5EgYXJpdGhtZXRpYyBtZWFuIHJhdGlvIGPhu6dhIHBow6JuIG5ow7NtIEcyIGNobyBNdSwgU2lnbWE6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KY29lZl9saXN0JT4lDQogIGdyb3VwX2J5KFBhcmEsVGVybSklPiUNCiAgICBzdW1tYXJpc2VfYXQoIlJhdGlvIixmdW5zKG49bGVuZ3RoKC4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTWVhbj1tZWFuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTWVkaWFuPW1lZGlhbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIExMPXF1YW50aWxlKC4sMC4wMjUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVUw9cXVhbnRpbGUoLiwwLjk3NSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNaW49bWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTWF4PW1heCkpDQpgYGANCg0KS+G6v3QgcXXhuqMgbsOgeSBraMOpcCBs4bqhaSBiw6BpIHRo4buxYyBow6BuaCwgaHkgduG7jW5nIGPDoWMgYuG6oW4gxJHDoyBjw7Mgbmjhu69uZyBnacOieSBwaMO6dCBo4buNYyB04bqtcCB2dWkgduG6uy4gTmjGsCBi4bqhbiB0aOG6pXksIG3DtCBow6xuaCBo4buTaSBxdXkgbMOgIG3hu5l0IGPDtG5nIGPhu6UgaOG7r3Ugw61jaCA7IGjhurluIGfhurdwIGPDoWMgYuG6oW4gdHJvbmcgbeG7mXQgbOG6p24ga2jDoWMuDQo=