Looking deeper into the liver

This notebook presents a demonstration canonical correlation analysis - a method for comparing inter-relationships between two sets of variables. In this case, we have three measures of liver health derived from MRI; liver iron, liver inflammation factor (LIF) and liver proton density fat fraction (PDFF). We also have a small set of biochemistry tests measured on the same participants.

Source the data and summarise the variables
source("R/0_setup.R")

library(CCA)

IP<- readRDS("DemoSet2.rds") # 502505 by 71

# Variable sets
biochem<- c("red_blood_cell_erythrocyte_count", "high_light_scatter_reticulocyte_count",                                     "alanine_aminotransferase", "triglycerides", "urate", "c_reactive_protein")

liver<- c("liver_iron", "LIF", "PDFF")

# Make the set

sett<- IP %>% select(all_of(liver), all_of(biochem)) %>% 
              filter(complete.cases(.)) 

sk<- skim(sett) %>% select(variable= skim_variable, mean= numeric.mean, min= numeric.p0, median= numeric.p50,
                           max= numeric.p100, numeric.hist)
sk[, 2:4]<- apply(sk[, 2:4], 2, function(x) round(x, 2))
print(sk)

Pairwise Panels for X and Y

Let’s begin by visualising the variables in X and Y and how they relate to each other.

# Separate and standardise the X and Y matrices
X<- sett[, biochem] %>% scale()
Y<- sett[, liver] %>% scale()

# Pairwise panel for the X matrix (biochemistry)
psych::pairs.panels(X, method = "pearson", hist.col = "#b3d9ff", density = TRUE)

# Pairwise panel for the Y matrix (liver)
psych::pairs.panels(Y, method = "pearson", hist.col = "#b3d9ff", density = TRUE)

The pairwise panels show that for both X and Y matrices, there is some visible correlation between variables. In this situation, canonical correlation analysis is ideal for exploring the overall relationship between the two sets.

Cross-Correlation Panel

correl<-matcor(X, Y)
img.matcor(correl,type = 3)

The cross-correlation panel gives us a preview of the range in which the cross-correlations are likely to sit. Nothing is strongly standing out, so we can expect cross-correlations in the range of 0-0.4 or so.

Applying the CCA method

The main CCA method is given here via the {CCA} package.

can.model<- cc(X,Y)
can.model$cor
[1] 0.42335127 0.06836749 0.04168428

These are the “canonical correlations”, which indicate the overall relationship between X and Y in 3 dimensions. We have three since our smallest input matrix has 3 columns.

Which canonical variates are significant?

rho<- can.model$cor
n<-   nrow(X)
p<-   ncol(X)
q<-   ncol(Y)
CCP::p.asym(rho, n, p, q, tstat="Wilks")
Wilks' Lambda, using F-approximation (Rao's F):
              stat    approx df1      df2     p.value
1 to 3:  0.8155178 47.522623  18 11441.47 0.000000000
2 to 3:  0.9935964  2.603395  10  8092.00 0.003733462
3 to 3:  0.9982624  1.761056   4  4047.00 0.133797814

An assymptotic test on the size of the correlations indicates that only the first two canonical correlations are significantly different from zero. Since the first canoncial correlation is so much larger than the second, the remainder of the analysis will focus on the first component.
In simple terms, the rest of the analysis is geared towards understanding how the matrices relate to each other, via this variate.

Standardised Canonical Weights

# Standardised Canonical Weights

sw1<-diag(sqrt(diag(cov(X)))) %*% can.model$xcoef %>% 
       data.frame() %>% 
       mutate(Var= biochem, Set= "Biochemistry") %>% 
       select(Var, X1, Set) 

sw2<-diag(sqrt(diag(cov(Y)))) %*% can.model$ycoef %>% 
      data.frame() %>% 
      mutate(Var= liver, Set= "Liver") %>% 
      select(Var, X1, Set) 

t0<- bind_rows(sw1, sw2) %>%
       mutate(Direction= ifelse(X1 > 0, "Positive", "Negative"))

ggplot(t0, aes(x= X1, y= fct_reorder(Var, X1), fill= Direction)) +
  geom_col(size= 0.2, color= "grey50") +
  geom_text(aes(label= sprintf("%2.1f", X1*100)), hjust= ifelse(t0$X1 > 0, 1.2, -0.3))+
  facet_wrap(~ Set, scales= "free") +
  theme_bw() +
  theme(legend.position = "None",
         strip.background = element_rect(fill= "grey30"),
         strip.text = element_text(color="white", face= "bold"))  +
  scale_fill_manual(values= c("#a3dca3", "#b3d9ff"))


sw1
sw2

Canonical loadings and cross-loadings

Canonical weights (standardised or not) have fallen out of use in favour of canonical loadings. These are the correlations between the original variables and the scores of the canonical variate.

# Canonical loadings and cross-loadings

can_loadings<- comput(X,Y,cancor)
can_loadings[3:6]
$corr.X.xscores
                                            [,1]        [,2]         [,3]
red_blood_cell_erythrocyte_count      -0.4108776  0.35522284 -0.163735110
high_light_scatter_reticulocyte_count -0.7215862 -0.09267242 -0.009252762
alanine_aminotransferase              -0.6873395  0.20784334 -0.110979461
triglycerides                         -0.6427031  0.35672320 -0.236499349
urate                                 -0.5187563 -0.47980607 -0.588432085
c_reactive_protein                    -0.4918877 -0.38984102  0.675329116

$corr.Y.xscores
                 [,1]         [,2]         [,3]
liver_iron -0.1825606 -0.050259395 -0.021804439
LIF        -0.2537582 -0.034332977  0.025982655
PDFF       -0.4214048 -0.005667556 -0.002000076

$corr.X.yscores
                                            [,1]        [,2]          [,3]
red_blood_cell_erythrocyte_count      -0.1739455  0.02428569 -0.0068251808
high_light_scatter_reticulocyte_count -0.3054844 -0.00633578 -0.0003856948
alanine_aminotransferase              -0.2909861  0.01420973 -0.0046260994
triglycerides                         -0.2720892  0.02438827 -0.0098583060
urate                                 -0.2196162 -0.03280314 -0.0245283701
c_reactive_protein                    -0.2082413 -0.02665245  0.0281506106

$corr.Y.yscores
                 [,1]       [,2]        [,3]
liver_iron -0.4312272 -0.7351359 -0.52308537
LIF        -0.5994034 -0.5021828  0.62332017
PDFF       -0.9954022 -0.0828984 -0.04798153
can_redunds<-  candisc::redundancy(candisc::cancor(X,Y))
can_redunds

Redundancies for the X variables & total X canonical redundancy

    Xcan1     Xcan2     Xcan3 total X|Y 
0.0623131 0.0005355 0.0002599 0.0631086 

Redundancies for the Y variables & total Y canonical redundancy

    Ycan1     Ycan2     Ycan3 total Y|X 
0.0917679 0.0012456 0.0003848 0.0933983 
# X Plot
t0<- tibble(Var= c(biochem, biochem),
            X1= c(can_loadings$corr.X.xscores[,1], can_loadings$corr.X.yscores[,1]),
            Loading= c(rep("X-X Loading", 6), rep("X-Y Cross-loading", 6)))
ggplot(t0, aes(x= X1, y= fct_reorder(Var, -X1), fill= Loading)) +
  geom_col(size= 0.2, color= "grey50") +
  geom_text(aes(label= sprintf("%2.1f", X1*100)), hjust= -0.6)+
  facet_wrap(~ Loading, scales= "free_x") +
  theme_bw() +
  theme(legend.position = "None",
         strip.background = element_rect(fill= "grey30"),
         strip.text = element_text(color="white", face= "bold")) +
  scale_fill_manual(values= c("#a3dca3", "#b3d9ff")) +
  labs(x="", y= "Variable", title= "Loadings and Cross-Loadings for X (Biochemistry)")

# Y Plot
t0<- tibble(Var= c(liver, liver),
            X1= c(can_loadings$corr.Y.yscores[,1], can_loadings$corr.Y.xscores[,1]),
            Loading= c(rep("Y-Y Loading", 3), rep("Y-X Cross-loading", 3))) %>%
     mutate(Loading= ordered(Loading, levels=c("Y-Y Loading", "Y-X Cross-loading")))
ggplot(t0, aes(x= X1, y= fct_reorder(Var, -X1), fill= Loading)) +
  geom_col(size= 0.2, color= "grey50") +
  geom_text(aes(label= sprintf("%2.1f", X1*100)), hjust= -0.6)+
  facet_wrap(~ Loading, scales= "free_x") +
  theme_bw() +
  theme(legend.position = "None",
         strip.background = element_rect(fill= "grey30"),
         strip.text = element_text(color="white", face= "bold")) +
  scale_fill_manual(values= c("#a3dca3", "#b3d9ff")) +
  labs(x="", y= "Variable", title= "Loadings and Cross-Loadings for Y (Liver)")

Examination of the loadings to the first canonical variate show that all variables included in our analysis have a part to play in explaining the relation between biochemistry and liver health. From the liver side, PDFF is the strongest variable, while from the biochemistry side the three most influential variables are light scatter reticulocyte count, alanine aminotransferase and triglycerides.

plt.cc(cc(X,Y),var.label = TRUE)

LS0tCnRpdGxlOiAiQ2Fub25pY2FsIENvcnJlbGF0aW9uIEFuYWx5c2lzOiBMaXZlciB2cyBCaW9jaGVtaXN0cnkiCm91dHB1dDogaHRtbF9ub3RlYm9vawpkYXRlOiAiMjAyMC0xMC0xMiIKYXV0aG9yOiAiQ2VsIE1jQ3JhY2tlbiIKLS0tCgoKIVtdKC9Vc2Vycy9DZWxlc3RlL0Rlc2t0b3AvTElWRVIucG5nKQoKIyMjIExvb2tpbmcgZGVlcGVyIGludG8gdGhlIGxpdmVyCgpUaGlzIG5vdGVib29rIHByZXNlbnRzIGEgZGVtb25zdHJhdGlvbiBjYW5vbmljYWwgY29ycmVsYXRpb24gYW5hbHlzaXMgLSBhIG1ldGhvZCBmb3IgY29tcGFyaW5nIGludGVyLXJlbGF0aW9uc2hpcHMgYmV0d2VlbiB0d28gc2V0cyBvZiB2YXJpYWJsZXMuICBJbiB0aGlzIGNhc2UsIHdlIGhhdmUgdGhyZWUgbWVhc3VyZXMgb2YgbGl2ZXIgaGVhbHRoIGRlcml2ZWQgZnJvbSBNUkk7IGxpdmVyIGlyb24sIGxpdmVyIGluZmxhbW1hdGlvbiBmYWN0b3IgKExJRikgYW5kIGxpdmVyIHByb3RvbiBkZW5zaXR5IGZhdCBmcmFjdGlvbiAoUERGRikuIFdlIGFsc28gaGF2ZSBhIHNtYWxsIHNldCBvZiBiaW9jaGVtaXN0cnkgdGVzdHMgbWVhc3VyZWQgb24gdGhlIHNhbWUgcGFydGljaXBhbnRzLgoKIyMjIyMgU291cmNlIHRoZSBkYXRhIGFuZCBzdW1tYXJpc2UgdGhlIHZhcmlhYmxlcwpgYGB7ciwgbWVzc2FnZT0gRkFMU0UsIHdhcm5pbmc9IEZBTFNFfQpzb3VyY2UoIlIvMF9zZXR1cC5SIikKCmxpYnJhcnkoQ0NBKQoKSVA8LSByZWFkUkRTKCJEZW1vU2V0Mi5yZHMiKSAjIDUwMjUwNSBieSA3MQoKIyBWYXJpYWJsZSBzZXRzCmJpb2NoZW08LSBjKCJyZWRfYmxvb2RfY2VsbF9lcnl0aHJvY3l0ZV9jb3VudCIsICJoaWdoX2xpZ2h0X3NjYXR0ZXJfcmV0aWN1bG9jeXRlX2NvdW50IiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImFsYW5pbmVfYW1pbm90cmFuc2ZlcmFzZSIsICJ0cmlnbHljZXJpZGVzIiwgInVyYXRlIiwgImNfcmVhY3RpdmVfcHJvdGVpbiIpCgpsaXZlcjwtIGMoImxpdmVyX2lyb24iLCAiTElGIiwgIlBERkYiKQoKIyBNYWtlIHRoZSBzZXQKCnNldHQ8LSBJUCAlPiUgc2VsZWN0KGFsbF9vZihsaXZlciksIGFsbF9vZihiaW9jaGVtKSkgJT4lIAogICAgICAgICAgICAgIGZpbHRlcihjb21wbGV0ZS5jYXNlcyguKSkgCgpzazwtIHNraW0oc2V0dCkgJT4lIHNlbGVjdCh2YXJpYWJsZT0gc2tpbV92YXJpYWJsZSwgbWVhbj0gbnVtZXJpYy5tZWFuLCBtaW49IG51bWVyaWMucDAsIG1lZGlhbj0gbnVtZXJpYy5wNTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heD0gbnVtZXJpYy5wMTAwLCBudW1lcmljLmhpc3QpCnNrWywgMjo0XTwtIGFwcGx5KHNrWywgMjo0XSwgMiwgZnVuY3Rpb24oeCkgcm91bmQoeCwgMikpCnByaW50KHNrKQpgYGAKIyMjIFBhaXJ3aXNlIFBhbmVscyBmb3IgWCBhbmQgWQpMZXQncyBiZWdpbiBieSB2aXN1YWxpc2luZyB0aGUgdmFyaWFibGVzIGluIFggYW5kIFkgYW5kIGhvdyB0aGV5IHJlbGF0ZSB0byBlYWNoIG90aGVyLgpgYGB7cn0KIyBTZXBhcmF0ZSBhbmQgc3RhbmRhcmRpc2UgdGhlIFggYW5kIFkgbWF0cmljZXMKWDwtIHNldHRbLCBiaW9jaGVtXSAlPiUgc2NhbGUoKQpZPC0gc2V0dFssIGxpdmVyXSAlPiUgc2NhbGUoKQoKIyBQYWlyd2lzZSBwYW5lbCBmb3IgdGhlIFggbWF0cml4IChiaW9jaGVtaXN0cnkpCnBzeWNoOjpwYWlycy5wYW5lbHMoWCwgbWV0aG9kID0gInBlYXJzb24iLCBoaXN0LmNvbCA9ICIjYjNkOWZmIiwgZGVuc2l0eSA9IFRSVUUpCmBgYApgYGB7cn0KIyBQYWlyd2lzZSBwYW5lbCBmb3IgdGhlIFkgbWF0cml4IChsaXZlcikKcHN5Y2g6OnBhaXJzLnBhbmVscyhZLCBtZXRob2QgPSAicGVhcnNvbiIsIGhpc3QuY29sID0gIiNiM2Q5ZmYiLCBkZW5zaXR5ID0gVFJVRSkKYGBgClRoZSBwYWlyd2lzZSBwYW5lbHMgc2hvdyB0aGF0IGZvciBib3RoIFggYW5kIFkgbWF0cmljZXMsIHRoZXJlIGlzIHNvbWUgdmlzaWJsZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHZhcmlhYmxlcy4gSW4gdGhpcyBzaXR1YXRpb24sIGNhbm9uaWNhbCBjb3JyZWxhdGlvbiBhbmFseXNpcyBpcyBpZGVhbCBmb3IgZXhwbG9yaW5nIHRoZSBvdmVyYWxsIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSB0d28gc2V0cy4KCiMjIyBDcm9zcy1Db3JyZWxhdGlvbiBQYW5lbApgYGB7cn0KY29ycmVsPC1tYXRjb3IoWCwgWSkKaW1nLm1hdGNvcihjb3JyZWwsdHlwZSA9IDMpCmBgYApUaGUgY3Jvc3MtY29ycmVsYXRpb24gcGFuZWwgZ2l2ZXMgdXMgYSBwcmV2aWV3IG9mIHRoZSByYW5nZSBpbiB3aGljaCB0aGUgY3Jvc3MtY29ycmVsYXRpb25zIGFyZSBsaWtlbHkgdG8gc2l0LiBOb3RoaW5nIGlzIHN0cm9uZ2x5IHN0YW5kaW5nIG91dCwgc28gd2UgY2FuIGV4cGVjdCBjcm9zcy1jb3JyZWxhdGlvbnMgaW4gdGhlIHJhbmdlIG9mIDAtMC40IG9yIHNvLgoKIyMjIEFwcGx5aW5nIHRoZSBDQ0EgbWV0aG9kClRoZSBtYWluIENDQSBtZXRob2QgaXMgZ2l2ZW4gaGVyZSB2aWEgdGhlIHtDQ0F9IHBhY2thZ2UuCmBgYHtyfQpjYW4ubW9kZWw8LSBjYyhYLFkpCmNhbi5tb2RlbCRjb3IKYGBgClRoZXNlIGFyZSB0aGUgImNhbm9uaWNhbCBjb3JyZWxhdGlvbnMiLCB3aGljaCBpbmRpY2F0ZSB0aGUgb3ZlcmFsbCByZWxhdGlvbnNoaXAgYmV0d2VlbiBYIGFuZCBZIGluIDMgZGltZW5zaW9ucy4gV2UgaGF2ZSB0aHJlZSBzaW5jZSBvdXIgc21hbGxlc3QgaW5wdXQgbWF0cml4IGhhcyAzIGNvbHVtbnMuIAoKIyMjIFdoaWNoIGNhbm9uaWNhbCB2YXJpYXRlcyBhcmUgc2lnbmlmaWNhbnQ/CmBgYHtyfQpyaG88LSBjYW4ubW9kZWwkY29yCm48LSAgIG5yb3coWCkKcDwtICAgbmNvbChYKQpxPC0gICBuY29sKFkpCkNDUDo6cC5hc3ltKHJobywgbiwgcCwgcSwgdHN0YXQ9IldpbGtzIikKYGBgCkFuIGFzc3ltcHRvdGljIHRlc3Qgb24gdGhlIHNpemUgb2YgdGhlIGNvcnJlbGF0aW9ucyBpbmRpY2F0ZXMgdGhhdCBvbmx5IHRoZSBmaXJzdCB0d28gY2Fub25pY2FsIGNvcnJlbGF0aW9ucyBhcmUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgZnJvbSB6ZXJvLiAgU2luY2UgdGhlIGZpcnN0IGNhbm9uY2lhbCBjb3JyZWxhdGlvbiBpcyBzbyBtdWNoIGxhcmdlciB0aGFuIHRoZSBzZWNvbmQsIHRoZSByZW1haW5kZXIgb2YgdGhlIGFuYWx5c2lzIHdpbGwgZm9jdXMgb24gdGhlIGZpcnN0IGNvbXBvbmVudC4gIApJbiBzaW1wbGUgdGVybXMsIHRoZSByZXN0IG9mIHRoZSBhbmFseXNpcyBpcyBnZWFyZWQgdG93YXJkcyB1bmRlcnN0YW5kaW5nIGhvdyB0aGUgbWF0cmljZXMgcmVsYXRlIHRvIGVhY2ggb3RoZXIsIHZpYSB0aGlzIHZhcmlhdGUuCgojIyMgU3RhbmRhcmRpc2VkIENhbm9uaWNhbCBXZWlnaHRzCmBgYHtyfQojIFN0YW5kYXJkaXNlZCBDYW5vbmljYWwgV2VpZ2h0cwoKc3cxPC1kaWFnKHNxcnQoZGlhZyhjb3YoWCkpKSkgJSolIGNhbi5tb2RlbCR4Y29lZiAlPiUgCiAgICAgICBkYXRhLmZyYW1lKCkgJT4lIAogICAgICAgbXV0YXRlKFZhcj0gYmlvY2hlbSwgU2V0PSAiQmlvY2hlbWlzdHJ5IikgJT4lIAogICAgICAgc2VsZWN0KFZhciwgWDEsIFNldCkgCgpzdzI8LWRpYWcoc3FydChkaWFnKGNvdihZKSkpKSAlKiUgY2FuLm1vZGVsJHljb2VmICU+JSAKICAgICAgZGF0YS5mcmFtZSgpICU+JSAKICAgICAgbXV0YXRlKFZhcj0gbGl2ZXIsIFNldD0gIkxpdmVyIikgJT4lIAogICAgICBzZWxlY3QoVmFyLCBYMSwgU2V0KSAKCnQwPC0gYmluZF9yb3dzKHN3MSwgc3cyKSAlPiUKICAgICAgIG11dGF0ZShEaXJlY3Rpb249IGlmZWxzZShYMSA+IDAsICJQb3NpdGl2ZSIsICJOZWdhdGl2ZSIpKQoKZ2dwbG90KHQwLCBhZXMoeD0gWDEsIHk9IGZjdF9yZW9yZGVyKFZhciwgWDEpLCBmaWxsPSBEaXJlY3Rpb24pKSArCiAgZ2VvbV9jb2woc2l6ZT0gMC4yLCBjb2xvcj0gImdyZXk1MCIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPSBzcHJpbnRmKCIlMi4xZiIsIFgxKjEwMCkpLCBoanVzdD0gaWZlbHNlKHQwJFgxID4gMCwgMS4yLCAtMC4zKSkrCiAgZmFjZXRfd3JhcCh+IFNldCwgc2NhbGVzPSAiZnJlZSIpICsKICB0aGVtZV9idygpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiTm9uZSIsCiAgICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0gImdyZXkzMCIpLAogICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yPSJ3aGl0ZSIsIGZhY2U9ICJib2xkIikpICArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPSBjKCIjYTNkY2EzIiwgIiNiM2Q5ZmYiKSkKCnN3MQpzdzIKYGBgCiMjIyBDYW5vbmljYWwgbG9hZGluZ3MgYW5kIGNyb3NzLWxvYWRpbmdzCkNhbm9uaWNhbCB3ZWlnaHRzIChzdGFuZGFyZGlzZWQgb3Igbm90KSBoYXZlIGZhbGxlbiBvdXQgb2YgdXNlIGluIGZhdm91ciBvZiBjYW5vbmljYWwgbG9hZGluZ3MuICBUaGVzZSBhcmUgdGhlIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHRoZSBvcmlnaW5hbCB2YXJpYWJsZXMgYW5kIHRoZSBzY29yZXMgb2YgdGhlIGNhbm9uaWNhbCB2YXJpYXRlLgpgYGB7cn0KIyBDYW5vbmljYWwgbG9hZGluZ3MgYW5kIGNyb3NzLWxvYWRpbmdzCgpjYW5fbG9hZGluZ3M8LSBjb21wdXQoWCxZLGNhbmNvcikKY2FuX2xvYWRpbmdzWzM6Nl0KCmNhbl9yZWR1bmRzPC0gIGNhbmRpc2M6OnJlZHVuZGFuY3koY2FuZGlzYzo6Y2FuY29yKFgsWSkpCmNhbl9yZWR1bmRzCmBgYApgYGB7cn0KIyBYIFBsb3QKdDA8LSB0aWJibGUoVmFyPSBjKGJpb2NoZW0sIGJpb2NoZW0pLAogICAgICAgICAgICBYMT0gYyhjYW5fbG9hZGluZ3MkY29yci5YLnhzY29yZXNbLDFdLCBjYW5fbG9hZGluZ3MkY29yci5YLnlzY29yZXNbLDFdKSwKICAgICAgICAgICAgTG9hZGluZz0gYyhyZXAoIlgtWCBMb2FkaW5nIiwgNiksIHJlcCgiWC1ZIENyb3NzLWxvYWRpbmciLCA2KSkpCmdncGxvdCh0MCwgYWVzKHg9IFgxLCB5PSBmY3RfcmVvcmRlcihWYXIsIC1YMSksIGZpbGw9IExvYWRpbmcpKSArCiAgZ2VvbV9jb2woc2l6ZT0gMC4yLCBjb2xvcj0gImdyZXk1MCIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPSBzcHJpbnRmKCIlMi4xZiIsIFgxKjEwMCkpLCBoanVzdD0gLTAuNikrCiAgZmFjZXRfd3JhcCh+IExvYWRpbmcsIHNjYWxlcz0gImZyZWVfeCIpICsKICB0aGVtZV9idygpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiTm9uZSIsCiAgICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0gImdyZXkzMCIpLAogICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yPSJ3aGl0ZSIsIGZhY2U9ICJib2xkIikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9IGMoIiNhM2RjYTMiLCAiI2IzZDlmZiIpKSArCiAgbGFicyh4PSIiLCB5PSAiVmFyaWFibGUiLCB0aXRsZT0gIkxvYWRpbmdzIGFuZCBDcm9zcy1Mb2FkaW5ncyBmb3IgWCAoQmlvY2hlbWlzdHJ5KSIpCmBgYApgYGB7cn0KIyBZIFBsb3QKdDA8LSB0aWJibGUoVmFyPSBjKGxpdmVyLCBsaXZlciksCiAgICAgICAgICAgIFgxPSBjKGNhbl9sb2FkaW5ncyRjb3JyLlkueXNjb3Jlc1ssMV0sIGNhbl9sb2FkaW5ncyRjb3JyLlkueHNjb3Jlc1ssMV0pLAogICAgICAgICAgICBMb2FkaW5nPSBjKHJlcCgiWS1ZIExvYWRpbmciLCAzKSwgcmVwKCJZLVggQ3Jvc3MtbG9hZGluZyIsIDMpKSkgJT4lCiAgICAgbXV0YXRlKExvYWRpbmc9IG9yZGVyZWQoTG9hZGluZywgbGV2ZWxzPWMoIlktWSBMb2FkaW5nIiwgIlktWCBDcm9zcy1sb2FkaW5nIikpKQpnZ3Bsb3QodDAsIGFlcyh4PSBYMSwgeT0gZmN0X3Jlb3JkZXIoVmFyLCAtWDEpLCBmaWxsPSBMb2FkaW5nKSkgKwogIGdlb21fY29sKHNpemU9IDAuMiwgY29sb3I9ICJncmV5NTAiKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD0gc3ByaW50ZigiJTIuMWYiLCBYMSoxMDApKSwgaGp1c3Q9IC0wLjYpKwogIGZhY2V0X3dyYXAofiBMb2FkaW5nLCBzY2FsZXM9ICJmcmVlX3giKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIk5vbmUiLAogICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9ICJncmV5MzAiKSwKICAgICAgICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvcj0id2hpdGUiLCBmYWNlPSAiYm9sZCIpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPSBjKCIjYTNkY2EzIiwgIiNiM2Q5ZmYiKSkgKwogIGxhYnMoeD0iIiwgeT0gIlZhcmlhYmxlIiwgdGl0bGU9ICJMb2FkaW5ncyBhbmQgQ3Jvc3MtTG9hZGluZ3MgZm9yIFkgKExpdmVyKSIpCmBgYApFeGFtaW5hdGlvbiBvZiB0aGUgbG9hZGluZ3MgdG8gdGhlIGZpcnN0IGNhbm9uaWNhbCB2YXJpYXRlIHNob3cgdGhhdCBhbGwgdmFyaWFibGVzIGluY2x1ZGVkIGluIG91ciBhbmFseXNpcyBoYXZlIGEgcGFydCB0byBwbGF5IGluIGV4cGxhaW5pbmcgdGhlIHJlbGF0aW9uIGJldHdlZW4gYmlvY2hlbWlzdHJ5IGFuZCBsaXZlciBoZWFsdGguIEZyb20gdGhlIGxpdmVyIHNpZGUsIFBERkYgaXMgdGhlIHN0cm9uZ2VzdCB2YXJpYWJsZSwgd2hpbGUgZnJvbSB0aGUgYmlvY2hlbWlzdHJ5IHNpZGUgdGhlIHRocmVlIG1vc3QgaW5mbHVlbnRpYWwgdmFyaWFibGVzIGFyZSBsaWdodCBzY2F0dGVyIHJldGljdWxvY3l0ZSBjb3VudCwgYWxhbmluZSBhbWlub3RyYW5zZmVyYXNlIGFuZCB0cmlnbHljZXJpZGVzLgpgYGB7cn0KcGx0LmNjKGNjKFgsWSksdmFyLmxhYmVsID0gVFJVRSkKYGBgCi4uLgoKCi4uLgoKCi4uLgo=