To complete this lab, you’ll need to install and load the following packages:

  • tidymodels
  • patchwork
  • GGally

This week, the labs are designed around fitting a model to a specific dataset, and how we visualize the results appropriately.

Today, you’ll first fit a simple linear regression model, then work through another online tutorial about multiple regression. In the first part, you’ll work with COVID survey data collected through Facebook.

The data we’ll use was collected by the Delphi Group, which is headquarted at Carnegie Mellon University and collects survey data through Facebook and makes results publicly available via their EpiData API. They make it super easy to access through their R package covidcast – I even tweeted about how easy it was to use!

If you’re interested in diving into more of this data for any of your projects, let me know and I’m happy to share the code to download different date ranges or variables.

Simple Linear Regression

Data

I’d highly recommend skimming the variables listed on Delphi’s Symptom Surveys Page for more information about the specific variables that are available. Here’s an excerpt:

Influenza-like illness or ILI is a standard indicator, and is defined by the CDC as: fever along with sore throat or cough. From the list of symptoms from Q1 on our survey, this means a and (b or c).

COVID-like illness or CLI is not a standard indicator. Through our discussions with the CDC, we chose to define it as: fever along with cough or shortness of breath or difficulty breathing.

Symptoms alone are not sufficient to diagnose influenza or coronavirus infections, and so these ILI and CLI indicators are not expected to be unbiased estimates of the true rate of influenza or coronavirus infections. These symptoms can be caused by many other conditions, and many true infections can be asymptomatic. Instead, we expect these indicators to be useful for comparison across the United States and across time, to determine where symptoms appear to be increasing.

The signals beginning with smoothed_ estimate the same quantities as their raw partners, but are smoothed in time to reduce day-to-day sampling noise; see details below. Crucially, because the smoothed signals combine information across multiple days, they have larger sample sizes and hence are available for more counties and MSAs than the raw signals.

The following chunk of code loads the data file that I’ve downloaded from Delphi and pre-processed. It contains responses from October 1st to December 31st, 2020 and the following variables:

covid = read_csv("data/covid_data.csv")
covid
## # A tibble: 65,688 x 6
##    signal       geo_value time_value value stderr sample_size
##    <chr>        <chr>     <date>     <dbl>  <dbl>       <dbl>
##  1 smoothed_cli ak        2020-10-01 1.59  0.429         760 
##  2 smoothed_cli al        2020-10-01 0.983 0.141        3742.
##  3 smoothed_cli ar        2020-10-01 1.06  0.174        2587.
##  4 smoothed_cli az        2020-10-01 0.597 0.0905       5682.
##  5 smoothed_cli ca        2020-10-01 0.450 0.0399      21930.
##  6 smoothed_cli co        2020-10-01 0.561 0.0917       5137.
##  7 smoothed_cli ct        2020-10-01 0.444 0.0978       3866 
##  8 smoothed_cli dc        2020-10-01 0.268 0.224         533 
##  9 smoothed_cli de        2020-10-01 0.203 0.134        1129.
## 10 smoothed_cli fl        2020-10-01 0.516 0.0472      17767.
## # … with 65,678 more rows

Pre-Processing

First, we’ll make a simplified version of the data that takes the average of each variable across each state for the associated time period.

covid %>%
  group_by(geo_value, signal) %>%
  summarize(
    avg = mean(value, na.rm = T)
  ) %>%
  pivot_wider(id_cols = geo_value, names_from = signal, values_from = avg) %>%
  ungroup() -> state_avg

Note the pivot_wider command – this is a super neat data wrangling trick that lets you reshape your data to create new columns or collapse columns into rows. Take a look at what the data looked like before that step:

covid %>%
  group_by(geo_value, signal) %>%
  summarize(
    avg = mean(value, na.rm = T)
  ) 
## # A tibble: 714 x 3
## # Groups:   geo_value [51]
##    geo_value signal                      avg
##    <chr>     <chr>                     <dbl>
##  1 ak        smoothed_anxious_5d       18.6 
##  2 ak        smoothed_cli               1.22
##  3 ak        smoothed_depressed_5d     13.7 
##  4 ak        smoothed_felt_isolated_5d 23.0 
##  5 ak        smoothed_ili               1.26
##  6 ak        smoothed_large_event_1d    8.89
##  7 ak        smoothed_restaurant_1d    12.8 
##  8 ak        smoothed_shop_1d          53.7 
##  9 ak        smoothed_spent_time_1d    33.8 
## 10 ak        smoothed_tested_14d       21.9 
## # … with 704 more rows

and after:

state_avg
## # A tibble: 51 x 15
##    geo_value smoothed_anxiou… smoothed_cli smoothed_depres… smoothed_felt_i…
##    <chr>                <dbl>        <dbl>            <dbl>            <dbl>
##  1 ak                    18.6        1.22              13.7             23.0
##  2 al                    16.6        1.41              12.7             16.7
##  3 ar                    19.7        1.20              14.8             19.1
##  4 az                    16.6        1.05              12.0             18.3
##  5 ca                    17.7        0.774             12.3             20.4
##  6 co                    20.0        1.00              13.7             21.6
##  7 ct                    18.2        0.723             11.4             17.8
##  8 dc                    23.2        0.434             11.8             22.5
##  9 de                    15.4        0.618             10.3             16.2
## 10 fl                    15.3        0.715             10.5             15.9
## # … with 41 more rows, and 10 more variables: smoothed_ili <dbl>,
## #   smoothed_large_event_1d <dbl>, smoothed_restaurant_1d <dbl>,
## #   smoothed_shop_1d <dbl>, smoothed_spent_time_1d <dbl>,
## #   smoothed_tested_14d <dbl>, smoothed_travel_outside_state_5d <dbl>,
## #   smoothed_wearing_mask <dbl>, smoothed_work_outside_home_1d <dbl>,
## #   smoothed_worried_finances <dbl>

So, what pivot_wider did was make the data “wider” (more columns) by mapping signal to be the new column names and avg to be the value for that column for each state. We end up with a new dataset that has a single row for each state.

EDA

Let’s start with some exploratory analysis. The following code chunk creates a pairs plot, from the GGally package, for all of the quantitative variables. (I also include a step of removing smoothed_ from each of the variable names before plotting so they’re easier to read)

state_avg %>%
  select(-geo_value) %>%
  rename_all(funs(stringr::str_replace(., "smoothed_", ""))) %>%
  GGally::ggpairs()

We’re going to focus on the relationship between smoothed_wearing_mask and smoothed_cli.

Each point here corresponds to a state. It would be great if we could tell which state each point corresponds to. Lucky for us geom_text does just that. Replace geom_point() with geom_text() and add the right label argument to aes(). Change the theme, axis labels, and title to something more appropriate.

state_avg %>%
  mutate(state = str_to_upper(geo_value)) %>%
  ggplot(aes(x = smoothed_wearing_mask, y = smoothed_cli)) +
  geom_point() 

Adding a model

This graph is screaming for a linear regression model. We can easily add the least-squares line to the graph using stat_smooth(method = lm tells R to fit a linear model and se = FALSE tells R to not include the standard error of the line on the graph):

state_avg %>%
  ggplot(aes(x = smoothed_wearing_mask, y = smoothed_cli)) +
  geom_point() +
  stat_smooth(method = "lm", se = FALSE) 

This is great for adding a line to the graph, but doesn’t tell us much about the model. For instance, what is \(\hat{\beta_1}\)? Is the estimate statistically significant? We have no idea.

Fitting the Model

Let’s go ahead and fit our model:

lm_mod = lm(smoothed_cli ~ smoothed_wearing_mask, data = state_avg)

(and, since we’re good statisticians, we’ll check our residual plots to make sure we don’t have any egregious violations) In your report, state the LINE assumptions, how we check each of them, and whether you have any concerns about this model. Note that we use the patchwork library here – this package allows us to use really simple notation for combining ggplots into a single figure (the p1 + p2 + p3 at the end). In your report, clean these figures up a bit.

library(patchwork)
lm_res = augment(lm_mod, interval = "prediction") 
p1 = ggplot(lm_res, aes(x = smoothed_wearing_mask, y = .resid)) +
  geom_point()

p2 = ggplot(lm_res, aes(x = .resid)) + 
  geom_histogram(bins = 15)

p3 = ggplot(lm_res, aes(x = .fitted, y = .resid)) +
  geom_point()

p1 + p2 + p3

The tidy part of tidymodels comes in handy when we want to look at our coefficients:

tidy(lm_mod, conf.int = TRUE)
## # A tibble: 2 x 7
##   term                  estimate std.error statistic  p.value conf.low conf.high
##   <chr>                    <dbl>     <dbl>     <dbl>    <dbl>    <dbl>     <dbl>
## 1 (Intercept)             6.69     0.452        14.8 1.05e-19   5.78      7.60  
## 2 smoothed_wearing_mask  -0.0637   0.00507     -12.6 6.30e-17  -0.0738   -0.0535

which make it super easy to make tables or figures based on the results. In your lab report, replicate the following graph using the results from tidy(lm_mod) - you can use a different theme or labels if you prefer. (Hint: check out the help page for geom_pointrange())

Multiple Linear Regression

OpenIntro Online Tutorial on Multiple Regression

Include screenshots of your two favorite plots. You can include a screenshot in an rmarkdown document by saving the screenshot in the same folder as your rmarkdown file and adding ![](SCREENSHOT_FILENAME), where you fill in SCREENSHOT_FILENAME with the name of your screenshot. (make sure to include .jpg or .png)

LS0tCnRpdGxlOiAiTGFiMDU6IExpbmVhciBSZWdyZXNzaW9uIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6IAogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGNzczogc3RhdDQxLWxhYi10aGVtZS5jc3MKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeW1vZGVscykKa25pdHI6Om9wdHNfY2h1bmskc2V0KG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFKQpgYGAKClRvIGNvbXBsZXRlIHRoaXMgbGFiLCB5b3UnbGwgbmVlZCB0byBpbnN0YWxsIGFuZCBsb2FkIHRoZSBmb2xsb3dpbmcgcGFja2FnZXM6IAoKKyBgdGlkeW1vZGVsc2AKKyBgcGF0Y2h3b3JrYAorIGBHR2FsbHlgCgpUaGlzIHdlZWssIHRoZSBsYWJzIGFyZSBkZXNpZ25lZCBhcm91bmQgZml0dGluZyBhIG1vZGVsIHRvIGEgc3BlY2lmaWMgZGF0YXNldCwgYW5kIGhvdyB3ZSB2aXN1YWxpemUgdGhlIHJlc3VsdHMgYXBwcm9wcmlhdGVseS4gCgpUb2RheSwgeW91J2xsIGZpcnN0IGZpdCBhIHNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCwgdGhlbiB3b3JrIHRocm91Z2ggYW5vdGhlciBvbmxpbmUgdHV0b3JpYWwgYWJvdXQgbXVsdGlwbGUgcmVncmVzc2lvbi4gSW4gdGhlIGZpcnN0IHBhcnQsIHlvdSdsbCB3b3JrIHdpdGggQ09WSUQgc3VydmV5IGRhdGEgY29sbGVjdGVkIHRocm91Z2ggRmFjZWJvb2suIAoKVGhlIGRhdGEgd2UnbGwgdXNlIHdhcyBjb2xsZWN0ZWQgYnkgdGhlIFtEZWxwaGkgR3JvdXBdKGh0dHBzOi8vZGVscGhpLmNtdS5lZHUvKSwgd2hpY2ggaXMgaGVhZHF1YXJ0ZWQgYXQgQ2FybmVnaWUgTWVsbG9uIFVuaXZlcnNpdHkgYW5kIGNvbGxlY3RzIHN1cnZleSBkYXRhIHRocm91Z2ggRmFjZWJvb2sgYW5kIG1ha2VzIHJlc3VsdHMgcHVibGljbHkgYXZhaWxhYmxlIHZpYSB0aGVpciBbRXBpRGF0YSBBUEldKGh0dHBzOi8vY211LWRlbHBoaS5naXRodWIuaW8vZGVscGhpLWVwaWRhdGEvKS4gVGhleSBtYWtlIGl0IHN1cGVyIGVhc3kgdG8gYWNjZXNzIHRocm91Z2ggdGhlaXIgUiBwYWNrYWdlIFtjb3ZpZGNhc3RdKGh0dHBzOi8vY211LWRlbHBoaS5naXRodWIuaW8vY292aWRjYXN0L2NvdmlkY2FzdFIvKSAtLSBJIGV2ZW4gdHdlZXRlZCBhYm91dCBob3cgZWFzeSBpdCB3YXMgdG8gdXNlISAKCjxibG9ja3F1b3RlIGNsYXNzPSJ0d2l0dGVyLXR3ZWV0Ij48cCBsYW5nPSJlbiIgZGlyPSJsdHIiPkp1c3QgZG93bmxvYWRlZCBhIGJ1bmNoIG9mIDxhIGhyZWY9Imh0dHBzOi8vdHdpdHRlci5jb20vQ211RGVscGhpP3JlZl9zcmM9dHdzcmMlNUV0ZnciPkBDbXVEZWxwaGk8L2E+IGRhdGEgeWVzdGVyZGF5IHRvIHdyaXRlIHNvbWUgbmV3IGxhYnMvaG9tZXdvcmtzL3Byb2plY3RzIGZvciBteSBzdHVkZW50cyAtIHRoZSBkYXRhIGlzIPCfkq8sIHRoZSBkb2N1bWVudGF0aW9uIGlzIPCfkq8sIGFuZCB0aGUgY292aWRjYXN0IFIg8J+TpiBtYWtlcyBpdCBTTyBFQVNZLiBTdWNoIGEgZ3JlYXQgcmVzb3VyY2UgZm9yIHJlc2VhcmNoZXJzICsgdGVhY2hlcnMhIDxhIGhyZWY9Imh0dHBzOi8vdC5jby9HbnZLU2NkcmJzIj5odHRwczovL3QuY28vR252S1NjZHJiczwvYT48L3A+Jm1kYXNoOyBBbWFuZGEgTHVieSwgUGhEIChAYW1hbmRhbHVieSkgPGEgaHJlZj0iaHR0cHM6Ly90d2l0dGVyLmNvbS9hbWFuZGFsdWJ5L3N0YXR1cy8xMzM0OTMxNzM0OTcwODk2Mzg0P3JlZl9zcmM9dHdzcmMlNUV0ZnciPkRlY2VtYmVyIDQsIDIwMjA8L2E+PC9ibG9ja3F1b3RlPiA8c2NyaXB0IGFzeW5jIHNyYz0iaHR0cHM6Ly9wbGF0Zm9ybS50d2l0dGVyLmNvbS93aWRnZXRzLmpzIiBjaGFyc2V0PSJ1dGYtOCI+PC9zY3JpcHQ+CgpJZiB5b3UncmUgaW50ZXJlc3RlZCBpbiBkaXZpbmcgaW50byBtb3JlIG9mIHRoaXMgZGF0YSBmb3IgYW55IG9mIHlvdXIgcHJvamVjdHMsIGxldCBtZSBrbm93IGFuZCBJJ20gaGFwcHkgdG8gc2hhcmUgdGhlIGNvZGUgdG8gZG93bmxvYWQgZGlmZmVyZW50IGRhdGUgcmFuZ2VzIG9yIHZhcmlhYmxlcy4gCgojIFNpbXBsZSBMaW5lYXIgUmVncmVzc2lvbgoKIyMjIERhdGEKCkknZCBoaWdobHkgcmVjb21tZW5kIHNraW1taW5nIHRoZSB2YXJpYWJsZXMgbGlzdGVkIG9uIFtEZWxwaGkncyBTeW1wdG9tIFN1cnZleXMgUGFnZV0oaHR0cHM6Ly9jbXUtZGVscGhpLmdpdGh1Yi5pby9kZWxwaGktZXBpZGF0YS9hcGkvY292aWRjYXN0LXNpZ25hbHMvZmItc3VydmV5Lmh0bWwjYmVoYXZpb3ItaW5kaWNhdG9ycykgZm9yIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHNwZWNpZmljIHZhcmlhYmxlcyB0aGF0IGFyZSBhdmFpbGFibGUuIEhlcmUncyBhbiBleGNlcnB0OiAKCj4gSW5mbHVlbnphLWxpa2UgaWxsbmVzcyBvciBJTEkgaXMgYSBzdGFuZGFyZCBpbmRpY2F0b3IsIGFuZCBpcyBkZWZpbmVkIGJ5IHRoZSBDREMgYXM6IGZldmVyIGFsb25nIHdpdGggc29yZSB0aHJvYXQgb3IgY291Z2guIEZyb20gdGhlIGxpc3Qgb2Ygc3ltcHRvbXMgZnJvbSBRMSBvbiBvdXIgc3VydmV5LCB0aGlzIG1lYW5zIGEgYW5kIChiIG9yIGMpLgoKPiBDT1ZJRC1saWtlIGlsbG5lc3Mgb3IgQ0xJIGlzIG5vdCBhIHN0YW5kYXJkIGluZGljYXRvci4gVGhyb3VnaCBvdXIgZGlzY3Vzc2lvbnMgd2l0aCB0aGUgQ0RDLCB3ZSBjaG9zZSB0byBkZWZpbmUgaXQgYXM6IGZldmVyIGFsb25nIHdpdGggY291Z2ggb3Igc2hvcnRuZXNzIG9mIGJyZWF0aCBvciBkaWZmaWN1bHR5IGJyZWF0aGluZy4KCj4gU3ltcHRvbXMgYWxvbmUgYXJlIG5vdCBzdWZmaWNpZW50IHRvIGRpYWdub3NlIGluZmx1ZW56YSBvciBjb3JvbmF2aXJ1cyBpbmZlY3Rpb25zLCBhbmQgc28gdGhlc2UgSUxJIGFuZCBDTEkgaW5kaWNhdG9ycyBhcmUgbm90IGV4cGVjdGVkIHRvIGJlIHVuYmlhc2VkIGVzdGltYXRlcyBvZiB0aGUgdHJ1ZSByYXRlIG9mIGluZmx1ZW56YSBvciBjb3JvbmF2aXJ1cyBpbmZlY3Rpb25zLiBUaGVzZSBzeW1wdG9tcyBjYW4gYmUgY2F1c2VkIGJ5IG1hbnkgb3RoZXIgY29uZGl0aW9ucywgYW5kIG1hbnkgdHJ1ZSBpbmZlY3Rpb25zIGNhbiBiZSBhc3ltcHRvbWF0aWMuIEluc3RlYWQsIHdlIGV4cGVjdCB0aGVzZSBpbmRpY2F0b3JzIHRvIGJlIHVzZWZ1bCBmb3IgY29tcGFyaXNvbiBhY3Jvc3MgdGhlIFVuaXRlZCBTdGF0ZXMgYW5kIGFjcm9zcyB0aW1lLCB0byBkZXRlcm1pbmUgd2hlcmUgc3ltcHRvbXMgYXBwZWFyIHRvIGJlIGluY3JlYXNpbmcuCgo+IFRoZSBzaWduYWxzIGJlZ2lubmluZyB3aXRoIHNtb290aGVkXyBlc3RpbWF0ZSB0aGUgc2FtZSBxdWFudGl0aWVzIGFzIHRoZWlyIHJhdyBwYXJ0bmVycywgYnV0IGFyZSBzbW9vdGhlZCBpbiB0aW1lIHRvIHJlZHVjZSBkYXktdG8tZGF5IHNhbXBsaW5nIG5vaXNlOyBzZWUgZGV0YWlscyBiZWxvdy4gQ3J1Y2lhbGx5LCBiZWNhdXNlIHRoZSBzbW9vdGhlZCBzaWduYWxzIGNvbWJpbmUgaW5mb3JtYXRpb24gYWNyb3NzIG11bHRpcGxlIGRheXMsIHRoZXkgaGF2ZSBsYXJnZXIgc2FtcGxlIHNpemVzIGFuZCBoZW5jZSBhcmUgYXZhaWxhYmxlIGZvciBtb3JlIGNvdW50aWVzIGFuZCBNU0FzIHRoYW4gdGhlIHJhdyBzaWduYWxzLgoKVGhlIGZvbGxvd2luZyBjaHVuayBvZiBjb2RlIGxvYWRzIHRoZSBkYXRhIGZpbGUgdGhhdCBJJ3ZlIGRvd25sb2FkZWQgZnJvbSBEZWxwaGkgYW5kIHByZS1wcm9jZXNzZWQuIEl0IGNvbnRhaW5zIHJlc3BvbnNlcyBmcm9tIE9jdG9iZXIgMXN0IHRvIERlY2VtYmVyIDMxc3QsIDIwMjAgYW5kIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzOiAKCmBgYHtyfQpjb3ZpZCA9IHJlYWRfY3N2KCJkYXRhL2NvdmlkX2RhdGEuY3N2IikKY292aWQKYGBgCiMjIyBQcmUtUHJvY2Vzc2luZyAKCkZpcnN0LCB3ZSdsbCBtYWtlIGEgc2ltcGxpZmllZCB2ZXJzaW9uIG9mIHRoZSBkYXRhIHRoYXQgdGFrZXMgdGhlIGF2ZXJhZ2Ugb2YgZWFjaCB2YXJpYWJsZSBhY3Jvc3MgZWFjaCBzdGF0ZSBmb3IgdGhlIGFzc29jaWF0ZWQgdGltZSBwZXJpb2QuIAoKYGBge3J9CmNvdmlkICU+JQogIGdyb3VwX2J5KGdlb192YWx1ZSwgc2lnbmFsKSAlPiUKICBzdW1tYXJpemUoCiAgICBhdmcgPSBtZWFuKHZhbHVlLCBuYS5ybSA9IFQpCiAgKSAlPiUKICBwaXZvdF93aWRlcihpZF9jb2xzID0gZ2VvX3ZhbHVlLCBuYW1lc19mcm9tID0gc2lnbmFsLCB2YWx1ZXNfZnJvbSA9IGF2ZykgJT4lCiAgdW5ncm91cCgpIC0+IHN0YXRlX2F2ZwpgYGAKCk5vdGUgdGhlIGBwaXZvdF93aWRlcmAgY29tbWFuZCAtLSB0aGlzIGlzIGEgKnN1cGVyIG5lYXQqIGRhdGEgd3JhbmdsaW5nIHRyaWNrIHRoYXQgbGV0cyB5b3UgcmVzaGFwZSB5b3VyIGRhdGEgdG8gY3JlYXRlIG5ldyBjb2x1bW5zIG9yIGNvbGxhcHNlIGNvbHVtbnMgaW50byByb3dzLiBUYWtlIGEgbG9vayBhdCB3aGF0IHRoZSBkYXRhIGxvb2tlZCBsaWtlICpiZWZvcmUqIHRoYXQgc3RlcDogCgpgYGB7cn0KY292aWQgJT4lCiAgZ3JvdXBfYnkoZ2VvX3ZhbHVlLCBzaWduYWwpICU+JQogIHN1bW1hcml6ZSgKICAgIGF2ZyA9IG1lYW4odmFsdWUsIG5hLnJtID0gVCkKICApIApgYGAKYW5kIGFmdGVyOiAKCmBgYHtyfQpzdGF0ZV9hdmcKYGBgCgpTbywgd2hhdCBgcGl2b3Rfd2lkZXJgIGRpZCB3YXMgbWFrZSB0aGUgZGF0YSAid2lkZXIiIChtb3JlIGNvbHVtbnMpIGJ5IG1hcHBpbmcgYHNpZ25hbGAgdG8gYmUgdGhlIG5ldyBjb2x1bW4gbmFtZXMgYW5kIGBhdmdgIHRvIGJlIHRoZSB2YWx1ZSBmb3IgdGhhdCBjb2x1bW4gZm9yIGVhY2ggc3RhdGUuIFdlIGVuZCB1cCB3aXRoIGEgbmV3IGRhdGFzZXQgdGhhdCBoYXMgYSBzaW5nbGUgcm93IGZvciBlYWNoIHN0YXRlLiAKCiMjIyBFREEgCgpMZXQncyBzdGFydCB3aXRoIHNvbWUgZXhwbG9yYXRvcnkgYW5hbHlzaXMuIFRoZSBmb2xsb3dpbmcgY29kZSBjaHVuayBjcmVhdGVzIGEgKnBhaXJzIHBsb3QqLCBmcm9tIHRoZSBgR0dhbGx5YCBwYWNrYWdlLCBmb3IgYWxsIG9mIHRoZSBxdWFudGl0YXRpdmUgdmFyaWFibGVzLiAoSSBhbHNvIGluY2x1ZGUgYSBzdGVwIG9mIHJlbW92aW5nIGBzbW9vdGhlZF9gIGZyb20gZWFjaCBvZiB0aGUgdmFyaWFibGUgbmFtZXMgYmVmb3JlIHBsb3R0aW5nIHNvIHRoZXkncmUgZWFzaWVyIHRvIHJlYWQpCgpgYGB7ciwgZmlnLndpZHRoPSAxMiwgZmlnLmhlaWdodCA9IDEyfQpzdGF0ZV9hdmcgJT4lCiAgc2VsZWN0KC1nZW9fdmFsdWUpICU+JQogIHJlbmFtZV9hbGwoZnVucyhzdHJpbmdyOjpzdHJfcmVwbGFjZSguLCAic21vb3RoZWRfIiwgIiIpKSkgJT4lCiAgR0dhbGx5OjpnZ3BhaXJzKCkKYGBgCgpXZSdyZSBnb2luZyB0byBmb2N1cyBvbiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYHNtb290aGVkX3dlYXJpbmdfbWFza2AgYW5kIGBzbW9vdGhlZF9jbGlgLiAKCkVhY2ggcG9pbnQgaGVyZSBjb3JyZXNwb25kcyB0byBhIHN0YXRlLiBJdCB3b3VsZCBiZSBncmVhdCBpZiB3ZSBjb3VsZCB0ZWxsICp3aGljaCogc3RhdGUgZWFjaCBwb2ludCBjb3JyZXNwb25kcyB0by4gTHVja3kgZm9yIHVzIGBnZW9tX3RleHRgIGRvZXMganVzdCB0aGF0LiBSZXBsYWNlIGBnZW9tX3BvaW50KClgIHdpdGggYGdlb21fdGV4dCgpYCBhbmQgYWRkIHRoZSByaWdodCBgbGFiZWxgIGFyZ3VtZW50IHRvIGBhZXMoKWAuIENoYW5nZSB0aGUgdGhlbWUsIGF4aXMgbGFiZWxzLCBhbmQgdGl0bGUgdG8gc29tZXRoaW5nIG1vcmUgYXBwcm9wcmlhdGUuIAoKYGBge3J9CnN0YXRlX2F2ZyAlPiUKICBtdXRhdGUoc3RhdGUgPSBzdHJfdG9fdXBwZXIoZ2VvX3ZhbHVlKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gc21vb3RoZWRfd2VhcmluZ19tYXNrLCB5ID0gc21vb3RoZWRfY2xpKSkgKwogIGdlb21fcG9pbnQoKSAKYGBgCgojIyMgQWRkaW5nIGEgbW9kZWwgCgpUaGlzIGdyYXBoIGlzICoqc2NyZWFtaW5nKiogZm9yIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwuIFdlIGNhbiBlYXNpbHkgYWRkIHRoZSBsZWFzdC1zcXVhcmVzIGxpbmUgdG8gdGhlIGdyYXBoIHVzaW5nIGBzdGF0X3Ntb290aGAoYG1ldGhvZCA9IGxtYCB0ZWxscyBSIHRvIGZpdCBhICpsaW5lYXIgbW9kZWwqIGFuZCBgc2UgPSBGQUxTRWAgdGVsbHMgUiB0byAqbm90KiBpbmNsdWRlIHRoZSBzdGFuZGFyZCBlcnJvciBvZiB0aGUgbGluZSBvbiB0aGUgZ3JhcGgpOiAKCmBgYHtyLCByZXN1bHRzPSAnaGlkZScgfQpzdGF0ZV9hdmcgJT4lCiAgZ2dwbG90KGFlcyh4ID0gc21vb3RoZWRfd2VhcmluZ19tYXNrLCB5ID0gc21vb3RoZWRfY2xpKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc3RhdF9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSkgCmBgYAoKVGhpcyBpcyBncmVhdCBmb3IgYWRkaW5nIGEgbGluZSB0byB0aGUgZ3JhcGgsIGJ1dCBkb2Vzbid0IHRlbGwgdXMgbXVjaCBhYm91dCB0aGUgKiptb2RlbCoqLiBGb3IgaW5zdGFuY2UsIHdoYXQgaXMgJFxoYXR7XGJldGFfMX0kPyBJcyB0aGUgZXN0aW1hdGUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudD8gV2UgaGF2ZSBubyBpZGVhLiAKCiMjIyBGaXR0aW5nIHRoZSBNb2RlbAoKTGV0J3MgZ28gYWhlYWQgYW5kIGZpdCBvdXIgbW9kZWw6IAoKYGBge3J9CmxtX21vZCA9IGxtKHNtb290aGVkX2NsaSB+IHNtb290aGVkX3dlYXJpbmdfbWFzaywgZGF0YSA9IHN0YXRlX2F2ZykKYGBgCgooYW5kLCBzaW5jZSB3ZSdyZSBnb29kIHN0YXRpc3RpY2lhbnMsIHdlJ2xsIGNoZWNrIG91ciByZXNpZHVhbCBwbG90cyB0byBtYWtlIHN1cmUgd2UgZG9uJ3QgaGF2ZSBhbnkgZWdyZWdpb3VzIHZpb2xhdGlvbnMpIEluIHlvdXIgcmVwb3J0LCBzdGF0ZSB0aGUgTElORSBhc3N1bXB0aW9ucywgaG93IHdlIGNoZWNrIGVhY2ggb2YgdGhlbSwgYW5kIHdoZXRoZXIgeW91IGhhdmUgYW55IGNvbmNlcm5zIGFib3V0IHRoaXMgbW9kZWwuIE5vdGUgdGhhdCB3ZSB1c2UgdGhlIGBwYXRjaHdvcmtgIGxpYnJhcnkgaGVyZSAtLSB0aGlzIHBhY2thZ2UgYWxsb3dzIHVzIHRvIHVzZSByZWFsbHkgc2ltcGxlIG5vdGF0aW9uIGZvciBjb21iaW5pbmcgYGdncGxvdHNgIGludG8gYSBzaW5nbGUgZmlndXJlICh0aGUgYHAxICsgcDIgKyBwM2AgYXQgdGhlIGVuZCkuIEluIHlvdXIgcmVwb3J0LCBjbGVhbiB0aGVzZSBmaWd1cmVzIHVwIGEgYml0LiAKCmBgYHtyLCBmaWcud2lkdGggPSA2LCBmaWcuaGVpZ2h0ID0gM30KbGlicmFyeShwYXRjaHdvcmspCmxtX3JlcyA9IGF1Z21lbnQobG1fbW9kLCBpbnRlcnZhbCA9ICJwcmVkaWN0aW9uIikgCnAxID0gZ2dwbG90KGxtX3JlcywgYWVzKHggPSBzbW9vdGhlZF93ZWFyaW5nX21hc2ssIHkgPSAucmVzaWQpKSArCiAgZ2VvbV9wb2ludCgpCgpwMiA9IGdncGxvdChsbV9yZXMsIGFlcyh4ID0gLnJlc2lkKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTUpCgpwMyA9IGdncGxvdChsbV9yZXMsIGFlcyh4ID0gLmZpdHRlZCwgeSA9IC5yZXNpZCkpICsKICBnZW9tX3BvaW50KCkKCnAxICsgcDIgKyBwMwpgYGAKClRoZSBgdGlkeWAgcGFydCBvZiBgdGlkeW1vZGVsc2AgY29tZXMgaW4gaGFuZHkgd2hlbiB3ZSB3YW50IHRvIGxvb2sgYXQgb3VyIGNvZWZmaWNpZW50czogCgpgYGB7cn0KdGlkeShsbV9tb2QsIGNvbmYuaW50ID0gVFJVRSkKYGBgCgp3aGljaCBtYWtlIGl0IHN1cGVyIGVhc3kgdG8gbWFrZSB0YWJsZXMgb3IgZmlndXJlcyBiYXNlZCBvbiB0aGUgcmVzdWx0cy4gSW4geW91ciBsYWIgcmVwb3J0LCByZXBsaWNhdGUgdGhlIGZvbGxvd2luZyBncmFwaCB1c2luZyB0aGUgcmVzdWx0cyBmcm9tIGB0aWR5KGxtX21vZClgIC0geW91IGNhbiB1c2UgYSBkaWZmZXJlbnQgdGhlbWUgb3IgbGFiZWxzIGlmIHlvdSBwcmVmZXIuICgqSGludDoqIGNoZWNrIG91dCB0aGUgaGVscCBwYWdlIGZvciBgZ2VvbV9wb2ludHJhbmdlKClgKQoKYGBge3IsIGZpZy53aWR0aCA9IDYsIGZpZy5oZWlnaHQgPTMsIGVjaG8gPSBGQUxTRX0KdGlkeShsbV9tb2QsIGNvbmYuaW50ID0gVFJVRSkgJT4lCiAgZ2dwbG90KC4sIGFlcyh5ID0gdGVybSwgeCA9IGVzdGltYXRlLCB4bWluID0gY29uZi5sb3csIHhtYXggPSBjb25mLmhpZ2gpKSArCiAgZ2VvbV9wb2ludHJhbmdlKCkgKyAKICB0aGVtZV9taW5pbWFsKCkgKyAKICBsYWJzKHkgPSAiVGVybSIsCiAgICAgICB4ID0gIk9MUyBFc3RpbWF0ZSIpCmBgYAoKCiMgTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24KCk9wZW5JbnRybyBbT25saW5lIFR1dG9yaWFsXShodHRwczovL29wZW5pbnRyby5zaGlueWFwcHMuaW8vaW1zLTA0LW11bHRpdmFyaWFibGUtYW5kLWxvZ2lzdGljLW1vZGVscy0wMy8jc2VjdGlvbi1tdWx0aXBsZS1yZWdyZXNzaW9uKSBvbiBNdWx0aXBsZSBSZWdyZXNzaW9uCgpJbmNsdWRlIHNjcmVlbnNob3RzIG9mIHlvdXIgdHdvIGZhdm9yaXRlIHBsb3RzLiBZb3UgY2FuIGluY2x1ZGUgYSBzY3JlZW5zaG90IGluIGFuIHJtYXJrZG93biBkb2N1bWVudCBieSBzYXZpbmcgdGhlIHNjcmVlbnNob3QgaW4gdGhlIHNhbWUgZm9sZGVyIGFzIHlvdXIgcm1hcmtkb3duIGZpbGUgYW5kIGFkZGluZyBgIVtdKFNDUkVFTlNIT1RfRklMRU5BTUUpYCwgd2hlcmUgeW91IGZpbGwgaW4gYFNDUkVFTlNIT1RfRklMRU5BTUVgIHdpdGggdGhlIG5hbWUgb2YgeW91ciBzY3JlZW5zaG90LiAobWFrZSBzdXJlIHRvIGluY2x1ZGUgYC5qcGdgIG9yIGAucG5nYCkgCgoK