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
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 \(X_h = 3\)
by hand
Start by calculating the different statistics we need:
\(\hat{Y}_h\)
\(t(1-\alpha/2; n -
2)\)
\(MSE\)
\(S_{XX}\)
\((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:
object = the lm object we created
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
interval = 'confidence' will have it create a
confidence interval
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 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==