Loading the data from the MASS package and cleaning the names

Checking the data

cats |>
  slice_sample(n = 10)
##    body heart
## 1   2.1   7.6
## 2   2.9  10.1
## 3   2.5  11.0
## 4   3.6  15.0
## 5   2.9  11.8
## 6   3.2  12.3
## 7   2.2   8.7
## 8   2.5   8.8
## 9   2.2  11.0
## 10  3.1  12.1

Scatter plot for body vs heart:

We should start with a plot of body vs heart weight:

gg_cats <- 
  ggplot(
    data = cats,
    mapping = aes(
      x = body,
      y = heart
    )
  ) + 
  geom_point() + 
  theme_bw() + 
  labs(
    title = 'Cats: Body weight vs heart weight',
    subtitle = 'LOESS line added',
    x = 'Body',
    y = 'Heart'
  ) + 
  # Adding the units to the axes
  scale_x_continuous(
    labels = scales::label_number(suffix = ' kg')
  ) + 
  scale_y_continuous(
    labels = scales::label_number(suffix = ' g')
  )

gg_cats +
  geom_smooth(
    method = 'loess',
    formula = y ~ x,
    se = F
  )

Fitting the linear model:

While we’ve been calculating the slope ‘by hand’, we typically use a function to do it for us, like lm(y ~ x, data = ...) and we can get the summary stats from summary(model)

cats_lm <- lm(heart ~ body, data = cats)

summary(cats_lm)
## 
## Call:
## lm(formula = heart ~ body, data = cats)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.5694 -0.9634 -0.0921  1.0426  5.1238 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -0.3567     0.6923  -0.515    0.607    
## body          4.0341     0.2503  16.119   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.452 on 142 degrees of freedom
## Multiple R-squared:  0.6466, Adjusted R-squared:  0.6441 
## F-statistic: 259.8 on 1 and 142 DF,  p-value: < 2.2e-16

The broom package has some useful functions that are popular as they return objects that are easier to work with

  • tidy(model) returns the coefficient table as a data frame

  • glance(model) returns a 1 row data frame with the fit statistics of the model

    • We’ll get to that later
  • augment_columns(model, data) will add the predicted response, residuals, and other useful stats to the data frame

For now, we’ll just work with tidy()

cats_lm_table <- broom::tidy(cats_lm)

cats_lm_table
## # A tibble: 2 × 5
##   term        estimate std.error statistic  p.value
##   <chr>          <dbl>     <dbl>     <dbl>    <dbl>
## 1 (Intercept)   -0.357     0.692    -0.515 6.07e- 1
## 2 body           4.03      0.250    16.1   6.97e-34
# Finding the MSE:
sum(cats_lm$residuals^2) / (nrow(cats) - 2)
## [1] 2.109388
# Finding S_XX
sum((cats$body - mean(cats$body))^2)
## [1] 33.67972

Inference for a mean of the response

Confidence Interval for \(E(Y|X)\) formula

What if I want to know the average heart weight for a cat that weighs 3 kilograms?

Instead of wanting to know how the heart weight changes as body weight increases, I’m interested in the mean for a particular body weight.

From the slides, we can get a confidence interval for the mean when \(X = X_h\)

\[\hat{Y}_h \pm t(1-\alpha/2; n - 2) \sqrt{MSE\left[\frac{1}{n} + \frac{(X_h - \bar{X})^2}{S_{XX}} \right]}\]

We’ll create the confidence interval ‘by hand’ first:

Confidence interval for \(X_h = 3\) by hand

Start by calculating the different statistics we need:

  1. \(\hat{Y}_h\)

  2. \(t(1-\alpha/2; n - 2)\)

  3. \(MSE\)

  4. \(S_{XX}\)

  5. \((X_h - \bar{X})^2\)

X_h <- 3
n <- nrow(cats)

# 1) Pred Y       Intercept                     slope
Y_hat_h <-   cats_lm$coefficients[1] + cats_lm$coefficients[2] * X_h


# 2) Critical value
crit_val <- qt(1 - 0.05/2, df = n - 2)

# 3) MSE
MSE_cats <- sum(cats_lm$residuals^2) / (n-2)

# 4) S_XX
S_XX <- sum((cats$body-mean(cats$body))^2)

# 5) X_h squared deviance
dev_X_h <- (X_h - mean(cats$body))^2

# The standard error using 3, 4, and 5
SE_h <- sqrt(MSE_cats * (1/n + dev_X_h/S_XX))

# Display results
tribble(
              ~ stats,                ~ value,
  'pred Y when X = 3',      round(Y_hat_h, 3),
     'Critical Value',     round(crit_val, 3),
                'MSE',     round(MSE_cats, 3),
               'S_XX',         round(S_XX, 3),
      '(X - X_bar)^2',      round(dev_X_h, 3),
     'Standard Error',         round(SE_h, 3)
) |> gt::gt()
stats value
pred Y when X = 3 11.746
Critical Value 1.977
MSE 2.109
S_XX 33.680
(X - X_bar)^2 0.076
Standard Error 0.139

Now that we have all the pieces, we can create the confidence interval:

pred_Y_h_CI <- 
  c(
    'lower' = as.numeric(Y_hat_h) - crit_val * sqrt(MSE_cats *(1/n + dev_X_h/S_XX)),
    'upper' = as.numeric(Y_hat_h) + crit_val * sqrt(MSE_cats *(1/n + dev_X_h/S_XX))
  )

round(pred_Y_h_CI, 2)
## lower upper 
## 11.47 12.02

We can be 95% confident that the average heart weight of cats that weigh 3 kg is between 11.5 g to 12 g.

Calculating the confidence interval using predict()

Instead of calculating the 95% confidence interval by hand, we can use predict() with the following arguments:

  1. object = the lm object we created
  2. newdata = the data frame that has the new values we want to predict
    • The columns of the data frame have to be the same as the explanatory variables used to build the model
  3. interval = 'confidence' will have it create a confidence interval
  4. level = can be used to determine the confidence level (defaults to 0.95).
# Body weight for the CI
new_cats <- 
  data.frame(
    name = c('Donut', 'Ferdinand', 'Salem'),
    body = c(2.5, 3, 3.5)
  )

# Adding the predicted values and interval to the new_cats data
new_cats <- 
  new_cats |> 
  mutate(
    predict(
    object = cats_lm,
    newdata = new_cats,
    interval = 'confidence',
    level = 0.99
  ) |> data.frame()
  )
  
new_cats
##        name body       fit       lwr      upr
## 1     Donut  2.5  9.728494  9.380351 10.07664
## 2 Ferdinand  3.0 11.745526 11.381561 12.10949
## 3     Salem  3.5 13.762557 13.164889 14.36022

Plotting the confidence band

We can also visualize the confidence interval for the predictions using geom_smooth(method = 'lm')

gg_cats +
  geom_smooth(
    method = 'lm',
    color = 'steelblue',
    fill = 'steelblue',
    formula = y ~ x,
    level = 0.95       # Confidence level
  ) + 
  labs(subtitle = 'Linear regression line with 95% confidence band')

The shaded area is the confidence interval for the average response of \(Y\) given the value of \(X\).

Prediction intervals

The confidence intervals in the previous section tries to estimate the mean response, aka, the average of all cats that weigh 3 kg.

What if we don’t want the expected value (mean) of the response, but instead to know the range where 90%, 95%, or 99% of the population will be for a given value of \(X\)?

Instead, we want to make a prediction interval. Like with the confidence interval, we can do it ‘by hand’, it just takes a small adjustment!

Prediction Interval Formula

From the slides, we found the variance for \(Y_h\) to be:

\[Var(Y_h) = Var(b_0 + b_1 X_h + \varepsilon_h)\]

Looks similar to the variance for \(\hat{Y}_h\), but now we also have \(\varepsilon_h\). Weirdly, \(b_0\) and \(b_1\) are independent of \(\varepsilon_h\) (but NOT each other!), we can split the variance term above:

\[Var(Y_h) = Var(b_0 + b_1 X_h) + Var(\varepsilon_h)\]

From previous results and our model assumptions, we get:

\[Var(Y_h) = \sigma^2\left[\frac{1}{n} + \frac{(X_h - \bar{X})^2}{S_{XX}} \right] + \sigma^2\]

which simplifies down to:

\[Var(Y_h) = \sigma^2\left[1 +\frac{1}{n} + \frac{(X_h - \bar{X})^2}{S_{XX}} \right]\]

The standard error is then:

\[SE(Y_h) = \sqrt{MSE\left[1 +\frac{1}{n} + \frac{(X_h - \bar{X})^2}{S_{XX}} \right]}\]

The only difference is the \(1\) inside the square root of the standard error!

Prediction Interval by hand:

Let’s update the SE for the prediction interval:

SE_h_PI <- sqrt(MSE_cats * (1 + 1/n + dev_X_h/S_XX))
round(SE_h_PI, 2)
## [1] 1.46

While we’re “only” adding 1 to go from the CI to PI, the standard error increases over 10 times!

Prediction interval for \(X_h = 3\)

c('lower' = as.numeric(Y_hat_h) - crit_val * SE_h_PI,
  'upper' = as.numeric(Y_hat_h) + crit_val * SE_h_PI) |>
  round(2)
## lower upper 
##  8.86 14.63

We are 95% confident that a cat that weighs 3 kg will have a heart weight between 8.9 to 14.6 grams.

Prediction band for cat heart weights

Like what we saw for the confidence interval, we can create a prediction band around the line of best fit.

Unfortunately, there isn’t a quick way to create the prediction band like what we had with geom_smooth(...). We have to ‘manually’ find the lower and upper band for the range of body weights.

We’ll create the band for the range of body weights in increments of 0.01 kg and use predict() to make the interval:

# Creating the lower and upper range of the prediction band:
cats_PI_band <- 
  tibble(
    # Range of body weight by 0.01 kg
    body = seq(min(cats$body), max(cats$body), by = 0.001)
  )


# We can find the interval using predict() 
# like we did with the confidence interval, but interval = 'prediction'
cats_PI_band <- 
  cats_PI_band |> 
  mutate(
    predict(
      object = cats_lm,
      newdata = cats_PI_band,
      interval = 'prediction',
      level = 0.95
    ) |> data.frame()
  )

cats_PI_band |>
  round(2)
## # A tibble: 1,901 × 4
##     body   fit   lwr   upr
##    <dbl> <dbl> <dbl> <dbl>
##  1  2     7.71  4.81  10.6
##  2  2     7.72  4.81  10.6
##  3  2     7.72  4.82  10.6
##  4  2     7.72  4.82  10.6
##  5  2     7.73  4.82  10.6
##  6  2     7.73  4.83  10.6
##  7  2.01  7.74  4.83  10.6
##  8  2.01  7.74  4.84  10.6
##  9  2.01  7.74  4.84  10.6
## 10  2.01  7.75  4.85  10.6
## # ℹ 1,891 more rows

Now we’ll add it to the scatterplot gg_cats using geom_ribbon(...):

gg_cats +
  # Changing the subtitle
  labs(subtitle = 'Linear regression line with 95% confidence and prediction bands') + 
  # Adding the prediction band
  geom_ribbon(
    data = cats_PI_band,
    mapping = aes(
      x = body,
      ymin = lwr,
      ymax = upr,
      y = NULL            # Since y is mapped in gg_cats, we need to remove it 
    ),
    alpha = 0.5,
    color = 'white',
    fill = 'steelblue'
  ) + 
 
  
  # Adding the prediction line and confidence band
  geom_smooth(
    method = 'lm',
    formula = y ~ x,
    color = 'steelblue',
    fill = 'steelblue'
  ) 

LS0tDQp0aXRsZTogJ0luZmVyZW5jZSBmb3IgdGhlIFJlc3BvbnNlIFZhcmlhYmxlIC0gQ2F0cyBleGFtcGxlJw0KYXV0aG9yOiAiQ2hhcHRlciAyIg0KZGF0ZTogIlNUQSA0MjEwIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGZpZ193aWR0aDogNg0KICAgIGZpZ19oZWlnaHQ6IDYNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHNtb290aF9zY3JvbGw6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbiA9ICdjZW50ZXInKQ0Kc2V0LnNlZWQoNDIxMCkNCg0KYGBgDQoNCkxvYWRpbmcgdGhlIGRhdGEgZnJvbSB0aGUgYE1BU1NgIHBhY2thZ2UgYW5kIGNsZWFuaW5nIHRoZSBuYW1lcw0KDQpgYGB7ciBwYWNrYWdlc19kYXRhLCBpbmNsdWRlID0gRn0NCiMgTG9hZGluZyBwYWNrYWdlcw0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCiMgR2V0dGluZyB0aGUgZGF0YQ0KY2F0cyA8LSBNQVNTOjpjYXRzIHw+DQogIGRwbHlyOjpzZWxlY3QoYm9keSA9IEJ3dCwNCiAgICAgICAgICAgICAgICBoZWFydCA9IEh3dCkNCmBgYA0KDQpDaGVja2luZyB0aGUgZGF0YQ0KDQpgYGB7cn0NCmNhdHMgfD4NCiAgc2xpY2Vfc2FtcGxlKG4gPSAxMCkNCmBgYA0KDQoNCiMjIFNjYXR0ZXIgcGxvdCBmb3IgYm9keSB2cyBoZWFydDoNCg0KV2Ugc2hvdWxkIHN0YXJ0IHdpdGggYSBwbG90IG9mIGJvZHkgdnMgaGVhcnQgd2VpZ2h0Og0KDQpgYGB7ciBzY2F0dGVyX3Bsb3R9DQpnZ19jYXRzIDwtIA0KICBnZ3Bsb3QoDQogICAgZGF0YSA9IGNhdHMsDQogICAgbWFwcGluZyA9IGFlcygNCiAgICAgIHggPSBib2R5LA0KICAgICAgeSA9IGhlYXJ0DQogICAgKQ0KICApICsgDQogIGdlb21fcG9pbnQoKSArIA0KICB0aGVtZV9idygpICsgDQogIGxhYnMoDQogICAgdGl0bGUgPSAnQ2F0czogQm9keSB3ZWlnaHQgdnMgaGVhcnQgd2VpZ2h0JywNCiAgICBzdWJ0aXRsZSA9ICdMT0VTUyBsaW5lIGFkZGVkJywNCiAgICB4ID0gJ0JvZHknLA0KICAgIHkgPSAnSGVhcnQnDQogICkgKyANCiAgIyBBZGRpbmcgdGhlIHVuaXRzIHRvIHRoZSBheGVzDQogIHNjYWxlX3hfY29udGludW91cygNCiAgICBsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzdWZmaXggPSAnIGtnJykNCiAgKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXMoDQogICAgbGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc3VmZml4ID0gJyBnJykNCiAgKQ0KDQpnZ19jYXRzICsNCiAgZ2VvbV9zbW9vdGgoDQogICAgbWV0aG9kID0gJ2xvZXNzJywNCiAgICBmb3JtdWxhID0geSB+IHgsDQogICAgc2UgPSBGDQogICkNCmBgYA0KDQojIyBGaXR0aW5nIHRoZSBsaW5lYXIgbW9kZWw6DQoNCldoaWxlIHdlJ3ZlIGJlZW4gY2FsY3VsYXRpbmcgdGhlIHNsb3BlICdieSBoYW5kJywgd2UgdHlwaWNhbGx5IHVzZSBhIGZ1bmN0aW9uIHRvIGRvIGl0IGZvciB1cywgbGlrZSBgbG0oeSB+IHgsIGRhdGEgPSAuLi4pYCBhbmQgd2UgY2FuIGdldCB0aGUgc3VtbWFyeSBzdGF0cyBmcm9tIGBzdW1tYXJ5KG1vZGVsKWANCg0KYGBge3IgY2F0c19sbX0NCmNhdHNfbG0gPC0gbG0oaGVhcnQgfiBib2R5LCBkYXRhID0gY2F0cykNCg0Kc3VtbWFyeShjYXRzX2xtKQ0KYGBgDQoNClRoZSBgYnJvb21gIHBhY2thZ2UgaGFzIHNvbWUgdXNlZnVsIGZ1bmN0aW9ucyB0aGF0IGFyZSBwb3B1bGFyIGFzIHRoZXkgcmV0dXJuIG9iamVjdHMgdGhhdCBhcmUgZWFzaWVyIHRvIHdvcmsgd2l0aA0KDQotIGB0aWR5KG1vZGVsKWAgcmV0dXJucyB0aGUgY29lZmZpY2llbnQgdGFibGUgYXMgYSBkYXRhIGZyYW1lDQoNCi0gYGdsYW5jZShtb2RlbClgIHJldHVybnMgYSAxIHJvdyBkYXRhIGZyYW1lIHdpdGggdGhlIGZpdCBzdGF0aXN0aWNzIG9mIHRoZSBtb2RlbA0KICAtIFdlJ2xsIGdldCB0byB0aGF0IGxhdGVyDQoNCi0gYGF1Z21lbnRfY29sdW1ucyhtb2RlbCwgZGF0YSlgIHdpbGwgYWRkIHRoZSBwcmVkaWN0ZWQgcmVzcG9uc2UsIHJlc2lkdWFscywgYW5kIG90aGVyIHVzZWZ1bCBzdGF0cyB0byB0aGUgZGF0YSBmcmFtZQ0KDQoNCkZvciBub3csIHdlJ2xsIGp1c3Qgd29yayB3aXRoIGB0aWR5KClgDQoNCmBgYHtyIHRpZHl9DQpjYXRzX2xtX3RhYmxlIDwtIGJyb29tOjp0aWR5KGNhdHNfbG0pDQoNCmNhdHNfbG1fdGFibGUNCmBgYA0KDQoNCmBgYHtyfQ0KIyBGaW5kaW5nIHRoZSBNU0U6DQpzdW0oY2F0c19sbSRyZXNpZHVhbHNeMikgLyAobnJvdyhjYXRzKSAtIDIpDQoNCiMgRmluZGluZyBTX1hYDQpzdW0oKGNhdHMkYm9keSAtIG1lYW4oY2F0cyRib2R5KSleMikNCmBgYA0KDQoNCiMjIEluZmVyZW5jZSBmb3IgYSBtZWFuIG9mIHRoZSByZXNwb25zZQ0KDQojIyMgQ29uZmlkZW5jZSBJbnRlcnZhbCBmb3IgJEUoWXxYKSQgZm9ybXVsYQ0KDQpXaGF0IGlmIEkgd2FudCB0byBrbm93IHRoZSBhdmVyYWdlIGhlYXJ0IHdlaWdodCBmb3IgYSBjYXQgdGhhdCB3ZWlnaHMgMyBraWxvZ3JhbXM/DQoNCkluc3RlYWQgb2Ygd2FudGluZyB0byBrbm93IGhvdyB0aGUgaGVhcnQgd2VpZ2h0ICpjaGFuZ2VzKiBhcyBib2R5IHdlaWdodCBpbmNyZWFzZXMsIEknbSBpbnRlcmVzdGVkIGluIHRoZSBtZWFuIGZvciBhIHBhcnRpY3VsYXIgYm9keSB3ZWlnaHQuDQoNCkZyb20gdGhlIHNsaWRlcywgd2UgY2FuIGdldCBhIGNvbmZpZGVuY2UgaW50ZXJ2YWwgZm9yIHRoZSBtZWFuIHdoZW4gJFggPSBYX2gkDQoNCiQkXGhhdHtZfV9oIFxwbSB0KDEtXGFscGhhLzI7IG4gLSAyKSBcc3FydHtNU0VcbGVmdFtcZnJhY3sxfXtufSArIFxmcmFjeyhYX2ggLSBcYmFye1h9KV4yfXtTX3tYWH19IFxyaWdodF19JCQNCg0KV2UnbGwgY3JlYXRlIHRoZSBjb25maWRlbmNlIGludGVydmFsICdieSBoYW5kJyBmaXJzdDoNCg0KIyMjIENvbmZpZGVuY2UgaW50ZXJ2YWwgZm9yICRYX2ggPSAzJCBieSBoYW5kDQoNClN0YXJ0IGJ5IGNhbGN1bGF0aW5nIHRoZSBkaWZmZXJlbnQgc3RhdGlzdGljcyB3ZSBuZWVkOg0KDQoxKSAkXGhhdHtZfV9oJA0KDQoyKSAkdCgxLVxhbHBoYS8yOyBuIC0gMikkDQoNCjMpICRNU0UkDQoNCjQpICRTX3tYWH0kDQoNCjUpICQoWF9oIC0gXGJhcntYfSleMiQNCg0KYGBge3Igc3RhdF9uZWVkZWR9DQpYX2ggPC0gMw0KbiA8LSBucm93KGNhdHMpDQoNCiMgMSkgUHJlZCBZICAgICAgIEludGVyY2VwdCAgICAgICAgICAgICAgICAgICAgIHNsb3BlDQpZX2hhdF9oIDwtICAgY2F0c19sbSRjb2VmZmljaWVudHNbMV0gKyBjYXRzX2xtJGNvZWZmaWNpZW50c1syXSAqIFhfaA0KDQoNCiMgMikgQ3JpdGljYWwgdmFsdWUNCmNyaXRfdmFsIDwtIHF0KDEgLSAwLjA1LzIsIGRmID0gbiAtIDIpDQoNCiMgMykgTVNFDQpNU0VfY2F0cyA8LSBzdW0oY2F0c19sbSRyZXNpZHVhbHNeMikgLyAobi0yKQ0KDQojIDQpIFNfWFgNClNfWFggPC0gc3VtKChjYXRzJGJvZHktbWVhbihjYXRzJGJvZHkpKV4yKQ0KDQojIDUpIFhfaCBzcXVhcmVkIGRldmlhbmNlDQpkZXZfWF9oIDwtIChYX2ggLSBtZWFuKGNhdHMkYm9keSkpXjINCg0KIyBUaGUgc3RhbmRhcmQgZXJyb3IgdXNpbmcgMywgNCwgYW5kIDUNClNFX2ggPC0gc3FydChNU0VfY2F0cyAqICgxL24gKyBkZXZfWF9oL1NfWFgpKQ0KDQojIERpc3BsYXkgcmVzdWx0cw0KdHJpYmJsZSgNCiAgICAgICAgICAgICAgfiBzdGF0cywgICAgICAgICAgICAgICAgfiB2YWx1ZSwNCiAgJ3ByZWQgWSB3aGVuIFggPSAzJywgICAgICByb3VuZChZX2hhdF9oLCAzKSwNCiAgICAgJ0NyaXRpY2FsIFZhbHVlJywgICAgIHJvdW5kKGNyaXRfdmFsLCAzKSwNCiAgICAgICAgICAgICAgICAnTVNFJywgICAgIHJvdW5kKE1TRV9jYXRzLCAzKSwNCiAgICAgICAgICAgICAgICdTX1hYJywgICAgICAgICByb3VuZChTX1hYLCAzKSwNCiAgICAgICcoWCAtIFhfYmFyKV4yJywgICAgICByb3VuZChkZXZfWF9oLCAzKSwNCiAgICAgJ1N0YW5kYXJkIEVycm9yJywgICAgICAgICByb3VuZChTRV9oLCAzKQ0KKSB8PiBndDo6Z3QoKQ0KDQpgYGANCg0KTm93IHRoYXQgd2UgaGF2ZSBhbGwgdGhlIHBpZWNlcywgd2UgY2FuIGNyZWF0ZSB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbDoNCg0KYGBge3IgcHJlZF9oZWFydF9DSX0NCnByZWRfWV9oX0NJIDwtIA0KICBjKA0KICAgICdsb3dlcicgPSBhcy5udW1lcmljKFlfaGF0X2gpIC0gY3JpdF92YWwgKiBzcXJ0KE1TRV9jYXRzICooMS9uICsgZGV2X1hfaC9TX1hYKSksDQogICAgJ3VwcGVyJyA9IGFzLm51bWVyaWMoWV9oYXRfaCkgKyBjcml0X3ZhbCAqIHNxcnQoTVNFX2NhdHMgKigxL24gKyBkZXZfWF9oL1NfWFgpKQ0KICApDQoNCnJvdW5kKHByZWRfWV9oX0NJLCAyKQ0KYGBgDQoNCldlIGNhbiBiZSA5NSUgY29uZmlkZW50IHRoYXQgdGhlIGF2ZXJhZ2UgaGVhcnQgd2VpZ2h0IG9mIGNhdHMgdGhhdCB3ZWlnaCAzIGtnIGlzIGJldHdlZW4gMTEuNSBnIHRvIDEyIGcuDQoNCg0KIyMjIENhbGN1bGF0aW5nIHRoZSBjb25maWRlbmNlIGludGVydmFsIHVzaW5nIGBwcmVkaWN0KClgDQoNCkluc3RlYWQgb2YgY2FsY3VsYXRpbmcgdGhlIDk1JSBjb25maWRlbmNlIGludGVydmFsIGJ5IGhhbmQsIHdlIGNhbiB1c2UgYHByZWRpY3QoKWAgd2l0aCB0aGUgZm9sbG93aW5nIGFyZ3VtZW50czoNCg0KMSkgYG9iamVjdCA9IGAgdGhlIGBsbWAgb2JqZWN0IHdlIGNyZWF0ZWQNCjIpIGBuZXdkYXRhID0gYCB0aGUgZGF0YSBmcmFtZSB0aGF0IGhhcyB0aGUgbmV3IHZhbHVlcyB3ZSB3YW50IHRvIHByZWRpY3QNCiAgICAtIFRoZSBjb2x1bW5zIG9mIHRoZSBkYXRhIGZyYW1lIGhhdmUgdG8gYmUgdGhlIHNhbWUgYXMgdGhlIGV4cGxhbmF0b3J5IHZhcmlhYmxlcyB1c2VkIHRvIGJ1aWxkIHRoZSBtb2RlbA0KMykgYGludGVydmFsID0gJ2NvbmZpZGVuY2UnYCB3aWxsIGhhdmUgaXQgY3JlYXRlIGEgY29uZmlkZW5jZSBpbnRlcnZhbA0KNCkgYGxldmVsID0gYCBjYW4gYmUgdXNlZCB0byBkZXRlcm1pbmUgdGhlIGNvbmZpZGVuY2UgbGV2ZWwgKGRlZmF1bHRzIHRvIDAuOTUpLg0KDQpgYGB7ciBwcmVkaWN0X0NJfQ0KIyBCb2R5IHdlaWdodCBmb3IgdGhlIENJDQpuZXdfY2F0cyA8LSANCiAgZGF0YS5mcmFtZSgNCiAgICBuYW1lID0gYygnRG9udXQnLCAnRmVyZGluYW5kJywgJ1NhbGVtJyksDQogICAgYm9keSA9IGMoMi41LCAzLCAzLjUpDQogICkNCg0KIyBBZGRpbmcgdGhlIHByZWRpY3RlZCB2YWx1ZXMgYW5kIGludGVydmFsIHRvIHRoZSBuZXdfY2F0cyBkYXRhDQpuZXdfY2F0cyA8LSANCiAgbmV3X2NhdHMgfD4gDQogIG11dGF0ZSgNCiAgICBwcmVkaWN0KA0KICAgIG9iamVjdCA9IGNhdHNfbG0sDQogICAgbmV3ZGF0YSA9IG5ld19jYXRzLA0KICAgIGludGVydmFsID0gJ2NvbmZpZGVuY2UnLA0KICAgIGxldmVsID0gMC45OQ0KICApIHw+IGRhdGEuZnJhbWUoKQ0KICApDQogIA0KbmV3X2NhdHMNCmBgYA0KDQoNCg0KDQojIyMjIFBsb3R0aW5nIHRoZSBjb25maWRlbmNlIGJhbmQNCg0KDQpXZSBjYW4gYWxzbyB2aXN1YWxpemUgdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgZm9yIHRoZSBwcmVkaWN0aW9ucyB1c2luZyBgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJylgDQoNCmBgYHtyIHByZWRfQ0lfcGxvdH0NCmdnX2NhdHMgKw0KICBnZW9tX3Ntb290aCgNCiAgICBtZXRob2QgPSAnbG0nLA0KICAgIGNvbG9yID0gJ3N0ZWVsYmx1ZScsDQogICAgZmlsbCA9ICdzdGVlbGJsdWUnLA0KICAgIGZvcm11bGEgPSB5IH4geCwNCiAgICBsZXZlbCA9IDAuOTUgICAgICAgIyBDb25maWRlbmNlIGxldmVsDQogICkgKyANCiAgbGFicyhzdWJ0aXRsZSA9ICdMaW5lYXIgcmVncmVzc2lvbiBsaW5lIHdpdGggOTUlIGNvbmZpZGVuY2UgYmFuZCcpDQpgYGANCg0KVGhlIHNoYWRlZCBhcmVhIGlzIHRoZSBjb25maWRlbmNlIGludGVydmFsIGZvciB0aGUgYXZlcmFnZSByZXNwb25zZSBvZiAkWSQgZ2l2ZW4gdGhlIHZhbHVlIG9mICRYJC4NCg0KDQojIyBQcmVkaWN0aW9uIGludGVydmFscw0KDQpUaGUgY29uZmlkZW5jZSBpbnRlcnZhbHMgaW4gdGhlIHByZXZpb3VzIHNlY3Rpb24gdHJpZXMgdG8gZXN0aW1hdGUgdGhlIG1lYW4gcmVzcG9uc2UsIGFrYSwgdGhlIGF2ZXJhZ2Ugb2YgYWxsIGNhdHMgdGhhdCB3ZWlnaCAzIGtnLg0KDQpXaGF0IGlmIHdlIGRvbid0IHdhbnQgdGhlIGV4cGVjdGVkIHZhbHVlIChtZWFuKSBvZiB0aGUgcmVzcG9uc2UsIGJ1dCBpbnN0ZWFkIHRvIGtub3cgdGhlIHJhbmdlIHdoZXJlIDkwJSwgOTUlLCBvciA5OSUgb2YgdGhlIHBvcHVsYXRpb24gd2lsbCBiZSBmb3IgYSBnaXZlbiB2YWx1ZSBvZiAkWCQ/DQoNCkluc3RlYWQsIHdlIHdhbnQgdG8gbWFrZSBhIHByZWRpY3Rpb24gaW50ZXJ2YWwuIExpa2Ugd2l0aCB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbCwgd2UgY2FuIGRvIGl0ICdieSBoYW5kJywgaXQganVzdCB0YWtlcyBhIHNtYWxsIGFkanVzdG1lbnQhDQoNCiMjIyBQcmVkaWN0aW9uIEludGVydmFsIEZvcm11bGENCg0KRnJvbSB0aGUgc2xpZGVzLCB3ZSBmb3VuZCB0aGUgdmFyaWFuY2UgZm9yICRZX2gkIHRvIGJlOg0KDQokJFZhcihZX2gpID0gVmFyKGJfMCArIGJfMSBYX2ggKyBcdmFyZXBzaWxvbl9oKSQkDQoNCkxvb2tzIHNpbWlsYXIgdG8gdGhlIHZhcmlhbmNlIGZvciAkXGhhdHtZfV9oJCwgYnV0IG5vdyB3ZSBhbHNvIGhhdmUgJFx2YXJlcHNpbG9uX2gkLiBXZWlyZGx5LCAkYl8wJCBhbmQgJGJfMSQgYXJlIGluZGVwZW5kZW50IG9mICRcdmFyZXBzaWxvbl9oJCAoYnV0IE5PVCBlYWNoIG90aGVyISksIHdlIGNhbiBzcGxpdCB0aGUgdmFyaWFuY2UgdGVybSBhYm92ZToNCg0KJCRWYXIoWV9oKSA9IFZhcihiXzAgKyBiXzEgWF9oKSArIFZhcihcdmFyZXBzaWxvbl9oKSQkDQoNCkZyb20gcHJldmlvdXMgcmVzdWx0cyBhbmQgb3VyIG1vZGVsIGFzc3VtcHRpb25zLCB3ZSBnZXQ6DQoNCiQkVmFyKFlfaCkgPSBcc2lnbWFeMlxsZWZ0W1xmcmFjezF9e259ICsgXGZyYWN7KFhfaCAtIFxiYXJ7WH0pXjJ9e1Nfe1hYfX0gXHJpZ2h0XSAgKyBcc2lnbWFeMiQkDQoNCndoaWNoIHNpbXBsaWZpZXMgZG93biB0bzoNCg0KJCRWYXIoWV9oKSA9IFxzaWdtYV4yXGxlZnRbMSArXGZyYWN7MX17bn0gKyBcZnJhY3soWF9oIC0gXGJhcntYfSleMn17U197WFh9fSBccmlnaHRdJCQNCg0KVGhlIHN0YW5kYXJkIGVycm9yIGlzIHRoZW46DQoNCg0KJCRTRShZX2gpID0gXHNxcnR7TVNFXGxlZnRbMSArXGZyYWN7MX17bn0gKyBcZnJhY3soWF9oIC0gXGJhcntYfSleMn17U197WFh9fSBccmlnaHRdfSQkDQoNClRoZSBvbmx5IGRpZmZlcmVuY2UgaXMgdGhlICQxJCBpbnNpZGUgdGhlIHNxdWFyZSByb290IG9mIHRoZSBzdGFuZGFyZCBlcnJvciENCg0KIyMjIFByZWRpY3Rpb24gSW50ZXJ2YWwgYnkgaGFuZDoNCg0KTGV0J3MgdXBkYXRlIHRoZSBTRSBmb3IgdGhlIHByZWRpY3Rpb24gaW50ZXJ2YWw6DQoNCmBgYHtyfQ0KU0VfaF9QSSA8LSBzcXJ0KE1TRV9jYXRzICogKDEgKyAxL24gKyBkZXZfWF9oL1NfWFgpKQ0Kcm91bmQoU0VfaF9QSSwgMikNCmBgYA0KDQpXaGlsZSB3ZSdyZSAib25seSIgYWRkaW5nIDEgdG8gZ28gZnJvbSB0aGUgQ0kgdG8gUEksIHRoZSBzdGFuZGFyZCBlcnJvciBpbmNyZWFzZXMgb3ZlciAxMCB0aW1lcyENCg0KIyMjIyBQcmVkaWN0aW9uIGludGVydmFsIGZvciAkWF9oID0gMyQNCg0KYGBge3IgUElfWF8zfQ0KYygnbG93ZXInID0gYXMubnVtZXJpYyhZX2hhdF9oKSAtIGNyaXRfdmFsICogU0VfaF9QSSwNCiAgJ3VwcGVyJyA9IGFzLm51bWVyaWMoWV9oYXRfaCkgKyBjcml0X3ZhbCAqIFNFX2hfUEkpIHw+DQogIHJvdW5kKDIpDQpgYGANCg0KV2UgYXJlIDk1JSBjb25maWRlbnQgdGhhdCBhIGNhdCB0aGF0IHdlaWdocyAzIGtnIHdpbGwgaGF2ZSBhIGhlYXJ0IHdlaWdodCBiZXR3ZWVuIDguOSB0byAxNC42IGdyYW1zLg0KDQoNCg0KIyMjIFByZWRpY3Rpb24gYmFuZCBmb3IgY2F0IGhlYXJ0IHdlaWdodHMNCg0KTGlrZSB3aGF0IHdlIHNhdyBmb3IgdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwsIHdlIGNhbiBjcmVhdGUgYSBwcmVkaWN0aW9uIGJhbmQgYXJvdW5kIHRoZSBsaW5lIG9mIGJlc3QgZml0Lg0KDQpVbmZvcnR1bmF0ZWx5LCB0aGVyZSBpc24ndCBhIHF1aWNrIHdheSB0byBjcmVhdGUgdGhlIHByZWRpY3Rpb24gYmFuZCBsaWtlIHdoYXQgd2UgaGFkIHdpdGggYGdlb21fc21vb3RoKC4uLilgLiBXZSBoYXZlIHRvICdtYW51YWxseScgZmluZCB0aGUgbG93ZXIgYW5kIHVwcGVyIGJhbmQgZm9yIHRoZSByYW5nZSBvZiBib2R5IHdlaWdodHMuDQoNCldlJ2xsIGNyZWF0ZSB0aGUgYmFuZCBmb3IgdGhlIHJhbmdlIG9mIGJvZHkgd2VpZ2h0cyBpbiBpbmNyZW1lbnRzIG9mIDAuMDEga2cgYW5kIHVzZSBgcHJlZGljdCgpYCB0byBtYWtlIHRoZSBpbnRlcnZhbDoNCg0KYGBge3IgUElfYmFuZF9kYXRhfQ0KIyBDcmVhdGluZyB0aGUgbG93ZXIgYW5kIHVwcGVyIHJhbmdlIG9mIHRoZSBwcmVkaWN0aW9uIGJhbmQ6DQpjYXRzX1BJX2JhbmQgPC0gDQogIHRpYmJsZSgNCiAgICAjIFJhbmdlIG9mIGJvZHkgd2VpZ2h0IGJ5IDAuMDEga2cNCiAgICBib2R5ID0gc2VxKG1pbihjYXRzJGJvZHkpLCBtYXgoY2F0cyRib2R5KSwgYnkgPSAwLjAwMSkNCiAgKQ0KDQoNCiMgV2UgY2FuIGZpbmQgdGhlIGludGVydmFsIHVzaW5nIHByZWRpY3QoKSANCiMgbGlrZSB3ZSBkaWQgd2l0aCB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbCwgYnV0IGludGVydmFsID0gJ3ByZWRpY3Rpb24nDQpjYXRzX1BJX2JhbmQgPC0gDQogIGNhdHNfUElfYmFuZCB8PiANCiAgbXV0YXRlKA0KICAgIHByZWRpY3QoDQogICAgICBvYmplY3QgPSBjYXRzX2xtLA0KICAgICAgbmV3ZGF0YSA9IGNhdHNfUElfYmFuZCwNCiAgICAgIGludGVydmFsID0gJ3ByZWRpY3Rpb24nLA0KICAgICAgbGV2ZWwgPSAwLjk1DQogICAgKSB8PiBkYXRhLmZyYW1lKCkNCiAgKQ0KDQpjYXRzX1BJX2JhbmQgfD4NCiAgcm91bmQoMikNCmBgYA0KDQpOb3cgd2UnbGwgYWRkIGl0IHRvIHRoZSBzY2F0dGVycGxvdCBgZ2dfY2F0c2AgdXNpbmcgYGdlb21fcmliYm9uKC4uLilgOg0KDQpgYGB7ciBwbG90X3dpdGhfcGl9DQpnZ19jYXRzICsNCiAgIyBDaGFuZ2luZyB0aGUgc3VidGl0bGUNCiAgbGFicyhzdWJ0aXRsZSA9ICdMaW5lYXIgcmVncmVzc2lvbiBsaW5lIHdpdGggOTUlIGNvbmZpZGVuY2UgYW5kIHByZWRpY3Rpb24gYmFuZHMnKSArIA0KICAjIEFkZGluZyB0aGUgcHJlZGljdGlvbiBiYW5kDQogIGdlb21fcmliYm9uKA0KICAgIGRhdGEgPSBjYXRzX1BJX2JhbmQsDQogICAgbWFwcGluZyA9IGFlcygNCiAgICAgIHggPSBib2R5LA0KICAgICAgeW1pbiA9IGx3ciwNCiAgICAgIHltYXggPSB1cHIsDQogICAgICB5ID0gTlVMTCAgICAgICAgICAgICMgU2luY2UgeSBpcyBtYXBwZWQgaW4gZ2dfY2F0cywgd2UgbmVlZCB0byByZW1vdmUgaXQgDQogICAgKSwNCiAgICBhbHBoYSA9IDAuNSwNCiAgICBjb2xvciA9ICd3aGl0ZScsDQogICAgZmlsbCA9ICdzdGVlbGJsdWUnDQogICkgKyANCiANCiAgDQogICMgQWRkaW5nIHRoZSBwcmVkaWN0aW9uIGxpbmUgYW5kIGNvbmZpZGVuY2UgYmFuZA0KICBnZW9tX3Ntb290aCgNCiAgICBtZXRob2QgPSAnbG0nLA0KICAgIGZvcm11bGEgPSB5IH4geCwNCiAgICBjb2xvciA9ICdzdGVlbGJsdWUnLA0KICAgIGZpbGwgPSAnc3RlZWxibHVlJw0KICApIA0KDQoNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg==