library(tidyverse)

Exercise 1: Data Manipulation

Part 1: The map functions from purrr

  1. Use purrr functions to find the column means for mtcars dataset. Note that the mean performance cars in this dataset is very poor. That’s because these models are from the 70s!
map_dbl(mtcars, mean)
       mpg        cyl       disp         hp       drat         wt       qsec         vs 
 20.090625   6.187500 230.721875 146.687500   3.596563   3.217250  17.848750   0.437500 
        am       gear       carb 
  0.406250   3.687500   2.812500 
  1. Use purrr functions to generate 10 random normals for each of the mean parameter: \(\mu = -18, 5, 10, 30\).
map(c(-18, 5, 10, 30), rnorm, n = 10)
[[1]]
 [1] -18.18672 -19.21094 -18.98525 -17.38768 -18.37241 -18.65973 -18.50938 -16.32759
 [9] -17.83721 -16.94049

[[2]]
 [1] 4.239685 4.028045 6.054269 6.761718 3.786109 5.606558 6.752187 4.734244 5.102416
[10] 5.852126

[[3]]
 [1]  9.319304  9.128619 10.128532 11.069123  9.171212  7.801036 10.571461 11.529022
 [9] 10.306849 10.788314

[[4]]
 [1] 29.79419 32.22650 29.03999 30.65289 27.28731 30.26733 31.25100 27.90372 28.48056
[10] 29.02555
  1. What is the difference between running `map(1:5, runif) map(list(1:5), rnorm), map(list(1, 2, 3, 4, 5), rnorm) and map(1:5, rnorm, n = 5)?

Below, map takes in each argument from a vector: 1, 2, 3, 4, 5 as the first argument n = for rnorm(). So the function will generate iteratively: one, two, three, four, five random normal numbers (with default mean 0, and standard deviaton 1).

map(1:5, rnorm)
[[1]]
[1] 0.3543547

[[2]]
[1] -1.0807805 -0.3617925

[[3]]
[1] -2.59539565  1.04837239 -0.08422438

[[4]]
[1]  0.01762317 -0.22745079 -1.11850532 -0.45947053

[[5]]
[1]  0.2661181  1.3736770 -1.8404807  1.4866985 -1.0442055

Below, note that list(1:5) is a list of one argument. This first argument is equal to a vector c(1,2,3,4,5), and is supplied to rnorm() as the first argument, so map only loops over once and is equivalent to calling: rnorm(c(1,2,3,4,5)).

map(list(1:5), rnorm)
[[1]]
[1]  0.03501295 -1.13494274 -2.01695308 -1.06612147  0.54139114

The syntax below will be equivalent to the first example, since now list(1, 2, 3, 4, 5) is a list of 5 elements.

map(list(1, 2, 3, 4, 5), rnorm)
[[1]]
[1] 0.07671454

[[2]]
[1] -0.4366112 -0.5760937

[[3]]
[1]  0.6754205 -0.7238659 -0.5108671

[[4]]
[1] -1.3759907 -0.6210061  0.4072337  0.4596481

[[5]]
[1] -0.8108628  2.4343855  1.1015686  0.7011843  1.5210154

The example below is slightly different. Here, since we already force the n parameter to equal 7, the map function will loop over the vector 1:5 and supply each of the five elements to rnorm() as the second argument, mean =. Thus, resulting in a list of random normals, each of length 7, and each with different mean parameter.

map(1:5, rnorm, n = 7)
[[1]]
[1]  0.72775020  0.82556315  0.03440454  0.33252510  2.08009368  1.59179423 -0.57368740

[[2]]
[1] 1.2699895 0.7281602 4.6343781 1.9799894 1.0692924 1.7431891 1.9119823

[[3]]
[1] 4.373457 2.403356 3.049195 3.350636 2.416042 2.364417 3.469767

[[4]]
[1] 2.453333 3.379174 5.364128 2.917396 2.550765 4.287269 5.384675

[[5]]
[1] 4.370332 3.204837 5.230435 5.440600 3.635311 4.868689 6.862728
  1. For looping over multiple arguments you can use map2() or pmap() function. Use map2() to generate 5 random numbers with for each pair of mean, and standard devtaion equal to: c(2, 1), c(-3, 10), c(20, 0.1), c(-17, 2).
map2(c(2, -3, 20, -17), c(1, 10, 0.1, 2), rnorm, n = 5)
[[1]]
[1] 1.693059 2.779361 4.028268 2.752682 3.002951

[[2]]
[1]  1.5636282 12.1845171  7.1027527 15.1386819 -0.8149126

[[3]]
[1] 19.95008 19.92959 20.02988 20.06964 19.96810

[[4]]
[1] -18.12892 -16.15568 -17.49418 -14.22181 -15.94779

Part 2: Merging

  1. Identify the keys in the following datasets (you might need to install some packages and read some documentation):
  • Lahman::Batting
  • nasaweather::atmos
  • ggplot2::diamonds
Lahman::Batting %>% 
    count(playerID, yearID, teamID, lgID, stint) %>% 
    filter(n > 1)
nasaweather::atmos %>%
    count(lat, long, year, month) %>% 
    filter(n > 1)

None of the combination of the variables uniquely identifies observations. Thus, the row number is the key for the diamonds dataset.

ggplot2::diamonds %>%
    count(carat, cut, color, clarity, depth, table, price, x, y, z)  %>% 
    filter(n > 1)
  1. Add the location (i.e. the lat and lon) of the destination airport
    to flights dataset from nycflights13 package. Then, construct a new data table mean_delay containing information on the average arrival delay for each destination airport and its location (i.e. the lat and lon). Remember that some of the recoreds are missing, so use na.rm = TRUE when computing means.
library(nycflights13)
flights2 <- flights %>%
    left_join(airports, by = c("dest" = "faa")) 
mean_delay <- flights2 %>%
    group_by(lat, lon, origin) %>%
    summarise(mean_dep_delay = mean(arr_delay, na.rm = TRUE))

You can plot your data on the map as follows:

(plt <- ggplot(mean_delay, aes(lon, lat)) +
    borders("state") +
    geom_point(aes(color = mean_dep_delay), size = 3) +
    coord_quickmap() + theme_bw() +
    scale_color_viridis_c())

Part 3: Export datasets

  1. Export the mean_delay data as a ‘.tsv’ file. Save it on your “Desktop” then check the file was properly saved, then delete it.
write_tsv(mean_delay, path = "~/Desktop/mean_delay.tsv")
  1. Check what objects are currently stored in your environment using ls(). Save mean_delay as a R object using saveRDS() function to save it as am ‘.rds’ file. Then, select 2 different objects from the environment and use save() to save them to an ‘.rda’ file. Then, remove these objects and the mean_delay tibble. Check that you can reload these objects from files you saved.
ls()
[1] "flights2"   "mean_delay" "plt"       

Saving as R objects:

saveRDS(mean_delay, file = "~/Desktop/mean_delay.rds")
save(list = c("plt", "flights2"), file = "~/Desktop/two_objects.rda")

Now, you can delete the objects:

rm(mean_delay, flights2, plt)
ls()
character(0)

And then load them in using the following:

mean_delay <- readRDS("~/Desktop/mean_delay.rds")
mean_delay
load("~/Desktop/two_objects.rda")
ls()
[1] "flights2"   "mean_delay" "plt"       

Exercise 2: Exploratory Data Analysis

Zillow provides data on home prices, including median rental price per square feet and the median estimated home value. There are many more statistics provided by zillow in this website that you can explore.

zillow_url1 <- paste0("http://files.zillowstatic.com/research/public/City/",
                    "City_MedianRentalPricePerSqft_AllHomes.csv")
zillow_url2 <- paste0("http://files.zillowstatic.com/research/public/City/",
                      "City_ZriPerSqft_AllHomes.csv")
price_per_sqft <- read_csv(zillow_url1) 
Parsed with column specification:
cols(
  .default = col_double(),
  RegionName = col_character(),
  State = col_character(),
  Metro = col_character(),
  CountyName = col_character(),
  SizeRank = col_integer()
)
See spec(...) for full column specifications.
value_per_sqft <- read_csv(zillow_url2) 
Parsed with column specification:
cols(
  .default = col_double(),
  RegionID = col_integer(),
  RegionName = col_character(),
  State = col_character(),
  Metro = col_character(),
  CountyName = col_character(),
  SizeRank = col_integer()
)
See spec(...) for full column specifications.
# First, we tidy the datasets:
price_per_sqft <- price_per_sqft %>%
    select(-Metro) %>%
    gather(`2010-01`:`2018-08`, key = "date", value = "price") 
value_per_sqft <- value_per_sqft %>%
    select(RegionID:CountyName, `2011-01`:`2018-08`) %>%
    gather(`2011-01`:`2018-08`, key = "date", value = "value") 
# Do the inner join:
home_per_sqft <- price_per_sqft %>%
    inner_join(value_per_sqft)
Joining, by = c("RegionName", "State", "CountyName", "date")
# Format dates:
home_per_sqft <- home_per_sqft %>%
    mutate(date = paste0(date, "-01"),
           date = as.Date(date))
# We filter to only top 10 priciest states this year
# excluding DC and HI:
top10_States <- home_per_sqft %>%
    filter(date >= as.Date("2018-01-01")) %>%
    filter(State != "HI", State != "DC") %>%
    group_by(State) %>%
    summarise(price = mean(price, na.rm = TRUE)) %>%
    top_n(10)
Selecting by price
home_per_sqft <- home_per_sqft %>%
    filter(State %in% top10_States$State)
#rm(price_per_sqft, value_per_sqft)

The most expensive states by median home price per sq. ft. are:

top10_States
  1. For the current year (2018), using the filtered home_per_sqft dataset show the relationship between the value and the price per square foot of homes in 10 chosen states. The value is a dollar-denominated and estimated by Zillow.
  • Is there a correlation between the price and the value per square foot?
  • Are there any atypical trends you observe?
  • Are there any clusters of regions that are pricier than, expected based on their estimated value? Find, the RegionName for an example these outliers, and see if it makes sense.
  • Are there any clusters of regions that have higher value than, expected based on how cheap they are? Find, the RegionName for an example of these outliers, and see if it makes sense.
home_per_sqft %>%
    filter(date >= as.Date("2018-01-01")) %>%
    ggplot(aes(x = value, y = price)) +
    geom_point(aes(color = State), alpha = 0.3) +
    theme(legend.position = "bottom") +
    geom_smooth()

home_per_sqft %>% 
    filter(date >= as.Date("2018-01-01")) %>%
    filter(price > 5, value > 4)
home_per_sqft %>% 
    filter(date >= as.Date("2018-01-01")) %>%
    filter(price > 3, value < 2)
home_per_sqft %>% 
    filter(date >= as.Date("2018-01-01")) %>%
    filter(price < 2.2, value > 2.8)
  1. Collapse the data by computing the average of the median home price per square foot for each (state, date) pair. Then show the price trend over time for each of the top 10 states.
df <- home_per_sqft %>%
    group_by(State, date) %>%
    summarise(
        price = mean(price, na.rm = TRUE),
        value = mean(value, na.rm = TRUE)) 
ggplot(data = df, aes(x = date, y = price, color = State)) +
    geom_line(lwd = 0.7)

  1. Now, subset your home_per_sqft dataset to homes located in the Bay Area:
bayarea_counties <- c("Alameda", "Napa", "Santa Clara", "Contra Costa", 
                      "San Francisco", "Solano", "Marin", "San Mateo", 
                      "Sonoma")
bay_area_home <- home_per_sqft %>%
    filter(CountyName %in% bayarea_counties)

Filter your observations to the once after “2013-09-01”. Then generate a boxplot of home prices per square foot for the following regions:

cities <- c("Oakland", "San Francisco", "Berkeley", "San Jose",
            "San Mateo", "Redwood City", "Mountain View", "Napa",
            "South San Francisco", "Menlo Park", "Cupertino")

For which region were the median home prices per square foot highest over the most recent 5 years period?

df <- bay_area_home %>%
    filter(date >= as.Date("2013-09-01")) %>%
    filter(RegionName %in% cities) 
ggplot(df, aes(x = RegionName, y = price)) +
    geom_boxplot(aes(color = CountyName)) +
    coord_flip()

  1. For the same regions plot the prices over time.
ggplot(df) +
    geom_line(aes(date, price, color = RegionName))

If you are curious, you can repeat this type of analysis for the listing prices of home sales using the following files from zillow: ‘City_MedianListingPricePerSqft_AllHomes.csv’, ‘City_Zhvi_AllHomes.csv’.

Exercise 3: Interactive graphics with plotly

First generate the following (x,y,z) coordinates for your scatter 3D plot:

library(plotly)

Attaching package: ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
theta <- 2 * pi * runif(1000)
phi <- pi * runif(1000)
x <- sin(phi) * cos(theta)
y <- sin(phi) * sin(theta)
z <- cos(phi)
dat <- data.frame(x, y, z)
  1. Generate a scatter plot with (x, y, z) coordinates, just computed. What do you observe?
plot_ly(dat, x = ~x, y = ~y, z = ~z,
        type = "scatter3d", mode = "markers", marker = list(size = 3))
  1. Now generate new points as follows.
N <- 10
theta <- rep(2*pi * seq(0, 1, length.out = 100), N)
phi <- rep(pi * seq(0, 1, length.out = N*100))
x <- sin(phi) * cos(theta)
y <- sin(phi) * sin(theta)
z <- cos(phi)
dat <- data.frame(x, y, z)

Then, use plotly to create a 3D scatter point plot and a separate line plot. Color the points by their ordering in the data-frame.

plot_ly(dat, x = ~x, y = ~y, z = ~z, color = 1:1000) %>%
  add_markers(marker = list(size = 2))
plot_ly(dat, x = ~x, y = ~y, z = ~z, color = 1:1000, type = "scatter3d", 
        mode = "lines", line = list(width = 3))
LS0tCnRpdGxlOiAiTGVjdHVyZSA1OiBFeGVyY2lzZXMgd2l0aCBhbnN3ZXJzIgpkYXRlOiBPY3RvYmVyIDExdGgsIDIwMTgKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKCiMgRXhlcmNpc2UgMTogRGF0YSBNYW5pcHVsYXRpb24KCiMjIFBhcnQgMTogVGhlIG1hcCBmdW5jdGlvbnMgZnJvbSBgcHVycnJgIAoKYS4gVXNlIGBwdXJycmAgZnVuY3Rpb25zIHRvIGZpbmQgdGhlIGNvbHVtbiBtZWFucyBmb3IgYG10Y2Fyc2AgZGF0YXNldC4KTm90ZSB0aGF0IHRoZSBtZWFuIHBlcmZvcm1hbmNlIGNhcnMgaW4gdGhpcyBkYXRhc2V0IGlzIHZlcnkgCnBvb3IuIFRoYXQncyBiZWNhdXNlIHRoZXNlIG1vZGVscyBhcmUgZnJvbSB0aGUgNzBzIQoKYGBge3J9Cm1hcF9kYmwobXRjYXJzLCBtZWFuKQpgYGAKCmIuIFVzZSBgcHVycnJgIGZ1bmN0aW9ucyB0byBnZW5lcmF0ZSAxMCByYW5kb20gbm9ybWFscyBmb3IgZWFjaCBvZiAKdGhlIG1lYW4gcGFyYW1ldGVyOiAkXG11ID0gLTE4LCA1LCAxMCwgMzAkLgoKYGBge3J9Cm1hcChjKC0xOCwgNSwgMTAsIDMwKSwgcm5vcm0sIG4gPSAxMCkKYGBgCgoKYy4gV2hhdCBpcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHJ1bm5pbmcgYGBtYXAoMTo1LCBydW5pZilgIApgbWFwKGxpc3QoMTo1KSwgcm5vcm0pYCwgYG1hcChsaXN0KDEsIDIsIDMsIDQsIDUpLCBybm9ybSlgIGFuZCAKYG1hcCgxOjUsIHJub3JtLCBuID0gNSlgPwoKQmVsb3csIGBtYXBgIHRha2VzIGluIGVhY2ggYXJndW1lbnQgZnJvbSBhIHZlY3RvcjogMSwgMiwgMywgNCwgNQphcyB0aGUgZmlyc3QgYXJndW1lbnQgYG4gPSBgIGZvciBgcm5vcm0oKWAuIFNvIHRoZSBmdW5jdGlvbgp3aWxsIGdlbmVyYXRlIGl0ZXJhdGl2ZWx5OiBvbmUsIHR3bywgdGhyZWUsIGZvdXIsIGZpdmUgCnJhbmRvbSBub3JtYWwgbnVtYmVycyAod2l0aCBkZWZhdWx0IG1lYW4gMCwgYW5kIHN0YW5kYXJkIGRldmlhdG9uIDEpLgoKYGBge3J9Cm1hcCgxOjUsIHJub3JtKQpgYGAKCkJlbG93LCBub3RlIHRoYXQgYGxpc3QoMTo1KWAgaXMgYSBsaXN0IG9mIG9uZSBhcmd1bWVudC4gVGhpcyBmaXJzdCAKYXJndW1lbnQgaXMgZXF1YWwgdG8gYSB2ZWN0b3IgYGMoMSwyLDMsNCw1KWAsIGFuZCBpcyBzdXBwbGllZCB0bwpgcm5vcm0oKWAgYXMgdGhlIGZpcnN0IGFyZ3VtZW50LCBzbyBtYXAgb25seSBsb29wcyBvdmVyIApvbmNlIGFuZCBpcyBlcXVpdmFsZW50IHRvIGNhbGxpbmc6IGBybm9ybShjKDEsMiwzLDQsNSkpYC4KCmBgYHtyfQptYXAobGlzdCgxOjUpLCBybm9ybSkKYGBgCgpUaGUgc3ludGF4IGJlbG93IHdpbGwgYmUgZXF1aXZhbGVudCB0byB0aGUgZmlyc3QgZXhhbXBsZSwKc2luY2Ugbm93IGBsaXN0KDEsIDIsIDMsIDQsIDUpYCBpcyBhIGxpc3Qgb2YgNSBlbGVtZW50cy4KCmBgYHtyfQptYXAobGlzdCgxLCAyLCAzLCA0LCA1KSwgcm5vcm0pCmBgYAoKVGhlIGV4YW1wbGUgYmVsb3cgaXMgc2xpZ2h0bHkgZGlmZmVyZW50LiBIZXJlLCBzaW5jZSB3ZSBhbHJlYWR5CmZvcmNlIHRoZSBgbmAgcGFyYW1ldGVyIHRvIGVxdWFsIDcsIHRoZSBgbWFwYCBmdW5jdGlvbiB3aWxsIGxvb3Agb3Zlcgp0aGUgdmVjdG9yIGAxOjVgIGFuZCBzdXBwbHkgZWFjaCBvZiB0aGUgZml2ZSBlbGVtZW50cyB0byBgcm5vcm0oKWAgYXMgdGhlCnNlY29uZCBhcmd1bWVudCwgYG1lYW4gPSBgLiBUaHVzLCByZXN1bHRpbmcgaW4gYSBsaXN0IG9mIHJhbmRvbSBub3JtYWxzLAplYWNoIG9mIGxlbmd0aCA3LCBhbmQgZWFjaCB3aXRoIGRpZmZlcmVudCBtZWFuIHBhcmFtZXRlci4KCmBgYHtyfQptYXAoMTo1LCBybm9ybSwgbiA9IDcpCmBgYAoKZC4gRm9yIGxvb3Bpbmcgb3ZlciBtdWx0aXBsZSBhcmd1bWVudHMgeW91IGNhbiB1c2UgYG1hcDIoKWAgb3IgYHBtYXAoKWAKZnVuY3Rpb24uIFVzZSBgbWFwMigpYCB0byBnZW5lcmF0ZSA1IHJhbmRvbSBudW1iZXJzIHdpdGggZm9yIAplYWNoIHBhaXIgb2YgbWVhbiwgYW5kIHN0YW5kYXJkIGRldnRhaW9uIGVxdWFsIHRvOgpgYygyLCAxKSwgYygtMywgMTApLCBjKDIwLCAwLjEpLCBjKC0xNywgMilgLgoKYGBge3J9Cm1hcDIoYygyLCAtMywgMjAsIC0xNyksIGMoMSwgMTAsIDAuMSwgMiksIHJub3JtLCBuID0gNSkKYGBgCgoKCiMjIFBhcnQgMjogTWVyZ2luZwoKYS4gSWRlbnRpZnkgdGhlIGtleXMgaW4gdGhlIGZvbGxvd2luZyBkYXRhc2V0cwooeW91IG1pZ2h0IG5lZWQgdG8gaW5zdGFsbCBzb21lIHBhY2thZ2VzIGFuZCByZWFkIHNvbWUgZG9jdW1lbnRhdGlvbik6CgoKKiBgTGFobWFuOjpCYXR0aW5nYAoqIGBuYXNhd2VhdGhlcjo6YXRtb3NgCiogYGdncGxvdDI6OmRpYW1vbmRzYAoKCmBgYHtyfQpMYWhtYW46OkJhdHRpbmcgJT4lIHRibF9kZigpCiAgICBjb3VudChwbGF5ZXJJRCwgeWVhcklELCB0ZWFtSUQsIGxnSUQsIHN0aW50KSAlPiUgCiAgICBmaWx0ZXIobiA+IDEpCmBgYAoKYGBge3J9Cm5hc2F3ZWF0aGVyOjphdG1vcyAlPiUKICAgIGNvdW50KGxhdCwgbG9uZywgeWVhciwgbW9udGgpICU+JSAKICAgIGZpbHRlcihuID4gMSkKYGBgCgpOb25lIG9mIHRoZSBjb21iaW5hdGlvbiBvZiB0aGUgdmFyaWFibGVzIHVuaXF1ZWx5IGlkZW50aWZpZXMgb2JzZXJ2YXRpb25zLgpUaHVzLCB0aGUgYHJvdyBudW1iZXJgIGlzIHRoZSBrZXkgZm9yIHRoZSBgZGlhbW9uZHNgIGRhdGFzZXQuCgpgYGB7cn0KZ2dwbG90Mjo6ZGlhbW9uZHMgJT4lCiAgICBjb3VudChjYXJhdCwgY3V0LCBjb2xvciwgY2xhcml0eSwgZGVwdGgsIHRhYmxlLCBwcmljZSwgeCwgeSwgeikgICU+JSAKICAgIGZpbHRlcihuID4gMSkKYGBgCgpiLiBBZGQgdGhlIGxvY2F0aW9uIChpLmUuIHRoZSBsYXQgYW5kIGxvbikgb2YgdGhlIGRlc3RpbmF0aW9uIGFpcnBvcnQgIAp0byBgZmxpZ2h0c2AgZGF0YXNldCBmcm9tIGBueWNmbGlnaHRzMTNgIHBhY2thZ2UuIFRoZW4sIGNvbnN0cnVjdCBhIG5ldwpkYXRhIHRhYmxlIGBtZWFuX2RlbGF5YCBjb250YWluaW5nIGluZm9ybWF0aW9uIG9uIHRoZSBhdmVyYWdlIGFycml2YWwKZGVsYXkgZm9yIGVhY2ggZGVzdGluYXRpb24gYWlycG9ydCBhbmQgaXRzIGxvY2F0aW9uIChpLmUuIHRoZSBsYXQgYW5kIGxvbikuClJlbWVtYmVyIHRoYXQgc29tZSBvZiB0aGUgcmVjb3JlZHMgYXJlIG1pc3NpbmcsIHNvIHVzZSBgbmEucm0gPSBUUlVFYAp3aGVuIGNvbXB1dGluZyBtZWFucy4KCmBgYHtyfQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykKZmxpZ2h0czIgPC0gZmxpZ2h0cyAlPiUKICAgIGxlZnRfam9pbihhaXJwb3J0cywgYnkgPSBjKCJkZXN0IiA9ICJmYWEiKSkgCm1lYW5fZGVsYXkgPC0gZmxpZ2h0czIgJT4lCiAgICBncm91cF9ieShsYXQsIGxvbiwgb3JpZ2luKSAlPiUKICAgIHN1bW1hcmlzZShtZWFuX2RlcF9kZWxheSA9IG1lYW4oYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpKQpgYGAKCgpZb3UgY2FuIHBsb3QgeW91ciBkYXRhIG9uIHRoZSBtYXAgYXMgZm9sbG93czoKCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD01fQoocGx0IDwtIGdncGxvdChtZWFuX2RlbGF5LCBhZXMobG9uLCBsYXQpKSArCiAgICBib3JkZXJzKCJzdGF0ZSIpICsKICAgIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gbWVhbl9kZXBfZGVsYXkpLCBzaXplID0gMykgKwogICAgY29vcmRfcXVpY2ttYXAoKSArIHRoZW1lX2J3KCkgKwogICAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkpCmBgYAoKCgojIyBQYXJ0IDM6IEV4cG9ydCBkYXRhc2V0cwoKYS4gRXhwb3J0IHRoZSBgbWVhbl9kZWxheWAgZGF0YSBhcyBhICcudHN2JyBmaWxlLiBTYXZlIGl0IG9uIHlvdXIgIkRlc2t0b3AiCnRoZW4gY2hlY2sgdGhlIGZpbGUgd2FzIHByb3Blcmx5IHNhdmVkLCB0aGVuIGRlbGV0ZSBpdC4KCmBgYHtyLCBldmFsID0gRkFMU0V9CndyaXRlX3RzdihtZWFuX2RlbGF5LCBwYXRoID0gIn4vRGVza3RvcC9tZWFuX2RlbGF5LnRzdiIpCmBgYAoKCmIuIENoZWNrIHdoYXQgb2JqZWN0cyBhcmUgY3VycmVudGx5IHN0b3JlZCBpbiB5b3VyIGVudmlyb25tZW50IHVzaW5nIGBscygpYC4KU2F2ZSBgbWVhbl9kZWxheWAgYXMgYSBSIG9iamVjdCB1c2luZyBgc2F2ZVJEUygpYCBmdW5jdGlvbiB0byBzYXZlCml0IGFzIGFtICcucmRzJyBmaWxlLiBUaGVuLCBzZWxlY3QgMiBkaWZmZXJlbnQgb2JqZWN0cyBmcm9tIHRoZSBlbnZpcm9ubWVudCAKYW5kIHVzZSBgc2F2ZSgpYCB0byBzYXZlIHRoZW0gdG8gYW4gJy5yZGEnIGZpbGUuIFRoZW4sIHJlbW92ZSB0aGVzZQpvYmplY3RzIGFuZCB0aGUgYG1lYW5fZGVsYXlgIHRpYmJsZS4gQ2hlY2sgdGhhdCB5b3UgY2FuIHJlbG9hZCB0aGVzZQpvYmplY3RzIGZyb20gZmlsZXMgeW91IHNhdmVkLgoKCmBgYHtyfQpscygpCmBgYAoKU2F2aW5nIGFzIFIgb2JqZWN0czoKCmBgYHtyfQpzYXZlUkRTKG1lYW5fZGVsYXksIGZpbGUgPSAifi9EZXNrdG9wL21lYW5fZGVsYXkucmRzIikKc2F2ZShsaXN0ID0gYygicGx0IiwgImZsaWdodHMyIiksIGZpbGUgPSAifi9EZXNrdG9wL3R3b19vYmplY3RzLnJkYSIpCmBgYAoKTm93LCB5b3UgY2FuIGRlbGV0ZSB0aGUgb2JqZWN0czoKCmBgYHtyfQpybShtZWFuX2RlbGF5LCBmbGlnaHRzMiwgcGx0KQpscygpCmBgYAoKQW5kIHRoZW4gbG9hZCB0aGVtIGluIHVzaW5nIHRoZSBmb2xsb3dpbmc6CgpgYGB7cn0KbWVhbl9kZWxheSA8LSByZWFkUkRTKCJ+L0Rlc2t0b3AvbWVhbl9kZWxheS5yZHMiKQptZWFuX2RlbGF5CmBgYAoKCmBgYHtyfQpsb2FkKCJ+L0Rlc2t0b3AvdHdvX29iamVjdHMucmRhIikKbHMoKQpgYGAKCgoKIyBFeGVyY2lzZSAyOiBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzCgpaaWxsb3cgcHJvdmlkZXMgZGF0YSBvbiBob21lIHByaWNlcywgaW5jbHVkaW5nIG1lZGlhbiByZW50YWwKcHJpY2UgcGVyIHNxdWFyZSBmZWV0IGFuZCB0aGUgbWVkaWFuIGVzdGltYXRlZCBob21lIHZhbHVlLiAKVGhlcmUgYXJlIG1hbnkgbW9yZSBzdGF0aXN0aWNzIHByb3ZpZGVkIGJ5IHppbGxvdyBpbiBbdGhpcyB3ZWJzaXRlXShodHRwczovL3d3dy56aWxsb3cuY29tL3Jlc2VhcmNoL2RhdGEvKSAKdGhhdCB5b3UgY2FuIGV4cGxvcmUuCgpgYGB7cn0KemlsbG93X3VybDEgPC0gcGFzdGUwKCJodHRwOi8vZmlsZXMuemlsbG93c3RhdGljLmNvbS9yZXNlYXJjaC9wdWJsaWMvQ2l0eS8iLAogICAgICAgICAgICAgICAgICAgICJDaXR5X01lZGlhblJlbnRhbFByaWNlUGVyU3FmdF9BbGxIb21lcy5jc3YiKQp6aWxsb3dfdXJsMiA8LSBwYXN0ZTAoImh0dHA6Ly9maWxlcy56aWxsb3dzdGF0aWMuY29tL3Jlc2VhcmNoL3B1YmxpYy9DaXR5LyIsCiAgICAgICAgICAgICAgICAgICAgICAiQ2l0eV9acmlQZXJTcWZ0X0FsbEhvbWVzLmNzdiIpCgpwcmljZV9wZXJfc3FmdCA8LSByZWFkX2Nzdih6aWxsb3dfdXJsMSkgCnZhbHVlX3Blcl9zcWZ0IDwtIHJlYWRfY3N2KHppbGxvd191cmwyKSAKYGBgCgoKYGBge3J9CiMgRmlyc3QsIHdlIHRpZHkgdGhlIGRhdGFzZXRzOgpwcmljZV9wZXJfc3FmdCA8LSBwcmljZV9wZXJfc3FmdCAlPiUKICAgIHNlbGVjdCgtTWV0cm8pICU+JQogICAgZ2F0aGVyKGAyMDEwLTAxYDpgMjAxOC0wOGAsIGtleSA9ICJkYXRlIiwgdmFsdWUgPSAicHJpY2UiKSAKdmFsdWVfcGVyX3NxZnQgPC0gdmFsdWVfcGVyX3NxZnQgJT4lCiAgICBzZWxlY3QoUmVnaW9uSUQ6Q291bnR5TmFtZSwgYDIwMTEtMDFgOmAyMDE4LTA4YCkgJT4lCiAgICBnYXRoZXIoYDIwMTEtMDFgOmAyMDE4LTA4YCwga2V5ID0gImRhdGUiLCB2YWx1ZSA9ICJ2YWx1ZSIpIAoKIyBEbyB0aGUgaW5uZXIgam9pbjoKaG9tZV9wZXJfc3FmdCA8LSBwcmljZV9wZXJfc3FmdCAlPiUKICAgIGlubmVyX2pvaW4odmFsdWVfcGVyX3NxZnQpCgojIEZvcm1hdCBkYXRlczoKaG9tZV9wZXJfc3FmdCA8LSBob21lX3Blcl9zcWZ0ICU+JQogICAgbXV0YXRlKGRhdGUgPSBwYXN0ZTAoZGF0ZSwgIi0wMSIpLAogICAgICAgICAgIGRhdGUgPSBhcy5EYXRlKGRhdGUpKQoKIyBXZSBmaWx0ZXIgdG8gb25seSB0b3AgMTAgcHJpY2llc3Qgc3RhdGVzIHRoaXMgeWVhcgojIGV4Y2x1ZGluZyBEQyBhbmQgSEk6CnRvcDEwX1N0YXRlcyA8LSBob21lX3Blcl9zcWZ0ICU+JQogICAgZmlsdGVyKGRhdGUgPj0gYXMuRGF0ZSgiMjAxOC0wMS0wMSIpKSAlPiUKICAgIGZpbHRlcihTdGF0ZSAhPSAiSEkiLCBTdGF0ZSAhPSAiREMiKSAlPiUKICAgIGdyb3VwX2J5KFN0YXRlKSAlPiUKICAgIHN1bW1hcmlzZShwcmljZSA9IG1lYW4ocHJpY2UsIG5hLnJtID0gVFJVRSkpICU+JQogICAgdG9wX24oMTApCgpob21lX3Blcl9zcWZ0IDwtIGhvbWVfcGVyX3NxZnQgJT4lCiAgICBmaWx0ZXIoU3RhdGUgJWluJSB0b3AxMF9TdGF0ZXMkU3RhdGUpCgojcm0ocHJpY2VfcGVyX3NxZnQsIHZhbHVlX3Blcl9zcWZ0KQpgYGAKClRoZSBtb3N0IGV4cGVuc2l2ZSBzdGF0ZXMgYnkgbWVkaWFuIGhvbWUgcHJpY2UgcGVyIHNxLiBmdC4gYXJlOgoKYGBge3J9CnRvcDEwX1N0YXRlcwpgYGAKCgphLiBGb3IgdGhlIGN1cnJlbnQgeWVhciAoMjAxOCksIAp1c2luZyB0aGUgZmlsdGVyZWQgYGhvbWVfcGVyX3NxZnRgIGRhdGFzZXQgc2hvdyB0aGUgcmVsYXRpb25zaGlwCmJldHdlZW4gdGhlIHZhbHVlIGFuZCB0aGUgcHJpY2UgcGVyIHNxdWFyZSBmb290IG9mIGhvbWVzIGluCjEwIGNob3NlbiBzdGF0ZXMuIFRoZSB2YWx1ZSBpcyBhIGRvbGxhci1kZW5vbWluYXRlZCAKYW5kIGVzdGltYXRlZCBieSBaaWxsb3cuCgoqIElzIHRoZXJlIGEgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgcHJpY2UgYW5kIHRoZSB2YWx1ZSBwZXIgc3F1YXJlIGZvb3Q/CiogQXJlIHRoZXJlIGFueSBhdHlwaWNhbCB0cmVuZHMgeW91IG9ic2VydmU/CiogQXJlIHRoZXJlIGFueSBjbHVzdGVycyBvZiByZWdpb25zIHRoYXQgYXJlIHByaWNpZXIgdGhhbiwKZXhwZWN0ZWQgYmFzZWQgb24gdGhlaXIgZXN0aW1hdGVkIHZhbHVlPyBGaW5kLCB0aGUgYFJlZ2lvbk5hbWVgCmZvciBhbiBleGFtcGxlIHRoZXNlIG91dGxpZXJzLCBhbmQgc2VlIGlmIGl0IG1ha2VzIHNlbnNlLgoqIEFyZSB0aGVyZSBhbnkgY2x1c3RlcnMgb2YgcmVnaW9ucyB0aGF0IGhhdmUgaGlnaGVyIHZhbHVlIHRoYW4sCmV4cGVjdGVkIGJhc2VkIG9uIGhvdyBjaGVhcCB0aGV5IGFyZT8gRmluZCwgdGhlIGBSZWdpb25OYW1lYApmb3IgYW4gZXhhbXBsZSBvZiB0aGVzZSBvdXRsaWVycywgYW5kIHNlZSBpZiBpdCBtYWtlcyBzZW5zZS4KCmBgYHtyIH0KaG9tZV9wZXJfc3FmdCAlPiUKICAgIGZpbHRlcihkYXRlID49IGFzLkRhdGUoIjIwMTgtMDEtMDEiKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSwgeSA9IHByaWNlKSkgKwogICAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBTdGF0ZSksIGFscGhhID0gMC4zKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKwogICAgZ2VvbV9zbW9vdGgoKQpgYGAKCmBgYHtyfQpob21lX3Blcl9zcWZ0ICU+JSAKICAgIGZpbHRlcihkYXRlID49IGFzLkRhdGUoIjIwMTgtMDEtMDEiKSkgJT4lCiAgICBmaWx0ZXIocHJpY2UgPiA1LCB2YWx1ZSA+IDQpCmBgYAoKYGBge3J9CmhvbWVfcGVyX3NxZnQgJT4lIAogICAgZmlsdGVyKGRhdGUgPj0gYXMuRGF0ZSgiMjAxOC0wMS0wMSIpKSAlPiUKICAgIGZpbHRlcihwcmljZSA+IDMsIHZhbHVlIDwgMikKYGBgCgpgYGB7cn0KaG9tZV9wZXJfc3FmdCAlPiUgCiAgICBmaWx0ZXIoZGF0ZSA+PSBhcy5EYXRlKCIyMDE4LTAxLTAxIikpICU+JQogICAgZmlsdGVyKHByaWNlIDwgMi4yLCB2YWx1ZSA+IDIuOCkKYGBgCgpiLiBDb2xsYXBzZSB0aGUgZGF0YSBieSBjb21wdXRpbmcgdGhlIGF2ZXJhZ2Ugb2YgdGhlIG1lZGlhbiBob21lIHByaWNlIApwZXIgc3F1YXJlIGZvb3QgZm9yIGVhY2ggKHN0YXRlLCBkYXRlKSBwYWlyLiBUaGVuIHNob3cgdGhlIHByaWNlIHRyZW5kIApvdmVyIHRpbWUgZm9yIGVhY2ggb2YgdGhlIHRvcCAxMCBzdGF0ZXMuIAoKYGBge3J9CmRmIDwtIGhvbWVfcGVyX3NxZnQgJT4lCiAgICBncm91cF9ieShTdGF0ZSwgZGF0ZSkgJT4lCiAgICBzdW1tYXJpc2UoCiAgICAgICAgcHJpY2UgPSBtZWFuKHByaWNlLCBuYS5ybSA9IFRSVUUpLAogICAgICAgIHZhbHVlID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgCgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeCA9IGRhdGUsIHkgPSBwcmljZSwgY29sb3IgPSBTdGF0ZSkpICsKICAgIGdlb21fbGluZShsd2QgPSAwLjcpCmBgYAoKCmMuIE5vdywgc3Vic2V0IHlvdXIgYGhvbWVfcGVyX3NxZnRgIGRhdGFzZXQgdG8gaG9tZXMgbG9jYXRlZCBpbiB0aGUgQmF5IEFyZWE6CgpgYGB7cn0KYmF5YXJlYV9jb3VudGllcyA8LSBjKCJBbGFtZWRhIiwgIk5hcGEiLCAiU2FudGEgQ2xhcmEiLCAiQ29udHJhIENvc3RhIiwgCiAgICAgICAgICAgICAgICAgICAgICAiU2FuIEZyYW5jaXNjbyIsICJTb2xhbm8iLCAiTWFyaW4iLCAiU2FuIE1hdGVvIiwgCiAgICAgICAgICAgICAgICAgICAgICAiU29ub21hIikKYmF5X2FyZWFfaG9tZSA8LSBob21lX3Blcl9zcWZ0ICU+JQogICAgZmlsdGVyKENvdW50eU5hbWUgJWluJSBiYXlhcmVhX2NvdW50aWVzKQpgYGAKCiFbXShodHRwOi8vd3d3LmJheWFyZWFjZW5zdXMuY2EuZ292L2dyYXBoaWNzL0JheUFyZWFDb3VudGllczEwLmdpZikKCkZpbHRlciB5b3VyIG9ic2VydmF0aW9ucyB0byB0aGUgb25jZSBhZnRlciAiMjAxMy0wOS0wMSIuClRoZW4gZ2VuZXJhdGUgYSBib3hwbG90IG9mIGhvbWUgcHJpY2VzIHBlciBzcXVhcmUgZm9vdCBmb3IKdGhlIGZvbGxvd2luZyByZWdpb25zOiAKCmBgYHtyfQpjaXRpZXMgPC0gYygiT2FrbGFuZCIsICJTYW4gRnJhbmNpc2NvIiwgIkJlcmtlbGV5IiwgIlNhbiBKb3NlIiwKICAgICAgICAgICAgIlNhbiBNYXRlbyIsICJSZWR3b29kIENpdHkiLCAiTW91bnRhaW4gVmlldyIsICJOYXBhIiwKICAgICAgICAgICAgIlNvdXRoIFNhbiBGcmFuY2lzY28iLCAiTWVubG8gUGFyayIsICJDdXBlcnRpbm8iKQpgYGAKCkZvciB3aGljaCByZWdpb24gd2VyZSB0aGUgbWVkaWFuIGhvbWUgcHJpY2VzIHBlciBzcXVhcmUgZm9vdApoaWdoZXN0IG92ZXIgdGhlIG1vc3QgcmVjZW50IDUgeWVhcnMgcGVyaW9kPwoKYGBge3J9CmRmIDwtIGJheV9hcmVhX2hvbWUgJT4lCiAgICBmaWx0ZXIoZGF0ZSA+PSBhcy5EYXRlKCIyMDEzLTA5LTAxIikpICU+JQogICAgZmlsdGVyKFJlZ2lvbk5hbWUgJWluJSBjaXRpZXMpIApnZ3Bsb3QoZGYsIGFlcyh4ID0gUmVnaW9uTmFtZSwgeSA9IHByaWNlKSkgKwogICAgZ2VvbV9ib3hwbG90KGFlcyhjb2xvciA9IENvdW50eU5hbWUpKSArCiAgICBjb29yZF9mbGlwKCkKYGBgCgpkLiBGb3IgdGhlIHNhbWUgcmVnaW9ucyBwbG90IHRoZSBwcmljZXMgb3ZlciB0aW1lLgoKYGBge3J9CmdncGxvdChkZikgKwogICAgZ2VvbV9saW5lKGFlcyhkYXRlLCBwcmljZSwgY29sb3IgPSBSZWdpb25OYW1lKSkKYGBgCgoKSWYgeW91IGFyZSBjdXJpb3VzLCB5b3UgY2FuIHJlcGVhdCB0aGlzIHR5cGUgb2YgYW5hbHlzaXMgZm9yIAp0aGUgbGlzdGluZyBwcmljZXMgb2YgaG9tZSBzYWxlcyB1c2luZyB0aGUgZm9sbG93aW5nCmZpbGVzIGZyb20gemlsbG93OiAnQ2l0eV9NZWRpYW5MaXN0aW5nUHJpY2VQZXJTcWZ0X0FsbEhvbWVzLmNzdicsCidDaXR5X1podmlfQWxsSG9tZXMuY3N2Jy4KCgojIEV4ZXJjaXNlIDM6IEludGVyYWN0aXZlIGdyYXBoaWNzIHdpdGggYHBsb3RseWAKCkZpcnN0IGdlbmVyYXRlIHRoZSBmb2xsb3dpbmcgKHgseSx6KSBjb29yZGluYXRlcyBmb3IgeW91ciBzY2F0dGVyIDNEIHBsb3Q6CmBgYHtyfQpsaWJyYXJ5KHBsb3RseSkKdGhldGEgPC0gMiAqIHBpICogcnVuaWYoMTAwMCkKcGhpIDwtIHBpICogcnVuaWYoMTAwMCkKeCA8LSBzaW4ocGhpKSAqIGNvcyh0aGV0YSkKeSA8LSBzaW4ocGhpKSAqIHNpbih0aGV0YSkKeiA8LSBjb3MocGhpKQpkYXQgPC0gZGF0YS5mcmFtZSh4LCB5LCB6KQpgYGAKCmEuIEdlbmVyYXRlIGEgc2NhdHRlciBwbG90IHdpdGggKHgsIHksIHopIGNvb3JkaW5hdGVzLCBqdXN0IGNvbXB1dGVkLiBXaGF0IGRvCnlvdSBvYnNlcnZlPwoKYGBge3J9CnBsb3RfbHkoZGF0LCB4ID0gfngsIHkgPSB+eSwgeiA9IH56LAogICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJtYXJrZXJzIiwgbWFya2VyID0gbGlzdChzaXplID0gMykpCmBgYAoKYi4gTm93IGdlbmVyYXRlIG5ldyBwb2ludHMgYXMgZm9sbG93cy4gCgpgYGB7cn0KTiA8LSAxMAp0aGV0YSA8LSByZXAoMipwaSAqIHNlcSgwLCAxLCBsZW5ndGgub3V0ID0gMTAwKSwgTikKcGhpIDwtIHJlcChwaSAqIHNlcSgwLCAxLCBsZW5ndGgub3V0ID0gTioxMDApKQp4IDwtIHNpbihwaGkpICogY29zKHRoZXRhKQp5IDwtIHNpbihwaGkpICogc2luKHRoZXRhKQp6IDwtIGNvcyhwaGkpCmRhdCA8LSBkYXRhLmZyYW1lKHgsIHksIHopCmBgYAoKVGhlbiwgdXNlIGBwbG90bHlgIHRvIGNyZWF0ZSBhIDNEIApzY2F0dGVyIHBvaW50IHBsb3QgYW5kIGEgc2VwYXJhdGUgbGluZSBwbG90LiBDb2xvciB0aGUgcG9pbnRzIGJ5CnRoZWlyIG9yZGVyaW5nIGluIHRoZSBkYXRhLWZyYW1lLgoKCmBgYHtyfQpwbG90X2x5KGRhdCwgeCA9IH54LCB5ID0gfnksIHogPSB+eiwgY29sb3IgPSAxOjEwMDApICU+JQogIGFkZF9tYXJrZXJzKG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDIpKQpgYGAKCmBgYHtyfQpwbG90X2x5KGRhdCwgeCA9IH54LCB5ID0gfnksIHogPSB+eiwgY29sb3IgPSAxOjEwMDAsIHR5cGUgPSAic2NhdHRlcjNkIiwgCiAgICAgICAgbW9kZSA9ICJsaW5lcyIsIGxpbmUgPSBsaXN0KHdpZHRoID0gMykpCmBgYAoKCgoK