Loading the ISLR package. We would be using the dataset Wage

require(ISLR)
Loading required package: ISLR

Polynomial regression

With one response age, and we fit a 4 degree polynomial.

poly_fit <- lm(wage~poly(age, 4), data = Wage)
summary(poly_fit)

Call:
lm(formula = wage ~ poly(age, 4), data = Wage)

Residuals:
    Min      1Q  Median      3Q     Max 
-98.707 -24.626  -4.993  15.217 203.693 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)    111.7036     0.7287 153.283  < 2e-16 ***
poly(age, 4)1  447.0679    39.9148  11.201  < 2e-16 ***
poly(age, 4)2 -478.3158    39.9148 -11.983  < 2e-16 ***
poly(age, 4)3  125.5217    39.9148   3.145  0.00168 ** 
poly(age, 4)4  -77.9112    39.9148  -1.952  0.05104 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 39.91 on 2995 degrees of freedom
Multiple R-squared:  0.08626,   Adjusted R-squared:  0.08504 
F-statistic: 70.69 on 4 and 2995 DF,  p-value: < 2.2e-16

This looks to be significant up to 3 degrees at 0.05 level.

Plotting response

#Getting the range of age and create a sequence of ages in between to use as new_data for prediction
age_range <- range(Wage$age)
age_grid <- seq(from = age_range[1], to = age_range[2])
poly_fit_preds <- predict(poly_fit, newdata = list(age = age_grid), se = T)
poly_fit_se_bands <- cbind(poly_fit_preds$fit + 1.96 * poly_fit_preds$se, poly_fit_preds$fit - 1.96 * poly_fit_preds$se)
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, poly_fit_preds$fit, lwd = 2, col = "blue")
matlines(age_grid, poly_fit_se_bands, col = "blue", lty = 2)


Another way to achieve this in R, because there are so many packages and methods -

fit_age_2=lm(wage~age+I(age^2)+I(age^3)+I(age^4),data=Wage)
summary(fit_age_2)

Call:
lm(formula = wage ~ age + I(age^2) + I(age^3) + I(age^4), data = Wage)

Residuals:
    Min      1Q  Median      3Q     Max 
-98.707 -24.626  -4.993  15.217 203.693 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.842e+02  6.004e+01  -3.067 0.002180 ** 
age          2.125e+01  5.887e+00   3.609 0.000312 ***
I(age^2)    -5.639e-01  2.061e-01  -2.736 0.006261 ** 
I(age^3)     6.811e-03  3.066e-03   2.221 0.026398 *  
I(age^4)    -3.204e-05  1.641e-05  -1.952 0.051039 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 39.91 on 2995 degrees of freedom
Multiple R-squared:  0.08626,   Adjusted R-squared:  0.08504 
F-statistic: 70.69 on 4 and 2995 DF,  p-value: < 2.2e-16

But the fitted value remains the same. We should get a straight line when we compare this with our previous fit.

So, we see that only the first three degrees are significant and useful. But we tested that separately using a simple fit. This only works with a single variable. If we really need to compare two models (Is 1 degree model better than a 2 degree model), we need to use an anova. This can be used when there are multiple variables in the model!


In case of nested linear models, where one model is an addition to an existing model, we use anova like -

fit_edu <- lm(wage ~ education, data = Wage)
fit_edu_age <- lm(wage ~ education + age, data = Wage)
poly_fit_edu_age <- lm(wage ~ education + poly(age, 2), data = Wage)
poly_fit_edu_age3 <- lm(wage ~ education + poly(age, 3), data = Wage)

Anova test

H0 - The additional variable has no impact on the model (current model and reduced model are the same) Ha - The additional variable does impact the model

anova(fit_edu, fit_edu_age, poly_fit_edu_age, poly_fit_edu_age3)
Analysis of Variance Table

Model 1: wage ~ education
Model 2: wage ~ education + age
Model 3: wage ~ education + poly(age, 2)
Model 4: wage ~ education + poly(age, 3)
  Res.Df     RSS Df Sum of Sq        F Pr(>F)    
1   2995 3995721                                 
2   2994 3867992  1    127729 102.7378 <2e-16 ***
3   2993 3725395  1    142597 114.6969 <2e-16 ***
4   2992 3719809  1      5587   4.4936 0.0341 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

It is clear that the first two linear models are significant. A polynomial on age, with degree 2 is also significant. But the fourth, with age^3, is only just marginally significant at the 0.05 level. We may want to choose and remove this degree.


Polynomial Logistic Regression

We can also proceed to use the same function for a logistic regression. Here we create a binary response of wage based on the plot we see. Big earners with wage > 250k tagged as 1, and remaining 0

summary(poly_logit_fit)

Call:
glm(formula = I(wage > 250) ~ poly(age, 3), family = binomial, 
    data = Wage)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-0.2808  -0.2736  -0.2487  -0.1758   3.2868  

Coefficients:
              Estimate Std. Error z value Pr(>|z|)    
(Intercept)    -3.8486     0.1597 -24.100  < 2e-16 ***
poly(age, 3)1  37.8846    11.4818   3.300 0.000968 ***
poly(age, 3)2 -29.5129    10.5626  -2.794 0.005205 ** 
poly(age, 3)3   9.7966     8.9990   1.089 0.276317    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 730.53  on 2999  degrees of freedom
Residual deviance: 707.92  on 2996  degrees of freedom
AIC: 715.92

Number of Fisher Scoring iterations: 8

Getting standard error bands

poly_logit_preds <- predict(poly_logit_fit,list(age=age_grid),se=T)
poly_logit_se_bands <- poly_logit_preds$fit + cbind(fit=0,lower=-1.96*poly_logit_preds$se,upper=1.96*poly_logit_preds$se)
poly_logit_se_bands[1:5,]
        fit      lower     upper
1 -7.664756 -10.697925 -4.631588
2 -7.324776 -10.051060 -4.598491
3 -7.001732  -9.443000 -4.560465
4 -6.695229  -8.872719 -4.517739
5 -6.404868  -8.339214 -4.470521

This predict is on the logit scale. We need to transform or perform the inverse logit in order to get the reverse mapping and get the probability values.

Inverse Logit function is

\[p=\frac{e^\eta}{1+e^\eta}.\]

prob_poly_logit_preds <- exp(poly_logit_se_bands)/(1+exp(poly_logit_se_bands))

matplot(age_grid, prob_poly_logit_preds, col = "blue", lwd = c(2,1,1), lty = c(1,2,2), type = "l", ylim = c(0,.1))

The blue line is the estimate of the fitted probability, and the dotted lines are the corresponding 95% confidence interval. We basically calculate everything on the logit scale and transform them back into the probability scale.

Adding the same points to the plot. The jitter function takes a point, adds some random uniform noise to it so that the points get kind of blown up to give the picture about its density. Think of it as a boxplot, but with noise now instead of all points concentrated at a single location.

matplot(age_grid, prob_poly_logit_preds, col = "blue", lwd = c(2,1,1), lty = c(1,2,2), type = "l", ylim = c(0,.1))
points(jitter(Wage$age), I(Wage$wage > 250)/10, pch = "|", cex = .5)

Only a max 4% of the population across all ages earned above 250k. In the above plot, we have divided the Wage scale by 10 in order to bring the points into the picture. Otherwise, it will be way up above the plot, and we wont be able to see it.


Splines

Fitting splines (cubic) on the same dataset. Splines are more flexible than polynomials.

require(splines)
#This fits a cubic spline at knots of age 25, 40 and 60
spline_fit <- lm(wage ~ bs(age, knots = c(25, 40, 60)), data = Wage)
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, predict(spline_fit, list(age = age_grid)), col = "darkgreen", lwd = 2)
abline(v = c(25,40,60), lty = 2, col = "darkgreen")

Spline - cubic polynomial. The bs() gives basis for cubic polynomial. Knots - Where we think discontinuity will happen (@ third derivative). Basically, we have a polynomial to the degree 3 at each of the region, constrained to “meet” or be continous at the knot. So this means they are more local than polynomials.

Comparing the polynomial fit with that of the GAM model

For the Wages dataset, it seems similar. But for a dataset with more sparse tail observations, the polynomial fit would be a little unpredictable, while the splines will be a little more flexible and not so “wagging”

par(mfrow = c(1,2))
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, poly_fit_preds$fit, lwd = 2, col = "blue")
matlines(age_grid, poly_fit_se_bands, col = "blue", lty = 2)
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, predict(spline_fit, list(age = age_grid)), col = "darkgreen", lwd = 2)
abline(v = c(25,40,60), lty = 2, col = "darkgreen")


In the previous fit, we fixed the knots. We can also have smoothing splines!

Smoothing splines considers every point to be a knot, and they have a roughness penalty to control the smoothness of the function. Basically, we do not have to manually select the knots. We specify the penalty through the effective degree of freedom.

smooth_spline_fit=smooth.spline(Wage$age, Wage$wage,df=16)

Adding the above fit to the fixed splines

par(mfrow = c(1,2))
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(smooth_spline_fit, col = "red", lwd = 2)
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, predict(spline_fit, list(age = age_grid)), col = "darkgreen", lwd = 2)
abline(v = c(25,40,60), lty = 2, col = "darkgreen")
lines(smooth_spline_fit, col = "red", lwd = 2)

16 degrees of freedom is a lot, which results in the fit in being too “Wiggly”

Another effective way to determine the degree (instead of forcing it to take a particular value manually), is through cross-validation. This selects the smoothing parameter for us automatically.

smooth_spline_cv_fit=smooth.spline(Wage$age, Wage$wage,cv=TRUE)
cross-validation with non-unique 'x' values seems doubtful

Adding the smooth spline with df chosen through cv to the above plot

par(mfrow = c(1,2))
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(smooth_spline_cv_fit, col = "purple", lwd = 2)
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, predict(spline_fit, list(age = age_grid)), col = "darkgreen", lwd = 2)
abline(v = c(25,40,60), lty = 2, col = "darkgreen")
lines(smooth_spline_fit,col="red",lwd=2)
lines(smooth_spline_cv_fit,col="purple",lwd=2)

The warning is because there are many ties in x

smooth_spline_cv_fit
Call:
smooth.spline(x = Wage$age, y = Wage$wage, cv = TRUE)

Smoothing Parameter  spar= 0.6988943  lambda= 0.02792303 (12 iterations)
Equivalent Degrees of Freedom (Df): 6.794596
Penalized Criterion (RSS): 75215.9
PRESS(l.o.o. CV): 1593.383

The effective degrees of freedom in the cv smoothing spline is 6.79 (heuristic for how rough the function is). For the fixed knot model, the degree of freedom was 6.


Generalized Additive Models

GAM package helps in fitting multiple non-linear functions (one for each variable) and makes it additive. It also provides good plotting functions.

We create a model with - a smooth term with age (4 degrees of freedom), a smooth term with year(4 degrees of freedom) and a linear term education (which is a factor variable, so it makes dummy variables)

#install.packages("gam")
require(gam)
Loading required package: gam
Loading required package: foreach
Loaded gam 1.16.1
gam_fit=gam(wage~s(age,df=4)+s(year,df=4)+education,data=Wage)
non-list contrasts argument ignored

When we plot the GAM, we will be able to see a fitted plot for each of the variable

par(mfrow=c(1,3))
plot(gam_fit,se=T)

There are three plots - one for each term in the GAM. We see that salaries tend to increase with year other than the dip. Salary also increases with education.


We can also fit GAMs with logistic regression model

This is plotting the contribution to the logit of the probability. The standard errors are actually very wide.


Using GAM, we can do some tests. Lets see if we need a nonlinear terms for year. So we fit the same model as before, except instead of having a smooth term with year, we have a linear term with year.

ANOVA with test = “Chisq” (because logistic regression)

anova(gam_fit2_linearYear,gam_fit2,test="Chisq")
Analysis of Deviance Table

Model 1: I(wage > 250) ~ s(age, df = 4) + year + education
Model 2: I(wage > 250) ~ s(age, df = 4) + s(year, df = 4) + education
  Resid. Df Resid. Dev Df Deviance Pr(>Chi)
1      2990     603.78                     
2      2987     602.87  3  0.90498   0.8242

We see that from the test resuts, the non_linear function for year is not needed. Linear term is good enough!


Plotting is powerful using the GAM package, which is not restricted to plotting GAM objects, but can be used even with LM or GLM objects

sample_lm_fit=lm(wage~ns(age,df=4)+ns(year,df=4)+education,data=Wage)
par(mfrow=c(1,3))
plot.Gam(sample_lm_fit,se=T)

ns() gives a basis of natural splines

LS0tDQp0aXRsZTogIk5vbiBMaW5lYXIgTW9kZWxzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KTG9hZGluZyB0aGUgSVNMUiBwYWNrYWdlLiBXZSB3b3VsZCBiZSB1c2luZyB0aGUgZGF0YXNldCBXYWdlDQoNCmBgYHtyfQ0KcmVxdWlyZShJU0xSKQ0KDQpkYXRhKFdhZ2UpDQpgYGANCg0KLS0tDQoNCiMjI1BvbHlub21pYWwgcmVncmVzc2lvbg0KDQpXaXRoIG9uZSByZXNwb25zZSBhZ2UsIGFuZCB3ZSBmaXQgYSA0IGRlZ3JlZSBwb2x5bm9taWFsLg0KDQpgYGB7cn0NCg0KcG9seV9maXQgPC0gbG0od2FnZX5wb2x5KGFnZSwgNCksIGRhdGEgPSBXYWdlKQ0Kc3VtbWFyeShwb2x5X2ZpdCkNCmBgYA0KDQpUaGlzIGxvb2tzIHRvIGJlIHNpZ25pZmljYW50IHVwIHRvIDMgZGVncmVlcyBhdCAwLjA1IGxldmVsLg0KDQpQbG90dGluZyByZXNwb25zZQ0KDQpgYGB7cn0NCiNHZXR0aW5nIHRoZSByYW5nZSBvZiBhZ2UgYW5kIGNyZWF0ZSBhIHNlcXVlbmNlIG9mIGFnZXMgaW4gYmV0d2VlbiB0byB1c2UgYXMgbmV3X2RhdGEgZm9yIHByZWRpY3Rpb24NCmFnZV9yYW5nZSA8LSByYW5nZShXYWdlJGFnZSkNCmFnZV9ncmlkIDwtIHNlcShmcm9tID0gYWdlX3JhbmdlWzFdLCB0byA9IGFnZV9yYW5nZVsyXSkNCg0KcG9seV9maXRfcHJlZHMgPC0gcHJlZGljdChwb2x5X2ZpdCwgbmV3ZGF0YSA9IGxpc3QoYWdlID0gYWdlX2dyaWQpLCBzZSA9IFQpDQpwb2x5X2ZpdF9zZV9iYW5kcyA8LSBjYmluZChwb2x5X2ZpdF9wcmVkcyRmaXQgKyAxLjk2ICogcG9seV9maXRfcHJlZHMkc2UsIHBvbHlfZml0X3ByZWRzJGZpdCAtIDEuOTYgKiBwb2x5X2ZpdF9wcmVkcyRzZSkNCg0KcGxvdChXYWdlJGFnZSwgV2FnZSR3YWdlLCBjb2wgPSAiZGFya2dyZXkiKQ0KbGluZXMoYWdlX2dyaWQsIHBvbHlfZml0X3ByZWRzJGZpdCwgbHdkID0gMiwgY29sID0gImJsdWUiKQ0KbWF0bGluZXMoYWdlX2dyaWQsIHBvbHlfZml0X3NlX2JhbmRzLCBjb2wgPSAiYmx1ZSIsIGx0eSA9IDIpDQoNCmBgYA0KDQotLS0NCg0KQW5vdGhlciB3YXkgdG8gYWNoaWV2ZSB0aGlzIGluIFIsIGJlY2F1c2UgdGhlcmUgYXJlIHNvIG1hbnkgcGFja2FnZXMgYW5kIG1ldGhvZHMgLSANCg0KYGBge3J9DQpmaXRfYWdlXzI9bG0od2FnZX5hZ2UrSShhZ2VeMikrSShhZ2VeMykrSShhZ2VeNCksZGF0YT1XYWdlKQ0Kc3VtbWFyeShmaXRfYWdlXzIpDQpgYGANCg0KLSBJKCkgaXMgYW4gSWRlbnRpdHkgZnVuY3Rpb24sIGJlY2F1c2UgIl4iIChjYXJldCkgc2lnbiBoYXMgYSBkaWZmZXJlbnQgbWVhbmluZyBpbiBSLiBTbywgYWdlXjIgaXMgbm90IHJlYWxseSBhZ2Ugc3F1YXJlZCwgYnV0IEkoYWdlXjIpIGlzLg0KDQotIEluIHRoZSBhYm92ZSBzdW1tYXJ5LCB0aGUgY29lZmZpY2llbnRzIGFuZCB0aGUgcC12YWx1ZSBhcmUgbm90IHRoZSBzYW1lIGFzIHRoZSBwcmV2aW91cyBjb2RlIGJsb2NrJ3MgcmVzdWx0LiBCZWNhdXNlIHRoZSBiYXNpcyBpcyBkaWZmZXJlbnQuDQoNCkJ1dCB0aGUgZml0dGVkIHZhbHVlIHJlbWFpbnMgdGhlIHNhbWUuIFdlIHNob3VsZCBnZXQgYSBzdHJhaWdodCBsaW5lIHdoZW4gd2UgY29tcGFyZSB0aGlzIHdpdGggb3VyIHByZXZpb3VzIGZpdC4NCg0KYGBge3J9DQpwbG90KGZpdHRlZChwb2x5X2ZpdCksIGZpdHRlZChmaXRfYWdlXzIpKQ0KYGBgDQoNCg0KU28sIHdlIHNlZSB0aGF0IG9ubHkgdGhlIGZpcnN0IHRocmVlIGRlZ3JlZXMgYXJlIHNpZ25pZmljYW50IGFuZCB1c2VmdWwuIEJ1dCB3ZSB0ZXN0ZWQgdGhhdCBzZXBhcmF0ZWx5IHVzaW5nIGEgc2ltcGxlIGZpdC4gVGhpcyBvbmx5IHdvcmtzIHdpdGggYSBzaW5nbGUgdmFyaWFibGUuIElmIHdlIHJlYWxseSBuZWVkIHRvIGNvbXBhcmUgdHdvIG1vZGVscyAoSXMgMSBkZWdyZWUgbW9kZWwgYmV0dGVyIHRoYW4gYSAyIGRlZ3JlZSBtb2RlbCksIHdlIG5lZWQgdG8gdXNlIGFuIGFub3ZhLiBUaGlzIGNhbiBiZSB1c2VkIHdoZW4gdGhlcmUgYXJlIG11bHRpcGxlIHZhcmlhYmxlcyBpbiB0aGUgbW9kZWwhDQoNCi0tLQ0KDQpJbiBjYXNlIG9mIG5lc3RlZCBsaW5lYXIgbW9kZWxzLCB3aGVyZSBvbmUgbW9kZWwgaXMgYW4gYWRkaXRpb24gdG8gYW4gZXhpc3RpbmcgbW9kZWwsIHdlIHVzZSBhbm92YSBsaWtlIC0gDQoNCmBgYHtyfQ0KZml0X2VkdSA8LSBsbSh3YWdlIH4gZWR1Y2F0aW9uLCBkYXRhID0gV2FnZSkNCmZpdF9lZHVfYWdlIDwtIGxtKHdhZ2UgfiBlZHVjYXRpb24gKyBhZ2UsIGRhdGEgPSBXYWdlKQ0KcG9seV9maXRfZWR1X2FnZSA8LSBsbSh3YWdlIH4gZWR1Y2F0aW9uICsgcG9seShhZ2UsIDIpLCBkYXRhID0gV2FnZSkNCnBvbHlfZml0X2VkdV9hZ2UzIDwtIGxtKHdhZ2UgfiBlZHVjYXRpb24gKyBwb2x5KGFnZSwgMyksIGRhdGEgPSBXYWdlKQ0KYGBgDQoNCkFub3ZhIHRlc3QNCg0KSDAgLSBUaGUgYWRkaXRpb25hbCB2YXJpYWJsZSBoYXMgbm8gaW1wYWN0IG9uIHRoZSBtb2RlbCAoY3VycmVudCBtb2RlbCBhbmQgcmVkdWNlZCBtb2RlbCBhcmUgdGhlIHNhbWUpDQpIYSAtIFRoZSBhZGRpdGlvbmFsIHZhcmlhYmxlIGRvZXMgaW1wYWN0IHRoZSBtb2RlbA0KDQpgYGB7cn0NCmFub3ZhKGZpdF9lZHUsIGZpdF9lZHVfYWdlLCBwb2x5X2ZpdF9lZHVfYWdlLCBwb2x5X2ZpdF9lZHVfYWdlMykNCmBgYA0KDQpJdCBpcyBjbGVhciB0aGF0IHRoZSBmaXJzdCB0d28gbGluZWFyIG1vZGVscyBhcmUgc2lnbmlmaWNhbnQuIEEgcG9seW5vbWlhbCBvbiBhZ2UsIHdpdGggZGVncmVlIDIgaXMgYWxzbyBzaWduaWZpY2FudC4gQnV0IHRoZSBmb3VydGgsIHdpdGggYWdlXjMsIGlzIG9ubHkganVzdCBtYXJnaW5hbGx5IHNpZ25pZmljYW50IGF0IHRoZSAwLjA1IGxldmVsLiBXZSBtYXkgd2FudCB0byBjaG9vc2UgYW5kIHJlbW92ZSB0aGlzIGRlZ3JlZS4gDQoNCi0tLQ0KDQojIyNQb2x5bm9taWFsIExvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KV2UgY2FuIGFsc28gcHJvY2VlZCB0byB1c2UgdGhlIHNhbWUgZnVuY3Rpb24gZm9yIGEgbG9naXN0aWMgcmVncmVzc2lvbi4gSGVyZSB3ZSBjcmVhdGUgYSBiaW5hcnkgcmVzcG9uc2Ugb2Ygd2FnZSBiYXNlZCBvbiB0aGUgcGxvdCB3ZSBzZWUuIEJpZyBlYXJuZXJzIHdpdGggd2FnZSA+IDI1MGsgdGFnZ2VkIGFzIDEsIGFuZCByZW1haW5pbmcgMA0KDQpgYGB7cn0NCnBsb3QoV2FnZSRhZ2UsIFdhZ2Ukd2FnZSkNCmBgYA0KDQpgYGB7cn0NCnBvbHlfbG9naXRfZml0PWdsbShJKHdhZ2U+MjUwKSB+IHBvbHkoYWdlLDMpLCBkYXRhPVdhZ2UsIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkocG9seV9sb2dpdF9maXQpDQpgYGANCg0KDQpHZXR0aW5nIHN0YW5kYXJkIGVycm9yIGJhbmRzDQpgYGB7cn0NCnBvbHlfbG9naXRfcHJlZHMgPC0gcHJlZGljdChwb2x5X2xvZ2l0X2ZpdCxsaXN0KGFnZT1hZ2VfZ3JpZCksc2U9VCkNCnBvbHlfbG9naXRfc2VfYmFuZHMgPC0gcG9seV9sb2dpdF9wcmVkcyRmaXQgKyBjYmluZChmaXQ9MCxsb3dlcj0tMS45Nipwb2x5X2xvZ2l0X3ByZWRzJHNlLHVwcGVyPTEuOTYqcG9seV9sb2dpdF9wcmVkcyRzZSkNCnBvbHlfbG9naXRfc2VfYmFuZHNbMTo1LF0NCmBgYA0KDQpUaGlzIHByZWRpY3QgaXMgb24gdGhlIGxvZ2l0IHNjYWxlLiBXZSBuZWVkIHRvIHRyYW5zZm9ybSBvciBwZXJmb3JtIHRoZSBpbnZlcnNlIGxvZ2l0IGluIG9yZGVyIHRvIGdldCB0aGUgcmV2ZXJzZSBtYXBwaW5nIGFuZCBnZXQgdGhlIHByb2JhYmlsaXR5IHZhbHVlcy4NCg0KSW52ZXJzZSBMb2dpdCBmdW5jdGlvbiBpcw0KDQokJHA9XGZyYWN7ZV5cZXRhfXsxK2VeXGV0YX0uJCQNCg0KYGBge3J9DQpwcm9iX3BvbHlfbG9naXRfcHJlZHMgPC0gZXhwKHBvbHlfbG9naXRfc2VfYmFuZHMpLygxK2V4cChwb2x5X2xvZ2l0X3NlX2JhbmRzKSkNCg0KbWF0cGxvdChhZ2VfZ3JpZCwgcHJvYl9wb2x5X2xvZ2l0X3ByZWRzLCBjb2wgPSAiYmx1ZSIsIGx3ZCA9IGMoMiwxLDEpLCBsdHkgPSBjKDEsMiwyKSwgdHlwZSA9ICJsIiwgeWxpbSA9IGMoMCwuMSkpDQpgYGANCg0KVGhlIGJsdWUgbGluZSBpcyB0aGUgZXN0aW1hdGUgb2YgdGhlIGZpdHRlZCBwcm9iYWJpbGl0eSwgYW5kIHRoZSBkb3R0ZWQgbGluZXMgYXJlIHRoZSBjb3JyZXNwb25kaW5nIDk1JSBjb25maWRlbmNlIGludGVydmFsLiBXZSBiYXNpY2FsbHkgY2FsY3VsYXRlIGV2ZXJ5dGhpbmcgb24gdGhlIGxvZ2l0IHNjYWxlIGFuZCB0cmFuc2Zvcm0gdGhlbSBiYWNrIGludG8gdGhlIHByb2JhYmlsaXR5IHNjYWxlLg0KDQpBZGRpbmcgdGhlIHNhbWUgcG9pbnRzIHRvIHRoZSBwbG90LiBUaGUgaml0dGVyIGZ1bmN0aW9uIHRha2VzIGEgcG9pbnQsIGFkZHMgc29tZSByYW5kb20gdW5pZm9ybSBub2lzZSB0byBpdCBzbyB0aGF0IHRoZSBwb2ludHMgZ2V0IGtpbmQgb2YgYmxvd24gdXAgdG8gZ2l2ZSB0aGUgcGljdHVyZSBhYm91dCBpdHMgZGVuc2l0eS4gVGhpbmsgb2YgaXQgYXMgYSBib3hwbG90LCBidXQgd2l0aCBub2lzZSBub3cgaW5zdGVhZCBvZiBhbGwgcG9pbnRzIGNvbmNlbnRyYXRlZCBhdCBhIHNpbmdsZSBsb2NhdGlvbi4gDQoNCmBgYHtyfQ0KbWF0cGxvdChhZ2VfZ3JpZCwgcHJvYl9wb2x5X2xvZ2l0X3ByZWRzLCBjb2wgPSAiYmx1ZSIsIGx3ZCA9IGMoMiwxLDEpLCBsdHkgPSBjKDEsMiwyKSwgdHlwZSA9ICJsIiwgeWxpbSA9IGMoMCwuMSkpDQpwb2ludHMoaml0dGVyKFdhZ2UkYWdlKSwgSShXYWdlJHdhZ2UgPiAyNTApLzEwLCBwY2ggPSAifCIsIGNleCA9IC41KQ0KYGBgDQoNCk9ubHkgYSBtYXggNCUgb2YgdGhlIHBvcHVsYXRpb24gYWNyb3NzIGFsbCBhZ2VzIGVhcm5lZCBhYm92ZSAyNTBrLiBJbiB0aGUgYWJvdmUgcGxvdCwgd2UgaGF2ZSBkaXZpZGVkIHRoZSBXYWdlIHNjYWxlIGJ5IDEwIGluIG9yZGVyIHRvIGJyaW5nIHRoZSBwb2ludHMgaW50byB0aGUgcGljdHVyZS4gT3RoZXJ3aXNlLCBpdCB3aWxsIGJlIHdheSB1cCBhYm92ZSB0aGUgcGxvdCwgYW5kIHdlIHdvbnQgYmUgYWJsZSB0byBzZWUgaXQuDQoNCg0KLS0tDQoNCiMjI1NwbGluZXMNCg0KDQpGaXR0aW5nIHNwbGluZXMgKGN1YmljKSBvbiB0aGUgc2FtZSBkYXRhc2V0LiBTcGxpbmVzIGFyZSBtb3JlIGZsZXhpYmxlIHRoYW4gcG9seW5vbWlhbHMuDQoNCg0KYGBge3J9DQpyZXF1aXJlKHNwbGluZXMpDQoNCiNUaGlzIGZpdHMgYSBjdWJpYyBzcGxpbmUgYXQga25vdHMgb2YgYWdlIDI1LCA0MCBhbmQgNjANCnNwbGluZV9maXQgPC0gbG0od2FnZSB+IGJzKGFnZSwga25vdHMgPSBjKDI1LCA0MCwgNjApKSwgZGF0YSA9IFdhZ2UpDQpgYGANCg0KYGBge3J9DQpwbG90KFdhZ2UkYWdlLCBXYWdlJHdhZ2UsIGNvbCA9ICJkYXJrZ3JleSIpDQpsaW5lcyhhZ2VfZ3JpZCwgcHJlZGljdChzcGxpbmVfZml0LCBsaXN0KGFnZSA9IGFnZV9ncmlkKSksIGNvbCA9ICJkYXJrZ3JlZW4iLCBsd2QgPSAyKQ0KYWJsaW5lKHYgPSBjKDI1LDQwLDYwKSwgbHR5ID0gMiwgY29sID0gImRhcmtncmVlbiIpDQpgYGANCg0KU3BsaW5lIC0gY3ViaWMgcG9seW5vbWlhbC4gVGhlIGJzKCkgZ2l2ZXMgYmFzaXMgZm9yIGN1YmljIHBvbHlub21pYWwuDQpLbm90cyAtIFdoZXJlIHdlIHRoaW5rIGRpc2NvbnRpbnVpdHkgd2lsbCBoYXBwZW4gKEAgdGhpcmQgZGVyaXZhdGl2ZSkuIEJhc2ljYWxseSwgd2UgaGF2ZSBhIHBvbHlub21pYWwgdG8gdGhlIGRlZ3JlZSAzIGF0IGVhY2ggb2YgdGhlIHJlZ2lvbiwgY29uc3RyYWluZWQgdG8gIm1lZXQiIG9yIGJlIGNvbnRpbm91cyBhdCB0aGUga25vdC4gU28gdGhpcyBtZWFucyB0aGV5IGFyZSBtb3JlIGxvY2FsIHRoYW4gcG9seW5vbWlhbHMuDQoNCkNvbXBhcmluZyB0aGUgcG9seW5vbWlhbCBmaXQgd2l0aCB0aGF0IG9mIHRoZSBHQU0gbW9kZWwNCg0KRm9yIHRoZSBXYWdlcyBkYXRhc2V0LCBpdCBzZWVtcyBzaW1pbGFyLiBCdXQgZm9yIGEgZGF0YXNldCB3aXRoIG1vcmUgc3BhcnNlIHRhaWwgb2JzZXJ2YXRpb25zLCB0aGUgcG9seW5vbWlhbCBmaXQgd291bGQgYmUgYSBsaXR0bGUgdW5wcmVkaWN0YWJsZSwgd2hpbGUgdGhlIHNwbGluZXMgd2lsbCBiZSBhIGxpdHRsZSBtb3JlIGZsZXhpYmxlIGFuZCBub3Qgc28gIndhZ2dpbmciDQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygxLDIpKQ0KDQpwbG90KFdhZ2UkYWdlLCBXYWdlJHdhZ2UsIGNvbCA9ICJkYXJrZ3JleSIpDQpsaW5lcyhhZ2VfZ3JpZCwgcG9seV9maXRfcHJlZHMkZml0LCBsd2QgPSAyLCBjb2wgPSAiYmx1ZSIpDQptYXRsaW5lcyhhZ2VfZ3JpZCwgcG9seV9maXRfc2VfYmFuZHMsIGNvbCA9ICJibHVlIiwgbHR5ID0gMikNCg0KcGxvdChXYWdlJGFnZSwgV2FnZSR3YWdlLCBjb2wgPSAiZGFya2dyZXkiKQ0KbGluZXMoYWdlX2dyaWQsIHByZWRpY3Qoc3BsaW5lX2ZpdCwgbGlzdChhZ2UgPSBhZ2VfZ3JpZCkpLCBjb2wgPSAiZGFya2dyZWVuIiwgbHdkID0gMikNCmFibGluZSh2ID0gYygyNSw0MCw2MCksIGx0eSA9IDIsIGNvbCA9ICJkYXJrZ3JlZW4iKQ0KDQpgYGANCg0KLS0tDQoNCkluIHRoZSBwcmV2aW91cyBmaXQsIHdlIGZpeGVkIHRoZSBrbm90cy4gV2UgY2FuIGFsc28gaGF2ZSBzbW9vdGhpbmcgc3BsaW5lcyENCg0KU21vb3RoaW5nIHNwbGluZXMgY29uc2lkZXJzIGV2ZXJ5IHBvaW50IHRvIGJlIGEga25vdCwgYW5kIHRoZXkgaGF2ZSBhIHJvdWdobmVzcyBwZW5hbHR5IHRvIGNvbnRyb2wgdGhlIHNtb290aG5lc3Mgb2YgdGhlIGZ1bmN0aW9uLiBCYXNpY2FsbHksIHdlIGRvIG5vdCBoYXZlIHRvIG1hbnVhbGx5IHNlbGVjdCB0aGUga25vdHMuIFdlIHNwZWNpZnkgdGhlIHBlbmFsdHkgdGhyb3VnaCB0aGUgZWZmZWN0aXZlIGRlZ3JlZSBvZiBmcmVlZG9tLg0KDQpgYGB7cn0NCnNtb290aF9zcGxpbmVfZml0PXNtb290aC5zcGxpbmUoV2FnZSRhZ2UsIFdhZ2Ukd2FnZSxkZj0xNikNCmBgYA0KDQpBZGRpbmcgdGhlIGFib3ZlIGZpdCB0byB0aGUgZml4ZWQgc3BsaW5lcw0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDEsMikpDQpwbG90KFdhZ2UkYWdlLCBXYWdlJHdhZ2UsIGNvbCA9ICJkYXJrZ3JleSIpDQpsaW5lcyhzbW9vdGhfc3BsaW5lX2ZpdCwgY29sID0gInJlZCIsIGx3ZCA9IDIpDQoNCnBsb3QoV2FnZSRhZ2UsIFdhZ2Ukd2FnZSwgY29sID0gImRhcmtncmV5IikNCmxpbmVzKGFnZV9ncmlkLCBwcmVkaWN0KHNwbGluZV9maXQsIGxpc3QoYWdlID0gYWdlX2dyaWQpKSwgY29sID0gImRhcmtncmVlbiIsIGx3ZCA9IDIpDQphYmxpbmUodiA9IGMoMjUsNDAsNjApLCBsdHkgPSAyLCBjb2wgPSAiZGFya2dyZWVuIikNCmxpbmVzKHNtb290aF9zcGxpbmVfZml0LCBjb2wgPSAicmVkIiwgbHdkID0gMikNCmBgYA0KDQoxNiBkZWdyZWVzIG9mIGZyZWVkb20gaXMgYSBsb3QsIHdoaWNoIHJlc3VsdHMgaW4gdGhlIGZpdCBpbiBiZWluZyB0b28gIldpZ2dseSINCg0KQW5vdGhlciBlZmZlY3RpdmUgd2F5IHRvIGRldGVybWluZSB0aGUgZGVncmVlIChpbnN0ZWFkIG9mIGZvcmNpbmcgaXQgdG8gdGFrZSBhIHBhcnRpY3VsYXIgdmFsdWUgbWFudWFsbHkpLCBpcyB0aHJvdWdoIGNyb3NzLXZhbGlkYXRpb24uIFRoaXMgc2VsZWN0cyB0aGUgc21vb3RoaW5nIHBhcmFtZXRlciBmb3IgdXMgYXV0b21hdGljYWxseS4NCg0KYGBge3J9DQpzbW9vdGhfc3BsaW5lX2N2X2ZpdD1zbW9vdGguc3BsaW5lKFdhZ2UkYWdlLCBXYWdlJHdhZ2UsY3Y9VFJVRSkNCmBgYA0KDQpBZGRpbmcgdGhlIHNtb290aCBzcGxpbmUgd2l0aCBkZiBjaG9zZW4gdGhyb3VnaCBjdiB0byB0aGUgYWJvdmUgcGxvdA0KYGBge3J9DQoNCnBhcihtZnJvdyA9IGMoMSwyKSkNCnBsb3QoV2FnZSRhZ2UsIFdhZ2Ukd2FnZSwgY29sID0gImRhcmtncmV5IikNCmxpbmVzKHNtb290aF9zcGxpbmVfY3ZfZml0LCBjb2wgPSAicHVycGxlIiwgbHdkID0gMikNCg0KDQpwbG90KFdhZ2UkYWdlLCBXYWdlJHdhZ2UsIGNvbCA9ICJkYXJrZ3JleSIpDQpsaW5lcyhhZ2VfZ3JpZCwgcHJlZGljdChzcGxpbmVfZml0LCBsaXN0KGFnZSA9IGFnZV9ncmlkKSksIGNvbCA9ICJkYXJrZ3JlZW4iLCBsd2QgPSAyKQ0KYWJsaW5lKHYgPSBjKDI1LDQwLDYwKSwgbHR5ID0gMiwgY29sID0gImRhcmtncmVlbiIpDQpsaW5lcyhzbW9vdGhfc3BsaW5lX2ZpdCxjb2w9InJlZCIsbHdkPTIpDQpsaW5lcyhzbW9vdGhfc3BsaW5lX2N2X2ZpdCxjb2w9InB1cnBsZSIsbHdkPTIpDQpgYGANCg0KVGhlIHdhcm5pbmcgaXMgYmVjYXVzZSB0aGVyZSBhcmUgbWFueSB0aWVzIGluIHgNCg0KYGBge3J9DQpzbW9vdGhfc3BsaW5lX2N2X2ZpdA0KYGBgDQoNClRoZSBlZmZlY3RpdmUgZGVncmVlcyBvZiBmcmVlZG9tIGluIHRoZSBjdiBzbW9vdGhpbmcgc3BsaW5lIGlzIDYuNzkgKGhldXJpc3RpYyBmb3IgaG93IHJvdWdoIHRoZSBmdW5jdGlvbiBpcykuIEZvciB0aGUgZml4ZWQga25vdCBtb2RlbCwgdGhlIGRlZ3JlZSBvZiBmcmVlZG9tIHdhcyA2Lg0KDQotLS0NCg0KIyMjR2VuZXJhbGl6ZWQgQWRkaXRpdmUgTW9kZWxzDQoNCkdBTSBwYWNrYWdlIGhlbHBzIGluIGZpdHRpbmcgbXVsdGlwbGUgbm9uLWxpbmVhciBmdW5jdGlvbnMgKG9uZSBmb3IgZWFjaCB2YXJpYWJsZSkgYW5kIG1ha2VzIGl0IGFkZGl0aXZlLiBJdCBhbHNvIHByb3ZpZGVzIGdvb2QgcGxvdHRpbmcgZnVuY3Rpb25zLg0KDQpXZSBjcmVhdGUgYSBtb2RlbCB3aXRoIC0gYSBzbW9vdGggdGVybSB3aXRoIGFnZSAoNCBkZWdyZWVzIG9mIGZyZWVkb20pLCBhIHNtb290aCB0ZXJtIHdpdGggeWVhcig0IGRlZ3JlZXMgb2YgZnJlZWRvbSkgYW5kIGEgbGluZWFyIHRlcm0gZWR1Y2F0aW9uICh3aGljaCBpcyBhIGZhY3RvciB2YXJpYWJsZSwgc28gaXQgbWFrZXMgZHVtbXkgdmFyaWFibGVzKQ0KDQpgYGB7cn0NCiNpbnN0YWxsLnBhY2thZ2VzKCJnYW0iKQ0KYGBgDQoNCmBgYHtyfQ0KcmVxdWlyZShnYW0pDQpnYW1fZml0PWdhbSh3YWdlfnMoYWdlLGRmPTQpK3MoeWVhcixkZj00KStlZHVjYXRpb24sZGF0YT1XYWdlKQ0KYGBgDQoNCldoZW4gd2UgcGxvdCB0aGUgR0FNLCB3ZSB3aWxsIGJlIGFibGUgdG8gc2VlIGEgZml0dGVkIHBsb3QgZm9yIGVhY2ggb2YgdGhlIHZhcmlhYmxlDQoNCmBgYHtyfQ0KcGFyKG1mcm93PWMoMSwzKSkNCnBsb3QoZ2FtX2ZpdCxzZT1UKQ0KYGBgDQoNClRoZXJlIGFyZSB0aHJlZSBwbG90cyAtIG9uZSBmb3IgZWFjaCB0ZXJtIGluIHRoZSBHQU0uIFdlIHNlZSB0aGF0IHNhbGFyaWVzIHRlbmQgdG8gaW5jcmVhc2Ugd2l0aCB5ZWFyIG90aGVyIHRoYW4gdGhlIGRpcC4gU2FsYXJ5IGFsc28gaW5jcmVhc2VzIHdpdGggZWR1Y2F0aW9uLg0KDQotLS0NCg0KV2UgY2FuIGFsc28gZml0IEdBTXMgd2l0aCBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsDQoNCmBgYHtyfQ0KZ2FtX2ZpdDI9Z2FtKEkod2FnZT4yNTApfnMoYWdlLGRmPTQpK3MoeWVhcixkZj00KStlZHVjYXRpb24sZGF0YT1XYWdlLGZhbWlseT1iaW5vbWlhbCkNCg0KcGFyKG1mcm93ID0gYygyLDMpKQ0KcGxvdChnYW1fZml0MikNCnBsb3QoZ2FtX2ZpdDIsIHNlID0gVCkNCmBgYA0KDQpUaGlzIGlzIHBsb3R0aW5nIHRoZSBjb250cmlidXRpb24gdG8gdGhlIGxvZ2l0IG9mIHRoZSBwcm9iYWJpbGl0eS4gVGhlIHN0YW5kYXJkIGVycm9ycyBhcmUgYWN0dWFsbHkgdmVyeSB3aWRlLg0KDQotLS0NCg0KVXNpbmcgR0FNLCB3ZSBjYW4gZG8gc29tZSB0ZXN0cy4gTGV0cyBzZWUgaWYgd2UgbmVlZCBhIG5vbmxpbmVhciB0ZXJtcyBmb3IgeWVhci4gU28gd2UgZml0IHRoZSBzYW1lIG1vZGVsIGFzIGJlZm9yZSwgZXhjZXB0IGluc3RlYWQgb2YgaGF2aW5nIGEgc21vb3RoIHRlcm0gd2l0aCB5ZWFyLCB3ZSBoYXZlIGEgbGluZWFyIHRlcm0gd2l0aCB5ZWFyLg0KDQpBTk9WQSB3aXRoIHRlc3QgPSAiQ2hpc3EiIChiZWNhdXNlIGxvZ2lzdGljIHJlZ3Jlc3Npb24pDQpgYGB7cn0NCmdhbV9maXQyX2xpbmVhclllYXI9Z2FtKEkod2FnZT4yNTApfnMoYWdlLGRmPTQpK3llYXIrZWR1Y2F0aW9uLGRhdGE9V2FnZSxmYW1pbHk9Ymlub21pYWwpDQphbm92YShnYW1fZml0Ml9saW5lYXJZZWFyLGdhbV9maXQyLHRlc3Q9IkNoaXNxIikNCmBgYA0KDQpXZSBzZWUgdGhhdCBmcm9tIHRoZSB0ZXN0IHJlc3V0cywgdGhlIG5vbl9saW5lYXIgZnVuY3Rpb24gZm9yIHllYXIgaXMgbm90IG5lZWRlZC4gTGluZWFyIHRlcm0gaXMgZ29vZCBlbm91Z2ghDQoNCi0tLQ0KDQpQbG90dGluZyBpcyBwb3dlcmZ1bCB1c2luZyB0aGUgR0FNIHBhY2thZ2UsIHdoaWNoIGlzIG5vdCByZXN0cmljdGVkIHRvIHBsb3R0aW5nIEdBTSBvYmplY3RzLCBidXQgY2FuIGJlIHVzZWQgZXZlbiB3aXRoIExNIG9yIEdMTSBvYmplY3RzDQoNCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01fQ0Kc2FtcGxlX2xtX2ZpdD1sbSh3YWdlfm5zKGFnZSxkZj00KStucyh5ZWFyLGRmPTQpK2VkdWNhdGlvbixkYXRhPVdhZ2UpDQoNCnBhcihtZnJvdz1jKDEsMykpDQpwbG90LkdhbShzYW1wbGVfbG1fZml0LHNlPVQpDQpgYGANCg0KbnMoKSBnaXZlcyBhIGJhc2lzIG9mIG5hdHVyYWwgc3BsaW5lcw0KDQoNCg0KDQogDQo=