Overview
This is a reproduction of the workflow for model building, which comes from Chapter 24 of R for Data Science. The purpose was to learn the concepts. I stick pretty close to the book, but you may find me taking a few creative liberties.
The diamond analysis walks through the process of building models with patterns that are hidden or confounded by other more prevalent patterns. For the diamond prices, carat weight confounds many of the other trends, and these trends are pulled out through the model building process.
The flight analysis walks through the process of building a model to predict flight counts by day over a time series. Two approaches are used. The first develops the model using day-of-the-week and seasonality trends to fit a model. The second (alternative) approach uses day-of-the-week and splines for seasonality. We use MASS::rlm() for the final model because it is much more resistant to outliers than lm().
Diamond Analysis
Answer question: Why are low quality diamonds more expensive?
ggplot(diamonds, aes(cut, price)) + geom_boxplot()

ggplot(diamonds, aes(color, price)) + geom_boxplot()

ggplot(diamonds, aes(clarity, price)) + geom_boxplot()

Price and Carat
Explore price and relationship to carat. Use hex scatter plot to show concentration.
ggplot(diamonds, aes(carat, price)) +
geom_hex(bins = 50)

Need to transform data to get linear trend:
A log transformation will create a linear pattern that can be easily modelled. Hadley uses log base 2 and filters to remove carat size > 2.5. Log base 2 is needed for graph to turn out when performing the re-transform. Filtering out diamonds above size 2.5 is needed because the predictions become way off beyond 2.5 carats.
# remove very large diamonds (outliers), log transform price and carat
diamonds2 <- diamonds %>%
filter(carat <= 2.5) %>%
mutate(lprice = log2(price),
lcarat = log2(carat))
# Visualize linear pattern of log transform
ggplot(diamonds2, aes(lcarat, lprice)) +
geom_hex(bins = 50)

Fit the model and visualize. Note that to visualize the data set, we re-transform to get price after the predictions to log2(price) are made.
# Model
mod_diamond <- lm(lprice ~ lcarat, data = diamonds2)
# Create data_grid() along range of carat, log transform and add_predicitons()
grid <- diamonds2 %>%
data_grid(carat = seq_range(carat, 20)) %>%
mutate(lcarat = log2(carat)) %>%
add_predictions(mod_diamond, "lprice") %>%
mutate(price = 2 ^ lprice)
# Visualize re-transformed prediction
ggplot(diamonds2, aes(carat, price)) +
geom_hex(bins = 50) +
geom_line(data = grid, color = "red", size = 1)

Check residuals to make sure linear pattern is removed that was due to carat size.
# add_residuals()
diamonds2 <- diamonds2 %>%
add_residuals(mod_diamond, 'lresid')
# Visulize residuals
ggplot(diamonds2, aes(lcarat, lresid)) +
geom_hex(bins = 50)

Plot the residuals as a function of cut, color, and clarity. Plotting against residuals removes the effect of carat size, which dominated cut, color, and clarity effects.
We now see the effect we expected:
ggplot(diamonds2, aes(cut, lresid)) + geom_boxplot()

ggplot(diamonds2, aes(color, lresid)) + geom_boxplot()

ggplot(diamonds2, aes(clarity, lresid)) + geom_boxplot()

A more complicated model
We can add in effects due to color, cut, and clarity.
mod_diamond2 <- lm(lprice ~ lcarat + color + cut + clarity, data = diamonds2)
Create grid for new model. Use .model argument to data_grid() so predictors not specified are filled in with typical values. See documentation ?data_gird.
# Show output of data_grid() and add_predictions()
grid <- diamonds2 %>%
data_grid(cut, .model = mod_diamond2) %>%
add_predictions(mod_diamond2)
grid
Visualize predictions (pred) of lprice versus cut:
grid %>%
ggplot(aes(cut, pred)) +
geom_point()

Check residuals of new model:
# Add residuals
diamonds2 <- diamonds2 %>%
add_residuals(mod_diamond2, "lresid2")
# Visualize residuals
ggplot(diamonds2, aes(lcarat, lresid2)) +
geom_hex(bins = 50)

Some diamonds have very large residuals. A residual of 2 indicates value is 2 ^ 2 = 4X our prediction. Can review diamonds with large residuals greater than abs(lresid2) > 1.
diamonds2 %>%
filter(abs(lresid2) > 1) %>%
add_predictions(mod_diamond2) %>%
mutate(pred = round(2 ^ pred)) %>%
select(price, pred, carat:table, x:z) %>%
arrange(price)
Exercises
Exercise 4
Does the final model, mod_diamonds2, do a good job of predicting diamond prices? Would you trust it to tell you how much to spend if you were buying a diamond?
One way to determine the accuracy of the model is to examine the difference between the actual and predicted price. We’ll take a look at a histogram first.
# Add error calculation
diamonds2 <- diamonds2 %>%
add_predictions(mod_diamond2) %>%
mutate(pred = round(2 ^ pred),
err = pred - price)
# Visualize error
diamonds2 %>%
ggplot(aes(err)) +
geom_histogram(bins = 50)

Th predictions appear to be centered around zero, but it’s difficult to see the range of errors. To better characterize the error, we can review the distribution using quantile().
probs <- c(0.005, 0.025, 0.25, 0.5, 0.75, 0.975, 0.995)
diamonds2$err %>% quantile(probs = probs)
0.5% 2.5% 25% 50% 75% 97.5% 99.5%
-2826.935 -1742.000 -195.000 1.000 156.000 1381.350 3196.480
The model error (difference between actual and predicted price) is 95% of the time within [-$1742, $1381]. This is relatively high considering the median price is $2,396. Therefore, there is considerable variability in the predictions that should be weighed before using the model.
Flight Analysis
Question to answer: What affects the number of daily flights?
daily <- flights %>%
mutate(date = make_date(year, month, day)) %>%
group_by(date) %>%
summarise(n = n())
daily
ggplot(daily, aes(date, n)) +
geom_line()

Modelling the weekday effect
Problem: Understanding the trend is difficult due to the day of week variability.
Solution: Analyze the day of the week and remove the variability using a model.
daily <- daily %>%
mutate(wday = wday(date, label = TRUE))
ggplot(daily, aes(wday, n)) +
geom_boxplot()

Saturday is noticeably less due to less business travel.
We will apply a model to help reduce the variability.
# Model
mod <- lm(n ~ wday, data = daily)
# Create grid and add predictions
grid <- daily %>%
data_grid(wday) %>%
add_predictions(mod, "n")
# Visualize model
ggplot(daily, aes(wday, n)) +
geom_boxplot() +
geom_point(data = grid, colour = "red", size = 4)

Next, visualize the residuals:
# add_residuals()
daily <- daily %>%
add_residuals(mod)
# Visualize residuals
daily %>%
ggplot(aes(date, resid)) +
geom_ref_line(h = 0) +
geom_line()

Day of the week effects have been removed, but there is still patterns that remain due to seasonality.
- Viewing by weekday helps to show the large deviations. Saturday is noticeably different during summer (more flights than expected) and fall (less flights than expected).
ggplot(daily, aes(date, resid, colour = wday)) +
geom_ref_line(h = 0) +
geom_line()

- Some days have far fewer flights than expected:
daily %>% filter(resid < -100)
- There appears to be a long term trend over the course of the year.
daily %>%
ggplot(aes(date, resid)) +
geom_ref_line(h = 0) +
geom_line(colour = "grey50") +
geom_smooth(se = FALSE, span = 0.20)

Modelling the seasonal effect
Viewing Saturday highlights the seasonality effect:
daily %>%
filter(wday == "Sat") %>%
ggplot(aes(date, n)) +
geom_point() +
geom_line() +
scale_x_date(NULL, date_breaks = "1 month", date_labels = "%b")

Create a term variable to split dates into spring, summer and fall seasons:
# Function to classify date as spring, summer, fall seasons
term <- function(date) {
cut(date,
breaks = ymd(20130101, 20130605, 20130825, 20140101),
labels = c("spring", "summer", "fall")
)
}
# Add season classification
daily <- daily %>%
mutate(term = term(date))
# Visualize season
daily %>%
filter(wday == "Sat") %>%
ggplot(aes(date, n, colour = term)) +
geom_point(alpha = 1/3) +
geom_line() +
scale_x_date(NULL, date_breaks = "1 month", date_labels = "%b")

We can visualize the term by day of the week:
daily %>%
ggplot(aes(wday, n, colour = term)) +
geom_boxplot()

We can see summer tends to be higher, while fall and spring tend to be about the same with the exception of Saturday.
We can model using an interaction effect between weekday (wday) and season (term). Use gather_residuals() to compute residuals on both models for comparison.
# Models to compare
mod1 <- lm(n ~ wday, data = daily)
mod2 <- lm(n ~ wday * term, data = daily)
# gather_residuals(), visualize both models
daily %>%
gather_residuals(without_term = mod1, with_term = mod2) %>%
ggplot(aes(date, resid, colour = model)) +
geom_line(alpha = 0.75)

Some improvement in summer, but not as much as we want. Visualizing predictions by term and day shows an issue.
# data_grid() by wday and term, add_predictions()
grid <- daily %>%
data_grid(wday, term) %>%
add_predictions(mod2, "n")
# Visualize predictions by term and wday
ggplot(daily, aes(wday, n)) +
geom_boxplot() +
geom_point(data = grid, colour = "red") +
facet_wrap(~ term)

Problem: Our model is finding the mean effect, but large outliers are throwing it off.
Solution: Use a modelling algorithm that is affected less by outliers. MASS::rlm() is resistant to outliers.
# Model using rlm()
mod3 <- MASS::rlm(n ~ wday * term, data = daily)
# Visualize residuals
daily %>%
add_residuals(mod3, "resid") %>%
ggplot(aes(date, resid)) +
geom_hline(yintercept = 0, size = 2, colour = "white") +
geom_line()

Alternative approach
We can use splines::ns() to model by weekday throughout the year.
library(splines)
# Model using rlm() with 5th order polynomial for date
mod <- MASS::rlm(n ~ wday * ns(date, 5), data = daily)
# data_grid() by wday along 13 evenly spaced points, add_predictions()
# and visualize
daily %>%
data_grid(wday, date = seq_range(date, n = 13)) %>%
add_predictions(mod) %>%
ggplot(aes(date, pred, colour = wday)) +
geom_line() +
geom_point()

We can also check the residuals to see how this approach compares to the previous model.
daily %>%
add_residuals(mod) %>%
ggplot(aes(date, resid)) +
geom_hline(yintercept = 0, size = 2, colour = "white") +
geom_line()

LS0tDQp0aXRsZTogJ0NoYXB0ZXIgMjQ6IE1vZGVsIEJ1aWxkaW5nJw0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMw0KICBodG1sX2RvY3VtZW50Og0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KbGlicmFyeShrbml0cikNCm9wdHNfY2h1bmskc2V0KGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTQsIGZpZy5hbGlnbj0nY2VudGVyJywNCiAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFKQ0KYGBgDQoNCiMgT3ZlcnZpZXcNCg0KVGhpcyBpcyBhIHJlcHJvZHVjdGlvbiBvZiB0aGUgd29ya2Zsb3cgZm9yIG1vZGVsIGJ1aWxkaW5nLCB3aGljaCBjb21lcyBmcm9tIFtDaGFwdGVyIDI0IG9mIF9SIGZvciBEYXRhIFNjaWVuY2VfXShodHRwOi8vcjRkcy5oYWQuY28ubnovbW9kZWwtYnVpbGRpbmcuaHRtbCkuIFRoZSBwdXJwb3NlIHdhcyB0byBsZWFybiB0aGUgY29uY2VwdHMuIEkgc3RpY2sgcHJldHR5IGNsb3NlIHRvIHRoZSBib29rLCBidXQgeW91IG1heSBmaW5kIG1lIHRha2luZyBhIGZldyBjcmVhdGl2ZSBsaWJlcnRpZXMuDQoNClRoZSBkaWFtb25kIGFuYWx5c2lzIHdhbGtzIHRocm91Z2ggdGhlIHByb2Nlc3Mgb2YgYnVpbGRpbmcgbW9kZWxzIHdpdGggcGF0dGVybnMgdGhhdCBhcmUgaGlkZGVuIG9yIGNvbmZvdW5kZWQgYnkgb3RoZXIgbW9yZSBwcmV2YWxlbnQgcGF0dGVybnMuIEZvciB0aGUgZGlhbW9uZCBwcmljZXMsIGNhcmF0IHdlaWdodCBjb25mb3VuZHMgbWFueSBvZiB0aGUgb3RoZXIgdHJlbmRzLCBhbmQgdGhlc2UgdHJlbmRzIGFyZSBwdWxsZWQgb3V0IHRocm91Z2ggdGhlIG1vZGVsIGJ1aWxkaW5nIHByb2Nlc3MuIA0KDQpUaGUgZmxpZ2h0IGFuYWx5c2lzIHdhbGtzIHRocm91Z2ggdGhlIHByb2Nlc3Mgb2YgYnVpbGRpbmcgYSBtb2RlbCB0byBwcmVkaWN0IGZsaWdodCBjb3VudHMgYnkgZGF5IG92ZXIgYSB0aW1lIHNlcmllcy4gVHdvIGFwcHJvYWNoZXMgYXJlIHVzZWQuIFRoZSBmaXJzdCBkZXZlbG9wcyB0aGUgbW9kZWwgdXNpbmcgZGF5LW9mLXRoZS13ZWVrIGFuZCBzZWFzb25hbGl0eSB0cmVuZHMgdG8gZml0IGEgbW9kZWwuIFRoZSBzZWNvbmQgKGFsdGVybmF0aXZlKSBhcHByb2FjaCB1c2VzIGRheS1vZi10aGUtd2VlayBhbmQgc3BsaW5lcyBmb3Igc2Vhc29uYWxpdHkuIFdlIHVzZSBgTUFTUzo6cmxtKClgIGZvciB0aGUgZmluYWwgbW9kZWwgYmVjYXVzZSBpdCBpcyBtdWNoIG1vcmUgcmVzaXN0YW50IHRvIG91dGxpZXJzIHRoYW4gYGxtKClgLg0KDQotLS0tDQoNCiMgUHJlcmVxdWlzaXRlcw0KDQpgYGB7cn0NCmxpYnJhcnkobW9kZWxyKQ0Kb3B0aW9ucyhuYS5hY3Rpb24gPSBuYS53YXJuKQ0KbGlicmFyeSh0aWR5dmVyc2UpICMgZ2dwbG90MiwgcHVycnIsIGRwbHlyLCB0aWR5ciwgcmVhZHIsIHRpYmJsZQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykNCmBgYA0KDQotLS0tDQoNCiMgRGlhbW9uZCBBbmFseXNpcw0KDQpfX0Fuc3dlciBxdWVzdGlvbjpfXyBXaHkgYXJlIGxvdyBxdWFsaXR5IGRpYW1vbmRzIG1vcmUgZXhwZW5zaXZlPw0KDQpgYGB7cn0NCmdncGxvdChkaWFtb25kcywgYWVzKGN1dCwgcHJpY2UpKSArIGdlb21fYm94cGxvdCgpDQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyhjb2xvciwgcHJpY2UpKSArIGdlb21fYm94cGxvdCgpDQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyhjbGFyaXR5LCBwcmljZSkpICsgZ2VvbV9ib3hwbG90KCkNCmBgYA0KDQojIyBQcmljZSBhbmQgQ2FyYXQNCg0KRXhwbG9yZSBwcmljZSBhbmQgcmVsYXRpb25zaGlwIHRvIGNhcmF0LiBVc2UgaGV4IHNjYXR0ZXIgcGxvdCB0byBzaG93IGNvbmNlbnRyYXRpb24uDQoNCmBgYHtyfQ0KZ2dwbG90KGRpYW1vbmRzLCBhZXMoY2FyYXQsIHByaWNlKSkgKyANCiAgZ2VvbV9oZXgoYmlucyA9IDUwKQ0KYGBgDQoNCl9fTmVlZCB0byB0cmFuc2Zvcm0gZGF0YSB0byBnZXQgbGluZWFyIHRyZW5kOl9fDQoNCkEgbG9nIHRyYW5zZm9ybWF0aW9uIHdpbGwgY3JlYXRlIGEgbGluZWFyIHBhdHRlcm4gdGhhdCBjYW4gYmUgZWFzaWx5IG1vZGVsbGVkLiBIYWRsZXkgdXNlcyBsb2cgYmFzZSAyIGFuZCBmaWx0ZXJzIHRvIHJlbW92ZSBjYXJhdCBzaXplID4gMi41LiBMb2cgYmFzZSAyIGlzIG5lZWRlZCBmb3IgZ3JhcGggdG8gdHVybiBvdXQgd2hlbiBwZXJmb3JtaW5nIHRoZSByZS10cmFuc2Zvcm0uIEZpbHRlcmluZyBvdXQgZGlhbW9uZHMgYWJvdmUgc2l6ZSAyLjUgaXMgbmVlZGVkIGJlY2F1c2UgdGhlIHByZWRpY3Rpb25zIGJlY29tZSB3YXkgb2ZmIGJleW9uZCAyLjUgY2FyYXRzLg0KDQpgYGB7cn0NCiMgcmVtb3ZlIHZlcnkgbGFyZ2UgZGlhbW9uZHMgKG91dGxpZXJzKSwgbG9nIHRyYW5zZm9ybSBwcmljZSBhbmQgY2FyYXQNCmRpYW1vbmRzMiA8LSBkaWFtb25kcyAlPiUNCiAgICBmaWx0ZXIoY2FyYXQgPD0gMi41KSAlPiUNCiAgICBtdXRhdGUobHByaWNlID0gbG9nMihwcmljZSksIA0KICAgICAgICAgICBsY2FyYXQgPSBsb2cyKGNhcmF0KSkNCiMgVmlzdWFsaXplIGxpbmVhciBwYXR0ZXJuIG9mIGxvZyB0cmFuc2Zvcm0NCmdncGxvdChkaWFtb25kczIsIGFlcyhsY2FyYXQsIGxwcmljZSkpICsNCiAgICBnZW9tX2hleChiaW5zID0gNTApDQpgYGANCg0KRml0IHRoZSBtb2RlbCBhbmQgdmlzdWFsaXplLiBOb3RlIHRoYXQgdG8gdmlzdWFsaXplIHRoZSBkYXRhIHNldCwgd2UgcmUtdHJhbnNmb3JtIHRvIGdldCBwcmljZSBhZnRlciB0aGUgcHJlZGljdGlvbnMgdG8gYGxvZzIocHJpY2UpYCBhcmUgbWFkZS4NCg0KYGBge3J9DQojIE1vZGVsDQptb2RfZGlhbW9uZCA8LSBsbShscHJpY2UgfiBsY2FyYXQsIGRhdGEgPSBkaWFtb25kczIpDQojIENyZWF0ZSBkYXRhX2dyaWQoKSBhbG9uZyByYW5nZSBvZiBjYXJhdCwgbG9nIHRyYW5zZm9ybSBhbmQgYWRkX3ByZWRpY2l0b25zKCkNCmdyaWQgPC0gZGlhbW9uZHMyICU+JQ0KICAgIGRhdGFfZ3JpZChjYXJhdCA9IHNlcV9yYW5nZShjYXJhdCwgMjApKSAlPiUNCiAgICBtdXRhdGUobGNhcmF0ID0gbG9nMihjYXJhdCkpICU+JQ0KICAgIGFkZF9wcmVkaWN0aW9ucyhtb2RfZGlhbW9uZCwgImxwcmljZSIpICU+JQ0KICAgIG11dGF0ZShwcmljZSA9IDIgXiBscHJpY2UpDQojIFZpc3VhbGl6ZSByZS10cmFuc2Zvcm1lZCBwcmVkaWN0aW9uDQpnZ3Bsb3QoZGlhbW9uZHMyLCBhZXMoY2FyYXQsIHByaWNlKSkgKyANCiAgICBnZW9tX2hleChiaW5zID0gNTApICsgDQogICAgZ2VvbV9saW5lKGRhdGEgPSBncmlkLCBjb2xvciA9ICJyZWQiLCBzaXplID0gMSkNCmBgYA0KDQpDaGVjayByZXNpZHVhbHMgdG8gbWFrZSBzdXJlIGxpbmVhciBwYXR0ZXJuIGlzIHJlbW92ZWQgdGhhdCB3YXMgZHVlIHRvIGNhcmF0IHNpemUuDQoNCmBgYHtyfQ0KIyBhZGRfcmVzaWR1YWxzKCkNCmRpYW1vbmRzMiA8LSBkaWFtb25kczIgJT4lDQogICAgYWRkX3Jlc2lkdWFscyhtb2RfZGlhbW9uZCwgJ2xyZXNpZCcpDQojIFZpc3VsaXplIHJlc2lkdWFscw0KZ2dwbG90KGRpYW1vbmRzMiwgYWVzKGxjYXJhdCwgbHJlc2lkKSkgKyANCiAgICBnZW9tX2hleChiaW5zID0gNTApDQpgYGANCg0KUGxvdCB0aGUgcmVzaWR1YWxzIGFzIGEgZnVuY3Rpb24gb2YgY3V0LCBjb2xvciwgYW5kIGNsYXJpdHkuIFBsb3R0aW5nIGFnYWluc3QgcmVzaWR1YWxzIHJlbW92ZXMgdGhlIGVmZmVjdCBvZiBjYXJhdCBzaXplLCB3aGljaCBkb21pbmF0ZWQgY3V0LCBjb2xvciwgYW5kIGNsYXJpdHkgZWZmZWN0cy4NCg0KV2Ugbm93IHNlZSB0aGUgZWZmZWN0IHdlIGV4cGVjdGVkOg0KDQpgYGB7cn0NCmdncGxvdChkaWFtb25kczIsIGFlcyhjdXQsIGxyZXNpZCkpICsgZ2VvbV9ib3hwbG90KCkNCmdncGxvdChkaWFtb25kczIsIGFlcyhjb2xvciwgbHJlc2lkKSkgKyBnZW9tX2JveHBsb3QoKQ0KZ2dwbG90KGRpYW1vbmRzMiwgYWVzKGNsYXJpdHksIGxyZXNpZCkpICsgZ2VvbV9ib3hwbG90KCkNCmBgYA0KDQojIyBBIG1vcmUgY29tcGxpY2F0ZWQgbW9kZWwNCg0KV2UgY2FuIGFkZCBpbiBlZmZlY3RzIGR1ZSB0byBgY29sb3JgLCBgY3V0YCwgYW5kIGBjbGFyaXR5YC4NCg0KYGBge3J9DQptb2RfZGlhbW9uZDIgPC0gbG0obHByaWNlIH4gbGNhcmF0ICsgY29sb3IgKyBjdXQgKyBjbGFyaXR5LCBkYXRhID0gZGlhbW9uZHMyKQ0KYGBgDQoNCkNyZWF0ZSBncmlkIGZvciBuZXcgbW9kZWwuIFVzZSBgLm1vZGVsYCBhcmd1bWVudCB0byBgZGF0YV9ncmlkKClgIHNvIHByZWRpY3RvcnMgbm90IHNwZWNpZmllZCBhcmUgZmlsbGVkIGluIHdpdGggdHlwaWNhbCB2YWx1ZXMuIFNlZSBkb2N1bWVudGF0aW9uIGA/ZGF0YV9naXJkYC4NCg0KYGBge3J9DQojIFNob3cgb3V0cHV0IG9mIGRhdGFfZ3JpZCgpIGFuZCBhZGRfcHJlZGljdGlvbnMoKQ0KZ3JpZCA8LSBkaWFtb25kczIgJT4lIA0KICBkYXRhX2dyaWQoY3V0LCAubW9kZWwgPSBtb2RfZGlhbW9uZDIpICU+JSANCiAgYWRkX3ByZWRpY3Rpb25zKG1vZF9kaWFtb25kMikNCmdyaWQgDQpgYGANCg0KVmlzdWFsaXplIHByZWRpY3Rpb25zIChgcHJlZGApIG9mIGBscHJpY2VgIHZlcnN1cyBgY3V0YDoNCg0KYGBge3J9DQpncmlkICU+JQ0KICAgIGdncGxvdChhZXMoY3V0LCBwcmVkKSkgKw0KICAgIGdlb21fcG9pbnQoKQ0KYGBgDQoNCg0KQ2hlY2sgcmVzaWR1YWxzIG9mIG5ldyBtb2RlbDoNCg0KYGBge3J9DQojIEFkZCByZXNpZHVhbHMNCmRpYW1vbmRzMiA8LSBkaWFtb25kczIgJT4lIA0KICBhZGRfcmVzaWR1YWxzKG1vZF9kaWFtb25kMiwgImxyZXNpZDIiKQ0KIyBWaXN1YWxpemUgcmVzaWR1YWxzDQpnZ3Bsb3QoZGlhbW9uZHMyLCBhZXMobGNhcmF0LCBscmVzaWQyKSkgKyANCiAgZ2VvbV9oZXgoYmlucyA9IDUwKQ0KYGBgDQoNCg0KU29tZSBkaWFtb25kcyBoYXZlIHZlcnkgbGFyZ2UgcmVzaWR1YWxzLiBBIHJlc2lkdWFsIG9mIDIgaW5kaWNhdGVzIHZhbHVlIGlzIDIgXiAyID0gNFggb3VyIHByZWRpY3Rpb24uIENhbiByZXZpZXcgZGlhbW9uZHMgd2l0aCBsYXJnZSByZXNpZHVhbHMgZ3JlYXRlciB0aGFuIGBhYnMobHJlc2lkMikgPiAxYC4gDQoNCmBgYHtyfQ0KZGlhbW9uZHMyICU+JSANCiAgICBmaWx0ZXIoYWJzKGxyZXNpZDIpID4gMSkgJT4lIA0KICAgIGFkZF9wcmVkaWN0aW9ucyhtb2RfZGlhbW9uZDIpICU+JSANCiAgICBtdXRhdGUocHJlZCA9IHJvdW5kKDIgXiBwcmVkKSkgJT4lIA0KICAgIHNlbGVjdChwcmljZSwgcHJlZCwgY2FyYXQ6dGFibGUsIHg6eikgJT4lIA0KICAgIGFycmFuZ2UocHJpY2UpIA0KYGBgDQoNCiMjIEV4ZXJjaXNlcw0KDQojIyMjIEV4ZXJjaXNlIDQNCg0KX0RvZXMgdGhlIGZpbmFsIG1vZGVsLCBtb2RfZGlhbW9uZHMyLCBkbyBhIGdvb2Qgam9iIG9mIHByZWRpY3RpbmcgZGlhbW9uZCBwcmljZXM/IFdvdWxkIHlvdSB0cnVzdCBpdCB0byB0ZWxsIHlvdSBob3cgbXVjaCB0byBzcGVuZCBpZiB5b3Ugd2VyZSBidXlpbmcgYSBkaWFtb25kP18NCg0KT25lIHdheSB0byBkZXRlcm1pbmUgdGhlIGFjY3VyYWN5IG9mIHRoZSBtb2RlbCBpcyB0byBleGFtaW5lIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGFjdHVhbCBhbmQgcHJlZGljdGVkIHByaWNlLiBXZSdsbCB0YWtlIGEgbG9vayBhdCBhIGhpc3RvZ3JhbSBmaXJzdC4NCg0KYGBge3J9DQojIEFkZCBlcnJvciBjYWxjdWxhdGlvbg0KZGlhbW9uZHMyIDwtIGRpYW1vbmRzMiAlPiUgDQogICAgYWRkX3ByZWRpY3Rpb25zKG1vZF9kaWFtb25kMikgJT4lIA0KICAgIG11dGF0ZShwcmVkID0gcm91bmQoMiBeIHByZWQpLA0KICAgICAgICAgICBlcnIgPSBwcmVkIC0gcHJpY2UpIA0KIyBWaXN1YWxpemUgZXJyb3INCmRpYW1vbmRzMiAlPiUNCiAgICBnZ3Bsb3QoYWVzKGVycikpICsNCiAgICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNTApIA0KYGBgDQoNClRoIHByZWRpY3Rpb25zIGFwcGVhciB0byBiZSBjZW50ZXJlZCBhcm91bmQgemVybywgYnV0IGl0J3MgZGlmZmljdWx0IHRvIHNlZSB0aGUgcmFuZ2Ugb2YgZXJyb3JzLiBUbyBiZXR0ZXIgY2hhcmFjdGVyaXplIHRoZSBlcnJvciwgd2UgY2FuIHJldmlldyB0aGUgZGlzdHJpYnV0aW9uIHVzaW5nIGBxdWFudGlsZSgpYC4NCg0KYGBge3J9DQpwcm9icyA8LSBjKDAuMDA1LCAwLjAyNSwgMC4yNSwgMC41LCAwLjc1LCAwLjk3NSwgMC45OTUpDQpkaWFtb25kczIkZXJyICU+JSBxdWFudGlsZShwcm9icyA9IHByb2JzKQ0KYGBgDQoNClRoZSBtb2RlbCBlcnJvciAoZGlmZmVyZW5jZSBiZXR3ZWVuIGFjdHVhbCBhbmQgcHJlZGljdGVkIHByaWNlKSBpcyA5NSUgb2YgdGhlIHRpbWUgd2l0aGluIGBbLSQxNzQyLCAkMTM4MV1gLiBUaGlzIGlzIHJlbGF0aXZlbHkgaGlnaCBjb25zaWRlcmluZyB0aGUgbWVkaWFuIHByaWNlIGlzIGByIHNjYWxlczo6ZG9sbGFyKG1lZGlhbihkaWFtb25kczIkcHJpY2UpKWAuIFRoZXJlZm9yZSwgdGhlcmUgaXMgY29uc2lkZXJhYmxlIHZhcmlhYmlsaXR5IGluIHRoZSBwcmVkaWN0aW9ucyB0aGF0IHNob3VsZCBiZSB3ZWlnaGVkIGJlZm9yZSB1c2luZyB0aGUgbW9kZWwuIA0KDQotLS0tDQoNCiMgRmxpZ2h0IEFuYWx5c2lzDQoNCl9fUXVlc3Rpb24gdG8gYW5zd2VyOl9fIFdoYXQgYWZmZWN0cyB0aGUgbnVtYmVyIG9mIGRhaWx5IGZsaWdodHM/DQoNCmBgYHtyfQ0KZGFpbHkgPC0gZmxpZ2h0cyAlPiUgDQogICAgbXV0YXRlKGRhdGUgPSBtYWtlX2RhdGUoeWVhciwgbW9udGgsIGRheSkpICU+JSANCiAgICBncm91cF9ieShkYXRlKSAlPiUgDQogICAgc3VtbWFyaXNlKG4gPSBuKCkpDQpkYWlseQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGRhaWx5LCBhZXMoZGF0ZSwgbikpICsgDQogICAgZ2VvbV9saW5lKCkNCmBgYA0KDQojIyBNb2RlbGxpbmcgdGhlIHdlZWtkYXkgZWZmZWN0DQoNCl9fUHJvYmxlbTpfXyBVbmRlcnN0YW5kaW5nIHRoZSB0cmVuZCBpcyBkaWZmaWN1bHQgZHVlIHRvIHRoZSBkYXkgb2Ygd2VlayB2YXJpYWJpbGl0eS4NCg0KX19Tb2x1dGlvbjpfXyBBbmFseXplIHRoZSBkYXkgb2YgdGhlIHdlZWsgYW5kIHJlbW92ZSB0aGUgdmFyaWFiaWxpdHkgdXNpbmcgYSBtb2RlbC4NCg0KYGBge3J9DQpkYWlseSA8LSBkYWlseSAlPiUgDQogICAgbXV0YXRlKHdkYXkgPSB3ZGF5KGRhdGUsIGxhYmVsID0gVFJVRSkpDQpnZ3Bsb3QoZGFpbHksIGFlcyh3ZGF5LCBuKSkgKyANCiAgICBnZW9tX2JveHBsb3QoKQ0KYGBgDQoNClNhdHVyZGF5IGlzIG5vdGljZWFibHkgbGVzcyBkdWUgdG8gbGVzcyBidXNpbmVzcyB0cmF2ZWwuDQoNCldlIHdpbGwgYXBwbHkgYSBtb2RlbCB0byBoZWxwIHJlZHVjZSB0aGUgdmFyaWFiaWxpdHkuDQoNCmBgYHtyfQ0KIyBNb2RlbA0KbW9kIDwtIGxtKG4gfiB3ZGF5LCBkYXRhID0gZGFpbHkpDQojIENyZWF0ZSBncmlkIGFuZCBhZGQgcHJlZGljdGlvbnMNCmdyaWQgPC0gZGFpbHkgJT4lIA0KICAgIGRhdGFfZ3JpZCh3ZGF5KSAlPiUgDQogICAgYWRkX3ByZWRpY3Rpb25zKG1vZCwgIm4iKQ0KIyBWaXN1YWxpemUgbW9kZWwNCmdncGxvdChkYWlseSwgYWVzKHdkYXksIG4pKSArIA0KICAgIGdlb21fYm94cGxvdCgpICsNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBncmlkLCBjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDQpDQpgYGANCg0KTmV4dCwgdmlzdWFsaXplIHRoZSByZXNpZHVhbHM6DQoNCmBgYHtyfQ0KIyBhZGRfcmVzaWR1YWxzKCkNCmRhaWx5IDwtIGRhaWx5ICU+JSANCiAgICBhZGRfcmVzaWR1YWxzKG1vZCkNCiMgVmlzdWFsaXplIHJlc2lkdWFscw0KZGFpbHkgJT4lIA0KICAgIGdncGxvdChhZXMoZGF0ZSwgcmVzaWQpKSArIA0KICAgIGdlb21fcmVmX2xpbmUoaCA9IDApICsgDQogICAgZ2VvbV9saW5lKCkNCmBgYA0KDQpEYXkgb2YgdGhlIHdlZWsgZWZmZWN0cyBoYXZlIGJlZW4gcmVtb3ZlZCwgYnV0IHRoZXJlIGlzIHN0aWxsIHBhdHRlcm5zIHRoYXQgcmVtYWluIGR1ZSB0byBzZWFzb25hbGl0eS4NCg0KMS4gVmlld2luZyBieSB3ZWVrZGF5IGhlbHBzIHRvIHNob3cgdGhlIGxhcmdlIGRldmlhdGlvbnMuIFNhdHVyZGF5IGlzIG5vdGljZWFibHkgZGlmZmVyZW50IGR1cmluZyBzdW1tZXIgKG1vcmUgZmxpZ2h0cyB0aGFuIGV4cGVjdGVkKSBhbmQgZmFsbCAobGVzcyBmbGlnaHRzIHRoYW4gZXhwZWN0ZWQpLg0KDQpgYGB7cn0NCmdncGxvdChkYWlseSwgYWVzKGRhdGUsIHJlc2lkLCBjb2xvdXIgPSB3ZGF5KSkgKyANCiAgICBnZW9tX3JlZl9saW5lKGggPSAwKSArIA0KICAgIGdlb21fbGluZSgpDQpgYGANCg0KMi4gU29tZSBkYXlzIGhhdmUgZmFyIGZld2VyIGZsaWdodHMgdGhhbiBleHBlY3RlZDoNCg0KYGBge3J9DQpkYWlseSAlPiUgZmlsdGVyKHJlc2lkIDwgLTEwMCkNCmBgYA0KDQozLiBUaGVyZSBhcHBlYXJzIHRvIGJlIGEgbG9uZyB0ZXJtIHRyZW5kIG92ZXIgdGhlIGNvdXJzZSBvZiB0aGUgeWVhci4NCg0KYGBge3J9DQpkYWlseSAlPiUgDQogICAgZ2dwbG90KGFlcyhkYXRlLCByZXNpZCkpICsgDQogICAgZ2VvbV9yZWZfbGluZShoID0gMCkgKyANCiAgICBnZW9tX2xpbmUoY29sb3VyID0gImdyZXk1MCIpICsgDQogICAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSwgc3BhbiA9IDAuMjApDQpgYGANCg0KIyMgTW9kZWxsaW5nIHRoZSBzZWFzb25hbCBlZmZlY3QNCg0KVmlld2luZyBTYXR1cmRheSBoaWdobGlnaHRzIHRoZSBzZWFzb25hbGl0eSBlZmZlY3Q6DQoNCmBgYHtyfQ0KZGFpbHkgJT4lIA0KICAgIGZpbHRlcih3ZGF5ID09ICJTYXQiKSAlPiUgDQogICAgZ2dwbG90KGFlcyhkYXRlLCBuKSkgKyANCiAgICBnZW9tX3BvaW50KCkgKyANCiAgICBnZW9tX2xpbmUoKSArDQogICAgc2NhbGVfeF9kYXRlKE5VTEwsIGRhdGVfYnJlYWtzID0gIjEgbW9udGgiLCBkYXRlX2xhYmVscyA9ICIlYiIpDQpgYGANCg0KQ3JlYXRlIGEgYHRlcm1gIHZhcmlhYmxlIHRvIHNwbGl0IGRhdGVzIGludG8gc3ByaW5nLCBzdW1tZXIgYW5kIGZhbGwgc2Vhc29uczoNCg0KYGBge3J9DQojIEZ1bmN0aW9uIHRvIGNsYXNzaWZ5IGRhdGUgYXMgc3ByaW5nLCBzdW1tZXIsIGZhbGwgc2Vhc29ucw0KdGVybSA8LSBmdW5jdGlvbihkYXRlKSB7DQogICAgY3V0KGRhdGUsIA0KICAgICAgICBicmVha3MgPSB5bWQoMjAxMzAxMDEsIDIwMTMwNjA1LCAyMDEzMDgyNSwgMjAxNDAxMDEpLA0KICAgICAgICBsYWJlbHMgPSBjKCJzcHJpbmciLCAic3VtbWVyIiwgImZhbGwiKSANCiAgICApDQp9DQojIEFkZCBzZWFzb24gY2xhc3NpZmljYXRpb24NCmRhaWx5IDwtIGRhaWx5ICU+JSANCiAgICBtdXRhdGUodGVybSA9IHRlcm0oZGF0ZSkpIA0KIyBWaXN1YWxpemUgc2Vhc29uDQpkYWlseSAlPiUgDQogICAgZmlsdGVyKHdkYXkgPT0gIlNhdCIpICU+JSANCiAgICBnZ3Bsb3QoYWVzKGRhdGUsIG4sIGNvbG91ciA9IHRlcm0pKSArDQogICAgZ2VvbV9wb2ludChhbHBoYSA9IDEvMykgKyANCiAgICBnZW9tX2xpbmUoKSArDQogICAgc2NhbGVfeF9kYXRlKE5VTEwsIGRhdGVfYnJlYWtzID0gIjEgbW9udGgiLCBkYXRlX2xhYmVscyA9ICIlYiIpDQpgYGANCg0KV2UgY2FuIHZpc3VhbGl6ZSB0aGUgdGVybSBieSBkYXkgb2YgdGhlIHdlZWs6DQoNCmBgYHtyfQ0KZGFpbHkgJT4lIA0KICAgIGdncGxvdChhZXMod2RheSwgbiwgY29sb3VyID0gdGVybSkpICsNCiAgICBnZW9tX2JveHBsb3QoKQ0KYGBgDQoNCldlIGNhbiBzZWUgc3VtbWVyIHRlbmRzIHRvIGJlIGhpZ2hlciwgd2hpbGUgZmFsbCBhbmQgc3ByaW5nIHRlbmQgdG8gYmUgYWJvdXQgdGhlIHNhbWUgd2l0aCB0aGUgZXhjZXB0aW9uIG9mIFNhdHVyZGF5LiANCg0KV2UgY2FuIG1vZGVsIHVzaW5nIGFuIGludGVyYWN0aW9uIGVmZmVjdCBiZXR3ZWVuIHdlZWtkYXkgKGB3ZGF5YCkgYW5kIHNlYXNvbiAoYHRlcm1gKS4gVXNlIGBnYXRoZXJfcmVzaWR1YWxzKClgIHRvIGNvbXB1dGUgcmVzaWR1YWxzIG9uIGJvdGggbW9kZWxzIGZvciBjb21wYXJpc29uLg0KDQpgYGB7cn0NCiMgTW9kZWxzIHRvIGNvbXBhcmUNCm1vZDEgPC0gbG0obiB+IHdkYXksIGRhdGEgPSBkYWlseSkNCm1vZDIgPC0gbG0obiB+IHdkYXkgKiB0ZXJtLCBkYXRhID0gZGFpbHkpDQojIGdhdGhlcl9yZXNpZHVhbHMoKSwgdmlzdWFsaXplIGJvdGggbW9kZWxzDQpkYWlseSAlPiUgDQogICAgZ2F0aGVyX3Jlc2lkdWFscyh3aXRob3V0X3Rlcm0gPSBtb2QxLCB3aXRoX3Rlcm0gPSBtb2QyKSAlPiUgDQogICAgZ2dwbG90KGFlcyhkYXRlLCByZXNpZCwgY29sb3VyID0gbW9kZWwpKSArDQogICAgZ2VvbV9saW5lKGFscGhhID0gMC43NSkNCmBgYA0KDQpTb21lIGltcHJvdmVtZW50IGluIHN1bW1lciwgYnV0IG5vdCBhcyBtdWNoIGFzIHdlIHdhbnQuIFZpc3VhbGl6aW5nIHByZWRpY3Rpb25zIGJ5IHRlcm0gYW5kIGRheSBzaG93cyBhbiBpc3N1ZS4NCg0KYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD01fQ0KIyBkYXRhX2dyaWQoKSBieSB3ZGF5IGFuZCB0ZXJtLCBhZGRfcHJlZGljdGlvbnMoKQ0KZ3JpZCA8LSBkYWlseSAlPiUgDQogICAgZGF0YV9ncmlkKHdkYXksIHRlcm0pICU+JSANCiAgICBhZGRfcHJlZGljdGlvbnMobW9kMiwgIm4iKQ0KIyBWaXN1YWxpemUgcHJlZGljdGlvbnMgYnkgdGVybSBhbmQgd2RheQ0KZ2dwbG90KGRhaWx5LCBhZXMod2RheSwgbikpICsNCiAgICBnZW9tX2JveHBsb3QoKSArIA0KICAgIGdlb21fcG9pbnQoZGF0YSA9IGdyaWQsIGNvbG91ciA9ICJyZWQiKSArIA0KICAgIGZhY2V0X3dyYXAofiB0ZXJtKQ0KYGBgDQoNCg0KX19Qcm9ibGVtOl9fIE91ciBtb2RlbCBpcyBmaW5kaW5nIHRoZSBtZWFuIGVmZmVjdCwgYnV0IGxhcmdlIG91dGxpZXJzIGFyZSB0aHJvd2luZyBpdCBvZmYuDQoNCl9fU29sdXRpb246X18gVXNlIGEgbW9kZWxsaW5nIGFsZ29yaXRobSB0aGF0IGlzIGFmZmVjdGVkIGxlc3MgYnkgb3V0bGllcnMuIGBNQVNTOjpybG0oKWAgaXMgcmVzaXN0YW50IHRvIG91dGxpZXJzLg0KDQpgYGB7cn0NCiMgTW9kZWwgdXNpbmcgcmxtKCkNCm1vZDMgPC0gTUFTUzo6cmxtKG4gfiB3ZGF5ICogdGVybSwgZGF0YSA9IGRhaWx5KQ0KIyBWaXN1YWxpemUgcmVzaWR1YWxzDQpkYWlseSAlPiUgDQogICAgYWRkX3Jlc2lkdWFscyhtb2QzLCAicmVzaWQiKSAlPiUgDQogICAgZ2dwbG90KGFlcyhkYXRlLCByZXNpZCkpICsgDQogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZSA9IDIsIGNvbG91ciA9ICJ3aGl0ZSIpICsgDQogICAgZ2VvbV9saW5lKCkNCmBgYA0KDQojIyBBbHRlcm5hdGl2ZSBhcHByb2FjaA0KDQpXZSBjYW4gdXNlIGBzcGxpbmVzOjpucygpYCB0byBtb2RlbCBieSB3ZWVrZGF5IHRocm91Z2hvdXQgdGhlIHllYXIuDQoNCmBgYHtyfQ0KbGlicmFyeShzcGxpbmVzKQ0KIyBNb2RlbCB1c2luZyBybG0oKSB3aXRoIDV0aCBvcmRlciBwb2x5bm9taWFsIGZvciBkYXRlDQptb2QgPC0gTUFTUzo6cmxtKG4gfiB3ZGF5ICogbnMoZGF0ZSwgNSksIGRhdGEgPSBkYWlseSkNCiMgZGF0YV9ncmlkKCkgYnkgd2RheSBhbG9uZyAxMyBldmVubHkgc3BhY2VkIHBvaW50cywgYWRkX3ByZWRpY3Rpb25zKCkgDQojIGFuZCB2aXN1YWxpemUNCmRhaWx5ICU+JSANCiAgICBkYXRhX2dyaWQod2RheSwgZGF0ZSA9IHNlcV9yYW5nZShkYXRlLCBuID0gMTMpKSAlPiUgDQogICAgYWRkX3ByZWRpY3Rpb25zKG1vZCkgJT4lIA0KICAgIGdncGxvdChhZXMoZGF0ZSwgcHJlZCwgY29sb3VyID0gd2RheSkpICsgDQogICAgZ2VvbV9saW5lKCkgKw0KICAgIGdlb21fcG9pbnQoKQ0KYGBgDQoNCldlIGNhbiBhbHNvIGNoZWNrIHRoZSByZXNpZHVhbHMgdG8gc2VlIGhvdyB0aGlzIGFwcHJvYWNoIGNvbXBhcmVzIHRvIHRoZSBwcmV2aW91cyBtb2RlbC4NCg0KYGBge3J9DQpkYWlseSAlPiUgDQogICAgYWRkX3Jlc2lkdWFscyhtb2QpICU+JSANCiAgICBnZ3Bsb3QoYWVzKGRhdGUsIHJlc2lkKSkgKyANCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplID0gMiwgY29sb3VyID0gIndoaXRlIikgKyANCiAgICBnZW9tX2xpbmUoKQ0KYGBgDQoNCg==