Overview of linear regression

simple regression

Some code has been adapted from broom package vignette.

lmfit <- lm(mpg ~ wt, mtcars)
lmfit

Call:
lm(formula = mpg ~ wt, data = mtcars)

Coefficients:
(Intercept)           wt  
     37.285       -5.344  

The formula notation can be read as “mpg explained by wt”. Let’s see the scatterplot between these two variables

ggplot(mtcars, aes(wt,mpg)) +
  geom_point()

We can get more information out of the model

summary(lmfit)

Call:
lm(formula = mpg ~ wt, data = mtcars)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.5432 -2.3647 -0.1252  1.4096  6.8727 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  37.2851     1.8776  19.858  < 2e-16 ***
wt           -5.3445     0.5591  -9.559 1.29e-10 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.046 on 30 degrees of freedom
Multiple R-squared:  0.7528,    Adjusted R-squared:  0.7446 
F-statistic: 91.38 on 1 and 30 DF,  p-value: 1.294e-10
summary(lmfit)$r.squared
[1] 0.7528328
coefficients(lmfit)
(Intercept)          wt 
  37.285126   -5.344472 
head(residuals(lmfit))
        Mazda RX4     Mazda RX4 Wag        Datsun 710    Hornet 4 Drive Hornet Sportabout           Valiant 
       -2.2826106        -0.9197704        -2.0859521         1.2973499        -0.2001440        -0.6932545 

how to extract results - tidy way

Let’s extract necessary information tidy way with help of broom package functions

broom::tidy(lmfit)
broom::augment(lmfit) %>%  head()
broom::glance(lmfit)

prediction with model

Let’s generate a dummy data frame

test <- data_frame(wt=c(3.5,5,2.5), dummy=c(10,15,20))
test

Let’s predict mpg values based on new wt values.

predict(lmfit,test)
       1        2        3 
18.57948 10.56277 23.92395 
# let's keep them together
test$prediction <-predict(lmfit,test)
test

Let’s visualize this concept

ggplot(mtcars, aes(wt,mpg)) +
  geom_point()+
  geom_point(data=test, aes(wt,prediction), col="red")

Quality of regression

RMSE is used to measure quality of regression

RMSE

RMSE

This code is from DataCamp

pred <- predict(lmfit)
pred
          Mazda RX4       Mazda RX4 Wag          Datsun 710      Hornet 4 Drive   Hornet Sportabout             Valiant          Duster 360 
          23.282611           21.919770           24.885952           20.102650           18.900144           18.793255           18.205363 
          Merc 240D            Merc 230            Merc 280           Merc 280C          Merc 450SE          Merc 450SL         Merc 450SLC 
          20.236262           20.450041           18.900144           18.900144           15.533127           17.350247           17.083024 
 Cadillac Fleetwood Lincoln Continental   Chrysler Imperial            Fiat 128         Honda Civic      Toyota Corolla       Toyota Corona 
           9.226650            8.296712            8.718926           25.527289           28.653805           27.478021           24.111004 
   Dodge Challenger         AMC Javelin          Camaro Z28    Pontiac Firebird           Fiat X1-9       Porsche 914-2        Lotus Europa 
          18.472586           18.926866           16.762355           16.735633           26.943574           25.847957           29.198941 
     Ford Pantera L        Ferrari Dino       Maserati Bora          Volvo 142E 
          20.343151           22.480940           18.205363           22.427495 
rmse <- sqrt(sum( (mtcars$mpg - pred) ^ 2) / nrow(mtcars))
rmse
[1] 2.949163
# OR
# Apply predict() to mtcars: 
mtcars_est <- predict(lmfit,mtcars)
# Calculate difference between the predicted and the true values: res
res <- mtcars$mpg - mtcars_est
# Calculate RMSE, assign it to rmse and print it
rmse <- sqrt(sum((res^2))/nrow(mtcars))
rmse
[1] 2.949163

Many Models - GapMinder

The code is taken from Hadley Wickham’s talk and Many Models chapter in R for Data Science book. Some code had been modified by alper yilmaz.

gapminder
gapminder <- gapminder %>% mutate(year1950 = year - 1950)

GapMinder, the good and ugly (or so?)

Remember Hans Rosling’s animation showing the relationship between GDP and Life Expectancy? Here’s the animation re-generated with ggplot

Gapminder Animation

Gapminder Animation

Below is Hadley Wickham’s line graph showing all countries for all years.

gapminder %>% 
  ggplot(aes(year, lifeExp, group = country)) +
  geom_line(alpha = 1/3)

There’s overall increasing trend. But some countries don’t follow the pattern, how can we find those countries.

For single country we can pull the data and draw the trend

tr <- filter(gapminder, country == "Turkey")
tr %>% 
  ggplot(aes(year, lifeExp)) + 
  geom_line() + 
  ggtitle("Full data = ")

tr_mod <- lm(lifeExp ~ year, data = tr)
tr %>% 
  add_predictions(tr_mod) %>%
  ggplot(aes(year, pred)) + 
  geom_line() + 
  ggtitle("Linear trend + ")

tr %>% 
  add_residuals(tr_mod) %>% 
  ggplot(aes(year, resid)) + 
  geom_hline(yintercept = 0, colour = "white", size = 3) + 
  geom_line() + 
  ggtitle("Remaining pattern")

But this is not feasible to do for many countries.

Visualization can surprise you, but it doesn’t scale well. Modeling scales well, but it can’t surprise you. - Hadley Wickham (by way of John D. Cook)

Nested data

Please refer to list columns section and following couple sections in R4DS book to understand list column concept better.

by_country <- gapminder %>% 
        group_by(continent, country) %>% 
        nest()
by_country
# str(by_country)       # does not help
by_country$data[[1]]    # can reach the contents of list column
## how do we do this with subset indexing?
# by_country$data[country=="Turkey"]
tr_data <-by_country %>% 
  filter(country == "Turkey") 
tr_data$data[[1]]
by_country %>% 
  filter(country == "Turkey") %>% 
  unnest(data)

Fit models

country_model <- function(df){ lm(lifeExp ~ year1950, data = df) }
models <- by_country %>% 
        mutate( mod = map(data, country_model) )
# try without function
models
models %>% filter(continent == "Africa")

Broom

As you might remember the output of summary() or glance() are not in tidy format. Below is the summary of model belonging to first country.

summary( models$mod[[1]] )

Call:
lm(formula = lifeExp ~ year1950, data = df)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.5447 -0.9905 -0.2757  0.8847  1.6868 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 29.35664    0.69898   42.00 1.40e-12 ***
year1950     0.27533    0.02045   13.46 9.84e-08 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.223 on 10 degrees of freedom
Multiple R-squared:  0.9477,    Adjusted R-squared:  0.9425 
F-statistic: 181.2 on 1 and 10 DF,  p-value: 9.835e-08

The broom package comes to rescue, especially when dealing with many models. Let’s extract r squared values from models and attach to tibble.

models %>% 
  mutate(glance = mod %>% map(broom::glance),
         rsq = glance %>% map_dbl("r.squared"))
   
# OR      
# models %>% 
#   mutate(rsq = mod %>% map(broom::glance) %>% map_dbl("r.squared"))

Let’s extract all model related data and keep as list column within same tibble.

models <- models %>% 
        mutate(
                glance = mod %>% map(broom::glance),
                rsq = glance %>% map_dbl("r.squared"),
                tidy = mod %>% map(broom::tidy),
                augment = mod %>% map(broom::augment)
        )
models

As we saw earlier, we can access the contents of each list columns if we had to

models$glance[[1]]
models$tidy[[1]]
models$augment[[1]]

The tibble still behaves same, dplyr verbs or ggplot can be used with it

models %>% arrange(desc(rsq))
models %>% filter(continent == "Africa")
models %>% 
        ggplot(aes(rsq, reorder(country, rsq))) +
        geom_point(aes(colour = continent))

Unnest

unnest(models, data)
unnest(models, glance, .drop = TRUE )
unnest(models, tidy)
models %>% 
        unnest(tidy) %>% 
        select(continent, country, term, estimate, rsq) %>% 
        spread(term, estimate) %>% 
        ggplot(aes(`(Intercept)`, year1950)) +
        geom_point(aes(colour = continent, size = rsq)) +
        geom_smooth(se = FALSE) +
        xlab("Life Expectancy (1950)") +
        ylab("Yearly Improvement") +
        scale_size_area()

unnest(models, augment)
models %>% 
        unnest(augment) %>% 
        ggplot(aes(year1950, .resid)) +
        geom_line(aes(group = country, alpha = 1/3)) +
        geom_smooth(se = FALSE) +
        geom_hline(yintercept = 0, colour = "white") +
        facet_wrap(~continent)

Let’s find the countries with bad fit

bad_fit <- models %>% 
  unnest(glance, .drop = TRUE) %>% 
  filter(r.squared < 0.25)
gapminder %>% 
  semi_join(bad_fit, by = "country") %>% 
  ggplot(aes(year, lifeExp, colour = country)) +
    geom_line()

Many Models - Gene Expression

Please refer to two part series of blog posts by David Robinson, part1 and part2

Assignment

About Quiz

It will be about calculation of correlations with text analysis.


Last Updated 2017-12-07

LS0tCnRpdGxlOiB8ICAgCiB8IERhdGEgQW5hbHlzaXMgYW5kIFZpc3VhbGl6YXRpb24gIAogfCBMZXNzb24gMTIgICAKIHwgTGluZWFyIFJlZ3Jlc3Npb24gYW5kIE1hbnkgTW9kZWxzIAphdXRob3I6ICJhbHBlciB5aWxtYXoiCmRhdGU6ICJEZWNlbWJlciA1dGgsIDIwMTciCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIHNldHVwLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQojIHBsZWFzZSBsb2FkIG5lY2Nhc2FyeSBwYWNrYWdlcywgaW5zdGFsbCBpZiBhbnkgbWlzc2luZwpsaWJyYXJ5KGdhcG1pbmRlcikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkobW9kZWxyKQpsaWJyYXJ5KHB1cnJyKSAjIGxvYWRlZCB3aXRoIHRpZHl2ZXJzZSBidXQgc3RpbGwgaGF2aW5nIGl0IGhlcmUgdG8gc2hvdyBtYXAoKSBuZWVkcyBwdXJyciBwYWNrYWdlCmxpYnJhcnkoZ2dwbG90MikKYGBgCgoqKioKCiMgT3ZlcnZpZXcgb2YgbGluZWFyIHJlZ3Jlc3Npb24KCiMjIHNpbXBsZSByZWdyZXNzaW9uCgoKU29tZSBjb2RlIGhhcyBiZWVuIGFkYXB0ZWQgZnJvbSBbYnJvb20gcGFja2FnZSB2aWduZXR0ZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2Jyb29tL3ZpZ25ldHRlcy9icm9vbS5odG1sKS4KCmBgYHtyfQpsbWZpdCA8LSBsbShtcGcgfiB3dCwgbXRjYXJzKQpsbWZpdApgYGAKClRoZSBmb3JtdWxhIG5vdGF0aW9uIGNhbiBiZSByZWFkIGFzICJgbXBnYCBleHBsYWluZWQgYnkgYHd0YCIuIExldCdzIHNlZSB0aGUgc2NhdHRlcnBsb3QgYmV0d2VlbiB0aGVzZSB0d28gdmFyaWFibGVzCgpgYGB7cn0KZ2dwbG90KG10Y2FycywgYWVzKHd0LG1wZykpICsKICBnZW9tX3BvaW50KCkKYGBgCgpXZSBjYW4gZ2V0IG1vcmUgaW5mb3JtYXRpb24gb3V0IG9mIHRoZSBtb2RlbAoKYGBge3J9CnN1bW1hcnkobG1maXQpCnN1bW1hcnkobG1maXQpJHIuc3F1YXJlZApjb2VmZmljaWVudHMobG1maXQpCmhlYWQocmVzaWR1YWxzKGxtZml0KSkKYGBgCgoKIyMgaG93IHRvIGV4dHJhY3QgcmVzdWx0cyAtIHRpZHkgd2F5CgpMZXQncyBleHRyYWN0IG5lY2Vzc2FyeSBpbmZvcm1hdGlvbiB0aWR5IHdheSB3aXRoIGhlbHAgb2YgYGJyb29tYCBwYWNrYWdlIGZ1bmN0aW9ucwoKYGBge3J9CmJyb29tOjp0aWR5KGxtZml0KQpicm9vbTo6YXVnbWVudChsbWZpdCkgJT4lICBoZWFkKCkKYnJvb206OmdsYW5jZShsbWZpdCkKYGBgCgojIyBwcmVkaWN0aW9uIHdpdGggbW9kZWwKCkxldCdzIGdlbmVyYXRlIGEgZHVtbXkgZGF0YSBmcmFtZQoKYGBge3J9CnRlc3QgPC0gZGF0YV9mcmFtZSh3dD1jKDMuNSw1LDIuNSksIGR1bW15PWMoMTAsMTUsMjApKQp0ZXN0CmBgYAoKTGV0J3MgcHJlZGljdCBgbXBnYCB2YWx1ZXMgYmFzZWQgb24gbmV3IGB3dGAgdmFsdWVzLgoKYGBge3J9CnByZWRpY3QobG1maXQsdGVzdCkKCiMgbGV0J3Mga2VlcCB0aGVtIHRvZ2V0aGVyCnRlc3QkcHJlZGljdGlvbiA8LXByZWRpY3QobG1maXQsdGVzdCkKCnRlc3QKYGBgCgpMZXQncyB2aXN1YWxpemUgdGhpcyBjb25jZXB0CgpgYGB7cn0KZ2dwbG90KG10Y2FycywgYWVzKHd0LG1wZykpICsKICBnZW9tX3BvaW50KCkrCiAgZ2VvbV9wb2ludChkYXRhPXRlc3QsIGFlcyh3dCxwcmVkaWN0aW9uKSwgY29sPSJyZWQiKQpgYGAKCiMjIFF1YWxpdHkgb2YgcmVncmVzc2lvbgoKUk1TRSBpcyB1c2VkIHRvIG1lYXN1cmUgcXVhbGl0eSBvZiByZWdyZXNzaW9uCgohW1JNU0VdKHJtc2UucG5nKQoKVGhpcyBjb2RlIGlzIGZyb20gRGF0YUNhbXAKCmBgYHtyfQpwcmVkIDwtIHByZWRpY3QobG1maXQpCnByZWQKcm1zZSA8LSBzcXJ0KHN1bSggKG10Y2FycyRtcGcgLSBwcmVkKSBeIDIpIC8gbnJvdyhtdGNhcnMpKQpybXNlCgojIE9SCiMgQXBwbHkgcHJlZGljdCgpIHRvIG10Y2FyczogCm10Y2Fyc19lc3QgPC0gcHJlZGljdChsbWZpdCxtdGNhcnMpCgojIENhbGN1bGF0ZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHByZWRpY3RlZCBhbmQgdGhlIHRydWUgdmFsdWVzOiByZXMKcmVzIDwtIG10Y2FycyRtcGcgLSBtdGNhcnNfZXN0CgojIENhbGN1bGF0ZSBSTVNFLCBhc3NpZ24gaXQgdG8gcm1zZSBhbmQgcHJpbnQgaXQKcm1zZSA8LSBzcXJ0KHN1bSgocmVzXjIpKS9ucm93KG10Y2FycykpCnJtc2UKYGBgCgoKIyBNYW55IE1vZGVscyAtIEdhcE1pbmRlcgoKVGhlIGNvZGUgaXMgdGFrZW4gZnJvbSBIYWRsZXkgV2lja2hhbSdzIFt0YWxrXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PXJ6M19GRFZ0OWVnKSBhbmQgWypNYW55IE1vZGVscypdKGh0dHA6Ly9yNGRzLmhhZC5jby5uei9tYW55LW1vZGVscy5odG1sKSBjaGFwdGVyIGluICpSIGZvciBEYXRhIFNjaWVuY2UqIGJvb2suIFNvbWUgY29kZSBoYWQgYmVlbiBtb2RpZmllZCBieSBhbHBlciB5aWxtYXouCgpgYGB7cn0KZ2FwbWluZGVyCmdhcG1pbmRlciA8LSBnYXBtaW5kZXIgJT4lIG11dGF0ZSh5ZWFyMTk1MCA9IHllYXIgLSAxOTUwKQpgYGAKCiMjIEdhcE1pbmRlciwgdGhlIGdvb2QgYW5kIHVnbHkgKG9yIHNvPykKClJlbWVtYmVyIEhhbnMgUm9zbGluZydzIFthbmltYXRpb25dKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9aFZpbVZ6Z3RENncjdD05bTI3cykgc2hvd2luZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gR0RQIGFuZCBMaWZlIEV4cGVjdGFuY3k/IEhlcmUncyB0aGUgYW5pbWF0aW9uIHJlLWdlbmVyYXRlZCB3aXRoIGBnZ3Bsb3RgCgohW0dhcG1pbmRlciBBbmltYXRpb25dKGh0dHBzOi8vczMtdXMtd2VzdC0yLmFtYXpvbmF3cy5jb20vdmVyaS1hbmFsaXppL2dhcG1pbmRlci1zbWFsbC5naWYpCgpCZWxvdyBpcyBIYWRsZXkgV2lja2hhbSdzIGxpbmUgZ3JhcGggc2hvd2luZyBhbGwgY291bnRyaWVzIGZvciBhbGwgeWVhcnMuCgpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD01fQpnYXBtaW5kZXIgJT4lIAogIGdncGxvdChhZXMoeWVhciwgbGlmZUV4cCwgZ3JvdXAgPSBjb3VudHJ5KSkgKwogIGdlb21fbGluZShhbHBoYSA9IDEvMykKYGBgCgpUaGVyZSdzIG92ZXJhbGwgaW5jcmVhc2luZyB0cmVuZC4gQnV0IHNvbWUgY291bnRyaWVzIGRvbid0IGZvbGxvdyB0aGUgcGF0dGVybiwgaG93IGNhbiB3ZSBmaW5kIHRob3NlIGNvdW50cmllcy4KCkZvciBzaW5nbGUgY291bnRyeSB3ZSBjYW4gcHVsbCB0aGUgZGF0YSBhbmQgZHJhdyB0aGUgdHJlbmQKCmBgYHtyIGZpZy5zaG93PSdob2xkJ30KdHIgPC0gZmlsdGVyKGdhcG1pbmRlciwgY291bnRyeSA9PSAiVHVya2V5IikKdHIgJT4lIAogIGdncGxvdChhZXMoeWVhciwgbGlmZUV4cCkpICsgCiAgZ2VvbV9saW5lKCkgKyAKICBnZ3RpdGxlKCJGdWxsIGRhdGEgPSAiKQoKdHJfbW9kIDwtIGxtKGxpZmVFeHAgfiB5ZWFyLCBkYXRhID0gdHIpCnRyICU+JSAKICBhZGRfcHJlZGljdGlvbnModHJfbW9kKSAlPiUKICBnZ3Bsb3QoYWVzKHllYXIsIHByZWQpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2d0aXRsZSgiTGluZWFyIHRyZW5kICsgIikKCnRyICU+JSAKICBhZGRfcmVzaWR1YWxzKHRyX21vZCkgJT4lIAogIGdncGxvdChhZXMoeWVhciwgcmVzaWQpKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG91ciA9ICJ3aGl0ZSIsIHNpemUgPSAzKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2d0aXRsZSgiUmVtYWluaW5nIHBhdHRlcm4iKQpgYGAKCkJ1dCB0aGlzIGlzIG5vdCBmZWFzaWJsZSB0byBkbyBmb3IgbWFueSBjb3VudHJpZXMuCgo+IFZpc3VhbGl6YXRpb24gY2FuIHN1cnByaXNlIHlvdSwgYnV0IGl0IGRvZXNu4oCZdCBzY2FsZSB3ZWxsLiBNb2RlbGluZyBzY2FsZXMgd2VsbCwgYnV0IGl0IGNhbuKAmXQgc3VycHJpc2UgeW91Lgo+IC0gSGFkbGV5IFdpY2toYW0gKGJ5IHdheSBvZiBKb2huIEQuIENvb2spCgoKIyMgTmVzdGVkIGRhdGEKClBsZWFzZSByZWZlciB0byBbKmxpc3QgY29sdW1ucypdKGh0dHA6Ly9yNGRzLmhhZC5jby5uei9tYW55LW1vZGVscy5odG1sI2xpc3QtY29sdW1ucy0xKSBzZWN0aW9uIGFuZCBmb2xsb3dpbmcgY291cGxlIHNlY3Rpb25zIGluIFI0RFMgYm9vayB0byB1bmRlcnN0YW5kIGxpc3QgY29sdW1uIGNvbmNlcHQgYmV0dGVyLgoKYGBge3J9CmJ5X2NvdW50cnkgPC0gZ2FwbWluZGVyICU+JSAKICAgICAgICBncm91cF9ieShjb250aW5lbnQsIGNvdW50cnkpICU+JSAKICAgICAgICBuZXN0KCkKCmJ5X2NvdW50cnkKIyBzdHIoYnlfY291bnRyeSkgICAgICAgIyBkb2VzIG5vdCBoZWxwCmJ5X2NvdW50cnkkZGF0YVtbMV1dICAgICMgY2FuIHJlYWNoIHRoZSBjb250ZW50cyBvZiBsaXN0IGNvbHVtbgoKIyMgaG93IGRvIHdlIGRvIHRoaXMgd2l0aCBzdWJzZXQgaW5kZXhpbmc/CiMgYnlfY291bnRyeSRkYXRhW2NvdW50cnk9PSJUdXJrZXkiXQoKdHJfZGF0YSA8LWJ5X2NvdW50cnkgJT4lIAogIGZpbHRlcihjb3VudHJ5ID09ICJUdXJrZXkiKSAKCnRyX2RhdGEkZGF0YVtbMV1dCgpieV9jb3VudHJ5ICU+JSAKICBmaWx0ZXIoY291bnRyeSA9PSAiVHVya2V5IikgJT4lIAogIHVubmVzdChkYXRhKQpgYGAKCiMjIEZpdCBtb2RlbHMKCmBgYHtyfQpjb3VudHJ5X21vZGVsIDwtIGZ1bmN0aW9uKGRmKXsgbG0obGlmZUV4cCB+IHllYXIxOTUwLCBkYXRhID0gZGYpIH0KCm1vZGVscyA8LSBieV9jb3VudHJ5ICU+JSAKICAgICAgICBtdXRhdGUoIG1vZCA9IG1hcChkYXRhLCBjb3VudHJ5X21vZGVsKSApCgojIHRyeSB3aXRob3V0IGZ1bmN0aW9uCgptb2RlbHMKbW9kZWxzICU+JSBmaWx0ZXIoY29udGluZW50ID09ICJBZnJpY2EiKQpgYGAKCiMjIEJyb29tCgpBcyB5b3UgbWlnaHQgcmVtZW1iZXIgdGhlIG91dHB1dCBvZiBgc3VtbWFyeSgpYCBvciBgZ2xhbmNlKClgIGFyZSBub3QgaW4gdGlkeSBmb3JtYXQuIEJlbG93IGlzIHRoZSBzdW1tYXJ5IG9mIG1vZGVsIGJlbG9uZ2luZyB0byBmaXJzdCBjb3VudHJ5LiAKCmBgYHtyfQpzdW1tYXJ5KCBtb2RlbHMkbW9kW1sxXV0gKQpgYGAKClRoZSBgYnJvb21gIHBhY2thZ2UgY29tZXMgdG8gcmVzY3VlLCBlc3BlY2lhbGx5IHdoZW4gZGVhbGluZyB3aXRoIG1hbnkgbW9kZWxzLiBMZXQncyBleHRyYWN0IHIgc3F1YXJlZCB2YWx1ZXMgZnJvbSBtb2RlbHMgYW5kIGF0dGFjaCB0byB0aWJibGUuCgpgYGB7cn0KbW9kZWxzICU+JSAKICBtdXRhdGUoZ2xhbmNlID0gbW9kICU+JSBtYXAoYnJvb206OmdsYW5jZSksCiAgICAgICAgIHJzcSA9IGdsYW5jZSAlPiUgbWFwX2RibCgici5zcXVhcmVkIikpCiAgIAojIE9SICAgICAgCiMgbW9kZWxzICU+JSAKIyAgIG11dGF0ZShyc3EgPSBtb2QgJT4lIG1hcChicm9vbTo6Z2xhbmNlKSAlPiUgbWFwX2RibCgici5zcXVhcmVkIikpCmBgYAoKTGV0J3MgZXh0cmFjdCBhbGwgbW9kZWwgcmVsYXRlZCBkYXRhIGFuZCBrZWVwIGFzIGxpc3QgY29sdW1uIHdpdGhpbiBzYW1lIHRpYmJsZS4KCmBgYHtyfQptb2RlbHMgPC0gbW9kZWxzICU+JSAKICAgICAgICBtdXRhdGUoCiAgICAgICAgICAgICAgICBnbGFuY2UgPSBtb2QgJT4lIG1hcChicm9vbTo6Z2xhbmNlKSwKICAgICAgICAgICAgICAgIHJzcSA9IGdsYW5jZSAlPiUgbWFwX2RibCgici5zcXVhcmVkIiksCiAgICAgICAgICAgICAgICB0aWR5ID0gbW9kICU+JSBtYXAoYnJvb206OnRpZHkpLAogICAgICAgICAgICAgICAgYXVnbWVudCA9IG1vZCAlPiUgbWFwKGJyb29tOjphdWdtZW50KQogICAgICAgICkKbW9kZWxzCmBgYAoKQXMgd2Ugc2F3IGVhcmxpZXIsIHdlIGNhbiBhY2Nlc3MgdGhlIGNvbnRlbnRzIG9mIGVhY2ggbGlzdCBjb2x1bW5zIGlmIHdlIGhhZCB0bwoKYGBge3J9Cm1vZGVscyRnbGFuY2VbWzFdXQptb2RlbHMkdGlkeVtbMV1dCm1vZGVscyRhdWdtZW50W1sxXV0KYGBgCgpUaGUgdGliYmxlIHN0aWxsIGJlaGF2ZXMgc2FtZSwgYGRwbHlyYCB2ZXJicyBvciBgZ2dwbG90YCBjYW4gYmUgdXNlZCB3aXRoIGl0CgpgYGB7cn0KbW9kZWxzICU+JSBhcnJhbmdlKGRlc2MocnNxKSkKbW9kZWxzICU+JSBmaWx0ZXIoY29udGluZW50ID09ICJBZnJpY2EiKQoKbW9kZWxzICU+JSAKICAgICAgICBnZ3Bsb3QoYWVzKHJzcSwgcmVvcmRlcihjb3VudHJ5LCByc3EpKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IGNvbnRpbmVudCkpCmBgYAoKIyMgVW5uZXN0CgpgYGB7cn0KdW5uZXN0KG1vZGVscywgZGF0YSkKdW5uZXN0KG1vZGVscywgZ2xhbmNlLCAuZHJvcCA9IFRSVUUgKQp1bm5lc3QobW9kZWxzLCB0aWR5KQoKbW9kZWxzICU+JSAKICAgICAgICB1bm5lc3QodGlkeSkgJT4lIAogICAgICAgIHNlbGVjdChjb250aW5lbnQsIGNvdW50cnksIHRlcm0sIGVzdGltYXRlLCByc3EpICU+JSAKICAgICAgICBzcHJlYWQodGVybSwgZXN0aW1hdGUpICU+JSAKICAgICAgICBnZ3Bsb3QoYWVzKGAoSW50ZXJjZXB0KWAsIHllYXIxOTUwKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IGNvbnRpbmVudCwgc2l6ZSA9IHJzcSkpICsKICAgICAgICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgICAgICAgeGxhYigiTGlmZSBFeHBlY3RhbmN5ICgxOTUwKSIpICsKICAgICAgICB5bGFiKCJZZWFybHkgSW1wcm92ZW1lbnQiKSArCiAgICAgICAgc2NhbGVfc2l6ZV9hcmVhKCkKCnVubmVzdChtb2RlbHMsIGF1Z21lbnQpCgptb2RlbHMgJT4lIAogICAgICAgIHVubmVzdChhdWdtZW50KSAlPiUgCiAgICAgICAgZ2dwbG90KGFlcyh5ZWFyMTk1MCwgLnJlc2lkKSkgKwogICAgICAgIGdlb21fbGluZShhZXMoZ3JvdXAgPSBjb3VudHJ5LCBhbHBoYSA9IDEvMykpICsKICAgICAgICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3VyID0gIndoaXRlIikgKwogICAgICAgIGZhY2V0X3dyYXAofmNvbnRpbmVudCkKYGBgCgpMZXQncyBmaW5kIHRoZSBjb3VudHJpZXMgd2l0aCBiYWQgZml0CgpgYGB7cn0KCmJhZF9maXQgPC0gbW9kZWxzICU+JSAKICB1bm5lc3QoZ2xhbmNlLCAuZHJvcCA9IFRSVUUpICU+JSAKICBmaWx0ZXIoci5zcXVhcmVkIDwgMC4yNSkKCmdhcG1pbmRlciAlPiUgCiAgc2VtaV9qb2luKGJhZF9maXQsIGJ5ID0gImNvdW50cnkiKSAlPiUgCiAgZ2dwbG90KGFlcyh5ZWFyLCBsaWZlRXhwLCBjb2xvdXIgPSBjb3VudHJ5KSkgKwogICAgZ2VvbV9saW5lKCkKYGBgCgoKIyBNYW55IE1vZGVscyAtIEdlbmUgRXhwcmVzc2lvbgoKUGxlYXNlIHJlZmVyIHRvIHR3byBwYXJ0IHNlcmllcyBvZiBibG9nIHBvc3RzIGJ5IERhdmlkIFJvYmluc29uLCBbcGFydDFdKGh0dHA6Ly92YXJpYW5jZWV4cGxhaW5lZC5vcmcvci90aWR5LWdlbm9taWNzLykgYW5kIFtwYXJ0Ml0oaHR0cDovL3ZhcmlhbmNlZXhwbGFpbmVkLm9yZy9yL3RpZHktZ2Vub21pY3MtYnJvb20vKQoKIyBBc3NpZ25tZW50CgoqIERhdGFDYW1wIC0gW0ludHJvZHVjdGlvbiB0byBNYWNoaW5lIExlYXJuaW5nXShodHRwczovL3d3dy5kYXRhY2FtcC5jb20vY291cnNlcy9pbnRyb2R1Y3Rpb24tdG8tbWFjaGluZS1sZWFybmluZy13aXRoLXIpIC0gKipDaGFwdGVycyAzIGFuZCA0KiogCgojIEFib3V0IFF1aXoKCkl0IHdpbGwgYmUgYWJvdXQgY2FsY3VsYXRpb24gb2YgY29ycmVsYXRpb25zIHdpdGggdGV4dCBhbmFseXNpcy4KCioqKgoKTGFzdCBVcGRhdGVkIGByIFN5cy5EYXRlKClg