The UC Berkeley data set is built into R already in the UCBAdmission data set!

library(tidyverse); library(broom); library(car)
theme_set(theme_bw())

# Creating the Berkeley Data set:
UCB <- 
  UCBAdmissions |> 
  data.frame() |> 
  pivot_wider(
    names_from = "Admit",
    values_from = "Freq"
  ) |> 
  mutate(
    applied = Admitted + Rejected,
    accept_rate = Admitted/applied
  ) |> 
  janitor::clean_names() |> 
  arrange(dept, gender) |> 
  # Have the results match the slides
  mutate(gender = (gender == 'Female')*1)

# Just looking at the three departments in the slides:
UCB3 <- UCB |> filter(dept %in% c("A", "B", "C")) 

Let’s look at the UCB data set:

UCB3
## # A tibble: 6 × 6
##   gender dept  admitted rejected applied accept_rate
##    <dbl> <fct>    <dbl>    <dbl>   <dbl>       <dbl>
## 1      0 A          512      313     825       0.621
## 2      1 A           89       19     108       0.824
## 3      0 B          353      207     560       0.630
## 4      1 B           17        8      25       0.68 
## 5      0 C          120      205     325       0.369
## 6      1 C          202      391     593       0.341

So we want to see how gender (\(X\)) and department (\(Z\)) change the probability someone is accepted (\(Y\))

We have two main logistic models we can use that includes all 3:

  1. Additive Model: \(\log \left ( \frac{\pi_{ik}}{1-\pi_{ik}} \right) = \alpha + \beta^X_i +\beta^Z_k\)

  2. Interaction Model: \(\log \left ( \frac{\pi_{ik}}{1-\pi_i} \right) = \alpha + \beta^X_i +\beta^Z_k + \beta^{XZ}_{ik}\)

    • This is our Saturated Model if \(X\) and \(Z\) are factors

Comparing the additive vs interaction model

Let’s start by fitting the two models:

# Additive Model
berk_add <- 
  glm(
    formula = accept_rate ~ gender + dept,
    weight = applied,
    family = binomial,
    data = UCB3
  )

tidy(berk_add, exponentiate = F)
## # A tibble: 4 × 5
##   term        estimate std.error statistic  p.value
##   <chr>          <dbl>     <dbl>     <dbl>    <dbl>
## 1 (Intercept)   0.571     0.0696     8.21  2.26e-16
## 2 gender        0.197     0.117      1.68  9.23e- 2
## 3 deptB        -0.0366    0.110     -0.333 7.40e- 1
## 4 deptC        -1.32      0.117    -11.3   1.86e-29
# Interaction model
berk_int <- 
  glm(
    formula = accept_rate ~ gender * dept,
    weight = applied,
    family = binomial,
    data = UCB3
  )

tidy(berk_int,
     exponentiate = F)
## # A tibble: 6 × 5
##   term         estimate std.error statistic  p.value
##   <chr>           <dbl>     <dbl>     <dbl>    <dbl>
## 1 (Intercept)    0.492     0.0717     6.86  6.94e-12
## 2 gender         1.05      0.263      4.00  6.21e- 5
## 3 deptB          0.0416    0.113      0.368 7.13e- 1
## 4 deptC         -1.03      0.135     -7.58  3.34e-14
## 5 gender:deptB  -0.832     0.510     -1.63  1.03e- 1
## 6 gender:deptC  -1.18      0.300     -3.93  8.53e- 5

If there is only 1 interaction term (aka both \(X\) and \(Z\) are binary) we can just conduct a hypothesis test to see if that term is 0 using tidy().

However, if at least one of \(X\) or \(Z\) are not binary, we’ll have more than 1 model term, so we can’t conduct a simple z-test :(

What we can do is compare the deviances of the two models:

fit_stats <- 
  bind_rows(
    "add" = glance(berk_add),
    "int" = glance(berk_int),
    .id = "model"
  ) 

fit_stats |> 
  dplyr::select(model, deviance, df.residual, nobs) |> 
  mutate(deviance = round(deviance, 3))
## # A tibble: 2 × 4
##   model deviance df.residual  nobs
##   <chr>    <dbl>       <int> <int>
## 1 add       17.2           2     6
## 2 int        0             0     6

A difference in the deviances will follow a \(\chi^2\) model with a difference in the number of model parameters

The difference in parameters between an additive and interaction model is the number of interaction terms that we have: df = \((I - 1)(K - 1)\)

For our example, \(I = 2\), \(K = 3\) then df = 2, which is also the difference in the df.redisual from glance()

fit_stats |> 
  summarize(
    comparison = paste(model,  collapse = ' vs '),
    G = deviance |> diff() |> abs(),
    df = df.residual |> diff() |> abs()
  ) |> 
  mutate(
    p_val = pchisq(q = G,df = df, lower = F)
  )
## # A tibble: 1 × 4
##   comparison     G    df    p_val
##   <chr>      <dbl> <int>    <dbl>
## 1 add vs int  17.2     2 0.000184

So we have strong evidence that the interaction term is needed :(

Instead of finding the test stat and p-value by hand, we can use Anova() in the car package with type = 3

car::Anova(mod = berk_int, type = 3)
## Analysis of Deviance Table (Type III tests)
## 
## Response: accept_rate
##             LR Chisq Df Pr(>Chisq)    
## gender        19.054  1  1.271e-05 ***
## dept          69.791  2  6.999e-16 ***
## gender:dept   17.197  2  0.0001844 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

The interaction row of the results match exactly what we have!

If you have two or more nested models that you want to compare directly, we can use the anova() function in base R.

Let’s fit a model that just has gender and another model that just has department:

berk_gen <- 
  glm(
    formula = accept_rate ~ gender,
    weight = applied,
    family = binomial,
    data = UCB3
  )


berk_dept <- 
  glm(
    formula = accept_rate ~ dept,
    weight = applied,
    family = binomial,
    data = UCB3
  )

Now we can use anova() by giving it multiple models from simplest to the most complicated:

options(width = 400)
anova(berk_gen, berk_add, berk_int, 
      test = "LRT") |> tidy()
## # A tibble: 3 × 6
##   term                        df.residual residual.deviance    df deviance   p.value
##   <chr>                             <dbl>             <dbl> <dbl>    <dbl>     <dbl>
## 1 accept_rate ~ gender                  4          1.67e+ 2    NA     NA   NA       
## 2 accept_rate ~ gender + dept           2          1.72e+ 1     2    150.   3.08e-33
## 3 accept_rate ~ gender * dept           0          1.79e-13     2     17.2  1.84e- 4
# Can we drop Gender entirely?
# That is, are Admission and Gender independent accounting for Department?
anova(berk_gen, berk_int, 
      test = "LRT")|> tidy()
## # A tibble: 2 × 6
##   term                        df.residual residual.deviance    df deviance   p.value
##   <chr>                             <dbl>             <dbl> <dbl>    <dbl>     <dbl>
## 1 accept_rate ~ gender                  4          1.67e+ 2    NA      NA  NA       
## 2 accept_rate ~ gender * dept           0          1.79e-13     4     167.  4.79e-35
# Can we just drop the interaction term?
anova(berk_dept, berk_int, 
      test = "LRT")|> tidy()
## # A tibble: 2 × 6
##   term                        df.residual residual.deviance    df deviance   p.value
##   <chr>                             <dbl>             <dbl> <dbl>    <dbl>     <dbl>
## 1 accept_rate ~ dept                    3          2.01e+ 1    NA     NA   NA       
## 2 accept_rate ~ gender * dept           0          1.79e-13     3     20.1  0.000165

anova() compares the model on a row with the in the previous row

So the test results for row 2 compares gender only vs the additive model and row 3 compares the additive model vs the interaction model

Testing Departments B - F

We saw in chapter 2 that department A was the problematic department with the odds a man is accepted compared to a woman being around 1/3.

The code chunk below removes department A. Test to see if gender is needed when department is in the model. That is, perform a test to see if Gender and Admission are conditional independent on Department!

UCB5 <- 
  UCB |> 
  filter(dept != 'A')

tibble(UCB5)
## # A tibble: 10 × 6
##    gender dept  admitted rejected applied accept_rate
##     <dbl> <fct>    <dbl>    <dbl>   <dbl>       <dbl>
##  1      0 B          353      207     560      0.630 
##  2      1 B           17        8      25      0.68  
##  3      0 C          120      205     325      0.369 
##  4      1 C          202      391     593      0.341 
##  5      0 D          138      279     417      0.331 
##  6      1 D          131      244     375      0.349 
##  7      0 E           53      138     191      0.277 
##  8      1 E           94      299     393      0.239 
##  9      0 F           22      351     373      0.0590
## 10      1 F           24      317     341      0.0704
LS0tDQp0aXRsZTogIkFOT1ZBIE1vZGVscyB3aXRoIExvZ2lzdGljIFJlZ3Jlc3Npb24iDQphdXRob3I6ICJDaGFwdGVyIDQiDQpkYXRlOiAiU1RBIDQ1MDQiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZmlnX3dpZHRoOiA4DQogICAgZmlnX2hlaWdodDogNg0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IG5vDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEYsDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEYsDQogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gJ2NlbnRlcicpDQoNCmBgYA0KDQpUaGUgVUMgQmVya2VsZXkgZGF0YSBzZXQgaXMgYnVpbHQgaW50byBSIGFscmVhZHkgaW4gdGhlIGBVQ0JBZG1pc3Npb25gIGRhdGEgc2V0IQ0KDQoNCmBgYHtyIGxpYnNfZGF0YX0NCmxpYnJhcnkodGlkeXZlcnNlKTsgbGlicmFyeShicm9vbSk7IGxpYnJhcnkoY2FyKQ0KdGhlbWVfc2V0KHRoZW1lX2J3KCkpDQoNCiMgQ3JlYXRpbmcgdGhlIEJlcmtlbGV5IERhdGEgc2V0Og0KVUNCIDwtIA0KICBVQ0JBZG1pc3Npb25zIHw+IA0KICBkYXRhLmZyYW1lKCkgfD4gDQogIHBpdm90X3dpZGVyKA0KICAgIG5hbWVzX2Zyb20gPSAiQWRtaXQiLA0KICAgIHZhbHVlc19mcm9tID0gIkZyZXEiDQogICkgfD4gDQogIG11dGF0ZSgNCiAgICBhcHBsaWVkID0gQWRtaXR0ZWQgKyBSZWplY3RlZCwNCiAgICBhY2NlcHRfcmF0ZSA9IEFkbWl0dGVkL2FwcGxpZWQNCiAgKSB8PiANCiAgamFuaXRvcjo6Y2xlYW5fbmFtZXMoKSB8PiANCiAgYXJyYW5nZShkZXB0LCBnZW5kZXIpIHw+IA0KICAjIEhhdmUgdGhlIHJlc3VsdHMgbWF0Y2ggdGhlIHNsaWRlcw0KICBtdXRhdGUoZ2VuZGVyID0gKGdlbmRlciA9PSAnRmVtYWxlJykqMSkNCg0KIyBKdXN0IGxvb2tpbmcgYXQgdGhlIHRocmVlIGRlcGFydG1lbnRzIGluIHRoZSBzbGlkZXM6DQpVQ0IzIDwtIFVDQiB8PiBmaWx0ZXIoZGVwdCAlaW4lIGMoIkEiLCAiQiIsICJDIikpIA0KDQpgYGANCg0KTGV0J3MgbG9vayBhdCB0aGUgVUNCIGRhdGEgc2V0Og0KDQpgYGB7ciBkYXRhfQ0KVUNCMw0KYGBgDQoNClNvIHdlIHdhbnQgdG8gc2VlIGhvdyBnZW5kZXIgKCRYJCkgYW5kIGRlcGFydG1lbnQgKCRaJCkgY2hhbmdlIHRoZSBwcm9iYWJpbGl0eSBzb21lb25lIGlzIGFjY2VwdGVkICgkWSQpDQoNCldlIGhhdmUgdHdvIG1haW4gbG9naXN0aWMgbW9kZWxzIHdlIGNhbiB1c2UgdGhhdCBpbmNsdWRlcyBhbGwgMzoNCg0KMSkgQWRkaXRpdmUgTW9kZWw6ICRcbG9nIFxsZWZ0ICggXGZyYWN7XHBpX3tpa319ezEtXHBpX3tpa319IFxyaWdodCkgPSBcYWxwaGEgKyBcYmV0YV5YX2kgK1xiZXRhXlpfayQNCiAgICANCg0KMikgSW50ZXJhY3Rpb24gTW9kZWw6ICRcbG9nIFxsZWZ0ICggXGZyYWN7XHBpX3tpa319ezEtXHBpX2l9IFxyaWdodCkgPSBcYWxwaGEgKyBcYmV0YV5YX2kgK1xiZXRhXlpfayArIFxiZXRhXntYWn1fe2lrfSQNCiAgICAtIFRoaXMgaXMgb3VyICoqU2F0dXJhdGVkKiogTW9kZWwgaWYgJFgkIGFuZCAkWiQgYXJlIGZhY3RvcnMNCg0KDQoNCg0KDQojIyBDb21wYXJpbmcgdGhlIGFkZGl0aXZlIHZzIGludGVyYWN0aW9uIG1vZGVsDQoNCkxldCdzIHN0YXJ0IGJ5IGZpdHRpbmcgdGhlIHR3byBtb2RlbHM6DQoNCmBgYHtyfQ0KIyBBZGRpdGl2ZSBNb2RlbA0KYmVya19hZGQgPC0gDQogIGdsbSgNCiAgICBmb3JtdWxhID0gYWNjZXB0X3JhdGUgfiBnZW5kZXIgKyBkZXB0LA0KICAgIHdlaWdodCA9IGFwcGxpZWQsDQogICAgZmFtaWx5ID0gYmlub21pYWwsDQogICAgZGF0YSA9IFVDQjMNCiAgKQ0KDQp0aWR5KGJlcmtfYWRkLCBleHBvbmVudGlhdGUgPSBGKQ0KDQojIEludGVyYWN0aW9uIG1vZGVsDQpiZXJrX2ludCA8LSANCiAgZ2xtKA0KICAgIGZvcm11bGEgPSBhY2NlcHRfcmF0ZSB+IGdlbmRlciAqIGRlcHQsDQogICAgd2VpZ2h0ID0gYXBwbGllZCwNCiAgICBmYW1pbHkgPSBiaW5vbWlhbCwNCiAgICBkYXRhID0gVUNCMw0KICApDQoNCnRpZHkoYmVya19pbnQsDQogICAgIGV4cG9uZW50aWF0ZSA9IEYpDQpgYGANCg0KSWYgdGhlcmUgaXMgb25seSAxIGludGVyYWN0aW9uIHRlcm0gKGFrYSBib3RoICRYJCBhbmQgJFokIGFyZSBiaW5hcnkpIHdlIGNhbiBqdXN0IGNvbmR1Y3QgYSBoeXBvdGhlc2lzIHRlc3QgdG8gc2VlIGlmIHRoYXQgdGVybSBpcyAwIHVzaW5nIGB0aWR5KClgLg0KDQpIb3dldmVyLCBpZiBhdCBsZWFzdCBvbmUgb2YgJFgkIG9yICRaJCBhcmUgbm90IGJpbmFyeSwgd2UnbGwgaGF2ZSBtb3JlIHRoYW4gMSBtb2RlbCB0ZXJtLCBzbyB3ZSBjYW4ndCBjb25kdWN0IGEgc2ltcGxlIHotdGVzdCA6KA0KDQpXaGF0IHdlIGNhbiBkbyBpcyBjb21wYXJlIHRoZSBkZXZpYW5jZXMgb2YgdGhlIHR3byBtb2RlbHM6DQoNCmBgYHtyfQ0KZml0X3N0YXRzIDwtIA0KICBiaW5kX3Jvd3MoDQogICAgImFkZCIgPSBnbGFuY2UoYmVya19hZGQpLA0KICAgICJpbnQiID0gZ2xhbmNlKGJlcmtfaW50KSwNCiAgICAuaWQgPSAibW9kZWwiDQogICkgDQoNCmZpdF9zdGF0cyB8PiANCiAgZHBseXI6OnNlbGVjdChtb2RlbCwgZGV2aWFuY2UsIGRmLnJlc2lkdWFsLCBub2JzKSB8PiANCiAgbXV0YXRlKGRldmlhbmNlID0gcm91bmQoZGV2aWFuY2UsIDMpKQ0KYGBgDQoNCkEgZGlmZmVyZW5jZSBpbiB0aGUgZGV2aWFuY2VzIHdpbGwgZm9sbG93IGEgJFxjaGleMiQgbW9kZWwgd2l0aCBhIGRpZmZlcmVuY2UgaW4gdGhlIG51bWJlciBvZiBtb2RlbCBwYXJhbWV0ZXJzDQoNClRoZSBkaWZmZXJlbmNlIGluIHBhcmFtZXRlcnMgYmV0d2VlbiBhbiBhZGRpdGl2ZSBhbmQgaW50ZXJhY3Rpb24gbW9kZWwgaXMgdGhlIG51bWJlciBvZiBpbnRlcmFjdGlvbiB0ZXJtcyB0aGF0IHdlIGhhdmU6IGRmID0gJChJIC0gMSkoSyAtIDEpJA0KDQpGb3Igb3VyIGV4YW1wbGUsICRJID0gMiQsICRLID0gMyQgdGhlbiBkZiA9IDIsIHdoaWNoIGlzIGFsc28gdGhlIGRpZmZlcmVuY2UgaW4gdGhlIGBkZi5yZWRpc3VhbGAgZnJvbSBgZ2xhbmNlKClgDQoNCmBgYHtyfQ0KZml0X3N0YXRzIHw+IA0KICBzdW1tYXJpemUoDQogICAgY29tcGFyaXNvbiA9IHBhc3RlKG1vZGVsLCAgY29sbGFwc2UgPSAnIHZzICcpLA0KICAgIEcgPSBkZXZpYW5jZSB8PiBkaWZmKCkgfD4gYWJzKCksDQogICAgZGYgPSBkZi5yZXNpZHVhbCB8PiBkaWZmKCkgfD4gYWJzKCkNCiAgKSB8PiANCiAgbXV0YXRlKA0KICAgIHBfdmFsID0gcGNoaXNxKHEgPSBHLGRmID0gZGYsIGxvd2VyID0gRikNCiAgKQ0KDQpgYGANCg0KU28gd2UgaGF2ZSBzdHJvbmcgZXZpZGVuY2UgdGhhdCB0aGUgaW50ZXJhY3Rpb24gdGVybSBpcyBuZWVkZWQgOigNCg0KSW5zdGVhZCBvZiBmaW5kaW5nIHRoZSB0ZXN0IHN0YXQgYW5kIHAtdmFsdWUgYnkgaGFuZCwgd2UgY2FuIHVzZSBgQW5vdmEoKWAgaW4gdGhlIGBjYXJgIHBhY2thZ2Ugd2l0aCBgdHlwZSA9IDNgDQoNCmBgYHtyfQ0KY2FyOjpBbm92YShtb2QgPSBiZXJrX2ludCwgdHlwZSA9IDMpDQoNCmBgYA0KDQpUaGUgaW50ZXJhY3Rpb24gcm93IG9mIHRoZSByZXN1bHRzIG1hdGNoIGV4YWN0bHkgd2hhdCB3ZSBoYXZlIQ0KDQpJZiB5b3UgaGF2ZSB0d28gb3IgbW9yZSAqKm5lc3RlZCoqIG1vZGVscyB0aGF0IHlvdSB3YW50IHRvIGNvbXBhcmUgZGlyZWN0bHksIHdlIGNhbiB1c2UgdGhlIGBhbm92YSgpYCBmdW5jdGlvbiBpbiBgYmFzZWAgUi4NCg0KTGV0J3MgZml0IGEgbW9kZWwgdGhhdCBqdXN0IGhhcyBnZW5kZXIgYW5kIGFub3RoZXIgbW9kZWwgdGhhdCBqdXN0IGhhcyBkZXBhcnRtZW50Og0KDQpgYGB7cn0NCmJlcmtfZ2VuIDwtIA0KICBnbG0oDQogICAgZm9ybXVsYSA9IGFjY2VwdF9yYXRlIH4gZ2VuZGVyLA0KICAgIHdlaWdodCA9IGFwcGxpZWQsDQogICAgZmFtaWx5ID0gYmlub21pYWwsDQogICAgZGF0YSA9IFVDQjMNCiAgKQ0KDQoNCmJlcmtfZGVwdCA8LSANCiAgZ2xtKA0KICAgIGZvcm11bGEgPSBhY2NlcHRfcmF0ZSB+IGRlcHQsDQogICAgd2VpZ2h0ID0gYXBwbGllZCwNCiAgICBmYW1pbHkgPSBiaW5vbWlhbCwNCiAgICBkYXRhID0gVUNCMw0KICApDQpgYGANCg0KDQpOb3cgd2UgY2FuIHVzZSBgYW5vdmEoKWAgYnkgZ2l2aW5nIGl0IG11bHRpcGxlIG1vZGVscyBmcm9tIHNpbXBsZXN0IHRvIHRoZSBtb3N0IGNvbXBsaWNhdGVkOg0KDQpgYGB7ciBhbm92YV9jb21wYXJlfQ0Kb3B0aW9ucyh3aWR0aCA9IDQwMCkNCmFub3ZhKGJlcmtfZ2VuLCBiZXJrX2FkZCwgYmVya19pbnQsIA0KICAgICAgdGVzdCA9ICJMUlQiKSB8PiB0aWR5KCkNCg0KIyBDYW4gd2UgZHJvcCBHZW5kZXIgZW50aXJlbHk/DQojIFRoYXQgaXMsIGFyZSBBZG1pc3Npb24gYW5kIEdlbmRlciBpbmRlcGVuZGVudCBhY2NvdW50aW5nIGZvciBEZXBhcnRtZW50Pw0KYW5vdmEoYmVya19nZW4sIGJlcmtfaW50LCANCiAgICAgIHRlc3QgPSAiTFJUIil8PiB0aWR5KCkNCg0KIyBDYW4gd2UganVzdCBkcm9wIHRoZSBpbnRlcmFjdGlvbiB0ZXJtPw0KYW5vdmEoYmVya19kZXB0LCBiZXJrX2ludCwgDQogICAgICB0ZXN0ID0gIkxSVCIpfD4gdGlkeSgpDQpgYGANCg0KYGFub3ZhKClgIGNvbXBhcmVzIHRoZSBtb2RlbCBvbiBhIHJvdyB3aXRoIHRoZSBpbiB0aGUgcHJldmlvdXMgcm93DQoNClNvIHRoZSB0ZXN0IHJlc3VsdHMgZm9yIHJvdyAyIGNvbXBhcmVzIGdlbmRlciBvbmx5IHZzIHRoZSBhZGRpdGl2ZSBtb2RlbCBhbmQgcm93IDMgY29tcGFyZXMgdGhlIGFkZGl0aXZlIG1vZGVsIHZzIHRoZSBpbnRlcmFjdGlvbiBtb2RlbA0KDQoNCiMjIFRlc3RpbmcgRGVwYXJ0bWVudHMgQiAtIEYNCg0KV2Ugc2F3IGluIGNoYXB0ZXIgMiB0aGF0IGRlcGFydG1lbnQgQSB3YXMgdGhlIHByb2JsZW1hdGljIGRlcGFydG1lbnQgd2l0aCB0aGUgb2RkcyBhIG1hbiBpcyBhY2NlcHRlZCBjb21wYXJlZCB0byBhIHdvbWFuIGJlaW5nIGFyb3VuZCAxLzMuDQoNClRoZSBjb2RlIGNodW5rIGJlbG93IHJlbW92ZXMgZGVwYXJ0bWVudCBBLiBUZXN0IHRvIHNlZSBpZiBnZW5kZXIgaXMgbmVlZGVkIHdoZW4gZGVwYXJ0bWVudCBpcyBpbiB0aGUgbW9kZWwuIFRoYXQgaXMsIHBlcmZvcm0gYSB0ZXN0IHRvIHNlZSBpZiBHZW5kZXIgYW5kIEFkbWlzc2lvbiBhcmUgY29uZGl0aW9uYWwgaW5kZXBlbmRlbnQgb24gRGVwYXJ0bWVudCENCg0KYGBge3IgVUNCNX0NClVDQjUgPC0gDQogIFVDQiB8PiANCiAgZmlsdGVyKGRlcHQgIT0gJ0EnKQ0KDQp0aWJibGUoVUNCNSkNCmBgYA0KDQpgYGB7ciBmaXR0aW5nXzVfZGVwdHN9DQoNCg0KDQpgYGANCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=