devtools::load_all() # if using the rproject dowloaded from the slides
# source("utils-glm.R") # if using a standard setup
library(here)
library(tidyr) # for data manipulation
library(dplyr) # for data manipulation
library(ggplot2) # plotting
library(car) # general utilities
library(effects) # for extracting and plotting effects 
library(emmeans) # for marginal means

Overview

We are gonna work with the admission.csv dataset containing \(n = 400\) students for the admission to the UCLA University. A researcher is interested in how variables, such as gre (Graduate Record Exam scores), gpa (GPA), rank (prestige of the undergraduate institution) have an influence for the admission into graduate school. The response variable, admit/don’t admit, is a binary variable.

…(continue from lab 8)

  1. Fitting the model with interactions
  2. Plotting results
  3. Interpreting the parameters
  4. Model comparison for the interactions effect

0. Importing data

We need to set the working directory on the root of the course folder using set.wd(). Using R Projects is just necessary to open the .RProj file and the working directory will be automatically correctly selected.

# relevant steps of the previous lab
admission <- read.csv(here("data", "admission.csv"), header=TRUE)
admission$rankc <- factor(admission$rank, levels = 1:4, labels = 1:4)

1. Fitting model with interactions

Here we fit the two 2-way interactions between gpa, gre and rankc. Let’s start from only one interaction:

# gpa * rankc + gre

fit1 <- glm(admit ~ gre + gpa + rankc + gpa:rankc, family = binomial(link = "logit"), data = admission)
# this is equivalent to 
# fit1 <- glm(admit ~ gpa * rankc + gre, family = binomial(link = "logit"), data = admission)
summary(fit1)
## 
## Call:
## glm(formula = admit ~ gre + gpa + rankc + gpa:rankc, family = binomial(link = "logit"), 
##     data = admission)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -1.6997  -0.8669  -0.6426   1.1582   2.1061  
## 
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)  
## (Intercept) -6.5706011  3.5059016  -1.874   0.0609 .
## gre          0.0019573  0.0009179   2.132   0.0330 *
## gpa          0.9832606  0.6600694   1.490   0.1363  
## rankc2       0.9869986  4.2059206   0.235   0.8145  
## rankc3       1.2269661  4.5908870   0.267   0.7893  
## rankc4      -1.5086918  5.9313652  -0.254   0.7992  
## gpa:rankc2  -0.3089665  0.7805070  -0.396   0.6922  
## gpa:rankc3  -0.4734705  0.8450248  -0.560   0.5753  
## gpa:rankc4  -0.0057418  1.1023671  -0.005   0.9958  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 499.98  on 399  degrees of freedom
## Residual deviance: 457.86  on 391  degrees of freedom
## AIC: 475.86
## 
## Number of Fisher Scoring iterations: 4

2. Plotting results

With interactions it is even more important and useful to plot the effects before anything else:

plot(effects::allEffects(fit1))

The plot on the left represent the main effect of gre and the plot on the right is the interaction between gpa and rankc. In this case we have an interaction between a numerical and a categorical variable. The model is essentially estimating the relationship between gpa and admit splitting by the level of rankc.

3. Interpreting the parameters

The interpretation of interactions (especially with categorical variables) from model parameters is not always easy because it depends on which contrasts are used. By default, R uses dummy coding where the reference level of the factor (rankc) is the first category and all the other categories are compared to the reference. This influence also other parameters:

  • (Intercept): log odds of being admitted for gpa = 0, gre = 0 and rankc at the reference (i.e., 1)
  • gpa: the increase in log odds of being admitted for a unit increase in the gpa for people in the reference level of rankc (i.e., 1)
  • gre: the increase in log odds of being admitted for a unit increase in the gre for people in the reference level of rankc (i.e., 1)
  • rankc2: is the difference in the log odds of being admitted between rankc2 and rankc1 (i.e. is the log odds ratio) when gpa and gre are 0
  • rankc3: is the difference in the log odds of being admitted between rankc3 and rankc1 (i.e. is the log odds ratio) when gpa and gre are 0
  • rankc4: is the difference in the log odds of being admitted between rankc4 and rankc1 (i.e. is the log odds ratio) when gpa and gre are 0
  • gpa:rankc2: the difference between rankc 2 and 1 in the rate of increase of the log odds of being admitted for a unit increase in the gpa
  • gpa:rankc3: the difference between rankc 3 and 1 in the rate of increase of the log odds of being admitted for a unit increase in the gpa
  • gpa:rankc4: the difference between rankc 4 and 1 in the rate of increase of the log odds of being admitted for a unit increase in the gpa

For complex interactions like this, a suggestion is to plot the effects (as we did before) and to estimate the individual slopes checking if the interpretation is correct:

# emmeans is an options to estimate effects regardless the model parameters
emm <- data.frame(emmeans::emtrends(fit1, ~rankc, var = "gpa"))
emm
##   rankc gpa.trend        SE  df  asymp.LCL asymp.UCL
## 1     1 0.9832606 0.6600694 Inf -0.3104517  2.276973
## 2     2 0.6742941 0.4370205 Inf -0.1822504  1.530839
## 3     3 0.5097901 0.5621005 Inf -0.5919067  1.611487
## 4     4 0.9775188 0.8931212 Inf -0.7729666  2.728004
# reference level
emm$gpa.trend[1]
## [1] 0.9832606
# gpa:rankc2
emm$gpa.trend[2] - emm$gpa.trend[1]
## [1] -0.3089665
# ...

The other difficulty with interactions is interpreting the categorical variable effects. The rankc2, rankc3 … effects are interpreted as usual BUT in the presence of interactions by definition the difference between i.e. rankc2 and rankc1 depends on the level of other variables (in particular gpa in this case). Let’s explain this visually:

This is the main effect of the rankc without considering the other variables:

plot(effects::effect("rankc", fit1))

However, in the presence of interactions, the odds ratio could be influenced by the gpa level where it is evaluated:

The model (without transformations), evaluate the effect of rankc when other variables are 0 and this could be meaningful or not.

Without interaction by definition the point at which I evaluate the renkc effect is not relevant.

4. Inference and model comparison for the interactions effect

Even if from the plot there is evidence for a little bit of interaction (i.e, the slopes are not the same across rankc) we need a statistical test. The first option is to see the Wald test of model coefficients testing if the slopes are different (e.g., gpa:rankc2).

To test the overall interaction we can use the car::Anova() function that reports main effects and interactions:

car::Anova(fit1)
## Analysis of Deviance Table (Type II tests)
## 
## Response: admit
##           LR Chisq Df Pr(>Chisq)    
## gre         4.6290  1    0.03144 *  
## gpa         5.9022  1    0.01512 *  
## rankc      21.8339  3  7.063e-05 ***
## gpa:rankc   0.4125  3    0.93765    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

We see that as reported in the Lab 8, there are the main effects of gre, gpa and rankc but there is no evidence for the interaction. The gpa:rankc test if there is at least one slope difference that is statistically significant.

To note, the car::Anova(fit1) results for the interaction is just a likelihood ratio test comparing a model with the interaction vs a model without the interaction:

fit_noint <- glm(admit ~ gre + gpa + rankc, family = binomial(link = "logit"), data = admission)

anova(fit_noint, fit1, test = "LRT")
## Analysis of Deviance Table
## 
## Model 1: admit ~ gre + gpa + rankc
## Model 2: admit ~ gre + gpa + rankc + gpa:rankc
##   Resid. Df Resid. Dev Df Deviance Pr(>Chi)
## 1       394     458.27                     
## 2       391     457.86  3  0.41252   0.9376

In fact the Chisq and the p values are the same. The model is just testing if including the interaction reduces the residual deviance.

LS0tDQp0aXRsZTogIlN0YXRpc3RpY2FsIE1ldGhvZHMgYW5kIERhdGEgQW5hbHlzaXMgaW4gRGV2ZWxvcG1lbnRhbCBQc3ljaG9sb2d5Ig0Kc3VidGl0bGU6ICJMYWIgOSINCmF1dGhvcjogIkZpbGlwcG8gR2FtYmFyb3RhIg0Kb3V0cHV0OiANCiAgICBodG1sX2RvY3VtZW50Og0KICAgICAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICAgICAgdG9jOiB0cnVlDQogICAgICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQpkYXRlOiAiVXBkYXRlZCBvbiBgciBTeXMuRGF0ZSgpYCINCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGRldiA9ICJzdmciKQ0KYGBgDQoNCmBgYHtyIHBhY2thZ2VzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGV2dG9vbHM6OmxvYWRfYWxsKCkgIyBpZiB1c2luZyB0aGUgcnByb2plY3QgZG93bG9hZGVkIGZyb20gdGhlIHNsaWRlcw0KIyBzb3VyY2UoInV0aWxzLWdsbS5SIikgIyBpZiB1c2luZyBhIHN0YW5kYXJkIHNldHVwDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KHRpZHlyKSAjIGZvciBkYXRhIG1hbmlwdWxhdGlvbg0KbGlicmFyeShkcGx5cikgIyBmb3IgZGF0YSBtYW5pcHVsYXRpb24NCmxpYnJhcnkoZ2dwbG90MikgIyBwbG90dGluZw0KbGlicmFyeShjYXIpICMgZ2VuZXJhbCB1dGlsaXRpZXMNCmxpYnJhcnkoZWZmZWN0cykgIyBmb3IgZXh0cmFjdGluZyBhbmQgcGxvdHRpbmcgZWZmZWN0cyANCmxpYnJhcnkoZW1tZWFucykgIyBmb3IgbWFyZ2luYWwgbWVhbnMNCmBgYA0KDQpgYGB7ciBvcHRpb25zLCBpbmNsdWRlID0gRkFMU0V9DQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNSkpDQpgYGANCg0KYGBge3Igc2NyaXB0LCBlY2hvPUZBTFNFfQ0KIyMgTGluayBpbiBHaXRodWIgcmVwbw0KZG93bmxvYWR0aGlzOjpkb3dubG9hZF9saW5rKA0KICBsaW5rID0gZG93bmxvYWRfbGluaygiaHR0cHM6Ly9naXRodWIuY29tL3N0YXQtdGVhY2hpbmcvU01EQS0yMDIzL2Jsb2IvbWFzdGVyL2xhYnMvbGFiOS5SIiksDQogIGJ1dHRvbl9sYWJlbCA9ICJEb3dubG9hZCB0aGUgUiBzY3JpcHQiLA0KICBidXR0b25fdHlwZSA9ICJkYW5nZXIiLA0KICBoYXNfaWNvbiA9IFRSVUUsDQogIGljb24gPSAiZmEgZmEtc2F2ZSIsDQogIHNlbGZfY29udGFpbmVkID0gRkFMU0UNCikNCmBgYA0KDQojIE92ZXJ2aWV3DQoNCldlIGFyZSBnb25uYSB3b3JrIHdpdGggdGhlIGBhZG1pc3Npb24uY3N2YCBkYXRhc2V0IGNvbnRhaW5pbmcgJG4gPSA0MDAkIHN0dWRlbnRzIGZvciB0aGUgYWRtaXNzaW9uIHRvIHRoZSBVQ0xBIFVuaXZlcnNpdHkuIEEgcmVzZWFyY2hlciBpcyBpbnRlcmVzdGVkIGluIGhvdyB2YXJpYWJsZXMsIHN1Y2ggYXMgYGdyZWAgKEdyYWR1YXRlIFJlY29yZCBFeGFtIHNjb3JlcyksIGBncGFgIChHUEEpLCBgcmFua2AgKHByZXN0aWdlIG9mIHRoZSB1bmRlcmdyYWR1YXRlIGluc3RpdHV0aW9uKSBoYXZlIGFuIGluZmx1ZW5jZSBmb3IgdGhlIGFkbWlzc2lvbiBpbnRvIGdyYWR1YXRlIHNjaG9vbC4gVGhlIHJlc3BvbnNlIHZhcmlhYmxlLCBhZG1pdC9kb27igJl0IGFkbWl0LCBpcyBhIGJpbmFyeSB2YXJpYWJsZS4NCg0KLi4uKGNvbnRpbnVlIGZyb20gbGFiIDgpDQoNCjEuIEZpdHRpbmcgdGhlIG1vZGVsIHdpdGggaW50ZXJhY3Rpb25zDQoyLiBQbG90dGluZyByZXN1bHRzDQozLiBJbnRlcnByZXRpbmcgdGhlIHBhcmFtZXRlcnMNCjQuIE1vZGVsIGNvbXBhcmlzb24gZm9yIHRoZSBpbnRlcmFjdGlvbnMgZWZmZWN0DQoNCiMgMC4gSW1wb3J0aW5nIGRhdGENCg0KV2UgbmVlZCB0byBzZXQgdGhlICoqd29ya2luZyBkaXJlY3RvcnkqKiBvbiB0aGUgcm9vdCBvZiB0aGUgY291cnNlIGZvbGRlciB1c2luZyBgc2V0LndkKClgLiBVc2luZyBSIFByb2plY3RzIGlzIGp1c3QgbmVjZXNzYXJ5IHRvIG9wZW4gdGhlIGAuUlByb2pgIGZpbGUgYW5kIHRoZSB3b3JraW5nIGRpcmVjdG9yeSB3aWxsIGJlIGF1dG9tYXRpY2FsbHkgY29ycmVjdGx5IHNlbGVjdGVkLg0KDQpgYGB7cn0NCiMgcmVsZXZhbnQgc3RlcHMgb2YgdGhlIHByZXZpb3VzIGxhYg0KYWRtaXNzaW9uIDwtIHJlYWQuY3N2KGhlcmUoImRhdGEiLCAiYWRtaXNzaW9uLmNzdiIpLCBoZWFkZXI9VFJVRSkNCmFkbWlzc2lvbiRyYW5rYyA8LSBmYWN0b3IoYWRtaXNzaW9uJHJhbmssIGxldmVscyA9IDE6NCwgbGFiZWxzID0gMTo0KQ0KYGBgDQoNCiMgMS4gRml0dGluZyBtb2RlbCB3aXRoIGludGVyYWN0aW9ucw0KDQpIZXJlIHdlIGZpdCB0aGUgdHdvIDItd2F5IGludGVyYWN0aW9ucyBiZXR3ZWVuIGBncGFgLCBgZ3JlYCBhbmQgYHJhbmtjYC4gTGV0J3Mgc3RhcnQgZnJvbSBvbmx5IG9uZSBpbnRlcmFjdGlvbjoNCg0KYGBge3J9DQojIGdwYSAqIHJhbmtjICsgZ3JlDQoNCmZpdDEgPC0gZ2xtKGFkbWl0IH4gZ3JlICsgZ3BhICsgcmFua2MgKyBncGE6cmFua2MsIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSAibG9naXQiKSwgZGF0YSA9IGFkbWlzc2lvbikNCiMgdGhpcyBpcyBlcXVpdmFsZW50IHRvIA0KIyBmaXQxIDwtIGdsbShhZG1pdCB+IGdwYSAqIHJhbmtjICsgZ3JlLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IiksIGRhdGEgPSBhZG1pc3Npb24pDQpzdW1tYXJ5KGZpdDEpDQpgYGANCg0KIyAyLiBQbG90dGluZyByZXN1bHRzDQoNCldpdGggaW50ZXJhY3Rpb25zIGl0IGlzIGV2ZW4gbW9yZSBpbXBvcnRhbnQgYW5kIHVzZWZ1bCB0byBwbG90IHRoZSBlZmZlY3RzIGJlZm9yZSBhbnl0aGluZyBlbHNlOg0KDQpgYGB7cn0NCnBsb3QoZWZmZWN0czo6YWxsRWZmZWN0cyhmaXQxKSkNCmBgYA0KDQpUaGUgcGxvdCBvbiB0aGUgbGVmdCByZXByZXNlbnQgdGhlIG1haW4gZWZmZWN0IG9mIGBncmVgIGFuZCB0aGUgcGxvdCBvbiB0aGUgcmlnaHQgaXMgdGhlIGludGVyYWN0aW9uIGJldHdlZW4gYGdwYWAgYW5kIGByYW5rY2AuIEluIHRoaXMgY2FzZSB3ZSBoYXZlIGFuIGludGVyYWN0aW9uIGJldHdlZW4gYSBudW1lcmljYWwgYW5kIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUuIFRoZSBtb2RlbCBpcyBlc3NlbnRpYWxseSBlc3RpbWF0aW5nIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBgZ3BhYCBhbmQgYGFkbWl0YCBzcGxpdHRpbmcgYnkgdGhlIGxldmVsIG9mIGByYW5rY2AuDQoNCiMgMy4gSW50ZXJwcmV0aW5nIHRoZSBwYXJhbWV0ZXJzDQoNClRoZSBpbnRlcnByZXRhdGlvbiBvZiBpbnRlcmFjdGlvbnMgKGVzcGVjaWFsbHkgd2l0aCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMpIGZyb20gbW9kZWwgcGFyYW1ldGVycyBpcyBub3QgYWx3YXlzIGVhc3kgYmVjYXVzZSBpdCBkZXBlbmRzIG9uIHdoaWNoICoqY29udHJhc3RzKiogYXJlIHVzZWQuIEJ5IGRlZmF1bHQsIFIgdXNlcyAqKmR1bW15IGNvZGluZyoqIHdoZXJlIHRoZSByZWZlcmVuY2UgbGV2ZWwgb2YgdGhlIGZhY3RvciAoYHJhbmtjYCkgaXMgdGhlIGZpcnN0IGNhdGVnb3J5IGFuZCBhbGwgdGhlIG90aGVyIGNhdGVnb3JpZXMgYXJlIGNvbXBhcmVkIHRvIHRoZSByZWZlcmVuY2UuIFRoaXMgaW5mbHVlbmNlIGFsc28gb3RoZXIgcGFyYW1ldGVyczoNCg0KLSBgKEludGVyY2VwdClgOiBsb2cgb2RkcyBvZiBiZWluZyBhZG1pdHRlZCBmb3IgYGdwYSA9IDBgLCBgZ3JlID0gMGAgYW5kIGByYW5rY2AgYXQgdGhlIHJlZmVyZW5jZSAoaS5lLiwgMSkNCi0gYGdwYWA6IHRoZSBpbmNyZWFzZSBpbiBsb2cgb2RkcyBvZiBiZWluZyBhZG1pdHRlZCBmb3IgYSB1bml0IGluY3JlYXNlIGluIHRoZSBgZ3BhYCBmb3IgcGVvcGxlIGluIHRoZSByZWZlcmVuY2UgbGV2ZWwgb2YgYHJhbmtjYCAoaS5lLiwgMSkNCi0gYGdyZWA6IHRoZSBpbmNyZWFzZSBpbiBsb2cgb2RkcyBvZiBiZWluZyBhZG1pdHRlZCBmb3IgYSB1bml0IGluY3JlYXNlIGluIHRoZSBgZ3JlYCBmb3IgcGVvcGxlIGluIHRoZSByZWZlcmVuY2UgbGV2ZWwgb2YgYHJhbmtjYCAoaS5lLiwgMSkNCi0gYHJhbmtjMmA6IGlzIHRoZSBkaWZmZXJlbmNlIGluIHRoZSBsb2cgb2RkcyBvZiBiZWluZyBhZG1pdHRlZCBiZXR3ZWVuIGByYW5rYzJgIGFuZCBgcmFua2MxYCAoaS5lLiBpcyB0aGUgbG9nIG9kZHMgcmF0aW8pIHdoZW4gYGdwYWAgYW5kIGBncmVgIGFyZSAwDQotIGByYW5rYzNgOiBpcyB0aGUgZGlmZmVyZW5jZSBpbiB0aGUgbG9nIG9kZHMgb2YgYmVpbmcgYWRtaXR0ZWQgYmV0d2VlbiBgcmFua2MzYCBhbmQgYHJhbmtjMWAgKGkuZS4gaXMgdGhlIGxvZyBvZGRzIHJhdGlvKSB3aGVuIGBncGFgIGFuZCBgZ3JlYCBhcmUgMA0KLSBgcmFua2M0YDogaXMgdGhlIGRpZmZlcmVuY2UgaW4gdGhlIGxvZyBvZGRzIG9mIGJlaW5nIGFkbWl0dGVkIGJldHdlZW4gYHJhbmtjNGAgYW5kIGByYW5rYzFgIChpLmUuIGlzIHRoZSBsb2cgb2RkcyByYXRpbykgd2hlbiBgZ3BhYCBhbmQgYGdyZWAgYXJlIDANCi0gYGdwYTpyYW5rYzJgOiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGByYW5rY2AgMiBhbmQgMSBpbiB0aGUgcmF0ZSBvZiBpbmNyZWFzZSBvZiB0aGUgbG9nIG9kZHMgb2YgYmVpbmcgYWRtaXR0ZWQgZm9yIGEgdW5pdCBpbmNyZWFzZSBpbiB0aGUgYGdwYWANCi0gYGdwYTpyYW5rYzNgOiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGByYW5rY2AgMyBhbmQgMSBpbiB0aGUgcmF0ZSBvZiBpbmNyZWFzZSBvZiB0aGUgbG9nIG9kZHMgb2YgYmVpbmcgYWRtaXR0ZWQgZm9yIGEgdW5pdCBpbmNyZWFzZSBpbiB0aGUgYGdwYWANCi0gYGdwYTpyYW5rYzRgOiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGByYW5rY2AgNCBhbmQgMSBpbiB0aGUgcmF0ZSBvZiBpbmNyZWFzZSBvZiB0aGUgbG9nIG9kZHMgb2YgYmVpbmcgYWRtaXR0ZWQgZm9yIGEgdW5pdCBpbmNyZWFzZSBpbiB0aGUgYGdwYWANCg0KRm9yIGNvbXBsZXggaW50ZXJhY3Rpb25zIGxpa2UgdGhpcywgYSBzdWdnZXN0aW9uIGlzIHRvIHBsb3QgdGhlIGVmZmVjdHMgKGFzIHdlIGRpZCBiZWZvcmUpIGFuZCB0byBlc3RpbWF0ZSB0aGUgaW5kaXZpZHVhbCBzbG9wZXMgY2hlY2tpbmcgaWYgdGhlIGludGVycHJldGF0aW9uIGlzIGNvcnJlY3Q6DQoNCmBgYHtyfQ0KIyBlbW1lYW5zIGlzIGFuIG9wdGlvbnMgdG8gZXN0aW1hdGUgZWZmZWN0cyByZWdhcmRsZXNzIHRoZSBtb2RlbCBwYXJhbWV0ZXJzDQplbW0gPC0gZGF0YS5mcmFtZShlbW1lYW5zOjplbXRyZW5kcyhmaXQxLCB+cmFua2MsIHZhciA9ICJncGEiKSkNCmVtbQ0KDQojIHJlZmVyZW5jZSBsZXZlbA0KZW1tJGdwYS50cmVuZFsxXQ0KDQojIGdwYTpyYW5rYzINCmVtbSRncGEudHJlbmRbMl0gLSBlbW0kZ3BhLnRyZW5kWzFdDQoNCiMgLi4uDQpgYGANCg0KVGhlIG90aGVyIGRpZmZpY3VsdHkgd2l0aCBpbnRlcmFjdGlvbnMgaXMgaW50ZXJwcmV0aW5nIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBlZmZlY3RzLiBUaGUgYHJhbmtjMmAsIGByYW5rYzNgIC4uLiBlZmZlY3RzIGFyZSBpbnRlcnByZXRlZCBhcyB1c3VhbCBCVVQgaW4gdGhlIHByZXNlbmNlIG9mIGludGVyYWN0aW9ucyBieSBkZWZpbml0aW9uIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gaS5lLiBgcmFua2MyYCBhbmQgYHJhbmtjMWAgZGVwZW5kcyBvbiB0aGUgbGV2ZWwgb2Ygb3RoZXIgdmFyaWFibGVzIChpbiBwYXJ0aWN1bGFyIGBncGFgIGluIHRoaXMgY2FzZSkuIExldCdzIGV4cGxhaW4gdGhpcyB2aXN1YWxseToNCg0KVGhpcyBpcyB0aGUgbWFpbiBlZmZlY3Qgb2YgdGhlIGByYW5rY2Agd2l0aG91dCBjb25zaWRlcmluZyB0aGUgb3RoZXIgdmFyaWFibGVzOg0KDQpgYGB7cn0NCnBsb3QoZWZmZWN0czo6ZWZmZWN0KCJyYW5rYyIsIGZpdDEpKQ0KYGBgDQoNCmBgYHtyLCBvdXQud2lkdGg9IjEwMCUiLCBlY2hvID0gRkFMU0V9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1nL21haW4tZWZmZWN0LXJhbmtjLnBuZyIpDQpgYGANCg0KSG93ZXZlciwgaW4gdGhlIHByZXNlbmNlIG9mIGludGVyYWN0aW9ucywgdGhlIG9kZHMgcmF0aW8gY291bGQgYmUgaW5mbHVlbmNlZCBieSB0aGUgYGdwYWAgbGV2ZWwgd2hlcmUgaXQgaXMgZXZhbHVhdGVkOg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFfQ0KcHJlZHMgPC0gZXhwYW5kLmdyaWQoDQogICAgcmFua2MgPSBjKCIxIiwgIjIiLCAiMyIsICI0IiksDQogICAgZ3BhID0gc2VxKDAsIDYsIDAuMDEpLA0KICAgIGdyZSA9IG1lYW4oYWRtaXNzaW9uJGdyZSkNCikNCg0KcHJlZHMgfD4gDQogICAgYWRkX3ByZWRpY3QoZml0MSkgfD4gDQogICAgZ2dwbG90KGFlcyh4ID0gZ3BhLCB5ID0gcHIsIGNvbG9yID0gcmFua2MpKSArDQogICAgZ2VvbV9saW5lKCkgKw0KICAgIHRoZW1lX21pbmltYWwoMjApICsNCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKDAsIDIsIDQsIDUsIDUuOCkpDQpgYGANCg0KVGhlIG1vZGVsICh3aXRob3V0IHRyYW5zZm9ybWF0aW9ucyksIGV2YWx1YXRlIHRoZSBlZmZlY3Qgb2YgYHJhbmtjYCB3aGVuIG90aGVyIHZhcmlhYmxlcyBhcmUgMCBhbmQgdGhpcyBjb3VsZCBiZSBtZWFuaW5nZnVsIG9yIG5vdC4NCg0KV2l0aG91dCBpbnRlcmFjdGlvbiBieSBkZWZpbml0aW9uIHRoZSBwb2ludCBhdCB3aGljaCBJIGV2YWx1YXRlIHRoZSBgcmVua2NgIGVmZmVjdCBpcyBub3QgcmVsZXZhbnQuDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQpmaXRfbm9pbnQgPC0gZ2xtKGFkbWl0IH4gZ3JlICsgZ3BhICsgcmFua2MsIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSAibG9naXQiKSwgZGF0YSA9IGFkbWlzc2lvbikNCnByZWRzIDwtIGV4cGFuZC5ncmlkKA0KICAgIHJhbmtjID0gYygiMSIsICIyIiwgIjMiLCAiNCIpLA0KICAgIGdwYSA9IHNlcSgwLCA2LCAwLjAxKSwNCiAgICBncmUgPSBtZWFuKGFkbWlzc2lvbiRncmUpDQopDQoNCnByZWRzIHw+IA0KICAgIGFkZF9wcmVkaWN0KGZpdF9ub2ludCkgfD4gDQogICAgZ2dwbG90KGFlcyh4ID0gZ3BhLCB5ID0gcHIsIGNvbG9yID0gcmFua2MpKSArDQogICAgZ2VvbV9saW5lKCkgKw0KICAgIHRoZW1lX21pbmltYWwoMjApICsNCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKDAsIDIsIDQsIDUsIDUuOCkpDQpgYGANCg0KIyA0LiBJbmZlcmVuY2UgYW5kIG1vZGVsIGNvbXBhcmlzb24gZm9yIHRoZSBpbnRlcmFjdGlvbnMgZWZmZWN0DQoNCkV2ZW4gaWYgZnJvbSB0aGUgcGxvdCB0aGVyZSBpcyBldmlkZW5jZSBmb3IgYSBsaXR0bGUgYml0IG9mIGludGVyYWN0aW9uIChpLmUsIHRoZSBzbG9wZXMgYXJlIG5vdCB0aGUgc2FtZSBhY3Jvc3MgYHJhbmtjYCkgd2UgbmVlZCBhIHN0YXRpc3RpY2FsIHRlc3QuIFRoZSBmaXJzdCBvcHRpb24gaXMgdG8gc2VlIHRoZSBXYWxkIHRlc3Qgb2YgbW9kZWwgY29lZmZpY2llbnRzIHRlc3RpbmcgaWYgdGhlIHNsb3BlcyBhcmUgZGlmZmVyZW50IChlLmcuLCBgZ3BhOnJhbmtjMmApLg0KDQpUbyB0ZXN0IHRoZSBvdmVyYWxsIGludGVyYWN0aW9uIHdlIGNhbiB1c2UgdGhlIGBjYXI6OkFub3ZhKClgIGZ1bmN0aW9uIHRoYXQgcmVwb3J0cyBtYWluIGVmZmVjdHMgYW5kIGludGVyYWN0aW9uczoNCg0KYGBge3J9DQpjYXI6OkFub3ZhKGZpdDEpDQpgYGANCg0KV2Ugc2VlIHRoYXQgYXMgcmVwb3J0ZWQgaW4gdGhlIExhYiA4LCB0aGVyZSBhcmUgdGhlIG1haW4gZWZmZWN0cyBvZiBgZ3JlYCwgYGdwYWAgYW5kIGByYW5rY2AgYnV0IHRoZXJlIGlzIG5vIGV2aWRlbmNlIGZvciB0aGUgaW50ZXJhY3Rpb24uIFRoZSBgZ3BhOnJhbmtjYCB0ZXN0IGlmIHRoZXJlIGlzIGF0IGxlYXN0IG9uZSBzbG9wZSBkaWZmZXJlbmNlIHRoYXQgaXMgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudC4NCg0KVG8gbm90ZSwgdGhlIGBjYXI6OkFub3ZhKGZpdDEpYCByZXN1bHRzIGZvciB0aGUgaW50ZXJhY3Rpb24gaXMganVzdCBhIGxpa2VsaWhvb2QgcmF0aW8gdGVzdCBjb21wYXJpbmcgYSBtb2RlbCB3aXRoIHRoZSBpbnRlcmFjdGlvbiB2cyBhIG1vZGVsIHdpdGhvdXQgdGhlIGludGVyYWN0aW9uOg0KDQpgYGB7cn0NCmZpdF9ub2ludCA8LSBnbG0oYWRtaXQgfiBncmUgKyBncGEgKyByYW5rYywgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpLCBkYXRhID0gYWRtaXNzaW9uKQ0KDQphbm92YShmaXRfbm9pbnQsIGZpdDEsIHRlc3QgPSAiTFJUIikNCmBgYA0KDQpJbiBmYWN0IHRoZSBgQ2hpc3FgIGFuZCB0aGUgYHAgdmFsdWVzYCBhcmUgdGhlIHNhbWUuIFRoZSBtb2RlbCBpcyBqdXN0IHRlc3RpbmcgaWYgaW5jbHVkaW5nIHRoZSBpbnRlcmFjdGlvbiByZWR1Y2VzIHRoZSByZXNpZHVhbCBkZXZpYW5jZS4NCg==