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

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

Hypothesis test for the slope

The first step of any hypothesis test is to state the null and alternative hypotheses:

\[H_0: \beta_1 = 0 \\ H_a: \beta_1 \ne 0\]

We’re interested in seeing if there is any type of linear association between body and heart weight.

Next, we’d check the conditions, but that’s for a later chapter.

Next step: Test statistic

By hand

We’ll start by performing the hypothesis test for the slope manually, then use some of the above methods to find the test statistic and p-value

Test stat:

The test statistic for the slope is:

\[t = \frac{b_1-\beta_{1,0}}{\sqrt{\frac{MSE}{S_{XX}}}}\]

# Start by finding n, MSE, S_XX, and slope
n <- nrow(cats)
MSE_cats <- sum(cats_lm$residuals^2) / (n - 2)
S_XX <- sum((cats$body - mean(cats$body))^2)
slope_cats <- cats_lm$coefficients[2]


# Standard error next
slope_SE <- sqrt(MSE_cats/S_XX)
slope_ts <- slope_cats / slope_SE

slope_ts
##     body 
## 16.11939

We have a huge test statistic, which would imply we will have what is called in scientific terms, ‘teeny tiny’.

P-value

The test statistic follows a t-distribution with \(df = n - 2\)

\[t = \frac{b_1-\beta_{1,0}}{\sqrt{\frac{MSE}{S_{XX}}}} \sim t(n-2)\]

slope_p_val <- 2 * pt(abs(slope_ts), df = n-2, lower.tail = F)

slope_p_val
##         body 
## 6.969045e-34

So we have a p-value that has 33 zeros between the decimal and 6.

0.0000000000000000000000000000000006969

So we have very, very strong evidence that there is a linear association between body and heart weight, which is not surprising given the scatter plot we created in the beginning. But it is nice to have the methods reaffirm our subjective conclusions.

95% confidence interval ‘by hand’

Given the conclusion isn’t all that useful (there is a linear association), we want to follow it up with a confidence interval.

\[b_1 \pm t(1 - \alpha/2; n - 2) \sqrt{\frac{MSE}{S_{XX}}}\]

cats_slope_CI <- 
  c('lower' = slope_cats - qt(0.975, df = n - 2) * slope_SE,
    'upper' = slope_cats + qt(0.975, df = n - 2) * slope_SE)

cats_slope_CI |>
  round(2)
## lower.body upper.body 
##       3.54       4.53

Slope inference using built-in functions

There are several functions we can use to calculate the test statistic, p-value, and confidence interval

summary(model)

Up first, the base function summary(model).

cats_lm_sum <- summary(cats_lm)

cats_lm_sum
## 
## 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

There’s a lot of output from the summary() function. If you want some smaller pieces, we can extract them using the $ operator. What can we put after the $?

names(object) will tell you all the names you can put after the $

names(cats_lm_sum)
##  [1] "call"          "terms"         "residuals"     "coefficients" 
##  [5] "aliased"       "sigma"         "df"            "r.squared"    
##  [9] "adj.r.squared" "fstatistic"    "cov.unscaled"
# Let's look at coefficients
cats_lm_sum$coefficients
##               Estimate Std. Error    t value     Pr(>|t|)
## (Intercept) -0.3566624  0.6922770 -0.5152019 6.072131e-01
## body         4.0340627  0.2502615 16.1193908 6.969045e-34

The code above returns just the coefficients table, which is want we want if we’re just interested in the slope.

And hooray, it returns the same standard error, test statistic, and p-value that we found by hand.

What about a confidence interval?

There’s not a way to get it directly from the summary() object. But there is a base function called confint(model) that will create a confidence interval for each parameter in the model

confint(cats_lm, level = 0.95) |> round(2)
##             2.5 % 97.5 %
## (Intercept) -1.73   1.01
## body         3.54   4.53

If you just want the interval for a specific parameter(s), you can give parm the name of the row:

confint(cats_lm, parm = 'body', level = 0.95) |> round(2)
##      2.5 % 97.5 %
## body  3.54   4.53

Important: The confidence interval created by confint() is different than the standard formula of \(stat \pm CV * SE\), but for larger samples or if the data are approximately Normal, the two will be very, very similar.

So what can we do if we want the test statistic, p-value, and confidence interval all in one place?

broom::tidy()

We can use the tidy(model) function in the broom package, which returns just the table of the model terms:

broom::tidy(cats_lm)
## # 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

Test statistic and p-value, but no confidence interval :(

It doesn’t include the CI by default, but if you specify conf.int = T, then it will create a 95% confidence interval. If you want a different confidence level, you can include conf.level = ... to pick a different one (needs to be between 0 and 1, not a percentage).

broom::tidy(
  cats_lm,
  conf.int = T,
  conf.level = 0.95
) |>
  mutate(
    across(
      .cols = where(is.numeric),
      .fns = ~ round(., 2)
    )
  )
## # A tibble: 2 × 7
##   term        estimate std.error statistic p.value conf.low conf.high
##   <chr>          <dbl>     <dbl>     <dbl>   <dbl>    <dbl>     <dbl>
## 1 (Intercept)    -0.36      0.69     -0.52    0.61    -1.73      1.01
## 2 body            4.03      0.25     16.1     0        3.54      4.53

Same that we got the previous two times!

Personally, I like the broom functions for linear models and will use them over summary() most of the time because it’s easy to alter them using the dplyr functions like mutate()

LS0tDQp0aXRsZTogJ0luZmVyZW5jZSBmb3IgU2xvcGUgLSBDYXRzIGV4YW1wbGUnDQphdXRob3I6ICJDaGFwdGVyIDIiDQpkYXRlOiAiU1RBIDQyMTAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZmlnX3dpZHRoOiA2DQogICAgZmlnX2hlaWdodDogNg0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IG5vDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gJ2NlbnRlcicpDQpzZXQuc2VlZCg0MjEwKQ0KDQpgYGANCg0KTG9hZGluZyB0aGUgZGF0YSBmcm9tIHRoZSBgTUFTU2AgcGFja2FnZSBhbmQgY2xlYW5pbmcgdGhlIG5hbWVzDQoNCmBgYHtyIHBhY2thZ2VzX2RhdGEsIGluY2x1ZGUgPSBGfQ0KIyBMb2FkaW5nIHBhY2thZ2VzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KIyBHZXR0aW5nIHRoZSBkYXRhDQpjYXRzIDwtIE1BU1M6OmNhdHMgfD4NCiAgZHBseXI6OnNlbGVjdChib2R5ID0gQnd0LA0KICAgICAgICAgICAgICAgIGhlYXJ0ID0gSHd0KQ0KYGBgDQoNCkNoZWNraW5nIHRoZSBkYXRhDQoNCmBgYHtyfQ0KY2F0cyB8Pg0KICBzbGljZV9zYW1wbGUobiA9IDEwKQ0KYGBgDQoNCg0KIyMgU2NhdHRlciBwbG90IGZvciBib2R5IHZzIGhlYXJ0Og0KDQpXZSBzaG91bGQgc3RhcnQgd2l0aCBhIHBsb3Qgb2YgYm9keSB2cyBoZWFydCB3ZWlnaHQ6DQoNCmBgYHtyIHNjYXR0ZXJfcGxvdH0NCmdnX2NhdHMgPC0gDQogIGdncGxvdCgNCiAgICBkYXRhID0gY2F0cywNCiAgICBtYXBwaW5nID0gYWVzKA0KICAgICAgeCA9IGJvZHksDQogICAgICB5ID0gaGVhcnQNCiAgICApDQogICkgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIHRoZW1lX2J3KCkgKyANCiAgbGFicygNCiAgICB0aXRsZSA9ICdDYXRzOiBCb2R5IHdlaWdodCB2cyBoZWFydCB3ZWlnaHQnLA0KICAgIHN1YnRpdGxlID0gJ0xPRVNTIGxpbmUgYWRkZWQnLA0KICAgIHggPSAnQm9keScsDQogICAgeSA9ICdIZWFydCcNCiAgKSArIA0KICAjIEFkZGluZyB0aGUgdW5pdHMgdG8gdGhlIGF4ZXMNCiAgc2NhbGVfeF9jb250aW51b3VzKA0KICAgIGxhYmVscyA9IHNjYWxlczo6bGFiZWxfbnVtYmVyKHN1ZmZpeCA9ICcga2cnKQ0KICApICsgDQogIHNjYWxlX3lfY29udGludW91cygNCiAgICBsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzdWZmaXggPSAnIGcnKQ0KICApDQoNCmdnX2NhdHMgKw0KICBnZW9tX3Ntb290aCgNCiAgICBtZXRob2QgPSAnbG9lc3MnLA0KICAgIGZvcm11bGEgPSB5IH4geCwNCiAgICBzZSA9IEYNCiAgKQ0KYGBgDQoNCiMjIEZpdHRpbmcgdGhlIGxpbmVhciBtb2RlbDoNCg0KV2hpbGUgd2UndmUgYmVlbiBjYWxjdWxhdGluZyB0aGUgc2xvcGUgJ2J5IGhhbmQnLCB3ZSB0eXBpY2FsbHkgdXNlIGEgZnVuY3Rpb24gdG8gZG8gaXQgZm9yIHVzLCBsaWtlIGBsbSh5IH4geCwgZGF0YSA9IC4uLilgIGFuZCB3ZSBjYW4gZ2V0IHRoZSBzdW1tYXJ5IHN0YXRzIGZyb20gYHN1bW1hcnkobW9kZWwpYA0KDQpgYGB7ciBjYXRzX2xtfQ0KY2F0c19sbSA8LSBsbShoZWFydCB+IGJvZHksIGRhdGEgPSBjYXRzKQ0KDQpzdW1tYXJ5KGNhdHNfbG0pDQpgYGANCg0KVGhlIGBicm9vbWAgcGFja2FnZSBoYXMgc29tZSB1c2VmdWwgZnVuY3Rpb25zIHRoYXQgYXJlIHBvcHVsYXIgYXMgdGhleSByZXR1cm4gb2JqZWN0cyB0aGF0IGFyZSBlYXNpZXIgdG8gd29yayB3aXRoDQoNCi0gYHRpZHkobW9kZWwpYCByZXR1cm5zIHRoZSBjb2VmZmljaWVudCB0YWJsZSBhcyBhIGRhdGEgZnJhbWUNCg0KLSBgZ2xhbmNlKG1vZGVsKWAgcmV0dXJucyBhIDEgcm93IGRhdGEgZnJhbWUgd2l0aCB0aGUgZml0IHN0YXRpc3RpY3Mgb2YgdGhlIG1vZGVsDQogIC0gV2UnbGwgZ2V0IHRvIHRoYXQgbGF0ZXINCg0KLSBgYXVnbWVudF9jb2x1bW5zKG1vZGVsLCBkYXRhKWAgd2lsbCBhZGQgdGhlIHByZWRpY3RlZCByZXNwb25zZSwgcmVzaWR1YWxzLCBhbmQgb3RoZXIgdXNlZnVsIHN0YXRzIHRvIHRoZSBkYXRhIGZyYW1lDQoNCg0KRm9yIG5vdywgd2UnbGwganVzdCB3b3JrIHdpdGggYHRpZHkoKWANCg0KYGBge3IgdGlkeX0NCmNhdHNfbG1fdGFibGUgPC0gYnJvb206OnRpZHkoY2F0c19sbSkNCg0KY2F0c19sbV90YWJsZQ0KYGBgDQoNCg0KYGBge3J9DQojIEZpbmRpbmcgdGhlIE1TRToNCnN1bShjYXRzX2xtJHJlc2lkdWFsc14yKSAvIChucm93KGNhdHMpIC0gMikNCg0KIyBGaW5kaW5nIFNfWFgNCnN1bSgoY2F0cyRib2R5IC0gbWVhbihjYXRzJGJvZHkpKV4yKQ0KYGBgDQoNCiMjIEh5cG90aGVzaXMgdGVzdCBmb3IgdGhlIHNsb3BlDQoNClRoZSBmaXJzdCBzdGVwIG9mIGFueSBoeXBvdGhlc2lzIHRlc3QgaXMgdG8gc3RhdGUgdGhlIG51bGwgYW5kIGFsdGVybmF0aXZlIGh5cG90aGVzZXM6DQoNCiQkSF8wOiBcYmV0YV8xID0gMCBcXCBIX2E6IFxiZXRhXzEgXG5lIDAkJA0KDQpXZSdyZSBpbnRlcmVzdGVkIGluIHNlZWluZyBpZiB0aGVyZSBpcyBhbnkgdHlwZSBvZiBsaW5lYXIgYXNzb2NpYXRpb24gYmV0d2VlbiBib2R5IGFuZCBoZWFydCB3ZWlnaHQuDQoNCg0KTmV4dCwgd2UnZCBjaGVjayB0aGUgY29uZGl0aW9ucywgYnV0IHRoYXQncyBmb3IgYSBsYXRlciBjaGFwdGVyLiANCg0KTmV4dCBzdGVwOiBUZXN0IHN0YXRpc3RpYw0KDQojIyMgQnkgaGFuZA0KDQpXZSdsbCBzdGFydCBieSBwZXJmb3JtaW5nIHRoZSBoeXBvdGhlc2lzIHRlc3QgZm9yIHRoZSBzbG9wZSBtYW51YWxseSwgdGhlbiB1c2Ugc29tZSBvZiB0aGUgYWJvdmUgbWV0aG9kcyB0byBmaW5kIHRoZSB0ZXN0IHN0YXRpc3RpYyBhbmQgcC12YWx1ZQ0KDQojIyMjIFRlc3Qgc3RhdDoNCg0KVGhlIHRlc3Qgc3RhdGlzdGljIGZvciB0aGUgc2xvcGUgaXM6DQoNCiQkdCA9IFxmcmFje2JfMS1cYmV0YV97MSwwfX17XHNxcnR7XGZyYWN7TVNFfXtTX3tYWH19fX0kJA0KDQoNCmBgYHtyIHRlc3Rfc3RhdH0NCiMgU3RhcnQgYnkgZmluZGluZyBuLCBNU0UsIFNfWFgsIGFuZCBzbG9wZQ0KbiA8LSBucm93KGNhdHMpDQpNU0VfY2F0cyA8LSBzdW0oY2F0c19sbSRyZXNpZHVhbHNeMikgLyAobiAtIDIpDQpTX1hYIDwtIHN1bSgoY2F0cyRib2R5IC0gbWVhbihjYXRzJGJvZHkpKV4yKQ0Kc2xvcGVfY2F0cyA8LSBjYXRzX2xtJGNvZWZmaWNpZW50c1syXQ0KDQoNCiMgU3RhbmRhcmQgZXJyb3IgbmV4dA0Kc2xvcGVfU0UgPC0gc3FydChNU0VfY2F0cy9TX1hYKQ0Kc2xvcGVfdHMgPC0gc2xvcGVfY2F0cyAvIHNsb3BlX1NFDQoNCnNsb3BlX3RzDQpgYGANCg0KV2UgaGF2ZSBhIGh1Z2UgdGVzdCBzdGF0aXN0aWMsIHdoaWNoIHdvdWxkIGltcGx5IHdlIHdpbGwgaGF2ZSB3aGF0IGlzIGNhbGxlZCBpbiBzY2llbnRpZmljIHRlcm1zLCAndGVlbnkgdGlueScuIA0KDQojIyMjIFAtdmFsdWUNCg0KVGhlIHRlc3Qgc3RhdGlzdGljIGZvbGxvd3MgYSB0LWRpc3RyaWJ1dGlvbiB3aXRoICRkZiA9IG4gLSAyJA0KDQokJHQgPSBcZnJhY3tiXzEtXGJldGFfezEsMH19e1xzcXJ0e1xmcmFje01TRX17U197WFh9fX19IFxzaW0gdChuLTIpJCQNCg0KYGBge3J9DQpzbG9wZV9wX3ZhbCA8LSAyICogcHQoYWJzKHNsb3BlX3RzKSwgZGYgPSBuLTIsIGxvd2VyLnRhaWwgPSBGKQ0KDQpzbG9wZV9wX3ZhbA0KYGBgDQoNClNvIHdlIGhhdmUgYSBwLXZhbHVlIHRoYXQgaGFzIDMzIHplcm9zIGJldHdlZW4gdGhlIGRlY2ltYWwgYW5kIDYuDQoNCjAuMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjk2OQ0KDQpTbyB3ZSBoYXZlIHZlcnksIHZlcnkgc3Ryb25nIGV2aWRlbmNlIHRoYXQgdGhlcmUgaXMgYSBsaW5lYXIgYXNzb2NpYXRpb24gYmV0d2VlbiBib2R5IGFuZCBoZWFydCB3ZWlnaHQsIHdoaWNoIGlzIG5vdCBzdXJwcmlzaW5nIGdpdmVuIHRoZSBzY2F0dGVyIHBsb3Qgd2UgY3JlYXRlZCBpbiB0aGUgYmVnaW5uaW5nLiBCdXQgaXQgaXMgbmljZSB0byBoYXZlIHRoZSBtZXRob2RzIHJlYWZmaXJtIG91ciBzdWJqZWN0aXZlIGNvbmNsdXNpb25zLg0KDQojIyMjIDk1JSBjb25maWRlbmNlIGludGVydmFsICdieSBoYW5kJw0KDQpHaXZlbiB0aGUgY29uY2x1c2lvbiBpc24ndCBhbGwgdGhhdCB1c2VmdWwgKHRoZXJlIGlzIGEgbGluZWFyIGFzc29jaWF0aW9uKSwgd2Ugd2FudCB0byBmb2xsb3cgaXQgdXAgd2l0aCBhIGNvbmZpZGVuY2UgaW50ZXJ2YWwuDQoNCiQkYl8xIFxwbSB0KDEgLSBcYWxwaGEvMjsgbiAtIDIpIFxzcXJ0e1xmcmFje01TRX17U197WFh9fX0kJA0KDQpgYGB7ciBzbG9wZV9DSX0NCmNhdHNfc2xvcGVfQ0kgPC0gDQogIGMoJ2xvd2VyJyA9IHNsb3BlX2NhdHMgLSBxdCgwLjk3NSwgZGYgPSBuIC0gMikgKiBzbG9wZV9TRSwNCiAgICAndXBwZXInID0gc2xvcGVfY2F0cyArIHF0KDAuOTc1LCBkZiA9IG4gLSAyKSAqIHNsb3BlX1NFKQ0KDQpjYXRzX3Nsb3BlX0NJIHw+DQogIHJvdW5kKDIpDQpgYGANCg0KIyMgU2xvcGUgaW5mZXJlbmNlIHVzaW5nIGJ1aWx0LWluIGZ1bmN0aW9ucw0KDQpUaGVyZSBhcmUgc2V2ZXJhbCBmdW5jdGlvbnMgd2UgY2FuIHVzZSB0byBjYWxjdWxhdGUgdGhlIHRlc3Qgc3RhdGlzdGljLCBwLXZhbHVlLCBhbmQgY29uZmlkZW5jZSBpbnRlcnZhbA0KDQojIyMgYHN1bW1hcnkobW9kZWwpYA0KDQpVcCBmaXJzdCwgdGhlIGBiYXNlYCBmdW5jdGlvbiBgc3VtbWFyeShtb2RlbClgLg0KDQpgYGB7ciBzdW1tYXJ5X2xtfQ0KY2F0c19sbV9zdW0gPC0gc3VtbWFyeShjYXRzX2xtKQ0KDQpjYXRzX2xtX3N1bQ0KYGBgDQoNClRoZXJlJ3MgYSBsb3Qgb2Ygb3V0cHV0IGZyb20gdGhlIGBzdW1tYXJ5KClgIGZ1bmN0aW9uLiBJZiB5b3Ugd2FudCBzb21lIHNtYWxsZXIgcGllY2VzLCB3ZSBjYW4gZXh0cmFjdCB0aGVtIHVzaW5nIHRoZSBgJGAgb3BlcmF0b3IuIFdoYXQgY2FuIHdlIHB1dCBhZnRlciB0aGUgYCRgPw0KDQpgbmFtZXMob2JqZWN0KWAgd2lsbCB0ZWxsIHlvdSBhbGwgdGhlIG5hbWVzIHlvdSBjYW4gcHV0IGFmdGVyIHRoZSBgJGANCg0KYGBge3IgbmFtZXN9DQpuYW1lcyhjYXRzX2xtX3N1bSkNCg0KIyBMZXQncyBsb29rIGF0IGNvZWZmaWNpZW50cw0KY2F0c19sbV9zdW0kY29lZmZpY2llbnRzDQpgYGANCg0KVGhlIGNvZGUgYWJvdmUgcmV0dXJucyBqdXN0IHRoZSBjb2VmZmljaWVudHMgdGFibGUsIHdoaWNoIGlzIHdhbnQgd2Ugd2FudCBpZiB3ZSdyZSBqdXN0IGludGVyZXN0ZWQgaW4gdGhlIHNsb3BlLiANCg0KQW5kIGhvb3JheSwgaXQgcmV0dXJucyB0aGUgc2FtZSBzdGFuZGFyZCBlcnJvciwgdGVzdCBzdGF0aXN0aWMsIGFuZCBwLXZhbHVlIHRoYXQgd2UgZm91bmQgYnkgaGFuZC4NCg0KV2hhdCBhYm91dCBhIGNvbmZpZGVuY2UgaW50ZXJ2YWw/DQoNClRoZXJlJ3Mgbm90IGEgd2F5IHRvIGdldCBpdCBkaXJlY3RseSBmcm9tIHRoZSBgc3VtbWFyeSgpYCBvYmplY3QuIEJ1dCB0aGVyZSBpcyBhIGBiYXNlYCBmdW5jdGlvbiBjYWxsZWQgYGNvbmZpbnQobW9kZWwpYCB0aGF0IHdpbGwgY3JlYXRlIGEgY29uZmlkZW5jZSBpbnRlcnZhbCBmb3IgZWFjaCBwYXJhbWV0ZXIgaW4gdGhlIG1vZGVsDQoNCmBgYHtyfQ0KY29uZmludChjYXRzX2xtLCBsZXZlbCA9IDAuOTUpIHw+IHJvdW5kKDIpDQpgYGANCg0KSWYgeW91IGp1c3Qgd2FudCB0aGUgaW50ZXJ2YWwgZm9yIGEgc3BlY2lmaWMgcGFyYW1ldGVyKHMpLCB5b3UgY2FuIGdpdmUgYHBhcm1gIHRoZSBuYW1lIG9mIHRoZSByb3c6DQoNCmBgYHtyIGNvbmZpbnRfYm9keX0NCmNvbmZpbnQoY2F0c19sbSwgcGFybSA9ICdib2R5JywgbGV2ZWwgPSAwLjk1KSB8PiByb3VuZCgyKQ0KYGBgDQoNCioqSW1wb3J0YW50OioqIFRoZSBjb25maWRlbmNlIGludGVydmFsIGNyZWF0ZWQgYnkgYGNvbmZpbnQoKWAgaXMgZGlmZmVyZW50IHRoYW4gdGhlIHN0YW5kYXJkIGZvcm11bGEgb2YgJHN0YXQgXHBtIENWICogU0UkLCBidXQgZm9yIGxhcmdlciBzYW1wbGVzIG9yIGlmIHRoZSBkYXRhIGFyZSBhcHByb3hpbWF0ZWx5IE5vcm1hbCwgdGhlIHR3byB3aWxsIGJlIHZlcnksIHZlcnkgc2ltaWxhci4NCg0KU28gd2hhdCBjYW4gd2UgZG8gaWYgd2Ugd2FudCB0aGUgdGVzdCBzdGF0aXN0aWMsIHAtdmFsdWUsIGFuZCBjb25maWRlbmNlIGludGVydmFsIGFsbCBpbiBvbmUgcGxhY2U/DQoNCiMjIyBicm9vbTo6dGlkeSgpDQoNCldlIGNhbiB1c2UgdGhlIGB0aWR5KG1vZGVsKWAgZnVuY3Rpb24gaW4gdGhlIGBicm9vbWAgcGFja2FnZSwgd2hpY2ggcmV0dXJucyBqdXN0IHRoZSB0YWJsZSBvZiB0aGUgbW9kZWwgdGVybXM6DQoNCmBgYHtyIGJyb29tX3RpZHl9DQpicm9vbTo6dGlkeShjYXRzX2xtKQ0KYGBgDQoNClRlc3Qgc3RhdGlzdGljIGFuZCBwLXZhbHVlLCBidXQgbm8gY29uZmlkZW5jZSBpbnRlcnZhbCA6KA0KDQpJdCBkb2Vzbid0IGluY2x1ZGUgdGhlIENJIGJ5IGRlZmF1bHQsIGJ1dCBpZiB5b3Ugc3BlY2lmeSBgY29uZi5pbnQgPSBUYCwgdGhlbiBpdCB3aWxsIGNyZWF0ZSBhIDk1JSBjb25maWRlbmNlIGludGVydmFsLiBJZiB5b3Ugd2FudCBhIGRpZmZlcmVudCBjb25maWRlbmNlIGxldmVsLCB5b3UgY2FuIGluY2x1ZGUgYGNvbmYubGV2ZWwgPSAuLi5gIHRvIHBpY2sgYSBkaWZmZXJlbnQgb25lIChuZWVkcyB0byBiZSBiZXR3ZWVuIDAgYW5kIDEsIG5vdCBhIHBlcmNlbnRhZ2UpLg0KDQpgYGB7cn0NCmJyb29tOjp0aWR5KA0KICBjYXRzX2xtLA0KICBjb25mLmludCA9IFQsDQogIGNvbmYubGV2ZWwgPSAwLjk1DQopIHw+DQogIG11dGF0ZSgNCiAgICBhY3Jvc3MoDQogICAgICAuY29scyA9IHdoZXJlKGlzLm51bWVyaWMpLA0KICAgICAgLmZucyA9IH4gcm91bmQoLiwgMikNCiAgICApDQogICkNCmBgYA0KDQpTYW1lIHRoYXQgd2UgZ290IHRoZSBwcmV2aW91cyB0d28gdGltZXMhDQoNClBlcnNvbmFsbHksIEkgbGlrZSB0aGUgYGJyb29tYCBmdW5jdGlvbnMgZm9yIGxpbmVhciBtb2RlbHMgYW5kIHdpbGwgdXNlIHRoZW0gb3ZlciBgc3VtbWFyeSgpYCBtb3N0IG9mIHRoZSB0aW1lIGJlY2F1c2UgaXQncyBlYXN5IHRvIGFsdGVyIHRoZW0gdXNpbmcgdGhlIGBkcGx5cmAgZnVuY3Rpb25zIGxpa2UgYG11dGF0ZSgpYA0KDQoNCg0KDQoNCg==