Cats Data

Like we have with the previous R examples, we’ll start by loading the packages and getting our data:

library(tidyverse); library(broom)
cats <- read.csv('https://raw.githubusercontent.com/Shammalamala/STA2410/refs/heads/main/data/Ch3/cats_with_names.csv')

slice_sample(cats, n = 10)
##      name sex body heart
## 1    Sage   F  2.3  10.1
## 2   Sassy   F  2.1   8.7
## 3    Dash   M  3.2  11.6
## 4   Ivory   F  2.6   8.7
## 5   Ebony   F  2.6  10.1
## 6  Marble   M  2.7   9.6
## 7   Vader   M  3.0  13.3
## 8  Nebula   M  2.7  12.0
## 9   Gizmo   M  2.4   9.3
## 10   Lynx   M  3.4  12.8

Fitting the linear model

We’ll fit our linear model using the built-in function, lm() and save the results as cats_lm.

cats_lm <- lm(heart ~ body, cats)

# View the resulting coefficients using tidy() from the broom package:
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

The p-value in the table above is only reliable IF the other pieces of our model are true. The complete model for linear regression is:

\[Y_i \sim N(\beta_0 + \beta_1 X_i, \sigma^2)\]

If any part of the model is not true, the p-value can be small, even when \(\beta_1 = 0\). So we need to check the other assumptions are correct.

Assumptions:

The assumptions for linear regression can be remembered with the acronym LINE:

  • Linear association: The relationship between \(X\) and \(Y\) needs to be linear

  • Independent: The rows in the data need to be independent

    • The cats can’t be related.
  • Normal errors: The residuals should appear to be approximately Normal

  • Equal Spread: We need constant variance so we can estimate a single variance, \(\sigma^2\)

    • Occasionally called homoscedasticity

We can check the LIE part of our acronym with a Residual Plot and the Normality assumption with a Q-Q Plot

Diagnostic Plot 1: Residual Plot

A residual plot is just like our scatter plot, but instead of \(Y\) on the y-axis, we replace it with the residuals: \(e_i = y_i - \hat{y}_i\)

It’s easier to see an violation of our assumptions using the residual plot rather than the scatter plot

Creating the residual plot

First, we need to get our residuals. We can do it by hand with cats\$res <- cats\$heart - cats_lm\$fit or simpler cats\$res <- cats_lm\$res. Alternatively, as seen below, we can use the augment() function from the broom package:

cats2 <- 
  augment(
    x = cats_lm,  # The linear model we'll get the residuals from
    data = cats   # The data to calculate the fitted and residuals
  )

slice_sample(cats2, n = 10)
## # A tibble: 10 × 10
##    name     sex    body heart .fitted .resid    .hat .sigma  .cooksd .std.resid
##    <chr>    <chr> <dbl> <dbl>   <dbl>  <dbl>   <dbl>  <dbl>    <dbl>      <dbl>
##  1 Gus      M       3.7  11     14.6  -3.57  0.0353    1.43 0.114        -2.50 
##  2 Domino   M       2.6   9.4   10.1  -0.732 0.00740   1.46 0.000953     -0.506
##  3 Chloe    F       2     9.5    7.71  1.79  0.0225    1.45 0.0178        1.25 
##  4 Fuzzball M       3.5  15.7   13.8   1.94  0.0248    1.45 0.0232        1.35 
##  5 Willow   F       2.3   7.3    8.92 -1.62  0.0123    1.45 0.00784      -1.12 
##  6 Maple    F       2.3   7.9    8.92 -1.02  0.0123    1.45 0.00311      -0.708
##  7 Cocoa    F       2.7   8.5   10.5  -2.04  0.00696   1.45 0.00693      -1.41 
##  8 Vader    M       3    13.3   11.7   1.55  0.00921   1.45 0.00538       1.08 
##  9 Rusty    M       3.8  16.8   15.0   1.83  0.0413    1.45 0.0356        1.28 
## 10 Gracie   F       2.2   9.7    8.52  1.18  0.0151    1.45 0.00515       0.820

All the columns augment() added have a . before their name to make it easier to ID which columns it added.

Now we can use cats2 to create our residual plot:

gg_cat_resplot <- 
  ggplot(
    data = cats2,
    mapping = aes(x = body, y = .resid)
  ) + 
  geom_point() +
  geom_hline(yintercept = 0, color = 'red') +
  theme_bw() + 
  labs(
    x = 'Body Weight (kg)', 
    y = 'Heart Weight Residuals',
    title = 'Residual plot of cats heart weight by body weight'
  ) 

gg_cat_resplot

Overall, it looks pretty good!

There is one thing we want to look at that is not in the LINE acronym: outliers!

There looks to be one cat that has a larger, positive residual:

gg_cat_resplot +
  geom_text(
    data = cats2 |> filter(.resid > 4), # Only getting the potential outlier
    mapping = aes(label = name),
    color = 'red',
    nudge_y = -0.3
  )

From a subjective stand point, it looks like Ranger might be an outlier. How can we be more sure?

We can convert the residuals to the standardized residuals:

\[e_i^* = \frac{e_i}{\sqrt{MSE}}\]

We can get the \(\sqrt{MSE}\) using summary(cats_lm)$sigma

cats_rMSE <- summary(cats_lm)$sigma
cats_rMSE
## [1] 1.452373
# Let's check we get the same thing:
cats2 |> 
  summarize(  # sum(e^2) / (n - 2)
    sigma2 = sum(.resid^2) / (n() - 2)
  ) |> 
  mutate(sigma = sqrt(sigma2))
## # A tibble: 1 × 2
##   sigma2 sigma
##    <dbl> <dbl>
## 1   2.11  1.45

Hooray, we get the same thing when we do it by hand!

Alternatively, you can use glance() from the broom package:

glance(cats_lm)$sigma
## [1] 1.452373

Multiple ways to ‘skin’ the cat to get the \(\sqrt{MSE}\).

Residual Plot for the standardized/studentized residuals

Let’s redo our residual plot, but now using the standardized residuals.

But why do we care about the standardized residuals? They’re equivalent to a z-score, and we can use our rule of 3 to help determine if an observation is an outlier:

\[|e_i^*| > 3 \rightarrow \text{Outlier}\]

Why 3? As long as the errors are Normal, the probability of getting a standardized residual above 3 is about 3 in 1000. So we’ll use that as our barometer if something is a residual.

Note: augment() creates a column called .std.resid, which is a type of standardized residual, but it is not the same as the one defined above. It uses something called Cook’s Distance in the calculation, and we’ll look at what Cook’s Distance is in a later chapter.

# Adding a column of the standardized residuals
cats2 <- 
  cats2 |> 
  mutate(
    stan_res = .resid / cats_rMSE
  )

# Creating the plot
gg_cats_stan_resplot <- 
  ggplot(
    data = cats2,
    mapping = aes(x = body, y = stan_res)
  ) + 
  geom_point() +
  geom_hline(yintercept = 0, color = 'red') +
  theme_bw() + 
  labs(
    x = 'Body Weight (kg)', 
    y = 'Standardized Residuals',
    title = 'Residual plot of cats heart weight by body weight'
  ) +
  # Adding tick marks at -3, -2, -1, 0, 1, 2, 3 on the y-axis
  scale_y_continuous(breaks = (-3):3,
                     minor_breaks = NULL)

gg_cats_stan_resplot

To help see if an observation falls outside of the (-3, 3) range, we can add dotted lines using geom_hline(), like we did with the line at 0:

gg_cats_stan_resplot +
  geom_hline(
    yintercept = c(-3, 3),
    color = 'red',
    linetype = 2 # makes the lines dashed instead of solid
  )

It does look like Ranger is an outlier!

So what do we do?

Let’s remove him from the data, refit the model, and see what changes:

Remedial Measure: Removing the Outlier

We’ll use filter() to remove the outlier and start over:

# Removing the outlier
cats_no_outlier <- 
  cats |> 
  filter(heart < 20)

# Next: Fit the model
cats_no_lm <- lm(heart ~ body, cats_no_outlier)

# Checking the model
tidy(cats_no_lm)
## # A tibble: 2 × 5
##   term        estimate std.error statistic  p.value
##   <chr>          <dbl>     <dbl>     <dbl>    <dbl>
## 1 (Intercept)    0.118     0.674     0.175 8.61e- 1
## 2 body           3.85      0.244    15.7   7.62e-33

It does look like the slope changed quite a bit, from 4.04 to 3.84. The hypothesis test does still show very strong evidence of a linear association between body and heart weight.

Let’s compare the fit statistics:

bind_rows(
  .id = 'data',
  'with outlier' = glance(cats_lm), 
  'without outlier' = glance(cats_no_lm)
)
## # A tibble: 2 × 13
##   data       r.squared adj.r.squared sigma statistic  p.value    df logLik   AIC
##   <chr>          <dbl>         <dbl> <dbl>     <dbl>    <dbl> <dbl>  <dbl> <dbl>
## 1 with outl…     0.647         0.644  1.45      260. 6.97e-34     1  -257.  520.
## 2 without o…     0.637         0.635  1.39      248. 7.62e-33     1  -249.  504.
## # ℹ 4 more variables: BIC <dbl>, deviance <dbl>, df.residual <int>, nobs <int>

The fit statistics barely changed! So while Ranger is an outlier, he doesn’t seem to be too much of an influential outlier.

Let’s compare the lines in the scatter plot:

ggplot(
  data = cats,
  mapping = aes(x = body, y = heart)
) + 
  geom_point() + 
  geom_abline(
    intercept = cats_lm$coef[1], 
    slope = cats_lm$coef[2],
    color = 'blue'
  ) + 
  geom_abline(
    intercept = cats_no_lm$coef[1], 
    slope = cats_no_lm$coef[2],
    color = 'red'
  )

Visually speaking, the two lines are very, very similar.

If we check the resulting residual plot:

# Calculating the standardized residuals for the no outlier data set
cats_no_outlier <- 
  cats_no_outlier |> 
  mutate(
    pred_heart = cats_no_lm$fit,
    res_heart = heart - pred_heart,
    stan_res = res_heart / summary(cats_no_lm)$sigma
  )
  
# Creating the residual plot for the cats without Ranger
gg_cats_stan_resplot +
  geom_hline(
    yintercept = c(-3, 3),
    linetype = 2,
    color = 'red'
  ) + 
  labs(title = 'Residual Plot without Ranger: the cat with the large heart') +
  # Replacing the data set with the one without any outliers
  cats_no_outlier

The residual plot looks great! No clear trend (Linear), there’s no funnel pattern (Equal Spread).

LS0tDQp0aXRsZTogIkRpYWdub3N0aWMgUGxvdHMgYW5kIFJlbWVkaWFsIE1lYXN1cmVzIg0KYXV0aG9yOiAiQ2hhcHRlciAzIg0KZGF0ZTogJ1NUQSA0MjEwJw0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGZpZ193aWR0aDogMTANCiAgICBmaWdfaGVpZ2h0OiA2DQogICAgZmlnX2NhcHRpb246IHRydWUNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHNtb290aF9zY3JvbGw6IHRydWUNCiAgICB0aGVtZTogbHVtZW4NCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEYsDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEYsDQogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gJ2NlbnRlcicpDQpgYGANCg0KIyMgQ2F0cyBEYXRhDQoNCkxpa2Ugd2UgaGF2ZSB3aXRoIHRoZSBwcmV2aW91cyBSIGV4YW1wbGVzLCB3ZSdsbCBzdGFydCBieSBsb2FkaW5nIHRoZSBwYWNrYWdlcyBhbmQgZ2V0dGluZyBvdXIgZGF0YToNCg0KYGBge3IgcGFja2FnZXNfZGF0YX0NCmxpYnJhcnkodGlkeXZlcnNlKTsgbGlicmFyeShicm9vbSkNCmNhdHMgPC0gcmVhZC5jc3YoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9TaGFtbWFsYW1hbGEvU1RBMjQxMC9yZWZzL2hlYWRzL21haW4vZGF0YS9DaDMvY2F0c193aXRoX25hbWVzLmNzdicpDQoNCnNsaWNlX3NhbXBsZShjYXRzLCBuID0gMTApDQpgYGANCg0KDQojIyBGaXR0aW5nIHRoZSBsaW5lYXIgbW9kZWwNCg0KV2UnbGwgZml0IG91ciBsaW5lYXIgbW9kZWwgdXNpbmcgdGhlIGJ1aWx0LWluIGZ1bmN0aW9uLCBgbG0oKWAgYW5kIHNhdmUgdGhlIHJlc3VsdHMgYXMgYGNhdHNfbG1gLg0KDQpgYGB7ciBjYXRzX2xtfQ0KY2F0c19sbSA8LSBsbShoZWFydCB+IGJvZHksIGNhdHMpDQoNCiMgVmlldyB0aGUgcmVzdWx0aW5nIGNvZWZmaWNpZW50cyB1c2luZyB0aWR5KCkgZnJvbSB0aGUgYnJvb20gcGFja2FnZToNCnRpZHkoY2F0c19sbSkgDQpgYGANCg0KDQpUaGUgcC12YWx1ZSBpbiB0aGUgdGFibGUgYWJvdmUgaXMgb25seSByZWxpYWJsZSAqKklGKiogdGhlIG90aGVyIHBpZWNlcyBvZiBvdXIgbW9kZWwgYXJlIHRydWUuIFRoZSBjb21wbGV0ZSBtb2RlbCBmb3IgbGluZWFyIHJlZ3Jlc3Npb24gaXM6DQoNCiQkWV9pIFxzaW0gTihcYmV0YV8wICsgXGJldGFfMSBYX2ksIFxzaWdtYV4yKSQkDQoNCklmIGFueSBwYXJ0IG9mIHRoZSBtb2RlbCBpcyBub3QgdHJ1ZSwgdGhlIHAtdmFsdWUgY2FuIGJlIHNtYWxsLCBldmVuIHdoZW4gJFxiZXRhXzEgPSAwJC4gU28gd2UgbmVlZCB0byBjaGVjayB0aGUgb3RoZXIgYXNzdW1wdGlvbnMgYXJlIGNvcnJlY3QuDQoNCiMjIEFzc3VtcHRpb25zOg0KDQpUaGUgYXNzdW1wdGlvbnMgZm9yIGxpbmVhciByZWdyZXNzaW9uIGNhbiBiZSByZW1lbWJlcmVkIHdpdGggdGhlIGFjcm9ueW0gTElORToNCg0KLSAqKkwqKmluZWFyIGFzc29jaWF0aW9uOiBUaGUgcmVsYXRpb25zaGlwIGJldHdlZW4gJFgkIGFuZCAkWSQgbmVlZHMgdG8gYmUgbGluZWFyDQoNCi0gKipJKipuZGVwZW5kZW50OiBUaGUgcm93cyBpbiB0aGUgZGF0YSBuZWVkIHRvIGJlIGluZGVwZW5kZW50DQogICAgLSBUaGUgY2F0cyBjYW4ndCBiZSByZWxhdGVkLg0KICAgIA0KLSAqKk4qKm9ybWFsIGVycm9yczogVGhlIHJlc2lkdWFscyBzaG91bGQgYXBwZWFyIHRvIGJlIGFwcHJveGltYXRlbHkgTm9ybWFsDQoNCi0gKipFKipxdWFsIFNwcmVhZDogV2UgbmVlZCBjb25zdGFudCB2YXJpYW5jZSBzbyB3ZSBjYW4gZXN0aW1hdGUgYSBzaW5nbGUgdmFyaWFuY2UsICRcc2lnbWFeMiQNCiAgICAtIE9jY2FzaW9uYWxseSBjYWxsZWQgKmhvbW9zY2VkYXN0aWNpdHkqDQogICAgDQoNCldlIGNhbiBjaGVjayB0aGUgTElFIHBhcnQgb2Ygb3VyIGFjcm9ueW0gd2l0aCBhICoqUmVzaWR1YWwgUGxvdCoqIGFuZCB0aGUgTm9ybWFsaXR5IGFzc3VtcHRpb24gd2l0aCBhICoqUS1RIFBsb3QqKg0KDQojIyBEaWFnbm9zdGljIFBsb3QgMTogUmVzaWR1YWwgUGxvdA0KDQpBIHJlc2lkdWFsIHBsb3QgaXMganVzdCBsaWtlIG91ciBzY2F0dGVyIHBsb3QsIGJ1dCBpbnN0ZWFkIG9mICRZJCBvbiB0aGUgeS1heGlzLCB3ZSByZXBsYWNlIGl0IHdpdGggdGhlIHJlc2lkdWFsczogJGVfaSA9IHlfaSAtIFxoYXR7eX1faSQNCg0KSXQncyBlYXNpZXIgdG8gc2VlIGFuIHZpb2xhdGlvbiBvZiBvdXIgYXNzdW1wdGlvbnMgdXNpbmcgdGhlIHJlc2lkdWFsIHBsb3QgcmF0aGVyIHRoYW4gdGhlIHNjYXR0ZXIgcGxvdA0KDQojIyMgQ3JlYXRpbmcgdGhlIHJlc2lkdWFsIHBsb3QNCg0KRmlyc3QsIHdlIG5lZWQgdG8gZ2V0IG91ciByZXNpZHVhbHMuIFdlIGNhbiBkbyBpdCBieSBoYW5kIHdpdGggYGNhdHNcJHJlcyA8LSBjYXRzXCRoZWFydCAtIGNhdHNfbG1cJGZpdGAgb3Igc2ltcGxlciBgY2F0c1wkcmVzIDwtIGNhdHNfbG1cJHJlc2AuIEFsdGVybmF0aXZlbHksIGFzIHNlZW4gYmVsb3csIHdlIGNhbiB1c2UgdGhlIGBhdWdtZW50KClgIGZ1bmN0aW9uIGZyb20gdGhlIGBicm9vbWAgcGFja2FnZToNCg0KYGBge3IgYXVnbWVudH0NCmNhdHMyIDwtIA0KICBhdWdtZW50KA0KICAgIHggPSBjYXRzX2xtLCAgIyBUaGUgbGluZWFyIG1vZGVsIHdlJ2xsIGdldCB0aGUgcmVzaWR1YWxzIGZyb20NCiAgICBkYXRhID0gY2F0cyAgICMgVGhlIGRhdGEgdG8gY2FsY3VsYXRlIHRoZSBmaXR0ZWQgYW5kIHJlc2lkdWFscw0KICApDQoNCnNsaWNlX3NhbXBsZShjYXRzMiwgbiA9IDEwKQ0KYGBgDQoNCkFsbCB0aGUgY29sdW1ucyBgYXVnbWVudCgpYCBhZGRlZCBoYXZlIGEgLiBiZWZvcmUgdGhlaXIgbmFtZSB0byBtYWtlIGl0IGVhc2llciB0byBJRCB3aGljaCBjb2x1bW5zIGl0IGFkZGVkLg0KDQpOb3cgd2UgY2FuIHVzZSBgY2F0czJgIHRvIGNyZWF0ZSBvdXIgcmVzaWR1YWwgcGxvdDoNCg0KYGBge3IgcmVzX3Bsb3R9DQpnZ19jYXRfcmVzcGxvdCA8LSANCiAgZ2dwbG90KA0KICAgIGRhdGEgPSBjYXRzMiwNCiAgICBtYXBwaW5nID0gYWVzKHggPSBib2R5LCB5ID0gLnJlc2lkKQ0KICApICsgDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gJ3JlZCcpICsNCiAgdGhlbWVfYncoKSArIA0KICBsYWJzKA0KICAgIHggPSAnQm9keSBXZWlnaHQgKGtnKScsIA0KICAgIHkgPSAnSGVhcnQgV2VpZ2h0IFJlc2lkdWFscycsDQogICAgdGl0bGUgPSAnUmVzaWR1YWwgcGxvdCBvZiBjYXRzIGhlYXJ0IHdlaWdodCBieSBib2R5IHdlaWdodCcNCiAgKSANCg0KZ2dfY2F0X3Jlc3Bsb3QNCmBgYA0KDQpPdmVyYWxsLCBpdCBsb29rcyBwcmV0dHkgZ29vZCENCg0KVGhlcmUgaXMgb25lIHRoaW5nIHdlIHdhbnQgdG8gbG9vayBhdCB0aGF0IGlzIG5vdCBpbiB0aGUgTElORSBhY3JvbnltOiBvdXRsaWVycyENCg0KVGhlcmUgbG9va3MgdG8gYmUgb25lIGNhdCB0aGF0IGhhcyBhIGxhcmdlciwgcG9zaXRpdmUgcmVzaWR1YWw6DQoNCmBgYHtyIHJlc3Bsb3Rfd2l0aF9sYXJnZV9jYXR9DQpnZ19jYXRfcmVzcGxvdCArDQogIGdlb21fdGV4dCgNCiAgICBkYXRhID0gY2F0czIgfD4gZmlsdGVyKC5yZXNpZCA+IDQpLCAjIE9ubHkgZ2V0dGluZyB0aGUgcG90ZW50aWFsIG91dGxpZXINCiAgICBtYXBwaW5nID0gYWVzKGxhYmVsID0gbmFtZSksDQogICAgY29sb3IgPSAncmVkJywNCiAgICBudWRnZV95ID0gLTAuMw0KICApDQpgYGANCg0KRnJvbSBhIHN1YmplY3RpdmUgc3RhbmQgcG9pbnQsIGl0IGxvb2tzIGxpa2UgUmFuZ2VyIG1pZ2h0IGJlIGFuIG91dGxpZXIuIEhvdyBjYW4gd2UgYmUgbW9yZSBzdXJlPw0KDQpXZSBjYW4gY29udmVydCB0aGUgcmVzaWR1YWxzIHRvIHRoZSAqKnN0YW5kYXJkaXplZCByZXNpZHVhbHMqKjoNCg0KJCRlX2leKiA9IFxmcmFje2VfaX17XHNxcnR7TVNFfX0kJA0KDQpXZSBjYW4gZ2V0IHRoZSAkXHNxcnR7TVNFfSQgdXNpbmcgYHN1bW1hcnkoY2F0c19sbSkkc2lnbWFgDQoNCmBgYHtyIHJNU0V9DQpjYXRzX3JNU0UgPC0gc3VtbWFyeShjYXRzX2xtKSRzaWdtYQ0KY2F0c19yTVNFDQojIExldCdzIGNoZWNrIHdlIGdldCB0aGUgc2FtZSB0aGluZzoNCmNhdHMyIHw+IA0KICBzdW1tYXJpemUoICAjIHN1bShlXjIpIC8gKG4gLSAyKQ0KICAgIHNpZ21hMiA9IHN1bSgucmVzaWReMikgLyAobigpIC0gMikNCiAgKSB8PiANCiAgbXV0YXRlKHNpZ21hID0gc3FydChzaWdtYTIpKQ0KYGBgDQoNCkhvb3JheSwgd2UgZ2V0IHRoZSBzYW1lIHRoaW5nIHdoZW4gd2UgZG8gaXQgYnkgaGFuZCENCg0KQWx0ZXJuYXRpdmVseSwgeW91IGNhbiB1c2UgYGdsYW5jZSgpYCBmcm9tIHRoZSBgYnJvb21gIHBhY2thZ2U6DQoNCmBgYHtyIGdsYW5jZV9zaWdtYX0NCmdsYW5jZShjYXRzX2xtKSRzaWdtYQ0KYGBgDQoNCk11bHRpcGxlIHdheXMgdG8gJ3NraW4nIHRoZSBjYXQgdG8gZ2V0IHRoZSAkXHNxcnR7TVNFfSQuDQoNCiMjIyBSZXNpZHVhbCBQbG90IGZvciB0aGUgc3RhbmRhcmRpemVkL3N0dWRlbnRpemVkIHJlc2lkdWFscw0KDQpMZXQncyByZWRvIG91ciByZXNpZHVhbCBwbG90LCBidXQgbm93IHVzaW5nIHRoZSBzdGFuZGFyZGl6ZWQgcmVzaWR1YWxzLiANCg0KQnV0IHdoeSBkbyB3ZSBjYXJlIGFib3V0IHRoZSBzdGFuZGFyZGl6ZWQgcmVzaWR1YWxzPyBUaGV5J3JlIGVxdWl2YWxlbnQgdG8gYSB6LXNjb3JlLCBhbmQgd2UgY2FuIHVzZSBvdXIgcnVsZSBvZiAzIHRvIGhlbHAgZGV0ZXJtaW5lIGlmIGFuIG9ic2VydmF0aW9uIGlzIGFuIG91dGxpZXI6DQoNCiQkfGVfaV4qfCA+IDMgXHJpZ2h0YXJyb3cgXHRleHR7T3V0bGllcn0kJA0KDQpXaHkgMz8gQXMgbG9uZyBhcyB0aGUgZXJyb3JzIGFyZSBOb3JtYWwsIHRoZSBwcm9iYWJpbGl0eSBvZiBnZXR0aW5nIGEgc3RhbmRhcmRpemVkIHJlc2lkdWFsIGFib3ZlIDMgaXMgYWJvdXQgMyBpbiAxMDAwLiBTbyB3ZSdsbCB1c2UgdGhhdCBhcyBvdXIgYmFyb21ldGVyIGlmIHNvbWV0aGluZyBpcyBhIHJlc2lkdWFsLg0KDQoqKk5vdGU6KiogYGF1Z21lbnQoKWAgY3JlYXRlcyBhIGNvbHVtbiBjYWxsZWQgYC5zdGQucmVzaWRgLCB3aGljaCBpcyBhIHR5cGUgb2Ygc3RhbmRhcmRpemVkIHJlc2lkdWFsLCBidXQgaXQgaXMgbm90IHRoZSBzYW1lIGFzIHRoZSBvbmUgZGVmaW5lZCBhYm92ZS4gSXQgdXNlcyBzb21ldGhpbmcgY2FsbGVkICpDb29rJ3MgRGlzdGFuY2UqIGluIHRoZSBjYWxjdWxhdGlvbiwgYW5kIHdlJ2xsIGxvb2sgYXQgd2hhdCBDb29rJ3MgRGlzdGFuY2UgaXMgaW4gYSBsYXRlciBjaGFwdGVyLg0KDQpgYGB7ciBzdGFuX3Jlc19wbG90fQ0KIyBBZGRpbmcgYSBjb2x1bW4gb2YgdGhlIHN0YW5kYXJkaXplZCByZXNpZHVhbHMNCmNhdHMyIDwtIA0KICBjYXRzMiB8PiANCiAgbXV0YXRlKA0KICAgIHN0YW5fcmVzID0gLnJlc2lkIC8gY2F0c19yTVNFDQogICkNCg0KIyBDcmVhdGluZyB0aGUgcGxvdA0KZ2dfY2F0c19zdGFuX3Jlc3Bsb3QgPC0gDQogIGdncGxvdCgNCiAgICBkYXRhID0gY2F0czIsDQogICAgbWFwcGluZyA9IGFlcyh4ID0gYm9keSwgeSA9IHN0YW5fcmVzKQ0KICApICsgDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gJ3JlZCcpICsNCiAgdGhlbWVfYncoKSArIA0KICBsYWJzKA0KICAgIHggPSAnQm9keSBXZWlnaHQgKGtnKScsIA0KICAgIHkgPSAnU3RhbmRhcmRpemVkIFJlc2lkdWFscycsDQogICAgdGl0bGUgPSAnUmVzaWR1YWwgcGxvdCBvZiBjYXRzIGhlYXJ0IHdlaWdodCBieSBib2R5IHdlaWdodCcNCiAgKSArDQogICMgQWRkaW5nIHRpY2sgbWFya3MgYXQgLTMsIC0yLCAtMSwgMCwgMSwgMiwgMyBvbiB0aGUgeS1heGlzDQogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSAoLTMpOjMsDQogICAgICAgICAgICAgICAgICAgICBtaW5vcl9icmVha3MgPSBOVUxMKQ0KDQpnZ19jYXRzX3N0YW5fcmVzcGxvdA0KYGBgDQoNClRvIGhlbHAgc2VlIGlmIGFuIG9ic2VydmF0aW9uIGZhbGxzIG91dHNpZGUgb2YgdGhlICgtMywgMykgcmFuZ2UsIHdlIGNhbiBhZGQgZG90dGVkIGxpbmVzIHVzaW5nIGBnZW9tX2hsaW5lKClgLCBsaWtlIHdlIGRpZCB3aXRoIHRoZSBsaW5lIGF0IDA6DQoNCmBgYHtyIGFkZF92ZXJ0X2xpbmV9DQpnZ19jYXRzX3N0YW5fcmVzcGxvdCArDQogIGdlb21faGxpbmUoDQogICAgeWludGVyY2VwdCA9IGMoLTMsIDMpLA0KICAgIGNvbG9yID0gJ3JlZCcsDQogICAgbGluZXR5cGUgPSAyICMgbWFrZXMgdGhlIGxpbmVzIGRhc2hlZCBpbnN0ZWFkIG9mIHNvbGlkDQogICkNCmBgYA0KDQpJdCBkb2VzIGxvb2sgbGlrZSBSYW5nZXIgaXMgYW4gb3V0bGllciENCg0KU28gd2hhdCBkbyB3ZSBkbz8NCg0KTGV0J3MgcmVtb3ZlIGhpbSBmcm9tIHRoZSBkYXRhLCByZWZpdCB0aGUgbW9kZWwsIGFuZCBzZWUgd2hhdCBjaGFuZ2VzOg0KDQoNCiMjIyBSZW1lZGlhbCBNZWFzdXJlOiBSZW1vdmluZyB0aGUgT3V0bGllcg0KDQpXZSdsbCB1c2UgYGZpbHRlcigpYCB0byByZW1vdmUgdGhlIG91dGxpZXIgYW5kIHN0YXJ0IG92ZXI6DQoNCmBgYHtyIHJlZG9fbm9fb3V0bGllcn0NCiMgUmVtb3ZpbmcgdGhlIG91dGxpZXINCmNhdHNfbm9fb3V0bGllciA8LSANCiAgY2F0cyB8PiANCiAgZmlsdGVyKGhlYXJ0IDwgMjApDQoNCiMgTmV4dDogRml0IHRoZSBtb2RlbA0KY2F0c19ub19sbSA8LSBsbShoZWFydCB+IGJvZHksIGNhdHNfbm9fb3V0bGllcikNCg0KIyBDaGVja2luZyB0aGUgbW9kZWwNCnRpZHkoY2F0c19ub19sbSkNCg0KYGBgDQoNCkl0IGRvZXMgbG9vayBsaWtlIHRoZSBzbG9wZSBjaGFuZ2VkIHF1aXRlIGEgYml0LCBmcm9tIDQuMDQgdG8gMy44NC4gVGhlIGh5cG90aGVzaXMgdGVzdCBkb2VzIHN0aWxsIHNob3cgdmVyeSBzdHJvbmcgZXZpZGVuY2Ugb2YgYSBsaW5lYXIgYXNzb2NpYXRpb24gYmV0d2VlbiBib2R5IGFuZCBoZWFydCB3ZWlnaHQuDQoNCkxldCdzIGNvbXBhcmUgdGhlIGZpdCBzdGF0aXN0aWNzOg0KDQpgYGB7ciBmaXRfc3RhdHNfbm9fb3V0bGllcn0NCmJpbmRfcm93cygNCiAgLmlkID0gJ2RhdGEnLA0KICAnd2l0aCBvdXRsaWVyJyA9IGdsYW5jZShjYXRzX2xtKSwgDQogICd3aXRob3V0IG91dGxpZXInID0gZ2xhbmNlKGNhdHNfbm9fbG0pDQopDQpgYGANCg0KVGhlIGZpdCBzdGF0aXN0aWNzIGJhcmVseSBjaGFuZ2VkISBTbyB3aGlsZSBSYW5nZXIgaXMgYW4gb3V0bGllciwgaGUgZG9lc24ndCBzZWVtIHRvIGJlIHRvbyBtdWNoIG9mIGFuIGluZmx1ZW50aWFsIG91dGxpZXIuDQoNCkxldCdzIGNvbXBhcmUgdGhlIGxpbmVzIGluIHRoZSBzY2F0dGVyIHBsb3Q6DQoNCmBgYHtyIHNjYXR0ZXJfd2l0aF9saW5lc30NCmdncGxvdCgNCiAgZGF0YSA9IGNhdHMsDQogIG1hcHBpbmcgPSBhZXMoeCA9IGJvZHksIHkgPSBoZWFydCkNCikgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIGdlb21fYWJsaW5lKA0KICAgIGludGVyY2VwdCA9IGNhdHNfbG0kY29lZlsxXSwgDQogICAgc2xvcGUgPSBjYXRzX2xtJGNvZWZbMl0sDQogICAgY29sb3IgPSAnYmx1ZScNCiAgKSArIA0KICBnZW9tX2FibGluZSgNCiAgICBpbnRlcmNlcHQgPSBjYXRzX25vX2xtJGNvZWZbMV0sIA0KICAgIHNsb3BlID0gY2F0c19ub19sbSRjb2VmWzJdLA0KICAgIGNvbG9yID0gJ3JlZCcNCiAgKQ0KYGBgDQoNClZpc3VhbGx5IHNwZWFraW5nLCB0aGUgdHdvIGxpbmVzIGFyZSB2ZXJ5LCB2ZXJ5IHNpbWlsYXIuDQoNCklmIHdlIGNoZWNrIHRoZSByZXN1bHRpbmcgcmVzaWR1YWwgcGxvdDoNCg0KYGBge3J9DQojIENhbGN1bGF0aW5nIHRoZSBzdGFuZGFyZGl6ZWQgcmVzaWR1YWxzIGZvciB0aGUgbm8gb3V0bGllciBkYXRhIHNldA0KY2F0c19ub19vdXRsaWVyIDwtIA0KICBjYXRzX25vX291dGxpZXIgfD4gDQogIG11dGF0ZSgNCiAgICBwcmVkX2hlYXJ0ID0gY2F0c19ub19sbSRmaXQsDQogICAgcmVzX2hlYXJ0ID0gaGVhcnQgLSBwcmVkX2hlYXJ0LA0KICAgIHN0YW5fcmVzID0gcmVzX2hlYXJ0IC8gc3VtbWFyeShjYXRzX25vX2xtKSRzaWdtYQ0KICApDQogIA0KIyBDcmVhdGluZyB0aGUgcmVzaWR1YWwgcGxvdCBmb3IgdGhlIGNhdHMgd2l0aG91dCBSYW5nZXINCmdnX2NhdHNfc3Rhbl9yZXNwbG90ICsNCiAgZ2VvbV9obGluZSgNCiAgICB5aW50ZXJjZXB0ID0gYygtMywgMyksDQogICAgbGluZXR5cGUgPSAyLA0KICAgIGNvbG9yID0gJ3JlZCcNCiAgKSArIA0KICBsYWJzKHRpdGxlID0gJ1Jlc2lkdWFsIFBsb3Qgd2l0aG91dCBSYW5nZXI6IHRoZSBjYXQgd2l0aCB0aGUgbGFyZ2UgaGVhcnQnKSArDQogICMgUmVwbGFjaW5nIHRoZSBkYXRhIHNldCB3aXRoIHRoZSBvbmUgd2l0aG91dCBhbnkgb3V0bGllcnMNCiAgY2F0c19ub19vdXRsaWVyDQpgYGANCg0KVGhlIHJlc2lkdWFsIHBsb3QgbG9va3MgZ3JlYXQhIE5vIGNsZWFyIHRyZW5kIChMaW5lYXIpLCB0aGVyZSdzIG5vIGZ1bm5lbCBwYXR0ZXJuIChFcXVhbCBTcHJlYWQpLg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=