1. State Average Unemployment Rates
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
lausURL <- "http://www.stat.uiowa.edu/~luke/data/laus/laucntycur14-2018.txt"
lausFile <- "laucntycur14-2018.txt"
if (! file.exists(lausFile))
download.file(lausURL, lausFile)
lausUS <- read.table(lausFile,
col.names = c("LAUSAreaCode", "State", "County",
"Title", "Period",
"LaborForce", "Employed",
"Unemployed", "UnempRate"),
quote = "", sep = "|", skip = 6, strip.white = TRUE,
fill = TRUE)
footstart <- grep("------", lausUS$LAUSAreaCode)
lausUS <- lausUS[1 : (footstart - 1), ]
lausUS <- mutate(lausUS,
UnempRate = as.numeric(UnempRate),
LaborForce = as.numeric(gsub(",", "", LaborForce)),
Employed = as.numeric(gsub(",", "", Employed)),
Unemployed = as.numeric(gsub(",", "", Unemployed)))
After reading the data into a data frame lausUS
the unemployment rate for each state can be computed as
\[ \frac{\text{number of unemployed in the state}}{\text{size of the
labor force in the state}}. \]
You can also compute this number as the weighted average of the county unemployment rates, weighted by the county labor force sizes.
Simply averaging the unemployment rates produces the wrong state-level result: small counties receive much more weight than they should.
To see the difference we can compute both values:
unemp_by_state <- summarize(group_by(lausUS, State),
urate = 100 * sum(Unemployed) / sum(LaborForce),
urateNW = mean(UnempRate))
The map data is obtained with
gusa <- map_data("state")
To allow the map data to be merged with the unemployment data we can arrange that both data frames contain the state FIPS code in a variable named fips
:
unemp_by_state <- rename(unemp_by_state, fips = State)
state.fips <-
select(maps::state.fips, fips, region = polyname) |>
mutate(region = sub(":.*", "", region)) |>
unique()
gusa <- left_join(gusa, state.fips, "region")
A left join of the map and unemployment data is placed in gusa_unemp
:
gusa_unemp <- left_join(gusa, unemp_by_state, "fips")
Since the unemployment rate is a continuous variable, a sequential palette is most appropriate. The default palette does not work well; the "Reds"
palette from RColorBrewer
is a good choice:
ggplot(gusa_unemp) +
geom_polygon(aes(long, lat, group = group, fill = urate)) +
coord_map() +
scale_fill_distiller(palette = "Reds", direction = 1) +
mapthm
mapthm
is a theme based on ggthemes::theme_map
that keeps the guide on the right.
Using a faceted display we can look at the result for the incorrect unweighted computation of the state unemployment rate:
gusa_unemp_td <- gather(gusa_unemp, which, rate, urate, urateNW)
ggplot(gusa_unemp_td) +
geom_polygon(aes(long, lat, group = group, fill = rate)) +
coord_map() +
scale_fill_distiller(palette = "Reds", direction = 1) +
mapthm +
facet_wrap(~ which, ncol = 1)
2. Iowa Monthly Unemployment Rates over Time
To create the four-month faceted plot it is useful to add county FIPS codes and to clean out the (p)
from the final period.
lausUS <- mutate(lausUS,
Period = substr(Period, 1, 6),
fips = 1000 * State + County)
The map data with county FIPS codes:
fipstab <-
transmute(maps::county.fips, fips, county = sub(":.*", "", polyname)) |>
unique() |>
separate(county, c("region", "subregion"), sep = ",")
giowa <- map_data("county", "iowa")
giowa <- left_join(giowa, fipstab, c("region", "subregion"))
A subset of the data for the four months to be shown and the variables needed:
periods <- paste(c("Mar", "Jun", "Sep", "Dec"), 18, sep = "-")
sublaus <- filter(lausUS, Period %in% periods)
sublaus <- select(sublaus, Period, UnempRate, fips)
Make the Period
into an ordered factor with levels in the right order:
sublaus <- mutate(sublaus,
Period = factor(Period, ordered = TRUE, levels = periods))
Left join the map data with the unemployment data
giowa_laus <- left_join(giowa, sublaus, "fips")
## Warning in left_join(giowa, sublaus, "fips"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 789 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
The faceted plot:
ggplot(giowa_laus) +
geom_polygon(aes(long, lat, fill = UnempRate, group = group)) +
coord_map() +
scale_fill_distiller(palette = "Reds", direction = 1) +
facet_wrap(~ Period) + mapthm
- A sequential palette is appropriate for the numeric unemployment rate.
- The
limits
argument to scale_fill_distiller
could be used to make the scales in the state and county plots the same.
3. Comparison of Iowa Unemployment Rates
Create plot data with the differences as Udiff
:
lausDec18 <- select(filter(lausUS, Period == "Dec-18"), fips, UnempRate)
lausDec17 <- select(filter(lausUS, Period == "Dec-17"), fips, UnempRate)
dlaus <- left_join(rename(lausDec18, U18 = UnempRate),
rename(lausDec17, U17 = UnempRate),
"fips")
dlaus <- mutate(dlaus, Udiff = U18 - U17)
giowa_dlaus <- left_join(giowa, dlaus, "fips")
A diverging color scheme is most appropriate for a comparison.
When using a diverging scheme it is important to match the neutral data value, zero in this case, with the neutral color.
The ssale_fill_gratient2
function makes this easy.
For other continuous scales you can use the limits
or the rescaler
arguments.
For a discrete scale you should make sure to place the neutral value in the middle of the neutral color bin.
A map using scale_fill_gratient2
:
p <- ggplot(giowa_dlaus) +
geom_polygon(aes(long, lat, fill = Udiff, group = group)) +
coord_map() + mapthm
p + scale_fill_gradient2()
To use the same hues with red mapped to the high value you can use the muted
function from the scales
package:
library(scales)
p + scale_fill_gradient2(low = muted("blue"), high = muted("red"))
Using "RdBu"
from RColorBrewer
without adjustment places the neutral zero value in the wrong place:
p + scale_fill_distiller(palette = "RdBu")
Using the limits
argument is one way to address this:
lim <- max(abs(range(giowa_dlaus$Udiff)))
p + scale_fill_distiller(palette = "RdBu", limits = c(-lim, lim))
An alternative is to provide a rescaler
function:
rscl <- function(x, from) 0.5 + 0.495 * x / max(abs(from))
p + scale_fill_distiller(palette = "RdBu", rescaler = rscl)
For a discretized scale, use breaks that include the neutral value zero in the middle of the middle interval and make sure the mapping uses all the classes to keep the neutral color on the middle interval:
breaks <- seq(-2.25, 2.25, len = 10)
breaks
## [1] -2.25 -1.75 -1.25 -0.75 -0.25 0.25 0.75 1.25 1.75 2.25
pd <- ggplot(giowa_dlaus) +
geom_polygon(aes(long, lat, fill = cut(Udiff, breaks), group = group)) +
coord_map() + mapthm
pd + scale_fill_brewer(palette = "RdBu", direction = -1, drop = FALSE)
It would be possible to drop the classes not represented on the map from the legend.
4. Optional: Animated Maps over Time
One possible approach is available here
LS0tCnRpdGxlOiAiQXNzaWdubWVudCA3IE5vdGVzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKLS0tCgpgYGB7ciBnbG9iYWxfb3B0aW9ucywgaW5jbHVkZSA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoY29sbGFwc2UgPSBUUlVFKQpgYGAKCiMjIDEuIFN0YXRlIEF2ZXJhZ2UgVW5lbXBsb3ltZW50IFJhdGVzCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkoZHBseXIpCmxhdXNVUkwgPC0gImh0dHA6Ly93d3cuc3RhdC51aW93YS5lZHUvfmx1a2UvZGF0YS9sYXVzL2xhdWNudHljdXIxNC0yMDE4LnR4dCIKbGF1c0ZpbGUgPC0gImxhdWNudHljdXIxNC0yMDE4LnR4dCIKaWYgKCEgZmlsZS5leGlzdHMobGF1c0ZpbGUpKQogICAgZG93bmxvYWQuZmlsZShsYXVzVVJMLCBsYXVzRmlsZSkKbGF1c1VTIDwtIHJlYWQudGFibGUobGF1c0ZpbGUsCiAgICAgICAgICAgICAgICAgICAgIGNvbC5uYW1lcyA9IGMoIkxBVVNBcmVhQ29kZSIsICJTdGF0ZSIsICJDb3VudHkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJUaXRsZSIsICJQZXJpb2QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJMYWJvckZvcmNlIiwgIkVtcGxveWVkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVW5lbXBsb3llZCIsICJVbmVtcFJhdGUiKSwKICAgICAgICAgICAgICAgICAgICAgcXVvdGUgPSAiIiwgc2VwID0gInwiLCBza2lwID0gNiwgc3RyaXAud2hpdGUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICBmaWxsID0gVFJVRSkKZm9vdHN0YXJ0IDwtIGdyZXAoIi0tLS0tLSIsIGxhdXNVUyRMQVVTQXJlYUNvZGUpCmxhdXNVUyA8LSBsYXVzVVNbMSA6IChmb290c3RhcnQgLSAxKSwgXQpsYXVzVVMgPC0gbXV0YXRlKGxhdXNVUywKICAgICAgICAgICAgICAgICBVbmVtcFJhdGUgPSBhcy5udW1lcmljKFVuZW1wUmF0ZSksCiAgICAgICAgICAgICAgICAgTGFib3JGb3JjZSA9IGFzLm51bWVyaWMoZ3N1YigiLCIsICIiLCBMYWJvckZvcmNlKSksCiAgICAgICAgICAgICAgICAgRW1wbG95ZWQgPSBhcy5udW1lcmljKGdzdWIoIiwiLCAiIiwgRW1wbG95ZWQpKSwKICAgICAgICAgICAgICAgICBVbmVtcGxveWVkID0gYXMubnVtZXJpYyhnc3ViKCIsIiwgIiIsIFVuZW1wbG95ZWQpKSkKYGBgCgpBZnRlciByZWFkaW5nIHRoZSBkYXRhIGludG8gYSBkYXRhIGZyYW1lIGBsYXVzVVNgIHRoZSB1bmVtcGxveW1lbnQKcmF0ZSBmb3IgZWFjaCBzdGF0ZSBjYW4gYmUgY29tcHV0ZWQgYXMKCiQkIFxmcmFje1x0ZXh0e251bWJlciBvZiB1bmVtcGxveWVkIGluIHRoZSBzdGF0ZX19e1x0ZXh0e3NpemUgb2YgdGhlCmxhYm9yIGZvcmNlIGluIHRoZSBzdGF0ZX19LiAkJAoKLSBZb3UgY2FuIGFsc28gY29tcHV0ZSB0aGlzIG51bWJlciBhcyB0aGUgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUKICBjb3VudHkgdW5lbXBsb3ltZW50IHJhdGVzLCB3ZWlnaHRlZCBieSB0aGUgY291bnR5IGxhYm9yIGZvcmNlIHNpemVzLgoKLSBTaW1wbHkgYXZlcmFnaW5nIHRoZSB1bmVtcGxveW1lbnQgcmF0ZXMgcHJvZHVjZXMgdGhlIHdyb25nCiAgc3RhdGUtbGV2ZWwgcmVzdWx0OiBzbWFsbCBjb3VudGllcyByZWNlaXZlIG11Y2ggbW9yZSB3ZWlnaHQgdGhhbgogIHRoZXkgc2hvdWxkLgoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpgYGAKClRvIHNlZSB0aGUgZGlmZmVyZW5jZSB3ZSBjYW4gY29tcHV0ZSBib3RoIHZhbHVlczoKCmBgYHtyfQp1bmVtcF9ieV9zdGF0ZSA8LSBzdW1tYXJpemUoZ3JvdXBfYnkobGF1c1VTLCBTdGF0ZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB1cmF0ZSA9IDEwMCAqIHN1bShVbmVtcGxveWVkKSAvIHN1bShMYWJvckZvcmNlKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVyYXRlTlcgPSBtZWFuKFVuZW1wUmF0ZSkpCmBgYAoKVGhlIG1hcCBkYXRhIGlzIG9idGFpbmVkIHdpdGgKCmBgYHtyfQpndXNhIDwtIG1hcF9kYXRhKCJzdGF0ZSIpCmBgYAoKVG8gYWxsb3cgdGhlIG1hcCBkYXRhIHRvIGJlIG1lcmdlZCB3aXRoIHRoZSB1bmVtcGxveW1lbnQgZGF0YSB3ZSBjYW4KYXJyYW5nZSB0aGF0IGJvdGggZGF0YSBmcmFtZXMgY29udGFpbiB0aGUgc3RhdGUgRklQUyBjb2RlIGluIGEKdmFyaWFibGUgbmFtZWQgYGZpcHNgOgoKYGBge3J9CnVuZW1wX2J5X3N0YXRlIDwtIHJlbmFtZSh1bmVtcF9ieV9zdGF0ZSwgZmlwcyA9IFN0YXRlKQpzdGF0ZS5maXBzIDwtCiAgICBzZWxlY3QobWFwczo6c3RhdGUuZmlwcywgZmlwcywgcmVnaW9uID0gcG9seW5hbWUpIHw+CiAgICBtdXRhdGUocmVnaW9uID0gc3ViKCI6LioiLCAiIiwgcmVnaW9uKSkgfD4KICAgIHVuaXF1ZSgpCmd1c2EgPC0gbGVmdF9qb2luKGd1c2EsIHN0YXRlLmZpcHMsICJyZWdpb24iKQpgYGAKCkEgbGVmdCBqb2luIG9mIHRoZSBtYXAgYW5kIHVuZW1wbG95bWVudCBkYXRhIGlzIHBsYWNlZCBpbiBgZ3VzYV91bmVtcGA6CgpgYGB7cn0KZ3VzYV91bmVtcCA8LSBsZWZ0X2pvaW4oZ3VzYSwgdW5lbXBfYnlfc3RhdGUsICJmaXBzIikKYGBgCgpTaW5jZSB0aGUgdW5lbXBsb3ltZW50IHJhdGUgaXMgYSBjb250aW51b3VzIHZhcmlhYmxlLCBhIHNlcXVlbnRpYWwKcGFsZXR0ZSBpcyBtb3N0IGFwcHJvcHJpYXRlLiBUaGUgZGVmYXVsdCBwYWxldHRlIGRvZXMgbm90IHdvcmsgd2VsbDsKdGhlIGAiUmVkcyJgIHBhbGV0dGUgZnJvbSBgUkNvbG9yQnJld2VyYCBpcyBhIGdvb2QgY2hvaWNlOgoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KbWFwdGhtIDwtIHRoZW1lX2J3KCkgJStyZXBsYWNlJQogICAgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5zcGFjaW5nID0gdW5pdCgwLCAibGluZXMiKSwKICAgICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpgYGB7cn0KZ2dwbG90KGd1c2FfdW5lbXApICsKICAgIGdlb21fcG9seWdvbihhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gdXJhdGUpKSArCiAgICBjb29yZF9tYXAoKSArCiAgICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlID0gIlJlZHMiLCBkaXJlY3Rpb24gPSAxKSArCiAgICBtYXB0aG0KYGBgCgpgbWFwdGhtYCBpcyBhIHRoZW1lIGJhc2VkIG9uIGBnZ3RoZW1lczo6dGhlbWVfbWFwYCB0aGF0IGtlZXBzIHRoZQpndWlkZSBvbiB0aGUgcmlnaHQuCgpVc2luZyBhIGZhY2V0ZWQgZGlzcGxheSB3ZSBjYW4gbG9vayBhdCB0aGUgcmVzdWx0IGZvciB0aGUgaW5jb3JyZWN0CnVud2VpZ2h0ZWQgY29tcHV0YXRpb24gb2YgdGhlIHN0YXRlIHVuZW1wbG95bWVudCByYXRlOgoKYGBge3J9Cmd1c2FfdW5lbXBfdGQgPC0gZ2F0aGVyKGd1c2FfdW5lbXAsIHdoaWNoLCByYXRlLCB1cmF0ZSwgdXJhdGVOVykKZ2dwbG90KGd1c2FfdW5lbXBfdGQpICsKICAgIGdlb21fcG9seWdvbihhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gcmF0ZSkpICsKICAgIGNvb3JkX21hcCgpICsKICAgIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiUmVkcyIsIGRpcmVjdGlvbiA9IDEpICsKICAgIG1hcHRobSArCiAgICBmYWNldF93cmFwKH4gd2hpY2gsIG5jb2wgPSAxKQpgYGAKCiMjIDIuIElvd2EgTW9udGhseSBVbmVtcGxveW1lbnQgUmF0ZXMgb3ZlciBUaW1lCgpUbyBjcmVhdGUgdGhlIGZvdXItbW9udGggZmFjZXRlZCBwbG90IGl0IGlzIHVzZWZ1bCB0byBhZGQgY291bnR5IEZJUFMKY29kZXMgYW5kIHRvIGNsZWFuIG91dCB0aGUgYChwKWAgZnJvbSB0aGUgZmluYWwgcGVyaW9kLgoKYGBge3J9CmxhdXNVUyA8LSBtdXRhdGUobGF1c1VTLAogICAgICAgICAgICAgICAgIFBlcmlvZCA9IHN1YnN0cihQZXJpb2QsIDEsIDYpLAogICAgICAgICAgICAgICAgIGZpcHMgPSAxMDAwICogU3RhdGUgKyBDb3VudHkpCmBgYAoKVGhlIG1hcCBkYXRhIHdpdGggY291bnR5IEZJUFMgY29kZXM6CgpgYGB7cn0KZmlwc3RhYiA8LQogICAgdHJhbnNtdXRlKG1hcHM6OmNvdW50eS5maXBzLCBmaXBzLCBjb3VudHkgPSBzdWIoIjouKiIsICIiLCBwb2x5bmFtZSkpIHw+CiAgICB1bmlxdWUoKSB8PgogICAgc2VwYXJhdGUoY291bnR5LCBjKCJyZWdpb24iLCAic3VicmVnaW9uIiksIHNlcCA9ICIsIikKZ2lvd2EgPC0gbWFwX2RhdGEoImNvdW50eSIsICJpb3dhIikKZ2lvd2EgPC0gbGVmdF9qb2luKGdpb3dhLCBmaXBzdGFiLCBjKCJyZWdpb24iLCAic3VicmVnaW9uIikpCmBgYAoKQSBzdWJzZXQgb2YgdGhlIGRhdGEgZm9yIHRoZSBmb3VyIG1vbnRocyB0byBiZSBzaG93biBhbmQgdGhlIHZhcmlhYmxlcyBuZWVkZWQ6CgpgYGB7cn0KcGVyaW9kcyA8LSBwYXN0ZShjKCJNYXIiLCAiSnVuIiwgIlNlcCIsICJEZWMiKSwgMTgsIHNlcCA9ICItIikKc3VibGF1cyA8LSBmaWx0ZXIobGF1c1VTLCBQZXJpb2QgJWluJSBwZXJpb2RzKQpzdWJsYXVzIDwtIHNlbGVjdChzdWJsYXVzLCBQZXJpb2QsIFVuZW1wUmF0ZSwgZmlwcykKYGBgCgpNYWtlIHRoZSBgUGVyaW9kYCBpbnRvIGFuIG9yZGVyZWQgZmFjdG9yIHdpdGggbGV2ZWxzIGluIHRoZSByaWdodCBvcmRlcjoKCmBgYHtyfQpzdWJsYXVzIDwtIG11dGF0ZShzdWJsYXVzLAogICAgICAgICAgICAgICAgICBQZXJpb2QgPSBmYWN0b3IoUGVyaW9kLCBvcmRlcmVkID0gVFJVRSwgbGV2ZWxzID0gcGVyaW9kcykpCmBgYAoKTGVmdCBqb2luIHRoZSBtYXAgZGF0YSB3aXRoIHRoZSB1bmVtcGxveW1lbnQgZGF0YQoKYGBge3J9Cmdpb3dhX2xhdXMgPC0gbGVmdF9qb2luKGdpb3dhLCBzdWJsYXVzLCAiZmlwcyIpCmBgYAoKVGhlIGZhY2V0ZWQgcGxvdDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KGdpb3dhX2xhdXMpICsKICAgIGdlb21fcG9seWdvbihhZXMobG9uZywgbGF0LCBmaWxsID0gVW5lbXBSYXRlLCBncm91cCA9IGdyb3VwKSkgKwogICAgY29vcmRfbWFwKCkgKwogICAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJSZWRzIiwgZGlyZWN0aW9uID0gMSkgKwogICAgZmFjZXRfd3JhcCh+IFBlcmlvZCkgKyBtYXB0aG0KYGBgCgotIEEgc2VxdWVudGlhbCBwYWxldHRlIGlzIGFwcHJvcHJpYXRlIGZvciB0aGUgbnVtZXJpYyB1bmVtcGxveW1lbnQgcmF0ZS4KLSBUaGUgYGxpbWl0c2AgYXJndW1lbnQgdG8gYHNjYWxlX2ZpbGxfZGlzdGlsbGVyYCBjb3VsZCBiZSB1c2VkIHRvCiAgbWFrZSB0aGUgc2NhbGVzIGluIHRoZSBzdGF0ZSBhbmQgY291bnR5IHBsb3RzIHRoZSBzYW1lLgoKCiMjIDMuIENvbXBhcmlzb24gb2YgSW93YSBVbmVtcGxveW1lbnQgUmF0ZXMKCkNyZWF0ZSBwbG90IGRhdGEgd2l0aCB0aGUgZGlmZmVyZW5jZXMgYXMgYFVkaWZmYDoKYGBge3J9CmxhdXNEZWMxOCA8LSBzZWxlY3QoZmlsdGVyKGxhdXNVUywgUGVyaW9kID09ICJEZWMtMTgiKSwgZmlwcywgVW5lbXBSYXRlKQpsYXVzRGVjMTcgPC0gc2VsZWN0KGZpbHRlcihsYXVzVVMsIFBlcmlvZCA9PSAiRGVjLTE3IiksIGZpcHMsIFVuZW1wUmF0ZSkKCmRsYXVzIDwtIGxlZnRfam9pbihyZW5hbWUobGF1c0RlYzE4LCBVMTggPSBVbmVtcFJhdGUpLAogICAgICAgICAgICAgICAgICAgcmVuYW1lKGxhdXNEZWMxNywgVTE3ID0gVW5lbXBSYXRlKSwKICAgICAgICAgICAgICAgICAgICJmaXBzIikKZGxhdXMgPC0gbXV0YXRlKGRsYXVzLCBVZGlmZiA9IFUxOCAtIFUxNykKCmdpb3dhX2RsYXVzIDwtIGxlZnRfam9pbihnaW93YSwgZGxhdXMsICJmaXBzIikKYGBgCgpBIGRpdmVyZ2luZyBjb2xvciBzY2hlbWUgaXMgbW9zdCBhcHByb3ByaWF0ZSBmb3IgYSBjb21wYXJpc29uLgoKLSBXaGVuIHVzaW5nIGEgZGl2ZXJnaW5nIHNjaGVtZSBpdCBpcyBpbXBvcnRhbnQgdG8gbWF0Y2ggdGhlIG5ldXRyYWwKICBkYXRhIHZhbHVlLCB6ZXJvIGluIHRoaXMgY2FzZSwgd2l0aCB0aGUgbmV1dHJhbCBjb2xvci4KCi0gVGhlIGBzc2FsZV9maWxsX2dyYXRpZW50MmAgZnVuY3Rpb24gbWFrZXMgdGhpcyBlYXN5LgoKLSBGb3Igb3RoZXIgY29udGludW91cyBzY2FsZXMgeW91IGNhbiB1c2UgdGhlIGBsaW1pdHNgIG9yIHRoZQogIGByZXNjYWxlcmAgYXJndW1lbnRzLgoKLSBGb3IgYSBkaXNjcmV0ZSBzY2FsZSB5b3Ugc2hvdWxkIG1ha2Ugc3VyZSB0byBwbGFjZSB0aGUgbmV1dHJhbCB2YWx1ZQogIGluIHRoZSBtaWRkbGUgb2YgdGhlIG5ldXRyYWwgY29sb3IgYmluLgoKCkEgbWFwIHVzaW5nIGBzY2FsZV9maWxsX2dyYXRpZW50MmA6CgpgYGB7cn0KcCA8LSBnZ3Bsb3QoZ2lvd2FfZGxhdXMpICsKICAgIGdlb21fcG9seWdvbihhZXMobG9uZywgbGF0LCBmaWxsID0gVWRpZmYsIGdyb3VwID0gZ3JvdXApKSArCiAgICBjb29yZF9tYXAoKSArIG1hcHRobQpwICsgc2NhbGVfZmlsbF9ncmFkaWVudDIoKQpgYGAKClRvIHVzZSB0aGUgc2FtZSBodWVzIHdpdGggcmVkIG1hcHBlZCB0byB0aGUgaGlnaCB2YWx1ZSB5b3UgY2FuIHVzZSB0aGUKYG11dGVkYCBmdW5jdGlvbiBmcm9tIHRoZSBgc2NhbGVzYCBwYWNrYWdlOgoKYGBge3J9CmxpYnJhcnkoc2NhbGVzKQpwICsgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gbXV0ZWQoImJsdWUiKSwgaGlnaCA9IG11dGVkKCJyZWQiKSkKYGBgCgpVc2luZyBgIlJkQnUiYCBmcm9tIGBSQ29sb3JCcmV3ZXJgIHdpdGhvdXQgYWRqdXN0bWVudCBwbGFjZXMgdGhlCm5ldXRyYWwgemVybyB2YWx1ZSBpbiB0aGUgd3JvbmcgcGxhY2U6CgpgYGB7cn0KcCArIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiUmRCdSIpCmBgYAoKVXNpbmcgdGhlIGBsaW1pdHNgIGFyZ3VtZW50IGlzIG9uZSB3YXkgdG8gYWRkcmVzcyB0aGlzOgoKYGBge3J9CmxpbSA8LSBtYXgoYWJzKHJhbmdlKGdpb3dhX2RsYXVzJFVkaWZmKSkpCnAgKyBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlID0gIlJkQnUiLCBsaW1pdHMgPSBjKC1saW0sIGxpbSkpCmBgYAoKQW4gYWx0ZXJuYXRpdmUgaXMgdG8gcHJvdmlkZSBhIGByZXNjYWxlcmAgZnVuY3Rpb246CgpgYGB7cn0KcnNjbCA8LSBmdW5jdGlvbih4LCBmcm9tKSAwLjUgKyAwLjQ5NSAqIHggLyBtYXgoYWJzKGZyb20pKQpwICsgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJSZEJ1IiwgcmVzY2FsZXIgPSByc2NsKQpgYGAKCkZvciBhIGRpc2NyZXRpemVkIHNjYWxlLCB1c2UgYnJlYWtzIHRoYXQgaW5jbHVkZSB0aGUgbmV1dHJhbCB2YWx1ZQp6ZXJvIGluIHRoZSBtaWRkbGUgb2YgdGhlIG1pZGRsZSBpbnRlcnZhbCBhbmQgbWFrZSBzdXJlIHRoZSBtYXBwaW5nCnVzZXMgYWxsIHRoZSBjbGFzc2VzIHRvIGtlZXAgdGhlIG5ldXRyYWwgY29sb3Igb24gdGhlIG1pZGRsZSBpbnRlcnZhbDoKCmBgYHtyfQpicmVha3MgPC0gc2VxKC0yLjI1LCAyLjI1LCBsZW4gPSAxMCkKYnJlYWtzCgpwZCA8LSBnZ3Bsb3QoZ2lvd2FfZGxhdXMpICsKICAgIGdlb21fcG9seWdvbihhZXMobG9uZywgbGF0LCBmaWxsID0gY3V0KFVkaWZmLCBicmVha3MpLCBncm91cCA9IGdyb3VwKSkgKwogICAgY29vcmRfbWFwKCkgKyBtYXB0aG0KcGQgKyBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlJkQnUiLCBkaXJlY3Rpb24gPSAtMSwgZHJvcCA9IEZBTFNFKQpgYGAKCkl0IHdvdWxkIGJlIHBvc3NpYmxlIHRvIGRyb3AgdGhlIGNsYXNzZXMgbm90IHJlcHJlc2VudGVkIG9uIHRoZSBtYXAKZnJvbSB0aGUgbGVnZW5kLgoKCiMjIDQuIE9wdGlvbmFsOiBBbmltYXRlZCBNYXBzIG92ZXIgVGltZQoKT25lIHBvc3NpYmxlIGFwcHJvYWNoIGlzIGF2YWlsYWJsZQpbaGVyZV0oaHR0cHM6Ly9zdGF0LnVpb3dhLmVkdS9+bHVrZS9jbGFzc2VzL1NUQVQ0NTgwL3NoaW55LWxhdXMvc2hpbnktbGF1cy5SbWQpCg==