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:
- What is model-fitting?
- How do we fit a model in R?
- 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:
- A car weighs 4000 lbs. What can we say about its mpg?
- 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)")

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:
- Make predictions.
- 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:
- Linear Regression:
lm
- Generalized Linear Regression:
glm
- Local regression:
loess
- Quantile regression:
quantreg::rq
- …
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:
- Fit a linear regression model to life expectancy (“Y”) from year (“X”) by filling in the formula. Notice what appears as the output.
- 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
Now, using the lm()
function we will create the linear model
my_lm
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 7 8 9 10
-0.38012821 -0.05263520 0.33485781 0.18235082 -0.18015618 0.07733683 -0.05517016 0.20232284 0.12981585 0.11730886
11 12
-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 10 11 12
67.79013 68.98264 70.17514 71.36765 72.56016 73.75266 74.94517 76.13768 77.33018 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:

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




Or we can predict on a new dataset:
predict(my_lm,years1)
1 2 3 4 5 6
79.2382 79.4767 79.7152 79.9537 80.1922 80.4307
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!):

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

Let’s try fitting a linear model to this relationship

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

lm_linear <- lm(data = gapminder,formula = FILL_THIS_IN)
lm_poly <- lm(data = gapminder,formula = FILL_THIS_IN))
anova
lets you compare between different models.
anova(lm_linear,lm_poly)
Regression with categorical variables
(lm_cat <- lm(gdpPercap ~ I(year - 1952) + continent, data = gapminder))
How did R know that continent was a categorical variable?
class(gapminder$continent)
levels(gapminder$continent)
contrasts(gapminder$continent)
How can we change the reference level?
gapminder$continent <- relevel(gapminder$continent, ref = "Oceania")
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.
tidy
: extract statistical summaries about each component of the model.
- Useful for interpretation task.
augment
: add columns to the original data frame, giving information corresponding to each row.
- Useful for prediction task.
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?
LS0tCnRpdGxlOiAiY20wMTQgV29ya3NoZWV0OiBUaGUgTW9kZWwtRml0dGluZyBQYXJhZGlnbSBpbiBSIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgpgYGB7cn0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkodGlkeXZlcnNlKSkKbGlicmFyeShnYXBtaW5kZXIpCmxpYnJhcnkoYnJvb20pCmBgYAoKU28geW91IHdhbnQgdG8gZml0IGEgbW9kZWwgdG8geW91ciBkYXRhLiBIb3cgY2FuIHlvdSBhY2hpZXZlIHRoaXMgd2l0aCBSPwoKVG9waWNzOgoKMS4gV2hhdCBfaXNfIG1vZGVsLWZpdHRpbmc/CjIuIEhvdyBkbyB3ZSBmaXQgYSBtb2RlbCBpbiBSPwozLiBIb3cgY2FuIHdlIG9idGFpbiB0aWR5IHJlc3VsdHMgZnJvbSB0aGUgbW9kZWwgb3V0cHV0PwoKIyMgV2hhdCBpcyBNb2RlbC1GaXR0aW5nPwoKV2hlbiB2YXJpYWJsZXMgYXJlIG5vdCBpbmRlcGVuZGVudCwgdGhlbiB3ZSBjYW4gZ2FpbiBpbmZvcm1hdGlvbiBhYm91dCBvbmUgdmFyaWFibGUgaWYgd2Uga25vdyBzb21ldGhpbmcgYWJvdXQgdGhlIG90aGVyLgoKRXhhbXBsZXM6IFVzZSB0aGUgc2NhdHRlcnBsb3QgYmVsb3c6CgoxLiBBIGNhciB3ZWlnaHMgNDAwMCBsYnMuIFdoYXQgY2FuIHdlIHNheSBhYm91dCBpdHMgbXBnPwoyLiBBIGNhciB3ZWlnaHRzIGxlc3MgdGhhbiAzMDAwIGxicy4gV2hhdCBjYW4gd2Ugc2F5IGFib3V0IGl0cyBtcGc/CgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9M30KbGlicmFyeSh0aWR5dmVyc2UpCmdncGxvdChtdGNhcnMsIGFlcyh3dCwgbXBnKSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh4ID0gIldlaWdodCAoMTAwMCdzIG9mIGxicykiKQpgYGAKCkV4YW1wbGU6IFdoYXQgY2FuIHdlIHNheSBhYm91dCByZWFyIGF4bGUgcmF0aW8gaWYgd2Uga25vdyBzb21ldGhpbmcgYWJvdXQgcXVhcnRlciBtaWxlIHRpbWU/CgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9M30KZ2dwbG90KG10Y2FycywgYWVzKHFzZWMsIGRyYXQpKSArIAogIGdlb21fcG9pbnQoKSArCiAgbGFicyh4ID0gIlF1YXJ0ZXIgbWlsZSB0aW1lIiwKICAgICAgIHkgPSAiUmVhciBheGxlIHJhdGlvIikKYGBgCgoKSWYgRURBIGlzbid0IGVub3VnaCwgd2UgY2FuIGFuc3dlciB0aGVzZSBxdWVzdGlvbnMgYnkgZml0dGluZyBhIG1vZGVsOiBhIGN1cnZlIHRoYXQgcHJlZGljdHMgWSBnaXZlbiBYLiBBa2EsIGEgX19yZWdyZXNzaW9uIGN1cnZlX18gb3IgYSBfX21hY2hpbmUgbGVhcm5pbmcgbW9kZWxfXy4gCgooVGhlcmUgYXJlIG1vcmUgY29tcHJlaGVuc2l2ZSBtb2RlbHMgdG9vLCBzdWNoIGFzIG1vZGVsbGluZyBlbnRpcmUgZGlzdHJpYnV0aW9ucywgYnV0IHRoYXQncyBub3Qgd2hhdCB3ZSdyZSBkb2luZyBoZXJlKQoKVGhlcmUgYXJlIHR5cGljYWxseSB0d28gZ29hbHMgb2YgZml0dGluZyBhIG1vZGVsOgoKMS4gTWFrZSBwcmVkaWN0aW9ucy4KMi4gSW50ZXJwcmV0IHZhcmlhYmxlIHJlbGF0aW9uc2hpcHMuCgojIyBGaXR0aW5nIGEgbW9kZWwgaW4gUgoKTW9kZWwgZml0dGluZyBtZXRob2RzIHRlbmQgdG8gdXNlIGEgY29tbW9uIGZvcm1hdCBpbiBSOgoKYGBgCm1ldGhvZChmb3JtdWxhLCBkYXRhLCBvcHRpb25zKQpgYGAKClRoZXkgYWxzbyB0ZW5kIHRvIGhhdmUgYSBjb21tb24gb3V0cHV0OiBhIHNwZWNpYWwgX2xpc3RfLiAKCl9fTWV0aG9kX186CgpBIGZ1bmN0aW9uIHN1Y2ggYXM6CgotIExpbmVhciBSZWdyZXNzaW9uOiBgbG1gCi0gR2VuZXJhbGl6ZWQgTGluZWFyIFJlZ3Jlc3Npb246IGBnbG1gCi0gTG9jYWwgcmVncmVzc2lvbjogYGxvZXNzYAotIFF1YW50aWxlIHJlZ3Jlc3Npb246IGBxdWFudHJlZzo6cnFgCi0gLi4uCgpfX0Zvcm11bGFfXzoKCkluIFIsIHRha2VzIHRoZSBmb3JtIGB5IH4geDEgKyB4MiArIC4uLiArIHhwYCAodXNlIGNvbHVtbiBuYW1lcyBpbiB5b3VyIGRhdGEgZnJhbWUpLgoKX19EYXRhX186IFRoZSBkYXRhIGZyYW1lLgoKX19PcHRpb25zX186IFNwZWNpZmljIHRvIHRoZSBtZXRob2QuCgpFeGVyY2lzZToKCjEuIEZpdCBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHRvIGxpZmUgZXhwZWN0YW5jeSAoIlkiKSBmcm9tIHllYXIgKCJYIikgYnkgZmlsbGluZyBpbiB0aGUgZm9ybXVsYS4gTm90aWNlIHdoYXQgYXBwZWFycyBhcyB0aGUgb3V0cHV0LgoyLiBPbiBhIG5ldyBsaW5lLCB1c2UgdGhlIGB1bmNsYXNzYCBmdW5jdGlvbiB0byB1bmNvdmVyIHRoZSBvYmplY3QncyB0cnVlIG5hdHVyZTogYSBsaXN0LiBOb3RlOiBpdCBtaWdodCBiZSBlYXNpZXIgdG8gdXNlIHRoZSBgbmFtZXNgIGZ1bmN0aW9uIHRvIHNlZSB3aGF0IGNvbXBvbmVudHMgYXJlIGluY2x1ZGVkIGluIHRoZSBsaXN0LiAKCkZpcnN0LCBjcmVhdGUgYSBzdWJzZXQgb2YgdGhlIGBnYXBtaW5kZXJgIGRhdGFzZXQgY29udGFpbmluZyBvbmx5IHRoZSBjb3VudHJ5IG9mIGBGcmFuY2UKYGBge3J9CmdhcG1pbmRlcl9GcmFuY2UgPC0gZ2FwbWluZGVyICU+JSAKICAgZmlsdGVyKGNvdW50cnkgPT0gIkZyYW5jZSIpCmdhcG1pbmRlcl9GcmFuY2UKYGBgCgpOb3csIHVzaW5nIHRoZSBgbG0oKWAgZnVuY3Rpb24gd2Ugd2lsbCBjcmVhdGUgdGhlIGxpbmVhciBtb2RlbApgYGB7cn0KbXlfbG0gPC0gbG0obGlmZUV4cCB+IHllYXIsIGdhcG1pbmRlcl9GcmFuY2UpCm15X2xtCmBgYApEb2VzIHRoYXQgbWVhbiB0aGF0IHRoZSBsaWZlIGV4cGVjdGVuY3kgYXQgInllYXIgMCIgd2FzIGVxdWFsIHRvIC0zOTcuNzY0Nj8hCldlIGFyZSBpbnRlcmVzdGVkIGluIHRoZSBtb2RlbGluZyByZXN1bHRzIGFyb3VuZCB0aGUgbW9kZWxpbmcgcGVyaW9kIHdoaWNoIHN0YXJ0cyBhdCB5ZWFyIDE5NTIuIFRvIGdldCBhIG1lYW5pbmluZ2Z1bCAiaW50ZXJwcmV0YWJsZSIgaW50ZXJjZXB0IHdlIGNhbiB1c2UgdGhlIGBJKClgIGZ1bmN0aW9uLgpgYGB7cn0KbXlfbG0gPC0gbG0obGlmZUV4cCB+IEkoeWVhci0xOTUyKSwgZGF0YSA9IGdhcG1pbmRlcl9GcmFuY2UpCm15X2xtCmBgYAoKVXNlIHRoZSBgdW5jbGFzcygpYCBmdW5jdGlvbiB0byB0YWtlIGEgbG9vayBhdCBob3cgdGhlIGBsbSgpYCBvYmplY3QgYWN0dWFsbHkgbG9va3MgbGlrZS4KYGBge3J9CnVuY2xhc3MobXlfbG0pCmBgYAoKVG8gY29tcGxpY2F0ZSB0aGluZ3MgZnVydGhlciwgc29tZSBpbmZvIGlzIHN0b3JlZCBpbiBfYW5vdGhlcl8gbGlzdCBhZnRlciBhcHBseWluZyB0aGUgYHN1bW1hcnlgIGZ1bmN0aW9uOgoKYGBge3J9CnN1bW1hcnkobXlfbG0pCmxtX3Jlc2lkIDwtIGF1Z21lbnQobXlfbG0pCmdncGxvdChsbV9yZXNpZCwgYWVzKC5yZXNpZCkpICsgCiAgZ2VvbV9mcmVxcG9seShiaW53aWR0aCA9IDAuNSkKYGBgCgpXZSBjYW4gdXNlIHRoZSBgcHJlZGljdCgpYCBmdW5jdGlvbiB0byBtYWtlIHByZWRpY3Rpb25zIGZyb20gdGhlIG1vZGVsIChkZWZhdWx0IGlzIHRvIHVzZSBmaXR0aW5nL3RyYWluaW5nIGRhdGEpLiBIZXJlIGFyZSB0aGUgcHJlZGljdGlvbnM6CgpgYGB7cn0KZ2FwbWluZGVyX0ZyYW5jZQpwcmVkaWN0KG15X2xtKSAlPiUgCiAgaGVhZCgpCnBsb3QobXlfbG0pCmBgYApPciB3ZSBjYW4gcHJlZGljdCBvbiBhIG5ldyBkYXRhc2V0OgpgYGB7cn0KeWVhcnMxID0gZGF0YS5mcmFtZSh5ZWFyID0gc2VxKDIwMDAsIDIwMDUpKQpwcmVkaWN0KG15X2xtLHllYXJzMSkKYGBgCgoKCldlIGNhbiBwbG90IG1vZGVscyAod2l0aCBvbmUgcHJlZGljdG9yLyBYIHZhcmlhYmxlKSB1c2luZyBgZ2dwbG90MmAgdGhyb3VnaCB0aGUgYGdlb21fc21vb3RoKClgIGxheWVyLiBTcGVjaWZ5aW5nIGBtZXRob2Q9ImxtImAgZ2l2ZXMgdXMgdGhlIGxpbmVhciByZWdyZXNzaW9uIGZpdCAoYnV0IG9ubHkgdmlzdWFsbHkhKToKCmBgYHtyfQpnZ3Bsb3QoZ2FwbWluZGVyLCBhZXMoZ2RwUGVyY2FwLCBsaWZlRXhwKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLCBzZSA9IEYpICsKICAgIHNjYWxlX3hfbG9nMTAoKQpgYGAKTGV0cyBjb25zaWRlciBhbm90aGVyIGNvdW50cnkgIlppbWJhYndlIiwgd2hpY2ggaGFzIGEgdW5pcXVlIGJlaGF2aW9yIGluIHRoZSBgbGlmZUV4cGAgYW5kIGB5ZWFyYCByZWxhdGlvbnNoaXAuCmBgYHtyfQpnYXBtaW5kZXJfWmltYmFid2UgPC0gZ2FwbWluZGVyICU+JSBmaWx0ZXIoY291bnRyeSA9PSAiWmltYmFid2UiKQpnYXBtaW5kZXJfWmltYmFid2UgJT4lIGdncGxvdChhZXMoeWVhciwgbGlmZUV4cCkpICsgZ2VvbV9wb2ludCgpCmBgYApMZXQncyB0cnkgZml0dGluZyBhIGxpbmVhciBtb2RlbCB0byB0aGlzIHJlbGF0aW9uc2hpcApgYGB7cn0KZ2dwbG90KGdhcG1pbmRlcl9aaW1iYWJ3ZSwgYWVzKHllYXIsbGlmZUV4cCkpICsgZ2VvbV9wb2ludCgpK2dlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRikKYGBgCk5vdyB3ZSB3aWxsIHRyeSB0byBmaXQgYSBzZWNvbmQgZGVncmVlIHBvbHlub21pYWwgYW5kIHNlZSB3aGF0IHdvdWxkIHRoYXQgbG9vayBsaWtlLgpgYGB7cn0KZ2dwbG90KGdhcG1pbmRlcl9aaW1iYWJ3ZSwgYWVzKHllYXIsIGxpZmVFeHApKSArIGdlb21fcG9pbnQoKStnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBmb3JtdWxhID0geSB+IHBvbHkoSSh4LTE5NTIpLCBkZWdyZWUgPSAyKSkKYGBgCgpgYGB7cn0KbG1fbGluZWFyIDwtIGxtKGRhdGEgPSBnYXBtaW5kZXIsZm9ybXVsYSA9IEZJTExfVEhJU19JTikKbG1fcG9seSA8LSBsbShkYXRhID0gZ2FwbWluZGVyLGZvcm11bGEgPSBGSUxMX1RISVNfSU4pKQpgYGAKYGFub3ZhYCBsZXRzIHlvdSBjb21wYXJlIGJldHdlZW4gZGlmZmVyZW50IG1vZGVscy4KYGBge3J9CmFub3ZhKGxtX2xpbmVhcixsbV9wb2x5KQpgYGAKIyMgUmVncmVzc2lvbiB3aXRoIGNhdGVnb3JpY2FsIHZhcmlhYmxlcwoKYGBge3J9CihsbV9jYXQgPC0gbG0oZ2RwUGVyY2FwIH4gSSh5ZWFyIC0gMTk1MikgKyBjb250aW5lbnQsIGRhdGEgPSBnYXBtaW5kZXIpKQpgYGAKSG93IGRpZCBSIGtub3cgdGhhdCBjb250aW5lbnQgd2FzIGEgY2F0ZWdvcmljYWwgdmFyaWFibGU/CmBgYHtyfQpjbGFzcyhnYXBtaW5kZXIkY29udGluZW50KQpsZXZlbHMoZ2FwbWluZGVyJGNvbnRpbmVudCkKY29udHJhc3RzKGdhcG1pbmRlciRjb250aW5lbnQpCmBgYApIb3cgY2FuIHdlIGNoYW5nZSB0aGUgcmVmZXJlbmNlIGxldmVsPwpgYGB7cn0KZ2FwbWluZGVyJGNvbnRpbmVudCA8LSByZWxldmVsKGdhcG1pbmRlciRjb250aW5lbnQsIHJlZiA9ICJPY2VhbmlhIikKYGBgCkxldCdzIGJ1aWxkIGEgbmV3IG1vZGVsCmBgYHtyfQpsbV9jYXQyIDwtIGxtKGdkcFBlcmNhcCB+IEkoeWVhciAtIDE5NTIpICsgY29udGluZW50LCBkYXRhID0gZ2FwbWluZGVyKQpgYGAKCgojIyBCcm9vbQoKTGV0J3MgbWFrZSBpdCBlYXNpZXIgdG8gZXh0cmFjdCBpbmZvLCB1c2luZyB0aGUgYGJyb29tYCBwYWNrYWdlLiBUaGVyZSBhcmUgdGhyZWUgY3Jvd24gZnVuY3Rpb25zIGluIHRoaXMgcGFja2FnZSwgYWxsIG9mIHdoaWNoIGlucHV0IGEgZml0dGVkIG1vZGVsLCBhbmQgb3V0cHV0cyBhIHRpZHkgZGF0YSBmcmFtZS4KCjEuIGB0aWR5YDogZXh0cmFjdCBzdGF0aXN0aWNhbCBzdW1tYXJpZXMgYWJvdXQgZWFjaCBjb21wb25lbnQgb2YgdGhlIG1vZGVsLgogICAgLSBVc2VmdWwgZm9yIF9pbnRlcnByZXRhdGlvbl8gdGFzay4KMi4gYGF1Z21lbnRgOiBhZGQgY29sdW1ucyB0byB0aGUgb3JpZ2luYWwgZGF0YSBmcmFtZSwgZ2l2aW5nIGluZm9ybWF0aW9uIGNvcnJlc3BvbmRpbmcgdG8gZWFjaCByb3cuCiAgICAtIFVzZWZ1bCBmb3IgX3ByZWRpY3Rpb25fIHRhc2suCjMuIGBnbGFuY2VgOiBleHRyYWN0IHN0YXRpc3RpY2FsIHN1bW1hcmllcyBhYm91dCB0aGUgbW9kZWwgYXMgYSB3aG9sZSAoMS1yb3cgdGliYmxlKS4KICAgIC0gVXNlZnVsIGZvciBjaGVja2luZyBnb29kbmVzcyBvZiBmaXQuCgpFeGVyY2lzZTogYXBwbHkgYWxsIHRocmVlIGZ1bmN0aW9ucyB0byBvdXIgZml0dGVkIG1vZGVsLCBgbXlfbG1gLiBXaGF0IGRvIHlvdSBzZWU/CgpgYGB7cn0KYGBg