Lab 03: Numerical Simulations: Simple Linear Regression

Feedback should be send to goran.milovanovic@datakolektiv.com. These notebooks accompany the Intro to Data Science: Non-Technical Background course 2020/21.


What do we want to do today?

We will simulate correlated variables and perform tons of linear regressions to inspect the distributions of linear coefficients and understand what is the standard error of the coefficient in linear regression.

0. Setup

library(tidyverse)
library(data.table)
library(MASS)
set.seed(9988)

1. Simulate correlated variables from a Multivariate Normal Distribution

Random draws from the Multivariate Normal Distribution in R can be performed with MASS:mvrnorm(). In the following chunk, meansVector defines a vector of the two variables’ means, and covMat is their variance-covariance matrix:

meansVector <- c(15, 25)
print(meansVector)
[1] 15 25
covMat <- matrix(
  c(0.6856935, 
    1.274315, 
    1.2743154, 
    3.116278),
  nrow = 2
  )
print(covMat)
          [,1]     [,2]
[1,] 0.6856935 1.274315
[2,] 1.2743150 3.116278

The covariance between the two variables is set to be 1.274315. Let’s take a sample of size 150 from the Multivariate Normal Distribution defined from meansVector and covMat:

mvn_sample <- as.data.frame(mvrnorm(n = 150,
                                    mu = meansVector,
                                    Sigma = covMat
                                    )
)
head(mvn_sample)

What is the observed correlation?

cor(mvn_sample$V1, 
    mvn_sample$V2)
[1] 0.873424

NOTE. We understand that meansVector and covMat are population parameters.

Linear regression: predict mvn_sample$V2 from mvn_sample$V1:

reg_model <- lm(V2 ~ V1,
                data = mvn_sample) 
summary(reg_model)

Call:
lm(formula = V2 ~ V1, data = mvn_sample)

Residuals:
    Min      1Q  Median      3Q     Max 
-2.0706 -0.6106 -0.0777  0.6661  2.6332 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -3.1800     1.3014  -2.444   0.0157 *  
V1            1.8831     0.0863  21.820   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.9181 on 148 degrees of freedom
Multiple R-squared:  0.7629,    Adjusted R-squared:  0.7613 
F-statistic: 476.1 on 1 and 148 DF,  p-value: < 2.2e-16

The coefficients are:

summary_reg_model <- summary(reg_model)
summary_reg_model$coefficients
             Estimate Std. Error   t value     Pr(>|t|)
(Intercept) -3.180016 1.30138920 -2.443555 1.571951e-02
V1           1.883055 0.08629802 21.820373 4.197941e-48

So the intercept is found at -3.180016 with a standard error of 1.30138920 while the estimate of the slope is 1.883055 with a standard error of 0.08629802; the respective t-tests against zero and the Type I Error probabilities are also reported.

Next we simulate 10,000 samples from the population parameters and each time perform a linear regression, recording the values of the parameter estimates:

mvn_samples <- function(meansVector, covMat) {
  sample <- mvrnorm(n = 150,
                    mu = meansVector,
                    Sigma = covMat)
  return(as.data.frame(sample))
}
lmSimulations <- lapply(1:10000, function(x) {
  newData <- mvn_samples(meansVector, covMat)
  model <- lm(V2 ~ V1, 
              data = newData)
  return(data.frame(intercept = coefficients(model)[1], 
                    slope = coefficients(model)[2]))
})
lmSimulations <- rbindlist(lmSimulations)
head(lmSimulations)

The distribution of the intercept estimates:

ggplot(lmSimulations, 
       aes(x = intercept)) + 
  geom_density(alpha = .15, color = "black") + 
  ggtitle("Distrubution of Model Intercept") + 
  xlab('Intercept') + 
  ylab('Density') + 
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5))

The estimated intercept from the first regression was found at -3.180016: no wonder the mean of this distribution falls close to three then, right?

mean(lmSimulations$intercept)
[1] -2.863263

The distribution of the slope estimates:

ggplot(lmSimulations, 
       aes(x = slope)) + 
  geom_density(alpha = .15, color = "black") + 
  ggtitle("Distrubution of Model Slope") + 
  xlab('Slope') + 
  ylab('Density') + 
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5))

mean(lmSimulations$slope)
[1] 1.857532

… while the initial regression estimated a slope of 1.883055!

And what about the standard errors? Initially, their values were 1.30138920 for the intercept and 0.08629802 for the slope. Let’s see:

sd(lmSimulations$intercept)
[1] 1.274814
sd(lmSimulations$slope)
[1] 0.0848986

Do you trust the Simple Linear Regression Model now? : ) Note. If you wonder why the mean(lmSimulation$intercept) is -2.863263 while in the initial regression it was estimated to be -3.180016: look at the standard errors of the model coefficients both in the initial regression, and their means obtained from numerical simulations.

R Markdown

R Markdown is what I have used to produce this beautiful Notebook. We will learn more about it near the end of the course, but if you already feel ready to dive deep, here’s a book: R Markdown: The Definitive Guide, Yihui Xie, J. J. Allaire, Garrett Grolemunds.


Goran S. Milovanović

DataKolektiv, 2020/21

contact:


License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.


LS0tDQp0aXRsZTogSW50cm8gdG8gRGF0YSBTY2llbmNlIChOb24tVGVjaG5pY2FsIEJhY2tncm91bmQsIFIpIC0gTGFiMDMNCmF1dGhvcjoNCi0gbmFtZTogR29yYW4gUy4gTWlsb3Zhbm92acSHLCBQaEQNCiAgYWZmaWxpYXRpb246IERhdGFLb2xla3RpdiwgQ2hpZWYgU2NpZW50aXN0ICYgT3duZXI7IERhdGEgU2NpZW50aXN0IGZvciBXaWtpZGF0YSwgV01ERQ0KYWJzdHJhY3Q6IA0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIHRoZW1lOiBzcGFjZWxhYg0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICB0b2NfZGVwdGg6IDUNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNQ0KLS0tDQoNCiFbXSguLi9faW1nL0RLX0xvZ29fMTAwLnBuZykNCg0KKioqDQojIExhYiAwMzogTnVtZXJpY2FsIFNpbXVsYXRpb25zOiBTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb24NCiANCioqRmVlZGJhY2sqKiBzaG91bGQgYmUgc2VuZCB0byBgZ29yYW4ubWlsb3Zhbm92aWNAZGF0YWtvbGVrdGl2LmNvbWAuIA0KVGhlc2Ugbm90ZWJvb2tzIGFjY29tcGFueSB0aGUgSW50cm8gdG8gRGF0YSBTY2llbmNlOiBOb24tVGVjaG5pY2FsIEJhY2tncm91bmQgY291cnNlIDIwMjAvMjEuDQoNCioqKg0KDQojIyMgV2hhdCBkbyB3ZSB3YW50IHRvIGRvIHRvZGF5Pw0KDQpXZSB3aWxsIHNpbXVsYXRlIGNvcnJlbGF0ZWQgdmFyaWFibGVzIGFuZCBwZXJmb3JtIHRvbnMgb2YgbGluZWFyIHJlZ3Jlc3Npb25zIHRvIGluc3BlY3QgdGhlIGRpc3RyaWJ1dGlvbnMgb2YgbGluZWFyIGNvZWZmaWNpZW50cyBhbmQgdW5kZXJzdGFuZCB3aGF0IGlzIHRoZSBzdGFuZGFyZCBlcnJvciBvZiB0aGUgY29lZmZpY2llbnQgaW4gbGluZWFyIHJlZ3Jlc3Npb24uIA0KDQojIyMgMC4gU2V0dXANCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShNQVNTKQ0Kc2V0LnNlZWQoOTk4OCkNCmBgYA0KDQojIyMgMS4gU2ltdWxhdGUgY29ycmVsYXRlZCB2YXJpYWJsZXMgZnJvbSBhIE11bHRpdmFyaWF0ZSBOb3JtYWwgRGlzdHJpYnV0aW9uDQoNClJhbmRvbSBkcmF3cyBmcm9tIHRoZSBbTXVsdGl2YXJpYXRlIE5vcm1hbCBEaXN0cmlidXRpb25dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL011bHRpdmFyaWF0ZV9ub3JtYWxfZGlzdHJpYnV0aW9uKSBpbiBSIGNhbiBiZSBwZXJmb3JtZWQgd2l0aCBgTUFTUzptdnJub3JtKClgLiBJbiB0aGUgZm9sbG93aW5nIGNodW5rLCBgbWVhbnNWZWN0b3JgIGRlZmluZXMgYSB2ZWN0b3Igb2YgdGhlIHR3byB2YXJpYWJsZXMnIG1lYW5zLCBhbmQgYGNvdk1hdGAgaXMgdGhlaXIgdmFyaWFuY2UtY292YXJpYW5jZSBtYXRyaXg6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbWVhbnNWZWN0b3IgPC0gYygxNSwgMjUpDQpwcmludChtZWFuc1ZlY3RvcikNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCmNvdk1hdCA8LSBtYXRyaXgoDQogIGMoMC42ODU2OTM1LCANCiAgICAxLjI3NDMxNSwgDQogICAgMS4yNzQzMTU0LCANCiAgICAzLjExNjI3OCksDQogIG5yb3cgPSAyDQogICkNCnByaW50KGNvdk1hdCkNCmBgYA0KVGhlIGNvdmFyaWFuY2UgYmV0d2VlbiB0aGUgdHdvIHZhcmlhYmxlcyBpcyBzZXQgdG8gYmUgYDEuMjc0MzE1YC4NCkxldCdzIHRha2UgYSBzYW1wbGUgb2Ygc2l6ZSBgMTUwYCBmcm9tIHRoZSBNdWx0aXZhcmlhdGUgTm9ybWFsIERpc3RyaWJ1dGlvbiBkZWZpbmVkIGZyb20gYG1lYW5zVmVjdG9yYCBhbmQgYGNvdk1hdGA6IA0KDQpgYGB7ciBlY2hvID0gVH0NCm12bl9zYW1wbGUgPC0gYXMuZGF0YS5mcmFtZShtdnJub3JtKG4gPSAxNTAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdSA9IG1lYW5zVmVjdG9yLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU2lnbWEgPSBjb3ZNYXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkNCikNCmhlYWQobXZuX3NhbXBsZSkNCmBgYA0KDQpXaGF0IGlzIHRoZSBvYnNlcnZlZCBjb3JyZWxhdGlvbj8NCg0KYGBge3IgZWNobyA9IFR9DQpjb3IobXZuX3NhbXBsZSRWMSwgDQogICAgbXZuX3NhbXBsZSRWMikNCmBgYA0KKipOT1RFLioqIFdlIHVuZGVyc3RhbmQgdGhhdCBgbWVhbnNWZWN0b3JgIGFuZCBgY292TWF0YCBhcmUgKipwb3B1bGF0aW9uIHBhcmFtZXRlcnMqKi4NCg0KTGluZWFyIHJlZ3Jlc3Npb246IHByZWRpY3QgYG12bl9zYW1wbGUkVjJgIGZyb20gYG12bl9zYW1wbGUkVjFgOg0KDQpgYGB7ciBlY2hvID0gVH0NCnJlZ19tb2RlbCA8LSBsbShWMiB+IFYxLA0KICAgICAgICAgICAgICAgIGRhdGEgPSBtdm5fc2FtcGxlKSANCnN1bW1hcnkocmVnX21vZGVsKQ0KYGBgDQpUaGUgY29lZmZpY2llbnRzIGFyZToNCg0KYGBge3IgZWNobyA9IFR9DQpzdW1tYXJ5X3JlZ19tb2RlbCA8LSBzdW1tYXJ5KHJlZ19tb2RlbCkNCnN1bW1hcnlfcmVnX21vZGVsJGNvZWZmaWNpZW50cw0KYGBgDQpTbyB0aGUgaW50ZXJjZXB0IGlzIGZvdW5kIGF0IGAtMy4xODAwMTZgIHdpdGggYSBzdGFuZGFyZCBlcnJvciBvZiBgMS4zMDEzODkyMGAgd2hpbGUgdGhlIGVzdGltYXRlIG9mIHRoZSBzbG9wZSBpcyBgMS44ODMwNTVgIHdpdGggYSBzdGFuZGFyZCBlcnJvciBvZiBgMC4wODYyOTgwMmA7IHRoZSByZXNwZWN0aXZlIHQtdGVzdHMgYWdhaW5zdCB6ZXJvIGFuZCB0aGUgVHlwZSBJIEVycm9yIHByb2JhYmlsaXRpZXMgYXJlIGFsc28gcmVwb3J0ZWQuDQoNCk5leHQgd2Ugc2ltdWxhdGUgMTAsMDAwIHNhbXBsZXMgZnJvbSB0aGUgcG9wdWxhdGlvbiBwYXJhbWV0ZXJzIGFuZCBlYWNoIHRpbWUgcGVyZm9ybSBhIGxpbmVhciByZWdyZXNzaW9uLCByZWNvcmRpbmcgdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVyIGVzdGltYXRlczoNCg0KYGBge3IgZWNobyA9IFR9DQptdm5fc2FtcGxlcyA8LSBmdW5jdGlvbihtZWFuc1ZlY3RvciwgY292TWF0KSB7DQogIHNhbXBsZSA8LSBtdnJub3JtKG4gPSAxNTAsDQogICAgICAgICAgICAgICAgICAgIG11ID0gbWVhbnNWZWN0b3IsDQogICAgICAgICAgICAgICAgICAgIFNpZ21hID0gY292TWF0KQ0KICByZXR1cm4oYXMuZGF0YS5mcmFtZShzYW1wbGUpKQ0KfQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBUfQ0KbG1TaW11bGF0aW9ucyA8LSBsYXBwbHkoMToxMDAwMCwgZnVuY3Rpb24oeCkgew0KICBuZXdEYXRhIDwtIG12bl9zYW1wbGVzKG1lYW5zVmVjdG9yLCBjb3ZNYXQpDQogIG1vZGVsIDwtIGxtKFYyIH4gVjEsIA0KICAgICAgICAgICAgICBkYXRhID0gbmV3RGF0YSkNCiAgcmV0dXJuKGRhdGEuZnJhbWUoaW50ZXJjZXB0ID0gY29lZmZpY2llbnRzKG1vZGVsKVsxXSwgDQogICAgICAgICAgICAgICAgICAgIHNsb3BlID0gY29lZmZpY2llbnRzKG1vZGVsKVsyXSkpDQp9KQ0KbG1TaW11bGF0aW9ucyA8LSByYmluZGxpc3QobG1TaW11bGF0aW9ucykNCmhlYWQobG1TaW11bGF0aW9ucykNCmBgYA0KDQpUaGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBpbnRlcmNlcHQgZXN0aW1hdGVzOg0KDQpgYGB7ciBlY2hvID0gVH0NCmdncGxvdChsbVNpbXVsYXRpb25zLCANCiAgICAgICBhZXMoeCA9IGludGVyY2VwdCkpICsgDQogIGdlb21fZGVuc2l0eShhbHBoYSA9IC4xNSwgY29sb3IgPSAiYmxhY2siKSArIA0KICBnZ3RpdGxlKCJEaXN0cnVidXRpb24gb2YgTW9kZWwgSW50ZXJjZXB0IikgKyANCiAgeGxhYignSW50ZXJjZXB0JykgKyANCiAgeWxhYignRGVuc2l0eScpICsgDQogIHRoZW1lX2J3KCkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSkpDQpgYGANClRoZSBlc3RpbWF0ZWQgaW50ZXJjZXB0IGZyb20gdGhlIGZpcnN0IHJlZ3Jlc3Npb24gd2FzIGZvdW5kIGF0IGAtMy4xODAwMTZgOiBubyB3b25kZXIgdGhlIG1lYW4gb2YgdGhpcyBkaXN0cmlidXRpb24gZmFsbHMgY2xvc2UgdG8gdGhyZWUgdGhlbiwgcmlnaHQ/DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbWVhbihsbVNpbXVsYXRpb25zJGludGVyY2VwdCkNCmBgYA0KVGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgc2xvcGUgZXN0aW1hdGVzOg0KDQpgYGB7ciBlY2hvID0gVH0NCmdncGxvdChsbVNpbXVsYXRpb25zLCANCiAgICAgICBhZXMoeCA9IHNsb3BlKSkgKyANCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gLjE1LCBjb2xvciA9ICJibGFjayIpICsgDQogIGdndGl0bGUoIkRpc3RydWJ1dGlvbiBvZiBNb2RlbCBTbG9wZSIpICsgDQogIHhsYWIoJ1Nsb3BlJykgKyANCiAgeWxhYignRGVuc2l0eScpICsgDQogIHRoZW1lX2J3KCkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSkpDQpgYGANCg0KYGBge3IgZWNobyA9IFR9DQptZWFuKGxtU2ltdWxhdGlvbnMkc2xvcGUpDQpgYGANCi4uLiB3aGlsZSB0aGUgaW5pdGlhbCByZWdyZXNzaW9uIGVzdGltYXRlZCBhIHNsb3BlIG9mIGAxLjg4MzA1NWAhDQoNCkFuZCB3aGF0IGFib3V0IHRoZSBzdGFuZGFyZCBlcnJvcnM/IEluaXRpYWxseSwgdGhlaXIgdmFsdWVzIHdlcmUgYDEuMzAxMzg5MjBgIGZvciB0aGUgaW50ZXJjZXB0IGFuZCBgMC4wODYyOTgwMmAgZm9yIHRoZSBzbG9wZS4NCkxldCdzIHNlZToNCg0KYGBge3IgZWNobyA9IFR9DQpzZChsbVNpbXVsYXRpb25zJGludGVyY2VwdCkNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCnNkKGxtU2ltdWxhdGlvbnMkc2xvcGUpDQpgYGANCkRvIHlvdSB0cnVzdCB0aGUgU2ltcGxlIExpbmVhciBSZWdyZXNzaW9uIE1vZGVsIG5vdz8gOiApDQoqKk5vdGUuKiogSWYgeW91IHdvbmRlciB3aHkgdGhlIGBtZWFuKGxtU2ltdWxhdGlvbiRpbnRlcmNlcHQpYCBpcyBgLTIuODYzMjYzYCB3aGlsZSBpbiB0aGUgaW5pdGlhbCByZWdyZXNzaW9uIGl0IHdhcyBlc3RpbWF0ZWQgdG8gYmUgYC0zLjE4MDAxNmA6ICoqbG9vayBhdCB0aGUgc3RhbmRhcmQgZXJyb3JzIG9mIHRoZSBtb2RlbCBjb2VmZmljaWVudHMgYm90aCBpbiB0aGUgaW5pdGlhbCByZWdyZXNzaW9uLCBhbmQgdGhlaXIgbWVhbnMgb2J0YWluZWQgZnJvbSBudW1lcmljYWwgc2ltdWxhdGlvbnMqKi4gDQoNCiMjIyBSIE1hcmtkb3duDQoNCltSIE1hcmtkb3duXShodHRwczovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbS8pIGlzIHdoYXQgSSBoYXZlIHVzZWQgdG8gcHJvZHVjZSB0aGlzIGJlYXV0aWZ1bCBOb3RlYm9vay4gV2Ugd2lsbCBsZWFybiBtb3JlIGFib3V0IGl0IG5lYXIgdGhlIGVuZCBvZiB0aGUgY291cnNlLCBidXQgaWYgeW91IGFscmVhZHkgZmVlbCByZWFkeSB0byBkaXZlIGRlZXAsIGhlcmUncyBhIGJvb2s6IFtSIE1hcmtkb3duOiBUaGUgRGVmaW5pdGl2ZSBHdWlkZSwgWWlodWkgWGllLCBKLiBKLiBBbGxhaXJlLCBHYXJyZXR0IEdyb2xlbXVuZHMuXShodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ybWFya2Rvd24vKSANCg0KDQoqKioNCkdvcmFuIFMuIE1pbG92YW5vdmnEhw0KDQpEYXRhS29sZWt0aXYsIDIwMjAvMjENCg0KY29udGFjdDogZ29yYW4ubWlsb3Zhbm92aWNAZGF0YWtvbGVrdGl2LmNvbQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KTGljZW5zZTogW0dQTHYzXShodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvZ3BsLTMuMC50eHQpDQpUaGlzIE5vdGVib29rIGlzIGZyZWUgc29mdHdhcmU6IHlvdSBjYW4gcmVkaXN0cmlidXRlIGl0IGFuZC9vciBtb2RpZnkgaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkgdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiwgZWl0aGVyIHZlcnNpb24gMyBvZiB0aGUgTGljZW5zZSwgb3IgKGF0IHlvdXIgb3B0aW9uKSBhbnkgbGF0ZXIgdmVyc2lvbi4NClRoaXMgTm90ZWJvb2sgaXMgZGlzdHJpYnV0ZWQgaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwgYnV0IFdJVEhPVVQgQU5ZIFdBUlJBTlRZOyB3aXRob3V0IGV2ZW4gdGhlIGltcGxpZWQgd2FycmFudHkgb2YgTUVSQ0hBTlRBQklMSVRZIG9yIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFLiAgU2VlIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLg0KWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgYWxvbmcgd2l0aCB0aGlzIE5vdGVib29rLiBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uDQoNCioqKg0KDQo=