suppressPackageStartupMessages(library(tidyverse))
library(gapminder)
library(broom)

So you want to fit a model to your data. How can you achieve this with R?

Topics:

  1. What is model-fitting?
  2. How do we fit a model in R?
  3. How can we obtain tidy results from the model output?

What is Model-Fitting?

When variables are not independent, then we can gain information about one variable if we know something about the other.

Examples: Use the scatterplot below:

  1. A car weighs 4000 lbs. What can we say about its mpg?
  2. A car weights less than 3000 lbs. What can we say about its mpg?
library(tidyverse)
ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  labs(x = "Weight (1000's of lbs)")


# models can fit many data points (using an 'averaged' data line is not always helpful)

Example: What can we say about rear axle ratio if we know something about quarter mile time?

ggplot(mtcars, aes(qsec, drat)) + 
  geom_point() +
  labs(x = "Quarter mile time",
       y = "Rear axle ratio")

If EDA isn’t enough, we can answer these questions by fitting a model: a curve that predicts Y given X. Aka, a regression curve or a machine learning model.

(There are more comprehensive models too, such as modelling entire distributions, but that’s not what we’re doing here)

There are typically two goals of fitting a model:

  1. Make predictions.
  2. Interpret variable relationships.

Fitting a model in R

Model fitting methods tend to use a common format in R:

method(formula, data, options)

They also tend to have a common output: a special list.

Method:

A function such as:

Formula:

In R, takes the form y ~ x1 + x2 + ... + xp (use column names in your data frame).

Data: The data frame.

Options: Specific to the method.

Exercise:

  1. Fit a linear regression model to life expectancy (“Y”) from year (“X”) by filling in the formula. Notice what appears as the output.
  2. On a new line, use the unclass function to uncover the object’s true nature: a list. Note: it might be easier to use the names function to see what components are included in the list.

First, create a subset of the gapminder dataset containing only the country of France

library(gapminder)
(gapminder_France <- gapminder %>%
   filter(country == "France"))

Now, using the lm() function we will create the linear model

(my_lm <- lm(lifeExp ~ year, gapminder_France))

Call:
lm(formula = lifeExp ~ year, data = gapminder_France)

Coefficients:
(Intercept)         year  
  -397.7646       0.2385  

Does that mean that the life expectency at “year 0” was equal to -397.7646?! We are interested in the modeling results around the modeling period which starts at year 1952. To get a meaniningful “interpretable” intercept we can use the I() function.

my_lm

Call:
lm(formula = lifeExp ~ I(year - 1952), data = gapminder_France)

Coefficients:
   (Intercept)  I(year - 1952)  
       67.7901          0.2385  

Use the unclass() function to take a look at how the lm() object actually looks like.

unclass(my_lm)
$coefficients
   (Intercept) I(year - 1952) 
    67.7901282      0.2385014 

$residuals
          1           2           3           4           5           6 
-0.38012821 -0.05263520  0.33485781  0.18235082 -0.18015618  0.07733683 
          7           8           9          10          11          12 
-0.05517016  0.20232284  0.12981585  0.11730886 -0.12519814 -0.25070513 

$effects
   (Intercept) I(year - 1952)                                              
 -257.55220231    14.26030956     0.41516662     0.26479522    -0.09557618 
                                                                           
    0.16405242     0.03368103     0.29330963     0.22293823     0.21256684 
                              
   -0.02780456    -0.15117596 

$rank
[1] 2

$fitted.values
       1        2        3        4        5        6        7        8        9 
67.79013 68.98264 70.17514 71.36765 72.56016 73.75266 74.94517 76.13768 77.33018 
      10       11       12 
78.52269 79.71520 80.90771 

$assign
[1] 0 1

$qr
$qr
   (Intercept) I(year - 1952)
1   -3.4641016   -95.26279442
2    0.2886751    59.79130372
3    0.2886751     0.18965544
4    0.2886751     0.10603124
5    0.2886751     0.02240704
6    0.2886751    -0.06121716
7    0.2886751    -0.14484136
8    0.2886751    -0.22846557
9    0.2886751    -0.31208977
10   0.2886751    -0.39571397
11   0.2886751    -0.47933817
12   0.2886751    -0.56296237
attr(,"assign")
[1] 0 1

$qraux
[1] 1.288675 1.273280

$pivot
[1] 1 2

$tol
[1] 1e-07

$rank
[1] 2

attr(,"class")
[1] "qr"

$df.residual
[1] 10

$xlevels
named list()

$call
lm(formula = lifeExp ~ I(year - 1952), data = gapminder_France)

$terms
lifeExp ~ I(year - 1952)
attr(,"variables")
list(lifeExp, I(year - 1952))
attr(,"factors")
               I(year - 1952)
lifeExp                     0
I(year - 1952)              1
attr(,"term.labels")
[1] "I(year - 1952)"
attr(,"order")
[1] 1
attr(,"intercept")
[1] 1
attr(,"response")
[1] 1
attr(,".Environment")
<environment: R_GlobalEnv>
attr(,"predvars")
list(lifeExp, I(year - 1952))
attr(,"dataClasses")
       lifeExp I(year - 1952) 
     "numeric"      "numeric" 

$model
NA

To complicate things further, some info is stored in another list after applying the summary function:

summary(my_lm)

Call:
lm(formula = lifeExp ~ I(year - 1952), data = gapminder_France)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.38013 -0.13894  0.01235  0.14295  0.33486 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)    67.79013    0.11949  567.33  < 2e-16 ***
I(year - 1952)  0.23850    0.00368   64.81 1.86e-14 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.22 on 10 degrees of freedom
Multiple R-squared:  0.9976,    Adjusted R-squared:  0.9974 
F-statistic:  4200 on 1 and 10 DF,  p-value: 1.863e-14

We can use the predict() function to make predictions from the model (default is to use fitting/training data). Here are the predictions:

# use predict() to predict for years that are within our year range but do not have explicit data points
(gapminder_France2 <- data.frame(year = seq(2000, 2005)))
predict(my_lm, newdata = gapminder_France2) %>% 
  head()
      1       2       3       4       5       6 
79.2382 79.4767 79.7152 79.9537 80.1922 80.4307 

Or we can predict on a new dataset:

# or use predict() to predict for years that are beyond the range of our dataset
# that is, extrapolate data using the model
years1 = data.frame(year = c(3000, 3005))
predict(my_lm,years1)
       1        2 
317.7396 318.9321 
plot(my_lm)

NA

We can plot models (with one predictor/ X variable) using ggplot2 through the geom_smooth() layer. Specifying method="lm" gives us the linear regression fit (but only visually!):

ggplot(gapminder, aes(gdpPercap, lifeExp)) +
    geom_point() +
    geom_smooth(method="lm") +
    scale_x_log10()

Lets consider another country “Zimbabwe”, which has a unique behavior in the lifeExp and year relationship.

gapminder_Zimbabwe <- gapminder %>%
  filter(country == "Zimbabwe")

gapminder_Zimbabwe %>% 
  ggplot(aes(year, lifeExp)) +
  geom_point()

Let’s try fitting a linear model to this relationship

ggplot(gapminder_Zimbabwe, aes(year,lifeExp)) + 
  geom_point() +
  geom_smooth(method = "lm", se = F)


# se means show confidence interval, since false, will not show

Now we will try to fit a second degree polynomial and see what would that look like.

ggplot(gapminder_Zimbabwe, aes(year, lifeExp)) + 
  geom_point()+
  geom_smooth(method = "lm", formula = y ~ poly(I(x - 1952), degree = 2))


# second degree polynomial, using degree = 2, after formula
lm_linear <- lm(data = gapminder,formula = lifeExp ~ I(year-1952))
lm_poly <- lm(data = gapminder,formula = lifeExp ~ poly(I(year-1952)))

anova lets you compare between different models.

anova(lm_linear,lm_poly)
Analysis of Variance Table

Model 1: lifeExp ~ I(year - 1952)
Model 2: lifeExp ~ poly(I(year - 1952))
  Res.Df    RSS Df  Sum of Sq F Pr(>F)
1   1702 230229                       
2   1702 230229  0 2.9104e-10         

Regression with categorical variables

(lm_cat <- lm(gdpPercap ~ I(year - 1952) + continent, data = gapminder))

Call:
lm(formula = gdpPercap ~ I(year - 1952) + continent, data = gapminder)

Coefficients:
      (Intercept)     I(year - 1952)  continentAmericas      continentAsia  
          -1375.3              129.8             4942.4             5708.4  
  continentEurope   continentOceania  
          12275.7            16427.9  

How did R know that continent was a categorical variable?

# factors are "categories"
class(gapminder$continent)
[1] "factor"
levels(gapminder$continent)
[1] "Africa"   "Americas" "Asia"     "Europe"   "Oceania" 
contrasts(gapminder$continent)
         Americas Asia Europe Oceania
Africa          0    0      0       0
Americas        1    0      0       0
Asia            0    1      0       0
Europe          0    0      1       0
Oceania         0    0      0       1

How can we change the reference level?

gapminder$continent <- relevel(gapminder$continent, ref = "Oceania")
contrasts(gapminder$continent)
         Africa Americas Asia Europe
Oceania       0        0    0      0
Africa        1        0    0      0
Americas      0        1    0      0
Asia          0        0    1      0
Europe        0        0    0      1

Let’s build a new model

lm_cat2 <- lm(gdpPercap ~ I(year - 1952) + continent, data = gapminder)

Broom

Let’s make it easier to extract info, using the broom package. There are three crown functions in this package, all of which input a fitted model, and outputs a tidy data frame.

  1. tidy: extract statistical summaries about each component of the model.
    • Useful for interpretation task.
  2. augment: add columns to the original data frame, giving information corresponding to each row.
    • Useful for prediction task.
  3. glance: extract statistical summaries about the model as a whole (1-row tibble).
    • Useful for checking goodness of fit.

Exercise: apply all three functions to our fitted model, my_lm. What do you see?

library(broom)
(lm_resid <- augment(my_lm))

ggplot(lm_resid, aes(.resid)) +
  geom_freqpoly(binwidth = 0.5)


(lm_resid <- glance(my_lm))
(lm_resid <- tidy(my_lm))
NA
NA
LS0tCnRpdGxlOiAnY20wMTQgV29ya3NoZWV0OiBUaGUgTW9kZWwtRml0dGluZyBQYXJhZGlnbSBpbiBSJwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiBwYXBlcgplZGl0b3Jfb3B0aW9uczoKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnYXBtaW5kZXIpCmxpYnJhcnkoYnJvb20pCmBgYAoKU28geW91IHdhbnQgdG8gZml0IGEgbW9kZWwgdG8geW91ciBkYXRhLiBIb3cgY2FuIHlvdSBhY2hpZXZlIHRoaXMgd2l0aCBSPwoKVG9waWNzOgoKMS4gV2hhdCBfaXNfIG1vZGVsLWZpdHRpbmc/CjIuIEhvdyBkbyB3ZSBmaXQgYSBtb2RlbCBpbiBSPwozLiBIb3cgY2FuIHdlIG9idGFpbiB0aWR5IHJlc3VsdHMgZnJvbSB0aGUgbW9kZWwgb3V0cHV0PwoKIyMgV2hhdCBpcyBNb2RlbC1GaXR0aW5nPwoKV2hlbiB2YXJpYWJsZXMgYXJlIG5vdCBpbmRlcGVuZGVudCwgdGhlbiB3ZSBjYW4gZ2FpbiBpbmZvcm1hdGlvbiBhYm91dCBvbmUgdmFyaWFibGUgaWYgd2Uga25vdyBzb21ldGhpbmcgYWJvdXQgdGhlIG90aGVyLgoKRXhhbXBsZXM6IFVzZSB0aGUgc2NhdHRlcnBsb3QgYmVsb3c6CgoxLiBBIGNhciB3ZWlnaHMgNDAwMCBsYnMuIFdoYXQgY2FuIHdlIHNheSBhYm91dCBpdHMgbXBnPwoyLiBBIGNhciB3ZWlnaHRzIGxlc3MgdGhhbiAzMDAwIGxicy4gV2hhdCBjYW4gd2Ugc2F5IGFib3V0IGl0cyBtcGc/CgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9Mywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMgbW9kZWxzIGNhbiBmaXQgbWFueSBkYXRhIHBvaW50cyAodXNpbmcgYW4gJ2F2ZXJhZ2VkJyBkYXRhIGxpbmUgaXMgbm90IGFsd2F5cyBoZWxwZnVsKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKZ2dwbG90KG10Y2FycywgYWVzKHd0LCBtcGcpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHggPSAiV2VpZ2h0ICgxMDAwJ3Mgb2YgbGJzKSIpCmBgYAoKRXhhbXBsZTogV2hhdCBjYW4gd2Ugc2F5IGFib3V0IHJlYXIgYXhsZSByYXRpbyBpZiB3ZSBrbm93IHNvbWV0aGluZyBhYm91dCBxdWFydGVyIG1pbGUgdGltZT8KCmBgYHtyLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD0zfQpnZ3Bsb3QobXRjYXJzLCBhZXMocXNlYywgZHJhdCkpICsgCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHggPSAiUXVhcnRlciBtaWxlIHRpbWUiLAogICAgICAgeSA9ICJSZWFyIGF4bGUgcmF0aW8iKQpgYGAKCgpJZiBFREEgaXNuJ3QgZW5vdWdoLCB3ZSBjYW4gYW5zd2VyIHRoZXNlIHF1ZXN0aW9ucyBieSBmaXR0aW5nIGEgbW9kZWw6IGEgY3VydmUgdGhhdCBwcmVkaWN0cyBZIGdpdmVuIFguIEFrYSwgYSBfX3JlZ3Jlc3Npb24gY3VydmVfXyBvciBhIF9fbWFjaGluZSBsZWFybmluZyBtb2RlbF9fLiAKCihUaGVyZSBhcmUgbW9yZSBjb21wcmVoZW5zaXZlIG1vZGVscyB0b28sIHN1Y2ggYXMgbW9kZWxsaW5nIGVudGlyZSBkaXN0cmlidXRpb25zLCBidXQgdGhhdCdzIG5vdCB3aGF0IHdlJ3JlIGRvaW5nIGhlcmUpCgpUaGVyZSBhcmUgdHlwaWNhbGx5IHR3byBnb2FscyBvZiBmaXR0aW5nIGEgbW9kZWw6CgoxLiBNYWtlIHByZWRpY3Rpb25zLgoyLiBJbnRlcnByZXQgdmFyaWFibGUgcmVsYXRpb25zaGlwcy4KCiMjIEZpdHRpbmcgYSBtb2RlbCBpbiBSCgpNb2RlbCBmaXR0aW5nIG1ldGhvZHMgdGVuZCB0byB1c2UgYSBjb21tb24gZm9ybWF0IGluIFI6CgpgYGAKbWV0aG9kKGZvcm11bGEsIGRhdGEsIG9wdGlvbnMpCmBgYAoKVGhleSBhbHNvIHRlbmQgdG8gaGF2ZSBhIGNvbW1vbiBvdXRwdXQ6IGEgc3BlY2lhbCBfbGlzdF8uIAoKX19NZXRob2RfXzoKCkEgZnVuY3Rpb24gc3VjaCBhczoKCi0gTGluZWFyIFJlZ3Jlc3Npb246IGBsbWAKLSBHZW5lcmFsaXplZCBMaW5lYXIgUmVncmVzc2lvbjogYGdsbWAKLSBMb2NhbCByZWdyZXNzaW9uOiBgbG9lc3NgCi0gUXVhbnRpbGUgcmVncmVzc2lvbjogYHF1YW50cmVnOjpycWAKLSAuLi4KCl9fRm9ybXVsYV9fOgoKSW4gUiwgdGFrZXMgdGhlIGZvcm0gYHkgfiB4MSArIHgyICsgLi4uICsgeHBgICh1c2UgY29sdW1uIG5hbWVzIGluIHlvdXIgZGF0YSBmcmFtZSkuCgpfX0RhdGFfXzogVGhlIGRhdGEgZnJhbWUuCgpfX09wdGlvbnNfXzogU3BlY2lmaWMgdG8gdGhlIG1ldGhvZC4KCkV4ZXJjaXNlOgoKMS4gRml0IGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdG8gbGlmZSBleHBlY3RhbmN5ICgiWSIpIGZyb20geWVhciAoIlgiKSBieSBmaWxsaW5nIGluIHRoZSBmb3JtdWxhLiBOb3RpY2Ugd2hhdCBhcHBlYXJzIGFzIHRoZSBvdXRwdXQuCjIuIE9uIGEgbmV3IGxpbmUsIHVzZSB0aGUgYHVuY2xhc3NgIGZ1bmN0aW9uIHRvIHVuY292ZXIgdGhlIG9iamVjdCdzIHRydWUgbmF0dXJlOiBhIGxpc3QuIE5vdGU6IGl0IG1pZ2h0IGJlIGVhc2llciB0byB1c2UgdGhlIGBuYW1lc2AgZnVuY3Rpb24gdG8gc2VlIHdoYXQgY29tcG9uZW50cyBhcmUgaW5jbHVkZWQgaW4gdGhlIGxpc3QuIAoKRmlyc3QsIGNyZWF0ZSBhIHN1YnNldCBvZiB0aGUgYGdhcG1pbmRlcmAgZGF0YXNldCBjb250YWluaW5nIG9ubHkgdGhlIGNvdW50cnkgb2YgYEZyYW5jZWAKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KGdhcG1pbmRlcikKKGdhcG1pbmRlcl9GcmFuY2UgPC0gZ2FwbWluZGVyICU+JQogICBmaWx0ZXIoY291bnRyeSA9PSAiRnJhbmNlIikpCmBgYAoKTm93LCB1c2luZyB0aGUgYGxtKClgIGZ1bmN0aW9uIHdlIHdpbGwgY3JlYXRlIHRoZSBsaW5lYXIgbW9kZWwKYGBge3J9CihteV9sbSA8LSBsbShsaWZlRXhwIH4geWVhciwgZ2FwbWluZGVyX0ZyYW5jZSkpCmBgYApEb2VzIHRoYXQgbWVhbiB0aGF0IHRoZSBsaWZlIGV4cGVjdGVuY3kgYXQgInllYXIgMCIgd2FzIGVxdWFsIHRvIC0zOTcuNzY0Nj8hCldlIGFyZSBpbnRlcmVzdGVkIGluIHRoZSBtb2RlbGluZyByZXN1bHRzIGFyb3VuZCB0aGUgbW9kZWxpbmcgcGVyaW9kIHdoaWNoIHN0YXJ0cyBhdCB5ZWFyIDE5NTIuIFRvIGdldCBhIG1lYW5pbmluZ2Z1bCAiaW50ZXJwcmV0YWJsZSIgaW50ZXJjZXB0IHdlIGNhbiB1c2UgdGhlIGBJKClgIGZ1bmN0aW9uLgpgYGB7cn0KIyB1c2UgSSgpIHRvIG1ha2UgaW50ZXJjZXB0IHNvICJiZWdpbm5pbmciIG9mIG91ciBkYXRhc2V0IGlzIDE5NTIgY29ycmVzcG9uZGluZyB0byAnMCcgb2YgdGhlIG1vZGVsCiMgdGhpcyBtYWtlcyBpdCBzbyBhbGwgdGhlIHllYXJzIGFyZSByZWxhdGl2ZSB0byBvdXIgZmlyc3QgeWVhciBvZiAxOTUyCihteV9sbSA8LSBsbShsaWZlRXhwIH4gSSh5ZWFyLTE5NTIpLCBnYXBtaW5kZXJfRnJhbmNlKSkKYGBgCgpVc2UgdGhlIGB1bmNsYXNzKClgIGZ1bmN0aW9uIHRvIHRha2UgYSBsb29rIGF0IGhvdyB0aGUgYGxtKClgIG9iamVjdCBhY3R1YWxseSBsb29rcyBsaWtlLgpgYGB7cn0KdW5jbGFzcyhteV9sbSkKYGBgCgpUbyBjb21wbGljYXRlIHRoaW5ncyBmdXJ0aGVyLCBzb21lIGluZm8gaXMgc3RvcmVkIGluIF9hbm90aGVyXyBsaXN0IGFmdGVyIGFwcGx5aW5nIHRoZSBgc3VtbWFyeWAgZnVuY3Rpb246CgpgYGB7cn0Kc3VtbWFyeShteV9sbSkKYGBgCgpXZSBjYW4gdXNlIHRoZSBgcHJlZGljdCgpYCBmdW5jdGlvbiB0byBtYWtlIHByZWRpY3Rpb25zIGZyb20gdGhlIG1vZGVsIChkZWZhdWx0IGlzIHRvIHVzZSBmaXR0aW5nL3RyYWluaW5nIGRhdGEpLiBIZXJlIGFyZSB0aGUgcHJlZGljdGlvbnM6CgpgYGB7cn0KIyB1c2UgcHJlZGljdCgpIHRvIHByZWRpY3QgZm9yIHllYXJzIHRoYXQgYXJlIHdpdGhpbiBvdXIgeWVhciByYW5nZSBidXQgZG8gbm90IGhhdmUgZXhwbGljaXQgZGF0YSBwb2ludHMKKGdhcG1pbmRlcl9GcmFuY2UyIDwtIGRhdGEuZnJhbWUoeWVhciA9IHNlcSgyMDAwLCAyMDA1KSkpCnByZWRpY3QobXlfbG0sIG5ld2RhdGEgPSBnYXBtaW5kZXJfRnJhbmNlMikgJT4lCiAgaGVhZCgpCmBgYApPciB3ZSBjYW4gcHJlZGljdCBvbiBhIG5ldyBkYXRhc2V0OgpgYGB7cn0KIyBvciB1c2UgcHJlZGljdCgpIHRvIHByZWRpY3QgZm9yIHllYXJzIHRoYXQgYXJlIGJleW9uZCB0aGUgcmFuZ2Ugb2Ygb3VyIGRhdGFzZXQKIyB0aGF0IGlzLCBleHRyYXBvbGF0ZSBkYXRhIHVzaW5nIHRoZSBtb2RlbAp5ZWFyczEgPSBkYXRhLmZyYW1lKHllYXIgPSBjKDMwMDAsIDMwMDUpKQpwcmVkaWN0KG15X2xtLHllYXJzMSkKCnBsb3QobXlfbG0pCgpgYGAKCgoKV2UgY2FuIHBsb3QgbW9kZWxzICh3aXRoIG9uZSBwcmVkaWN0b3IvIFggdmFyaWFibGUpIHVzaW5nIGBnZ3Bsb3QyYCB0aHJvdWdoIHRoZSBgZ2VvbV9zbW9vdGgoKWAgbGF5ZXIuIFNwZWNpZnlpbmcgYG1ldGhvZD0ibG0iYCBnaXZlcyB1cyB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gZml0IChidXQgb25seSB2aXN1YWxseSEpOgoKYGBge3J9CmdncGxvdChnYXBtaW5kZXIsIGFlcyhnZHBQZXJjYXAsIGxpZmVFeHApKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIpICsKICAgIHNjYWxlX3hfbG9nMTAoKQpgYGAKTGV0cyBjb25zaWRlciBhbm90aGVyIGNvdW50cnkgIlppbWJhYndlIiwgd2hpY2ggaGFzIGEgdW5pcXVlIGJlaGF2aW9yIGluIHRoZSBgbGlmZUV4cGAgYW5kIGB5ZWFyYCByZWxhdGlvbnNoaXAuCmBgYHtyfQpnYXBtaW5kZXJfWmltYmFid2UgPC0gZ2FwbWluZGVyICU+JQogIGZpbHRlcihjb3VudHJ5ID09ICJaaW1iYWJ3ZSIpCgpnYXBtaW5kZXJfWmltYmFid2UgJT4lIAogIGdncGxvdChhZXMoeWVhciwgbGlmZUV4cCkpICsKICBnZW9tX3BvaW50KCkKYGBgCkxldCdzIHRyeSBmaXR0aW5nIGEgbGluZWFyIG1vZGVsIHRvIHRoaXMgcmVsYXRpb25zaGlwCmBgYHtyfQojIHNlIG1lYW5zIHNob3cgY29uZmlkZW5jZSBpbnRlcnZhbCwgc2luY2UgZmFsc2UsIHdpbGwgbm90IHNob3cKZ2dwbG90KGdhcG1pbmRlcl9aaW1iYWJ3ZSwgYWVzKHllYXIsbGlmZUV4cCkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRikKYGBgCk5vdyB3ZSB3aWxsIHRyeSB0byBmaXQgYSBzZWNvbmQgZGVncmVlIHBvbHlub21pYWwgYW5kIHNlZSB3aGF0IHdvdWxkIHRoYXQgbG9vayBsaWtlLgpgYGB7cn0KIyBzZWNvbmQgZGVncmVlIHBvbHlub21pYWwsIHVzaW5nIGRlZ3JlZSA9IDIsIGFmdGVyIGZvcm11bGEKZ2dwbG90KGdhcG1pbmRlcl9aaW1iYWJ3ZSwgYWVzKHllYXIsIGxpZmVFeHApKSArIAogIGdlb21fcG9pbnQoKSsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBmb3JtdWxhID0geSB+IHBvbHkoSSh4IC0gMTk1MiksIGRlZ3JlZSA9IDIpKQpgYGAKCmBgYHtyfQpsbV9saW5lYXIgPC0gbG0oZGF0YSA9IGdhcG1pbmRlcixmb3JtdWxhID0gbGlmZUV4cCB+IEkoeWVhci0xOTUyKSkKbG1fcG9seSA8LSBsbShkYXRhID0gZ2FwbWluZGVyLGZvcm11bGEgPSBsaWZlRXhwIH4gcG9seShJKHllYXItMTk1MikpKQpgYGAKCmBhbm92YWAgbGV0cyB5b3UgY29tcGFyZSBiZXR3ZWVuIGRpZmZlcmVudCBtb2RlbHMuCgpgYGB7cn0KYW5vdmEobG1fbGluZWFyLGxtX3BvbHkpCmBgYAojIyBSZWdyZXNzaW9uIHdpdGggY2F0ZWdvcmljYWwgdmFyaWFibGVzCgpgYGB7cn0KKGxtX2NhdCA8LSBsbShnZHBQZXJjYXAgfiBJKHllYXIgLSAxOTUyKSArIGNvbnRpbmVudCwgZGF0YSA9IGdhcG1pbmRlcikpCmBgYApIb3cgZGlkIFIga25vdyB0aGF0IGNvbnRpbmVudCB3YXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZT8KYGBge3J9CiMgZmFjdG9ycyBhcmUgImNhdGVnb3JpZXMiCiMgdmlldyBjb250cmFzdHMgYmV0d2VlbiB0aGUgZmFjdG9ycwojIEFmcmljYSBoYXMgemVybyBzaW5jZSBpdCBpcyB0aGUgcmVmZXJlbmNlIGxldmVsCmNsYXNzKGdhcG1pbmRlciRjb250aW5lbnQpCmxldmVscyhnYXBtaW5kZXIkY29udGluZW50KQpjb250cmFzdHMoZ2FwbWluZGVyJGNvbnRpbmVudCkKYGBgCkhvdyBjYW4gd2UgY2hhbmdlIHRoZSByZWZlcmVuY2UgbGV2ZWw/CmBgYHtyfQojIE5vdyBPY2VhbmlhIGlzIHplcm8gc2luY2UgaXQgaXMgdGhlIHJlZmVyZW5jZSBsZXZlbApnYXBtaW5kZXIkY29udGluZW50IDwtIHJlbGV2ZWwoZ2FwbWluZGVyJGNvbnRpbmVudCwgcmVmID0gIk9jZWFuaWEiKQpjb250cmFzdHMoZ2FwbWluZGVyJGNvbnRpbmVudCkKYGBgCkxldCdzIGJ1aWxkIGEgbmV3IG1vZGVsCmBgYHtyfQpsbV9jYXQyIDwtIGxtKGdkcFBlcmNhcCB+IEkoeWVhciAtIDE5NTIpICsgY29udGluZW50LCBkYXRhID0gZ2FwbWluZGVyKQpgYGAKCgojIyBCcm9vbQoKTGV0J3MgbWFrZSBpdCBlYXNpZXIgdG8gZXh0cmFjdCBpbmZvLCB1c2luZyB0aGUgYGJyb29tYCBwYWNrYWdlLiBUaGVyZSBhcmUgdGhyZWUgY3Jvd24gZnVuY3Rpb25zIGluIHRoaXMgcGFja2FnZSwgYWxsIG9mIHdoaWNoIGlucHV0IGEgZml0dGVkIG1vZGVsLCBhbmQgb3V0cHV0cyBhIHRpZHkgZGF0YSBmcmFtZS4KCjEuIGB0aWR5YDogZXh0cmFjdCBzdGF0aXN0aWNhbCBzdW1tYXJpZXMgYWJvdXQgZWFjaCBjb21wb25lbnQgb2YgdGhlIG1vZGVsLgogICAgLSBVc2VmdWwgZm9yIF9pbnRlcnByZXRhdGlvbl8gdGFzay4KMi4gYGF1Z21lbnRgOiBhZGQgY29sdW1ucyB0byB0aGUgb3JpZ2luYWwgZGF0YSBmcmFtZSwgZ2l2aW5nIGluZm9ybWF0aW9uIGNvcnJlc3BvbmRpbmcgdG8gZWFjaCByb3cuCiAgICAtIFVzZWZ1bCBmb3IgX3ByZWRpY3Rpb25fIHRhc2suCjMuIGBnbGFuY2VgOiBleHRyYWN0IHN0YXRpc3RpY2FsIHN1bW1hcmllcyBhYm91dCB0aGUgbW9kZWwgYXMgYSB3aG9sZSAoMS1yb3cgdGliYmxlKS4KICAgIC0gVXNlZnVsIGZvciBjaGVja2luZyBnb29kbmVzcyBvZiBmaXQuCgpFeGVyY2lzZTogYXBwbHkgYWxsIHRocmVlIGZ1bmN0aW9ucyB0byBvdXIgZml0dGVkIG1vZGVsLCBgbXlfbG1gLiBXaGF0IGRvIHlvdSBzZWU/CgpgYGB7cn0KbGlicmFyeShicm9vbSkKKGxtX3Jlc2lkIDwtIGF1Z21lbnQobXlfbG0pKQoKZ2dwbG90KGxtX3Jlc2lkLCBhZXMoLnJlc2lkKSkgKwogIGdlb21fZnJlcXBvbHkoYmlud2lkdGggPSAwLjUpCgoobG1fcmVzaWQgPC0gZ2xhbmNlKG15X2xtKSkKKGxtX3Jlc2lkIDwtIHRpZHkobXlfbG0pKQpgYGA=