For this practical, we will use data from a sample of the Teaching dataset of the Health Survey for England (2003-2005), which is available here.

We will be fitting a series of binary logistic multilevel models for the probability of being overweight or obese.

The traditional measure used to determine whether an adult is overweight is the Body-Mass Index (BMI).

For an adult to be considered overweight, their BMI must be between 25 and 30. Meanwhile values over 30 are considered as “obese”.



Typical workflow setup and data preparation

1.1. Define a working directory

You can use any directory in your computer. As in the example below:

setwd("C:/myfolder")

Remember to download the data to the folder you will define as working directory, as this makes matters easier.

1.2. Load packages

You can always load packages later on, but it is a good practice to load packages at the beginning of the session on the top of your script or R markdown file.

In this practical, we will use the packages haven, lme4 and ggplot2. Remember that if you haven’t installed them before, you need to do so before you call the library function:

install.packages("tidyverse")
install.packages("haven")
install.packages("lme4")
install.packages("ggplot2")
install.packages("dplyr")
install.packages("broom.mixed")

Then you load them as such:

library(haven)
library(lme4)
library(ggplot2)
library(dplyr)
library(tidyverse)
library(broom.mixed)

1.3. Read in data

You can download the data from the UKDS website. There are four files in different formats. We will be using the Stata dataset named: “hse_data_for_workshop.dta”.

You can also download the file from the link below, but this link will stop working by the end of the session, so make sure to register and download the data from the UKDS if you want to continue working with this dataset.

To read this dataset into R, we need to use the package haven:

hse <- read_dta("https://github.com/A-mora/MLM_summer-school/raw/main/data/hse_data_for_workshop.dta")



Select variables to use

The dataset has many different variables, but we’re not going to be using all of them, so for easier work, we will select the following: year, pserial, area, bmival, sex, age and ethnic.

year is the year where the measures were taken

pserial is the anonymised person identifier

area is the anonymised postcode of the person

bmival is the valid measure of BMI

sex coded 0 for Men and 1 for Women

age from 0 to 99

ethnic coded 1 for White; 2 for Mixed; 3 for Asian or Asian British; 4 for Black or Black British and 5 for Chinese or any other group

For a detailed description of these variables, have a look in the documentation downloaded alongside with the data. Spoiler alert: You will also find a complete set of materials for a workshop on MLM.

To select variables, we (unsurprisingly) need to use the function select of the dplyr package:

hse2 <- select(hse, year, pserial, area, bmival, sex, age, ethnic, bmival) 



Preliminary tasks

We need to dichotomise our dependent variable bmival. As we mentioned before, the categories “overweight” and “obese” are for those with a BMI over 25 and 30, respectively, so we need to do the following to our continuous BMI measure:

hse2$bmi_bin <- ifelse(hse2$bmival >= 25, 1, 0)

To check this is done correctly, you can run a simple frequency table:

table(hse2$bmi_bin)
## 
##     0     1 
## 14751 15873

If there are more than two values there, then something must have gone wrong.

Also, given that the BMI in children is done differently, we will remove all under-18 individuals. We do this by typing:

hse2 <- hse2 %>% filter(age>18)

Task 1: Fit an empty model

We will start by fitting an empty multilevel model for the propensity of being overweight in people nested in areas.

empty <- glmer(bmi_bin ~ 1 + (1|area), data = hse2, family = binomial("logit"))

summary(empty)
## Generalized linear mixed model fit by maximum likelihood (Laplace
##   Approximation) [glmerMod]
##  Family: binomial  ( logit )
## Formula: bmi_bin ~ 1 + (1 | area)
##    Data: hse2
## 
##      AIC      BIC   logLik deviance df.resid 
##  31279.3  31295.5 -15637.6  31275.3    23895 
## 
## Scaled residuals: 
##     Min      1Q  Median      3Q     Max 
## -1.6139 -1.2694  0.7115  0.7547  0.9025 
## 
## Random effects:
##  Groups Name        Variance Std.Dev.
##  area   (Intercept) 0.06817  0.2611  
## Number of obs: 23897, groups:  area, 1833
## 
## Fixed effects:
##             Estimate Std. Error z value Pr(>|z|)    
## (Intercept)   0.5641     0.0153   36.87   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1


Questions:

1.1 What is the overall probability of being overweight?

Remember this formula:

\[logit(p_i)=\log\left(\frac{p_{i}}{1-p_{i}}\right)\] To get the odds from the estimated coefficients, you use the exponent function:

\[exp(\beta)=odds\]

Then, to convert to probability, you can do this:

\[p = \frac{odds}{1 + odds}\]

odds <- exp(empty@beta) # this is to retrieve the intercept from the model

prob <- odds/(1 + odds) # to estimate the overall probability

prob # to print the result to the screen
## [1] 0.6374097

1.2 What is the VPC for this model?

\[VPC = \frac{\sigma_u^2}{\sigma_u^2 + \sigma_e^2*}\] where \(\sigma_e^2*= \frac{\pi^2}{3} \approx 3.29\)

lev2var <- as.numeric(VarCorr(empty)) # this is to retrieve the level 2 variance from the model

(lev2var)/(lev2var+3.29) # VPC
## [1] 0.02029849



Task 2: Is the MLM better than a single-level model?

To assess the statistical significance of the MLM, we need to compare it to a single-level model. To fit a single-level model, we type:

single <- glm(bmi_bin ~ 1 , data = hse2, family = binomial("logit"))

summary(single)
## 
## Call:
## glm(formula = bmi_bin ~ 1, family = binomial("logit"), data = hse2)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -1.4234  -1.4234   0.9499   0.9499   0.9499  
## 
## Coefficients:
##             Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  0.56193    0.01345   41.77   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 31314  on 23896  degrees of freedom
## Residual deviance: 31314  on 23896  degrees of freedom
##   (4034 observations deleted due to missingness)
## AIC: 31316
## 
## Number of Fisher Scoring iterations: 4

These results are not very interesting in themselves, so we move on extract the loglikelihood and compare to the MLM.

L1 <- as.numeric(logLik(single)) # store this as numerical to re-use
L2 <- as.numeric(logLik(empty))

(D <- 2*(L2-L1)) # this is the deviance, we put it within brackets to print it immediately
## [1] 38.42078
pchisq(q=D, df=1, lower.tail=F) # To find the p-value
## [1] 5.702211e-10

Question

2.1. Is the addition of the area level statistically significant?

Answer: Yes, it is. The likelihood ratio test shows that the multilevel model fits the data better than the single-level model

2.2. What does this mean in practice?

Answer: This means that there are differences in the propensity towards being overweight that are not attributable to the individuals, but to (unknown) environmental factors of the areas where they live. Statistically speaking, this needs to be controlled for to be able to draw correct inferences.


Task 3: Random intercepts model

Just like in the case of normally-distributed responses, we can fit a random intercepts model by adding explanatory variables.

We want to find out whether there any differences between males and females on the probability of being overweight.

rand_int <- glmer(bmi_bin ~ factor(sex) + (1|area), data = hse2, family = binomial("logit"))

summary(rand_int)
## Generalized linear mixed model fit by maximum likelihood (Laplace
##   Approximation) [glmerMod]
##  Family: binomial  ( logit )
## Formula: bmi_bin ~ factor(sex) + (1 | area)
##    Data: hse2
## 
##      AIC      BIC   logLik deviance df.resid 
##  30962.2  30986.4 -15478.1  30956.2    23894 
## 
## Scaled residuals: 
##     Min      1Q  Median      3Q     Max 
## -1.9006 -1.1735  0.6406  0.7871  1.0249 
## 
## Random effects:
##  Groups Name        Variance Std.Dev.
##  area   (Intercept) 0.07516  0.2742  
## Number of obs: 23897, groups:  area, 1833
## 
## Fixed effects:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept)   0.84265    0.02246   37.52   <2e-16 ***
## factor(sex)1 -0.49290    0.02781  -17.72   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Correlation of Fixed Effects:
##             (Intr)
## factor(sx)1 -0.722

We can also get a more “human-readable” version of the table of coefficients by using the package broom.mixed:

(m1 <- tidy(rand_int, effects = "fixed",
          conf.int=TRUE, 
          exponentiate=FALSE))
## # A tibble: 2 × 8
##   effect term         estimate std.error statistic  p.value conf.low conf.high
##   <chr>  <chr>           <dbl>     <dbl>     <dbl>    <dbl>    <dbl>     <dbl>
## 1 fixed  (Intercept)     0.843    0.0225      37.5 0           0.799     0.887
## 2 fixed  factor(sex)1   -0.493    0.0278     -17.7 2.78e-70   -0.547    -0.438

You can plot these for easier inspection:

(m1_plot <- 
    ggplot(m1, 
           mapping = aes(x= estimate,
                         y = term, 
                         xmin = conf.low, 
                         xmax = conf.high)) +
    geom_vline(xintercept = 0, color = "tomato", size =1) +
    geom_pointrange() +
    theme_classic())
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.

If you want to obtain the odds ratios, you can change the option exponentiate to TRUE.

(m1_odds <- tidy(rand_int, effects = "fixed",
          conf.int=TRUE, 
          exponentiate=TRUE))
## # A tibble: 2 × 8
##   effect term         estimate std.error statistic  p.value conf.low conf.high
##   <chr>  <chr>           <dbl>     <dbl>     <dbl>    <dbl>    <dbl>     <dbl>
## 1 fixed  (Intercept)     2.32     0.0522      37.5 0           2.22      2.43 
## 2 fixed  factor(sex)1    0.611    0.0170     -17.7 2.78e-70    0.578     0.645

Unsurprisingly, you can plot the odds for easier inspection:

(m1_odds_plot <- 
    ggplot(m1_odds, 
           mapping = aes(x= estimate,
                         y = term, 
                         xmin = conf.low, 
                         xmax = conf.high)) +
    geom_vline(xintercept = 1, color = "tomato", size =1) +
    geom_pointrange() +
    theme_classic())

Question:

3.1. What is the effect of sex on being overweight?

Answer: Women are expected to be significantly less likely than men to be overweight.

3.2. What is the probability of a female being overweight in this sample?

Answer: Similar to the steps from question 1.1, but this time we need to do the following:

\[exp(\beta_0 + \beta_1)=odds\] \[exp(0.84265 - 0.4929) = 1.419\] And then:

\[p = \frac{1.419}{1 + 1.419}=0.587\]

Women in this sample have an expected probability of 0.587 of being overweight.

Bonus

You can also obtain expected probabilities, by using marginal effects. Have a look at the “marginaleffects” package on this link.


Task 4: Area-specific estimates

Just like in task 5 of practical 2, we can retrieve the residuals from the random intercepts and plot them to find out which areas have more or less than average people who are overweight.

This is what is sometimes called in the literature a “Caterpillar plot”.

m2 <- tidy(rand_int, effects = "ran_vals",
          conf.int=TRUE,
          exponentiate=FALSE)

(RE <- m2 %>%
    mutate(level = fct_reorder(level, estimate)) %>%
    ggplot( mapping = aes(x= estimate,
                         y = level, 
                         xmin = conf.low, 
                         xmax = conf.high)) +
    geom_vline(xintercept = 0, color = "tomato", size =1) +
    coord_flip() +
    labs(x = "logit",
         y = "Areas") +
    geom_pointrange() +
    theme_classic())

Question:

4.1. Are there any areas that are significantly above or below average?

Answer: Even though the plot may be hard to inspect closely, it seems as if all areas overlap with zero (national average). This would mean that having controlled for sex, no areas are significantly below or above average.


Additional tasks

You can borrow from practical 2 and repeat some of the tasks there, such as:

  • adding more explanatory variables

  • adding interaction effects

  • plot predictions.

LS0tDQp0aXRsZTogIkludHJvIHRvIE1MTTogUHJhY3RpY2FsIDMiDQphdXRob3I6IEFuYSBNb3JhbGVzLUfDs21leiBhbmQgUGF0cmljaW8gVHJvbmNvc28gDQpkYXRlOiAiSnVuZSAyMDI0Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBoaWdobGlnaHRlcjogbnVsbA0KICAgIHRoZW1lOiBjb3Ntbw0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBmb250c2l6ZTogMTJwdA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCg0KRm9yIHRoaXMgcHJhY3RpY2FsLCB3ZSB3aWxsIHVzZSBkYXRhIGZyb20gYSBzYW1wbGUgb2YgdGhlIFRlYWNoaW5nIGRhdGFzZXQgb2YgdGhlIEhlYWx0aCBTdXJ2ZXkgZm9yIEVuZ2xhbmQgKDIwMDMtMjAwNSksIHdoaWNoIGlzDQphdmFpbGFibGUgWyoqaGVyZSoqXShodHRwczovL2JldGEudWtkYXRhc2VydmljZS5hYy51ay9kYXRhY2F0YWxvZ3VlL3N0dWRpZXMvc3R1ZHk/aWQ9Njc2NSkuDQoNCldlIHdpbGwgYmUgZml0dGluZyBhIHNlcmllcyBvZiBiaW5hcnkgbG9naXN0aWMgbXVsdGlsZXZlbCBtb2RlbHMgZm9yIHRoZSBwcm9iYWJpbGl0eSBvZiBiZWluZyBvdmVyd2VpZ2h0IG9yIG9iZXNlLg0KDQpUaGUgdHJhZGl0aW9uYWwgbWVhc3VyZSB1c2VkIHRvIGRldGVybWluZSB3aGV0aGVyIGFuIGFkdWx0IGlzIG92ZXJ3ZWlnaHQgaXMgdGhlIEJvZHktTWFzcyBJbmRleCAoQk1JKS4NCg0KRm9yIGFuIGFkdWx0IHRvIGJlIGNvbnNpZGVyZWQgb3ZlcndlaWdodCwgdGhlaXIgQk1JIG11c3QgYmUgYmV0d2VlbiAyNSBhbmQgMzAuIE1lYW53aGlsZSB2YWx1ZXMgb3ZlciAzMCBhcmUgY29uc2lkZXJlZCBhcyAib2Jlc2UiLg0KDQo8YnI+DQoNCioqKg0KDQojIFR5cGljYWwgd29ya2Zsb3cgc2V0dXAgYW5kIGRhdGEgcHJlcGFyYXRpb24NCg0KIyMgMS4xLiBEZWZpbmUgYSB3b3JraW5nIGRpcmVjdG9yeQ0KDQpZb3UgY2FuIHVzZSBhbnkgZGlyZWN0b3J5IGluIHlvdXIgY29tcHV0ZXIuIEFzIGluIHRoZSBleGFtcGxlIGJlbG93Og0KDQpgYGB7ciwgZXZhbD1GfQ0Kc2V0d2QoIkM6L215Zm9sZGVyIikNCmBgYA0KDQpSZW1lbWJlciB0byBkb3dubG9hZCB0aGUgZGF0YSB0byB0aGUgZm9sZGVyIHlvdSB3aWxsIGRlZmluZSBhcyB3b3JraW5nIGRpcmVjdG9yeSwgYXMgdGhpcyBtYWtlcyBtYXR0ZXJzIGVhc2llci4NCg0KIyMgMS4yLiBMb2FkIHBhY2thZ2VzDQoNCllvdSBjYW4gYWx3YXlzIGxvYWQgcGFja2FnZXMgbGF0ZXIgb24sIGJ1dCBpdCBpcyBhIGdvb2QgcHJhY3RpY2UgdG8gbG9hZCBwYWNrYWdlcyBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSBzZXNzaW9uIG9uIHRoZSB0b3Agb2YgeW91ciBzY3JpcHQgb3IgUiBtYXJrZG93biBmaWxlLg0KDQpJbiB0aGlzIHByYWN0aWNhbCwgd2Ugd2lsbCB1c2UgdGhlIHBhY2thZ2VzIGBoYXZlbmAsIGBsbWU0YCBhbmQgYGdncGxvdDJgLiBSZW1lbWJlciB0aGF0IGlmIHlvdSBoYXZlbid0IGluc3RhbGxlZCB0aGVtIGJlZm9yZSwgeW91IG5lZWQgdG8gZG8gc28gYmVmb3JlIHlvdSBjYWxsIHRoZSBgbGlicmFyeWAgZnVuY3Rpb246DQoNCmBgYHt9DQppbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KaW5zdGFsbC5wYWNrYWdlcygiaGF2ZW4iKQ0KaW5zdGFsbC5wYWNrYWdlcygibG1lNCIpDQppbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmluc3RhbGwucGFja2FnZXMoImRwbHlyIikNCmluc3RhbGwucGFja2FnZXMoImJyb29tLm1peGVkIikNCmBgYA0KDQpUaGVuIHlvdSBsb2FkIHRoZW0gYXMgc3VjaDoNCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KbGlicmFyeShoYXZlbikNCmxpYnJhcnkobG1lNCkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoYnJvb20ubWl4ZWQpDQpgYGANCg0KIyMgMS4zLiBSZWFkIGluIGRhdGEgDQoNCllvdSBjYW4gZG93bmxvYWQgdGhlIGRhdGEgZnJvbSAgdGhlIFsqKlVLRFMgd2Vic2l0ZSoqXShodHRwczovL2JldGEudWtkYXRhc2VydmljZS5hYy51ay9kYXRhY2F0YWxvZ3VlL3N0dWRpZXMvc3R1ZHk/aWQ9Njc2NSkuIFRoZXJlIGFyZSBmb3VyIGZpbGVzIGluIGRpZmZlcmVudCBmb3JtYXRzLiBXZSB3aWxsIGJlIHVzaW5nIHRoZSBTdGF0YSBkYXRhc2V0IG5hbWVkOiAiaHNlX2RhdGFfZm9yX3dvcmtzaG9wLmR0YSIuDQoNCllvdSBjYW4gYWxzbyBkb3dubG9hZCB0aGUgZmlsZSBmcm9tIHRoZSBsaW5rIGJlbG93LCBidXQgdGhpcyBsaW5rIHdpbGwgc3RvcCB3b3JraW5nIGJ5IHRoZSBlbmQgb2YgdGhlIHNlc3Npb24sIHNvIG1ha2Ugc3VyZSB0byByZWdpc3RlciBhbmQgZG93bmxvYWQgdGhlIGRhdGEgZnJvbSB0aGUgVUtEUyBpZiB5b3Ugd2FudCB0byBjb250aW51ZSB3b3JraW5nIHdpdGggdGhpcyBkYXRhc2V0Lg0KDQpUbyByZWFkIHRoaXMgZGF0YXNldCBpbnRvIFIsIHdlIG5lZWQgdG8gdXNlIHRoZSBwYWNrYWdlIGBoYXZlbmA6DQoNCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KaHNlIDwtIHJlYWRfZHRhKCJodHRwczovL2dpdGh1Yi5jb20vQS1tb3JhL01MTV9zdW1tZXItc2Nob29sL3Jhdy9tYWluL2RhdGEvaHNlX2RhdGFfZm9yX3dvcmtzaG9wLmR0YSIpDQpgYGANCg0KPGJyPg0KDQoqKioNCg0KIyMgU2VsZWN0IHZhcmlhYmxlcyB0byB1c2UNCg0KVGhlIGRhdGFzZXQgaGFzIG1hbnkgZGlmZmVyZW50IHZhcmlhYmxlcywgYnV0IHdlJ3JlIG5vdCBnb2luZyB0byBiZSB1c2luZyBhbGwgb2YgdGhlbSwgc28gZm9yIGVhc2llciB3b3JrLCB3ZSB3aWxsIHNlbGVjdCB0aGUgZm9sbG93aW5nOiBgeWVhcmAsIGBwc2VyaWFsYCwgYGFyZWFgLCBgYm1pdmFsYCwgYHNleGAsIGBhZ2VgIGFuZCBgZXRobmljYC4NCg0KYHllYXJgIGlzIHRoZSB5ZWFyIHdoZXJlIHRoZSBtZWFzdXJlcyB3ZXJlIHRha2VuDQoNCmBwc2VyaWFsYCBpcyB0aGUgYW5vbnltaXNlZCBwZXJzb24gaWRlbnRpZmllcg0KDQpgYXJlYWAgaXMgdGhlIGFub255bWlzZWQgcG9zdGNvZGUgb2YgdGhlIHBlcnNvbg0KDQpgYm1pdmFsYCBpcyB0aGUgdmFsaWQgbWVhc3VyZSBvZiBCTUkNCg0KYHNleGAgY29kZWQgMCBmb3IgTWVuIGFuZCAxIGZvciBXb21lbg0KDQpgYWdlYCBmcm9tIDAgdG8gOTkNCg0KYGV0aG5pY2AgY29kZWQgMSBmb3IgV2hpdGU7IA0KICAgICAgICAgICAgICAgMiBmb3IgTWl4ZWQ7IA0KICAgICAgICAgICAgICAgMyBmb3IgQXNpYW4gb3IgQXNpYW4gQnJpdGlzaDsgDQogICAgICAgICAgICAgICA0IGZvciBCbGFjayBvciBCbGFjayBCcml0aXNoIGFuZCANCiAgICAgICAgICAgICAgIDUgZm9yIENoaW5lc2Ugb3IgYW55IG90aGVyIGdyb3VwDQoNCkZvciBhIGRldGFpbGVkIGRlc2NyaXB0aW9uIG9mIHRoZXNlIHZhcmlhYmxlcywgaGF2ZSBhIGxvb2sgaW4gdGhlIGRvY3VtZW50YXRpb24gZG93bmxvYWRlZCBhbG9uZ3NpZGUgd2l0aCB0aGUgZGF0YS4gDQoqKlNwb2lsZXIgYWxlcnQ6KiogWW91IHdpbGwgYWxzbyBmaW5kIGEgY29tcGxldGUgc2V0IG9mIG1hdGVyaWFscyBmb3IgYSB3b3Jrc2hvcCBvbiBNTE0uDQoNClRvIHNlbGVjdCB2YXJpYWJsZXMsIHdlICh1bnN1cnByaXNpbmdseSkgbmVlZCB0byB1c2UgdGhlIGZ1bmN0aW9uIGBzZWxlY3RgIG9mIHRoZSBgZHBseXJgIHBhY2thZ2U6DQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCg0KaHNlMiA8LSBzZWxlY3QoaHNlLCB5ZWFyLCBwc2VyaWFsLCBhcmVhLCBibWl2YWwsIHNleCwgYWdlLCBldGhuaWMsIGJtaXZhbCkgDQoNCmBgYA0KDQo8YnI+DQoNCioqKg0KIyBQcmVsaW1pbmFyeSB0YXNrcw0KDQpXZSBuZWVkIHRvIGRpY2hvdG9taXNlIG91ciBkZXBlbmRlbnQgdmFyaWFibGUgYGJtaXZhbGAuIEFzIHdlIG1lbnRpb25lZCBiZWZvcmUsIHRoZSBjYXRlZ29yaWVzICJvdmVyd2VpZ2h0IiBhbmQgIm9iZXNlIiBhcmUgZm9yIHRob3NlIHdpdGggYSBCTUkgb3ZlciAyNSBhbmQgMzAsIHJlc3BlY3RpdmVseSwgc28gd2UgbmVlZCB0byBkbyB0aGUgZm9sbG93aW5nIHRvIG91ciBjb250aW51b3VzIEJNSSBtZWFzdXJlOg0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQpoc2UyJGJtaV9iaW4gPC0gaWZlbHNlKGhzZTIkYm1pdmFsID49IDI1LCAxLCAwKQ0KYGBgDQoNClRvIGNoZWNrIHRoaXMgaXMgZG9uZSBjb3JyZWN0bHksIHlvdSBjYW4gcnVuIGEgc2ltcGxlIGZyZXF1ZW5jeSB0YWJsZToNCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KdGFibGUoaHNlMiRibWlfYmluKQ0KYGBgDQoNCklmIHRoZXJlIGFyZSBtb3JlIHRoYW4gdHdvIHZhbHVlcyB0aGVyZSwgdGhlbiBzb21ldGhpbmcgbXVzdCBoYXZlIGdvbmUgd3JvbmcuDQoNCkFsc28sIGdpdmVuIHRoYXQgdGhlIEJNSSBpbiBjaGlsZHJlbiBpcyBkb25lIGRpZmZlcmVudGx5LCB3ZSB3aWxsIHJlbW92ZSBhbGwgdW5kZXItMTggaW5kaXZpZHVhbHMuIFdlIGRvIHRoaXMgYnkgdHlwaW5nOg0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQpoc2UyIDwtIGhzZTIgJT4lIGZpbHRlcihhZ2U+MTgpDQpgYGANCg0KIyBUYXNrIDE6IEZpdCBhbiBlbXB0eSBtb2RlbA0KDQpXZSB3aWxsIHN0YXJ0IGJ5IGZpdHRpbmcgYW4gZW1wdHkgbXVsdGlsZXZlbCBtb2RlbCBmb3IgdGhlIHByb3BlbnNpdHkgb2YgYmVpbmcgb3ZlcndlaWdodCBpbiBwZW9wbGUgbmVzdGVkIGluIGFyZWFzLg0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQplbXB0eSA8LSBnbG1lcihibWlfYmluIH4gMSArICgxfGFyZWEpLCBkYXRhID0gaHNlMiwgZmFtaWx5ID0gYmlub21pYWwoImxvZ2l0IikpDQoNCnN1bW1hcnkoZW1wdHkpDQpgYGANCg0KDQo8YnI+DQoNCiMjIyBRdWVzdGlvbnM6DQoNCjEuMSBXaGF0IGlzIHRoZSBvdmVyYWxsIHByb2JhYmlsaXR5IG9mIGJlaW5nIG92ZXJ3ZWlnaHQ/DQoNClJlbWVtYmVyIHRoaXMgZm9ybXVsYToNCg0KJCRsb2dpdChwX2kpPVxsb2dcbGVmdChcZnJhY3twX3tpfX17MS1wX3tpfX1ccmlnaHQpJCQNClRvIGdldCB0aGUgb2RkcyBmcm9tIHRoZSBlc3RpbWF0ZWQgY29lZmZpY2llbnRzLCB5b3UgdXNlIHRoZSBleHBvbmVudCBmdW5jdGlvbjoNCg0KJCRleHAoXGJldGEpPW9kZHMkJA0KDQpUaGVuLCB0byBjb252ZXJ0IHRvIHByb2JhYmlsaXR5LCB5b3UgY2FuIGRvIHRoaXM6DQoNCiQkcCA9IFxmcmFje29kZHN9ezEgKyBvZGRzfSQkDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoNCm9kZHMgPC0gZXhwKGVtcHR5QGJldGEpICMgdGhpcyBpcyB0byByZXRyaWV2ZSB0aGUgaW50ZXJjZXB0IGZyb20gdGhlIG1vZGVsDQoNCnByb2IgPC0gb2Rkcy8oMSArIG9kZHMpICMgdG8gZXN0aW1hdGUgdGhlIG92ZXJhbGwgcHJvYmFiaWxpdHkNCg0KcHJvYiAjIHRvIHByaW50IHRoZSByZXN1bHQgdG8gdGhlIHNjcmVlbg0KYGBgDQoNCg0KMS4yIFdoYXQgaXMgdGhlIFZQQyBmb3IgdGhpcyBtb2RlbD8NCg0KJCRWUEMgPSBcZnJhY3tcc2lnbWFfdV4yfXtcc2lnbWFfdV4yICsgXHNpZ21hX2VeMip9JCQNCndoZXJlICRcc2lnbWFfZV4yKj0gXGZyYWN7XHBpXjJ9ezN9IFxhcHByb3ggMy4yOSQNCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KbGV2MnZhciA8LSBhcy5udW1lcmljKFZhckNvcnIoZW1wdHkpKSAjIHRoaXMgaXMgdG8gcmV0cmlldmUgdGhlIGxldmVsIDIgdmFyaWFuY2UgZnJvbSB0aGUgbW9kZWwNCg0KKGxldjJ2YXIpLyhsZXYydmFyKzMuMjkpICMgVlBDDQpgYGANCg0KPGJyPg0KDQoqKioNCg0KIyBUYXNrIDI6IElzIHRoZSBNTE0gYmV0dGVyIHRoYW4gYSBzaW5nbGUtbGV2ZWwgbW9kZWw/DQoNClRvIGFzc2VzcyB0aGUgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIG9mIHRoZSBNTE0sIHdlIG5lZWQgdG8gY29tcGFyZSBpdCB0byBhIHNpbmdsZS1sZXZlbCBtb2RlbC4gVG8gZml0IGEgc2luZ2xlLWxldmVsIG1vZGVsLCB3ZSB0eXBlOg0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoNCnNpbmdsZSA8LSBnbG0oYm1pX2JpbiB+IDEgLCBkYXRhID0gaHNlMiwgZmFtaWx5ID0gYmlub21pYWwoImxvZ2l0IikpDQoNCnN1bW1hcnkoc2luZ2xlKQ0KDQpgYGANCg0KVGhlc2UgcmVzdWx0cyBhcmUgbm90IHZlcnkgaW50ZXJlc3RpbmcgaW4gdGhlbXNlbHZlcywgc28gd2UgbW92ZSBvbiBleHRyYWN0IHRoZSBsb2dsaWtlbGlob29kIGFuZCBjb21wYXJlIHRvIHRoZSBNTE0uDQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCg0KTDEgPC0gYXMubnVtZXJpYyhsb2dMaWsoc2luZ2xlKSkgIyBzdG9yZSB0aGlzIGFzIG51bWVyaWNhbCB0byByZS11c2UNCkwyIDwtIGFzLm51bWVyaWMobG9nTGlrKGVtcHR5KSkNCg0KKEQgPC0gMiooTDItTDEpKSAjIHRoaXMgaXMgdGhlIGRldmlhbmNlLCB3ZSBwdXQgaXQgd2l0aGluIGJyYWNrZXRzIHRvIHByaW50IGl0IGltbWVkaWF0ZWx5DQoNCnBjaGlzcShxPUQsIGRmPTEsIGxvd2VyLnRhaWw9RikgIyBUbyBmaW5kIHRoZSBwLXZhbHVlDQoNCmBgYA0KDQojIyMgUXVlc3Rpb24NCg0KMi4xLiBJcyB0aGUgYWRkaXRpb24gb2YgdGhlIGFyZWEgbGV2ZWwgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudD8NCg0KKipBbnN3ZXI6KiogWWVzLCBpdCBpcy4gVGhlIGxpa2VsaWhvb2QgcmF0aW8gdGVzdCBzaG93cyB0aGF0IHRoZSBtdWx0aWxldmVsIG1vZGVsIGZpdHMgdGhlIGRhdGEgYmV0dGVyIHRoYW4gdGhlIHNpbmdsZS1sZXZlbCBtb2RlbA0KDQoyLjIuIFdoYXQgZG9lcyB0aGlzIG1lYW4gaW4gcHJhY3RpY2U/DQoNCioqQW5zd2VyOioqIFRoaXMgbWVhbnMgdGhhdCB0aGVyZSBhcmUgZGlmZmVyZW5jZXMgaW4gdGhlIHByb3BlbnNpdHkgdG93YXJkcyBiZWluZyBvdmVyd2VpZ2h0IHRoYXQgYXJlIG5vdCBhdHRyaWJ1dGFibGUgdG8gdGhlIGluZGl2aWR1YWxzLCBidXQgdG8gKHVua25vd24pIGVudmlyb25tZW50YWwgZmFjdG9ycyBvZiB0aGUgYXJlYXMgd2hlcmUgdGhleSBsaXZlLiBTdGF0aXN0aWNhbGx5IHNwZWFraW5nLCB0aGlzIG5lZWRzIHRvIGJlIGNvbnRyb2xsZWQgZm9yIHRvIGJlIGFibGUgdG8gZHJhdyBjb3JyZWN0IGluZmVyZW5jZXMuDQoNCg0KKioqDQoNCiMgVGFzayAzOiBSYW5kb20gaW50ZXJjZXB0cyBtb2RlbA0KDQpKdXN0IGxpa2UgaW4gdGhlIGNhc2Ugb2Ygbm9ybWFsbHktZGlzdHJpYnV0ZWQgcmVzcG9uc2VzLCB3ZSBjYW4gZml0IGEgYHJhbmRvbSBpbnRlcmNlcHRzYCBtb2RlbCBieSBhZGRpbmcgZXhwbGFuYXRvcnkgdmFyaWFibGVzLg0KDQpXZSB3YW50IHRvIGZpbmQgb3V0IHdoZXRoZXIgdGhlcmUgYW55IGRpZmZlcmVuY2VzIGJldHdlZW4gbWFsZXMgYW5kIGZlbWFsZXMgb24gdGhlIHByb2JhYmlsaXR5IG9mIGJlaW5nIG92ZXJ3ZWlnaHQuDQoNCmBgYHtyLCB3YXJuaW5nPVQsIG1lc3NhZ2U9Rn0NCnJhbmRfaW50IDwtIGdsbWVyKGJtaV9iaW4gfiBmYWN0b3Ioc2V4KSArICgxfGFyZWEpLCBkYXRhID0gaHNlMiwgZmFtaWx5ID0gYmlub21pYWwoImxvZ2l0IikpDQoNCnN1bW1hcnkocmFuZF9pbnQpDQpgYGANCg0KV2UgY2FuIGFsc28gZ2V0IGEgbW9yZSAiaHVtYW4tcmVhZGFibGUiIHZlcnNpb24gb2YgdGhlIHRhYmxlIG9mIGNvZWZmaWNpZW50cyBieSB1c2luZyB0aGUgcGFja2FnZSBgYnJvb20ubWl4ZWRgOg0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoobTEgPC0gdGlkeShyYW5kX2ludCwgZWZmZWN0cyA9ICJmaXhlZCIsDQogICAgICAgICAgY29uZi5pbnQ9VFJVRSwgDQogICAgICAgICAgZXhwb25lbnRpYXRlPUZBTFNFKSkNCmBgYA0KDQpZb3UgY2FuIHBsb3QgdGhlc2UgZm9yIGVhc2llciBpbnNwZWN0aW9uOg0KDQpgYGB7cn0NCihtMV9wbG90IDwtIA0KICAgIGdncGxvdChtMSwgDQogICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeD0gZXN0aW1hdGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHRlcm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHhtaW4gPSBjb25mLmxvdywgDQogICAgICAgICAgICAgICAgICAgICAgICAgeG1heCA9IGNvbmYuaGlnaCkpICsNCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJ0b21hdG8iLCBzaXplID0xKSArDQogICAgZ2VvbV9wb2ludHJhbmdlKCkgKw0KICAgIHRoZW1lX2NsYXNzaWMoKSkNCmBgYA0KDQpJZiB5b3Ugd2FudCB0byBvYnRhaW4gdGhlIG9kZHMgcmF0aW9zLCB5b3UgY2FuIGNoYW5nZSB0aGUgb3B0aW9uIGBleHBvbmVudGlhdGVgIHRvIGBUUlVFYC4NCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KKG0xX29kZHMgPC0gdGlkeShyYW5kX2ludCwgZWZmZWN0cyA9ICJmaXhlZCIsDQogICAgICAgICAgY29uZi5pbnQ9VFJVRSwgDQogICAgICAgICAgZXhwb25lbnRpYXRlPVRSVUUpKQ0KYGBgDQoNClVuc3VycHJpc2luZ2x5LCB5b3UgY2FuIHBsb3QgdGhlIG9kZHMgZm9yIGVhc2llciBpbnNwZWN0aW9uOg0KDQpgYGB7cn0NCihtMV9vZGRzX3Bsb3QgPC0gDQogICAgZ2dwbG90KG0xX29kZHMsIA0KICAgICAgICAgICBtYXBwaW5nID0gYWVzKHg9IGVzdGltYXRlLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSB0ZXJtLCANCiAgICAgICAgICAgICAgICAgICAgICAgICB4bWluID0gY29uZi5sb3csIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHhtYXggPSBjb25mLmhpZ2gpKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMSwgY29sb3IgPSAidG9tYXRvIiwgc2l6ZSA9MSkgKw0KICAgIGdlb21fcG9pbnRyYW5nZSgpICsNCiAgICB0aGVtZV9jbGFzc2ljKCkpDQpgYGANCg0KIyMjIFF1ZXN0aW9uOg0KDQozLjEuIFdoYXQgaXMgdGhlIGVmZmVjdCBvZiBzZXggb24gYmVpbmcgb3ZlcndlaWdodD8NCg0KKipBbnN3ZXI6KiogV29tZW4gYXJlIGV4cGVjdGVkIHRvIGJlIHNpZ25pZmljYW50bHkgbGVzcyBsaWtlbHkgdGhhbiBtZW4gdG8gYmUgb3ZlcndlaWdodC4NCg0KMy4yLiBXaGF0IGlzIHRoZSBwcm9iYWJpbGl0eSBvZiBhIGZlbWFsZSBiZWluZyBvdmVyd2VpZ2h0IGluIHRoaXMgc2FtcGxlPw0KDQoqKkFuc3dlcjoqKiBTaW1pbGFyIHRvIHRoZSBzdGVwcyBmcm9tIHF1ZXN0aW9uIDEuMSwgYnV0IHRoaXMgdGltZSB3ZSBuZWVkIHRvIGRvIHRoZSBmb2xsb3dpbmc6DQoNCiQkZXhwKFxiZXRhXzAgKyBcYmV0YV8xKT1vZGRzJCQNCiQkZXhwKDAuODQyNjUgLSAwLjQ5MjkpID0gMS40MTkkJA0KQW5kIHRoZW46DQoNCiQkcCA9IFxmcmFjezEuNDE5fXsxICsgMS40MTl9PTAuNTg3JCQNCg0KV29tZW4gaW4gdGhpcyBzYW1wbGUgaGF2ZSBhbiBleHBlY3RlZCBwcm9iYWJpbGl0eSBvZiAwLjU4NyBvZiBiZWluZyBvdmVyd2VpZ2h0Lg0KDQojIyBCb251cw0KDQpZb3UgY2FuIGFsc28gb2J0YWluIGV4cGVjdGVkIHByb2JhYmlsaXRpZXMsIGJ5IHVzaW5nIG1hcmdpbmFsIGVmZmVjdHMuIEhhdmUgYSBsb29rIGF0IHRoZSAibWFyZ2luYWxlZmZlY3RzIiBwYWNrYWdlIG9uIHRoaXMgW2xpbmsuXShodHRwczovL3ZpbmNlbnRhcmVsYnVuZG9jay5naXRodWIuaW8vbWFyZ2luYWxlZmZlY3RzLykgDQoNCioqKg0KDQojIFRhc2sgNDogQXJlYS1zcGVjaWZpYyBlc3RpbWF0ZXMNCg0KSnVzdCBsaWtlIGluIHRhc2sgNSBvZiBwcmFjdGljYWwgMiwgd2UgY2FuIHJldHJpZXZlIHRoZSByZXNpZHVhbHMgZnJvbSB0aGUgYHJhbmRvbSBpbnRlcmNlcHRzYCBhbmQgcGxvdCB0aGVtIHRvIGZpbmQgb3V0IHdoaWNoIGFyZWFzIGhhdmUgbW9yZSBvciBsZXNzIHRoYW4gYXZlcmFnZSBwZW9wbGUgd2hvIGFyZSBvdmVyd2VpZ2h0Lg0KDQpUaGlzIGlzIHdoYXQgaXMgc29tZXRpbWVzIGNhbGxlZCBpbiB0aGUgbGl0ZXJhdHVyZSBhICJDYXRlcnBpbGxhciBwbG90Ii4NCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KbTIgPC0gdGlkeShyYW5kX2ludCwgZWZmZWN0cyA9ICJyYW5fdmFscyIsDQogICAgICAgICAgY29uZi5pbnQ9VFJVRSwNCiAgICAgICAgICBleHBvbmVudGlhdGU9RkFMU0UpDQoNCihSRSA8LSBtMiAlPiUNCiAgICBtdXRhdGUobGV2ZWwgPSBmY3RfcmVvcmRlcihsZXZlbCwgZXN0aW1hdGUpKSAlPiUNCiAgICBnZ3Bsb3QoIG1hcHBpbmcgPSBhZXMoeD0gZXN0aW1hdGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGxldmVsLCANCiAgICAgICAgICAgICAgICAgICAgICAgICB4bWluID0gY29uZi5sb3csIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHhtYXggPSBjb25mLmhpZ2gpKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAidG9tYXRvIiwgc2l6ZSA9MSkgKw0KICAgIGNvb3JkX2ZsaXAoKSArDQogICAgbGFicyh4ID0gImxvZ2l0IiwNCiAgICAgICAgIHkgPSAiQXJlYXMiKSArDQogICAgZ2VvbV9wb2ludHJhbmdlKCkgKw0KICAgIHRoZW1lX2NsYXNzaWMoKSkNCg0KYGBgDQoNCiMjIyBRdWVzdGlvbjoNCg0KNC4xLiBBcmUgdGhlcmUgYW55IGFyZWFzIHRoYXQgYXJlIHNpZ25pZmljYW50bHkgYWJvdmUgb3IgYmVsb3cgYXZlcmFnZT8NCg0KKipBbnN3ZXI6KiogRXZlbiB0aG91Z2ggdGhlIHBsb3QgbWF5IGJlIGhhcmQgdG8gaW5zcGVjdCBjbG9zZWx5LCBpdCBzZWVtcyBhcyBpZiBhbGwgYXJlYXMgb3ZlcmxhcCB3aXRoIHplcm8gKG5hdGlvbmFsIGF2ZXJhZ2UpLiBUaGlzIHdvdWxkIG1lYW4gdGhhdCBoYXZpbmcgY29udHJvbGxlZCBmb3Igc2V4LCBubyBhcmVhcyBhcmUgc2lnbmlmaWNhbnRseSBiZWxvdyBvciBhYm92ZSBhdmVyYWdlLg0KDQoqKioNCg0KIyBBZGRpdGlvbmFsIHRhc2tzDQoNCllvdSBjYW4gYm9ycm93IGZyb20gcHJhY3RpY2FsIDIgYW5kIHJlcGVhdCBzb21lIG9mIHRoZSB0YXNrcyB0aGVyZSwgc3VjaCBhczoNCg0KLSBhZGRpbmcgbW9yZSBleHBsYW5hdG9yeSB2YXJpYWJsZXMgDQoNCi0gYWRkaW5nIGludGVyYWN0aW9uIGVmZmVjdHMNCg0KLSBwbG90IHByZWRpY3Rpb25zLg==