Introductory material

This document is a walkthrough on how to import data from Johns Hopkins University’s COVID tracking project and put it into R for processing. We’ll clean it up, do some calculations and filtering, and then chart it as part of this tutorial.

This document is called a “R Markdown” document. It combines your code, your narrative and your results all in one, self-contained document that can be shared with varying amounts of detail shown.

This first section sets up our R session. You’ll almost always do this, because there is very little you’ll do without a package called the tidyverse. It’s a collection of other packages that play together seamlessly, and make R much easier to use. Without it, R is just. too. hard.

knitr::opts_chunk$set(echo = TRUE)


library(tidyverse)
library(lubridate)
library(janitor)
library(skimr)
library(reactable)

options (scipen = 999)

Load the data from the Github repo

Johns Hopkins makes available weekly summaries of COVID from countries around the world. The one we’ll use for this tutorial is for Oct. 31, 2021, and we’ll use this version of it: https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/10-31-2021.csv

(When you want to get data from Github, you usually have to use the link to the “RAW” data, not the first one you see. )

The repo has information on each column and the sources in its readme.md file

Here’s what the data contains:

  • The first two columns are empty, because they only refer to county-level data in the U.S.
  • location - includes province_state, country_region, lat and long_ , which is where JHU puts the dots on their online map.
  • confirmed cumulative cases,
  • deaths
  • recovered (likely under-reported)
  • Active cases
  • incident_rate per 100,000 people
  • case_fatality_ratio, or the proportion of people who had Covid who died.

The column names are in the first row of the data.

Here’s how you get the data into R: * Insert a “code chunk” and give it a name. * use the read_csv function to pour the file into a dataset in our R environment.

jhu_covid <- read_csv ( "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/10-31-2021.csv" 
)
## Rows: 4006 Columns: 14
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (4): Admin2, Province_State, Country_Region, Combined_Key
## dbl  (9): FIPS, Lat, Long_, Confirmed, Deaths, Recovered, Active, Incident_R...
## dttm (1): Last_Update
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

What it’s done is say, “Here’s what I found!! Four columns that are words, nine columns that are numbers, and one column that’s a date/time. I found 4006 rows!”

Now, that doesn’t make much sense, does it? Why are there so many? There aren’t 4006 countries in the world! Let’s take a quick glance at the data:

glimpse(jhu_covid)
## Rows: 4,006
## Columns: 14
## $ FIPS                <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ Admin2              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ Province_State      <chr> NA, NA, NA, NA, NA, NA, NA, NA, "Australian Capita…
## $ Country_Region      <chr> "Afghanistan", "Albania", "Algeria", "Andorra", "A…
## $ Last_Update         <dttm> 2021-11-01 04:22:01, 2021-11-01 04:22:01, 2021-11…
## $ Lat                 <dbl> 33.93911, 41.15330, 28.03390, 42.50630, -11.20270,…
## $ Long_               <dbl> 67.70995, 20.16830, 1.65960, 1.52180, 17.87390, -6…
## $ Confirmed           <dbl> 156250, 185300, 206452, 15516, 64433, 4058, 528880…
## $ Deaths              <dbl> 7280, 2924, 5920, 130, 1710, 102, 115950, 6328, 13…
## $ Recovered           <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ Active              <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ Combined_Key        <chr> "Afghanistan", "Albania", "Algeria", "Andorra", "A…
## $ Incident_Rate       <dbl> 401.37852, 6438.94642, 470.80294, 20081.53757, 196…
## $ Case_Fatality_Ratio <dbl> 4.6592000, 1.5779817, 2.8674946, 0.8378448, 2.6539…

The reason is that there is a row for every province or state that the university could get, not just the countries.

We can also see here that there are a lot of things that say, NA . We’ll want to keep an eye on that, since they can corrupt everything you want to do with your data.

To get a more thorough view of what you have, we’ll use another package, called skimr to skim through the dataset:

skimr::skim(jhu_covid)
Data summary
Name jhu_covid
Number of rows 4006
Number of columns 14
_______________________
Column type frequency:
character 4
numeric 9
POSIXct 1
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
Admin2 734 0.82 3 41 0 1927 0
Province_State 174 0.96 3 44 0 594 0
Country_Region 0 1.00 2 32 0 196 0
Combined_Key 0 1.00 4 60 0 4006 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
FIPS 738 0.82 32405.94 18056.38 60.00 19048.50 30068.00 47041.50 99999.00 ▆▇▆▁▁
Lat 90 0.98 35.80 13.25 -52.37 33.20 37.90 42.18 71.71 ▁▁▁▇▁
Long_ 90 0.98 -71.35 54.90 -178.12 -96.60 -86.77 -77.39 178.06 ▁▇▁▁▁
Confirmed 0 1.00 61585.93 344002.19 0.00 1723.25 4895.00 20163.00 8032958.00 ▇▁▁▁▁
Deaths 0 1.00 1248.10 7025.05 0.00 27.00 81.00 289.00 152002.00 ▇▁▁▁▁
Recovered 4005 0.00 0.00 NA 0.00 0.00 0.00 0.00 0.00 ▁▁▇▁▁
Active 4005 0.00 0.00 NA 0.00 0.00 0.00 0.00 0.00 ▁▁▇▁▁
Incident_Rate 91 0.98 13041.57 5360.51 0.00 10334.37 14024.82 16531.42 54240.40 ▃▇▁▁▁
Case_Fatality_Ratio 43 0.99 2.78 46.09 0.00 1.13 1.61 2.24 2796.55 ▇▁▁▁▁

Variable type: POSIXct

skim_variable n_missing complete_rate min max median n_unique
Last_Update 0 1 2020-08-04 02:27:56 2021-11-01 04:22:01 2021-11-01 04:22:01 8

What if I want to see only the data for countries as a total? Let’s see what we find:

jhu_covid %>%
  filter ( is.na( Province_State))
## # A tibble: 174 × 14
##     FIPS Admin2 Province_State Country_Region   Last_Update           Lat  Long_
##    <dbl> <chr>  <chr>          <chr>            <dttm>              <dbl>  <dbl>
##  1    NA <NA>   <NA>           Afghanistan      2021-11-01 04:22:01  33.9  67.7 
##  2    NA <NA>   <NA>           Albania          2021-11-01 04:22:01  41.2  20.2 
##  3    NA <NA>   <NA>           Algeria          2021-11-01 04:22:01  28.0   1.66
##  4    NA <NA>   <NA>           Andorra          2021-11-01 04:22:01  42.5   1.52
##  5    NA <NA>   <NA>           Angola           2021-11-01 04:22:01 -11.2  17.9 
##  6    NA <NA>   <NA>           Antigua and Bar… 2021-11-01 04:22:01  17.1 -61.8 
##  7    NA <NA>   <NA>           Argentina        2021-11-01 04:22:01 -38.4 -63.6 
##  8    NA <NA>   <NA>           Armenia          2021-11-01 04:22:01  40.1  45.0 
##  9    NA <NA>   <NA>           Austria          2021-11-01 04:22:01  47.5  14.6 
## 10    NA <NA>   <NA>           Azerbaijan       2021-11-01 04:22:01  40.1  47.6 
## # … with 164 more rows, and 7 more variables: Confirmed <dbl>, Deaths <dbl>,
## #   Recovered <dbl>, Active <dbl>, Combined_Key <chr>, Incident_Rate <dbl>,
## #   Case_Fatality_Ratio <dbl>

Unfortunately for us, any country that has data for the state doesn’t have data for the country as a whole. And the US is listing EVERY COUNTY! No wonder there are so many.

So we want to reverse engineer this a little. To get to the country totals we need to:

  • Convert the incident rate to a population! (If confirmed / population is the incident rate, then we can back out the population from it.)
  • Add up the states or counties to the total for the country.
  • Re-compute the incident rate.

We’ll use the basic building blocks of the Tidyverse, the basic verbs:

select means to pick out columns filter means to pick out rows arrange means to sort the rows mutate means to create a new column from old information.

When we go from one verb to the next, we’ll use a pipe (%>% ) to connect them:

rladies

jhu_covid_counts <-
   jhu_covid %>%
   mutate ( rate_not_per100k = Incident_Rate / 100000 , 
            est_population = Confirmed / rate_not_per100k)

Now I want to just keep the columns that can add up, fix some NA’s, and lowercase all of the column names so I don’t have to remember what’s what.

Let’s go ahead and add them up by country. Introducing a new verb:

group_by and summarise, which combines rows by calculating a statistic. We’ll do it longhand, even though there are fancy ways to reduce the typing involved.

jhu_covid_count2 <- 
    jhu_covid_counts %>%
    group_by ( Country_Region) %>%
    summarise ( confirmed_total = sum ( Confirmed, na.rm=T), 
                deaths_total = sum (Deaths, na.rm=T) , 
                est_population = sum ( est_population , na.rm = T), 
                approx_lat = median ( Lat, na.rm=T), 
                approx_long = median (Long_, na.rm=T))

Want to see a chart, with the 196 countries listed by the number of COVID cases they have? I’ll use another package to make it look a little nicer, and to create a sortable, searchable table. That package is called reactable

jhu_covid_count2 %>%
   reactable (  
      searchable=TRUE, 
      filterable=FALSE,
      #compact=TRUE, 
      theme=reactableTheme(color="dark gray", 
                           style=list(fontSize="85%") 
                           ) , 
       defaultColDef = colDef ( format = colFormat(separators=TRUE, digits=0), minWidth=75)
   )

If we wanted to find out what percentage of the total worldwide cases each country had, we can do that with our data the same way:

jhu_covid_count2 %>%
  mutate ( pct_of_cases = confirmed_total / sum ( confirmed_total) * 100 , 
           pct_of_pop  = est_population / sum (est_population) * 100 ) %>%
  select (Country_Region, pct_of_cases, pct_of_pop) %>%
  arrange ( desc (pct_of_cases)) 
## # A tibble: 196 × 3
##    Country_Region pct_of_cases pct_of_pop
##    <chr>                 <dbl>      <dbl>
##  1 US                    18.6       4.32 
##  2 India                 13.9      17.8  
##  3 Brazil                 8.84      2.73 
##  4 United Kingdom         3.69      0.871
##  5 Russia                 3.40      1.91 
##  6 Turkey                 3.26      1.10 
##  7 France                 2.95      0.886
##  8 Iran                   2.40      1.09 
##  9 Argentina              2.14      0.588
## 10 Spain                  2.03      0.610
## # … with 186 more rows

Now let’s get the rate per 100,000 residents:

jhu_rates <- 
  jhu_covid_count2  %>%
  mutate ( rate_per_100k = confirmed_total / est_population * 100000, 
           death_rate    = deaths_total / confirmed_total * 100) 

Chart of 20 largest countries

Here’s one way to chart the death rates from COVID for the 20 largest countries in the dataset:

jhu_rates %>%
  slice_max ( n=20, est_population)  %>%
    ggplot ( aes ( x=reorder(Country_Region,death_rate),  y=death_rate, ) ) +
     geom_col (fill="blue") +
     ylim ( 0, 8) + 
     coord_flip()    +
     theme_minimal() +
     labs( y = "Pct of cases ending in death", x="")

World map

Now we’ll use a package that some of you may be a little familiar with - it’s an R version of the leaflet() javascript library:

library(leaflet)


leaflet ( jhu_rates) %>% 
     addTiles () %>%
     setView ( lng= 0, lat=0, zoom = 1) %>% 
     addCircles ( lng=~approx_long, lat=~approx_lat, weight=1, 
                  radius = sqrt(jhu_rates$rate_per_100k)*4000, 
                  popup= paste(jhu_rates$Country_Region, round(jhu_rates$rate_per_100k, 0), sep=": ")  
     )
LS0tCnRpdGxlOiAnR0lKTjogV2hhdCBpcyBSPycKYXV0aG9yOiAiU2FyYWggQ29oZW4iCmRhdGU6ICIxMS8xLzIwMjEiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIHRoZW1lOiB1bml0ZWQKLS0tCgoKIyMgSW50cm9kdWN0b3J5IG1hdGVyaWFsCgpUaGlzIGRvY3VtZW50IGlzIGEgd2Fsa3Rocm91Z2ggb24gaG93IHRvIGltcG9ydCBkYXRhIGZyb20gSm9obnMgSG9wa2lucyBVbml2ZXJzaXR5J3MgQ09WSUQgdHJhY2tpbmcgcHJvamVjdCBhbmQgcHV0IGl0IGludG8gUiBmb3IgcHJvY2Vzc2luZy4gV2UnbGwgY2xlYW4gaXQgdXAsIGRvIHNvbWUgY2FsY3VsYXRpb25zIGFuZCBmaWx0ZXJpbmcsIGFuZCB0aGVuIGNoYXJ0IGl0IGFzIHBhcnQgb2YgdGhpcyB0dXRvcmlhbC4gCgpUaGlzIGRvY3VtZW50IGlzIGNhbGxlZCBhICJSIE1hcmtkb3duIiBkb2N1bWVudC4gSXQgY29tYmluZXMgeW91ciBjb2RlLCB5b3VyIG5hcnJhdGl2ZSBhbmQgeW91ciByZXN1bHRzIGFsbCBpbiBvbmUsIHNlbGYtY29udGFpbmVkIGRvY3VtZW50IHRoYXQgY2FuIGJlIHNoYXJlZCB3aXRoIHZhcnlpbmcgYW1vdW50cyBvZiBkZXRhaWwgc2hvd24uIAoKVGhpcyBmaXJzdCBzZWN0aW9uIHNldHMgdXAgb3VyIFIgc2Vzc2lvbi4gWW91J2xsIGFsbW9zdCBhbHdheXMgZG8gdGhpcywgYmVjYXVzZSB0aGVyZSBpcyB2ZXJ5IGxpdHRsZSB5b3UnbGwgZG8gd2l0aG91dCBhIHBhY2thZ2UgY2FsbGVkIHRoZSBgdGlkeXZlcnNlYC4gSXQncyBhIGNvbGxlY3Rpb24gb2Ygb3RoZXIgcGFja2FnZXMgdGhhdCBwbGF5IHRvZ2V0aGVyIHNlYW1sZXNzbHksIGFuZCBtYWtlIFIgbXVjaCBlYXNpZXIgdG8gdXNlLiBXaXRob3V0IGl0LCBSIGlzIGp1c3QuIHRvby4gaGFyZC4KCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKCgpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoamFuaXRvcikKbGlicmFyeShza2ltcikKbGlicmFyeShyZWFjdGFibGUpCgpvcHRpb25zIChzY2lwZW4gPSA5OTkpCgpgYGAKCiMjIExvYWQgdGhlIGRhdGEgZnJvbSB0aGUgR2l0aHViIHJlcG8KCkpvaG5zIEhvcGtpbnMgbWFrZXMgYXZhaWxhYmxlIHdlZWtseSBzdW1tYXJpZXMgb2YgQ09WSUQgZnJvbSBjb3VudHJpZXMgYXJvdW5kIHRoZSB3b3JsZC4gVGhlIG9uZSB3ZSdsbCB1c2UgZm9yIHRoaXMgdHV0b3JpYWwgaXMgZm9yIE9jdC4gMzEsIDIwMjEsIGFuZCB3ZSdsbCB1c2UgdGhpcyB2ZXJzaW9uIG9mIGl0OiA8aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5L21hc3Rlci9jc3NlX2NvdmlkXzE5X2RhdGEvY3NzZV9jb3ZpZF8xOV9kYWlseV9yZXBvcnRzLzEwLTMxLTIwMjEuY3N2PgoKKFdoZW4geW91IHdhbnQgdG8gZ2V0IGRhdGEgZnJvbSBHaXRodWIsIHlvdSB1c3VhbGx5IGhhdmUgdG8gdXNlIHRoZSBsaW5rIHRvIHRoZSAiUkFXIiBkYXRhLCBub3QgdGhlIGZpcnN0IG9uZSB5b3Ugc2VlLiApCgpUaGUgcmVwbyBoYXMgaW5mb3JtYXRpb24gb24gZWFjaCBjb2x1bW4gYW5kIHRoZSBzb3VyY2VzIGluIGl0cyBbcmVhZG1lLm1kXShodHRwczovL2dpdGh1Yi5jb20vQ1NTRUdJU2FuZERhdGEvQ09WSUQtMTkvdHJlZS9tYXN0ZXIvY3NzZV9jb3ZpZF8xOV9kYXRhKSBmaWxlCgpIZXJlJ3Mgd2hhdCB0aGUgZGF0YSBjb250YWluczogCgoqIFRoZSBmaXJzdCB0d28gY29sdW1ucyBhcmUgZW1wdHksIGJlY2F1c2UgdGhleSBvbmx5IHJlZmVyIHRvIGNvdW50eS1sZXZlbCBkYXRhIGluIHRoZSBVLlMuCiogbG9jYXRpb24gLSBpbmNsdWRlcyBgcHJvdmluY2Vfc3RhdGVgLCBgY291bnRyeV9yZWdpb25gLCBgbGF0YCBhbmQgYGxvbmdfYCAsIHdoaWNoIGlzIHdoZXJlIEpIVSBwdXRzIHRoZSBkb3RzIG9uIHRoZWlyIG9ubGluZSBtYXAuIAoqIGNvbmZpcm1lZCBjdW11bGF0aXZlIGNhc2VzLAoqIGRlYXRocwoqIHJlY292ZXJlZCAobGlrZWx5IHVuZGVyLXJlcG9ydGVkKQoqIEFjdGl2ZSBjYXNlcwoqIGluY2lkZW50X3JhdGUgcGVyIDEwMCwwMDAgcGVvcGxlCiogY2FzZV9mYXRhbGl0eV9yYXRpbywgb3IgdGhlIHByb3BvcnRpb24gb2YgcGVvcGxlIHdobyBoYWQgQ292aWQgd2hvIGRpZWQuIAoKVGhlIGNvbHVtbiBuYW1lcyBhcmUgaW4gdGhlIGZpcnN0IHJvdyBvZiB0aGUgZGF0YS4gCgpIZXJlJ3MgaG93IHlvdSBnZXQgdGhlIGRhdGEgaW50byBSOiAKKiBJbnNlcnQgYSAiY29kZSBjaHVuayIgYW5kIGdpdmUgaXQgYSBuYW1lLiAKKiB1c2UgdGhlIHJlYWRfY3N2IGZ1bmN0aW9uIHRvIHBvdXIgdGhlIGZpbGUgaW50byBhIGRhdGFzZXQgaW4gb3VyIFIgZW52aXJvbm1lbnQuIAoKYGBge3IgcmVhZF9yYXdfZGF0YX0KCgpqaHVfY292aWQgPC0gcmVhZF9jc3YgKCAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5L21hc3Rlci9jc3NlX2NvdmlkXzE5X2RhdGEvY3NzZV9jb3ZpZF8xOV9kYWlseV9yZXBvcnRzLzEwLTMxLTIwMjEuY3N2IiAKKQoKCmBgYAoKCldoYXQgaXQncyBkb25lIGlzIHNheSwgIkhlcmUncyB3aGF0IEkgZm91bmQhISBGb3VyIGNvbHVtbnMgdGhhdCBhcmUgd29yZHMsIG5pbmUgY29sdW1ucyB0aGF0IGFyZSBudW1iZXJzLCBhbmQgb25lIGNvbHVtbiB0aGF0J3MgYSBkYXRlL3RpbWUuIEkgZm91bmQgNDAwNiByb3dzISIKCgpOb3csIHRoYXQgZG9lc24ndCBtYWtlIG11Y2ggc2Vuc2UsIGRvZXMgaXQ/IFdoeSBhcmUgdGhlcmUgc28gbWFueT8gVGhlcmUgYXJlbid0IDQwMDYgY291bnRyaWVzIGluIHRoZSB3b3JsZCEgTGV0J3MgdGFrZSBhIHF1aWNrIGdsYW5jZSBhdCB0aGUgZGF0YTogCgpgYGB7ciBsb29rX2F0X2podX0KCmdsaW1wc2Uoamh1X2NvdmlkKQoKCmBgYAoKVGhlIHJlYXNvbiBpcyB0aGF0IHRoZXJlIGlzIGEgcm93IGZvciBldmVyeSBwcm92aW5jZSBvciBzdGF0ZSB0aGF0IHRoZSB1bml2ZXJzaXR5IGNvdWxkIGdldCwgbm90IGp1c3QgdGhlIGNvdW50cmllcy4gCgoKV2UgY2FuIGFsc28gc2VlIGhlcmUgdGhhdCB0aGVyZSBhcmUgYSBsb3Qgb2YgdGhpbmdzIHRoYXQgc2F5LCBgTkFgIC4gV2UnbGwgd2FudCB0byBrZWVwIGFuIGV5ZSBvbiB0aGF0LCBzaW5jZSB0aGV5IGNhbiBjb3JydXB0IGV2ZXJ5dGhpbmcgeW91IHdhbnQgdG8gZG8gd2l0aCB5b3VyIGRhdGEuIAoKVG8gZ2V0IGEgbW9yZSB0aG9yb3VnaCB2aWV3IG9mIHdoYXQgeW91IGhhdmUsIHdlJ2xsIHVzZSBhbm90aGVyIHBhY2thZ2UsIGNhbGxlZCBgc2tpbXJgIHRvIHNraW0gdGhyb3VnaCB0aGUgZGF0YXNldDogCgpgYGB7ciBza2ltX2podX0KCnNraW1yOjpza2ltKGpodV9jb3ZpZCkKCgoKCmBgYAoKCldoYXQgaWYgSSB3YW50IHRvIHNlZSBvbmx5IHRoZSBkYXRhIGZvciBjb3VudHJpZXMgYXMgYSB0b3RhbD8gTGV0J3Mgc2VlIHdoYXQgd2UgZmluZDogCgpgYGB7ciBnZXRfbWlzc2luZ19zdGF0ZXN9CgpqaHVfY292aWQgJT4lCiAgZmlsdGVyICggaXMubmEoIFByb3ZpbmNlX1N0YXRlKSkKCgoKCmBgYAoKVW5mb3J0dW5hdGVseSBmb3IgdXMsIGFueSBjb3VudHJ5IHRoYXQgaGFzIGRhdGEgZm9yIHRoZSBzdGF0ZSBkb2Vzbid0IGhhdmUgZGF0YSBmb3IgdGhlIGNvdW50cnkgYXMgYSB3aG9sZS4gQW5kIHRoZSBVUyBpcyBsaXN0aW5nIEVWRVJZIENPVU5UWSEgTm8gd29uZGVyIHRoZXJlIGFyZSBzbyBtYW55LiAKClNvIHdlIHdhbnQgdG8gcmV2ZXJzZSBlbmdpbmVlciB0aGlzIGEgbGl0dGxlLiBUbyBnZXQgdG8gdGhlIGNvdW50cnkgdG90YWxzIHdlIG5lZWQgdG86CgoqIENvbnZlcnQgdGhlIGluY2lkZW50IHJhdGUgdG8gYSBwb3B1bGF0aW9uISAoSWYgY29uZmlybWVkIC8gcG9wdWxhdGlvbiBpcyB0aGUgaW5jaWRlbnQgcmF0ZSwgdGhlbiB3ZSBjYW4gYmFjayBvdXQgdGhlIHBvcHVsYXRpb24gZnJvbSBpdC4pCiogQWRkIHVwIHRoZSBzdGF0ZXMgb3IgY291bnRpZXMgdG8gdGhlIHRvdGFsIGZvciB0aGUgY291bnRyeS4gCiogUmUtY29tcHV0ZSB0aGUgaW5jaWRlbnQgcmF0ZS4gCgoKV2UnbGwgdXNlIHRoZSBiYXNpYyBidWlsZGluZyBibG9ja3Mgb2YgdGhlIFRpZHl2ZXJzZSwgdGhlIGJhc2ljIHZlcmJzOiAKCmBzZWxlY3RgIG1lYW5zIHRvIHBpY2sgb3V0IGNvbHVtbnMKYGZpbHRlcmAgbWVhbnMgdG8gcGljayBvdXQgcm93cwpgYXJyYW5nZWAgbWVhbnMgdG8gc29ydCB0aGUgcm93cwpgbXV0YXRlYCBtZWFucyB0byBjcmVhdGUgYSBuZXcgY29sdW1uIGZyb20gb2xkIGluZm9ybWF0aW9uLiAKCldoZW4gd2UgZ28gZnJvbSBvbmUgdmVyYiB0byB0aGUgbmV4dCwgd2UnbGwgdXNlIGEgcGlwZSAoJT4lICkgdG8gY29ubmVjdCB0aGVtOiAKCgohW3JsYWRpZXNdKHJsYWRpZXNfcGlwZS5wbmcpCgoKCgoKYGBge3IgZ29fYmFja3dhcmRzfQoKCmpodV9jb3ZpZF9jb3VudHMgPC0KICAgamh1X2NvdmlkICU+JQogICBtdXRhdGUgKCByYXRlX25vdF9wZXIxMDBrID0gSW5jaWRlbnRfUmF0ZSAvIDEwMDAwMCAsIAogICAgICAgICAgICBlc3RfcG9wdWxhdGlvbiA9IENvbmZpcm1lZCAvIHJhdGVfbm90X3BlcjEwMGspCgoKCmBgYAoKCgoKCk5vdyBJIHdhbnQgdG8ganVzdCBrZWVwIHRoZSBjb2x1bW5zIHRoYXQgY2FuIGFkZCB1cCwgZml4IHNvbWUgTkEncywgYW5kIGxvd2VyY2FzZSBhbGwgb2YgdGhlIGNvbHVtbiBuYW1lcyBzbyBJIGRvbid0IGhhdmUgdG8gcmVtZW1iZXIgd2hhdCdzIHdoYXQuIAoKTGV0J3MgZ28gYWhlYWQgYW5kIGFkZCB0aGVtIHVwIGJ5IGNvdW50cnkuIEludHJvZHVjaW5nIGEgbmV3IHZlcmI6IAoKZ3JvdXBfYnkgYW5kIHN1bW1hcmlzZSwgd2hpY2ggY29tYmluZXMgcm93cyBieSBjYWxjdWxhdGluZyBhIHN0YXRpc3RpYy4gV2UnbGwgZG8gaXQgbG9uZ2hhbmQsIGV2ZW4gdGhvdWdoIHRoZXJlIGFyZSBmYW5jeSB3YXlzIHRvIHJlZHVjZSB0aGUgdHlwaW5nIGludm9sdmVkLiAKCgpgYGB7ciBnZXRfY291bnRyeV90b3RhbHN9CgpqaHVfY292aWRfY291bnQyIDwtIAogICAgamh1X2NvdmlkX2NvdW50cyAlPiUKICAgIGdyb3VwX2J5ICggQ291bnRyeV9SZWdpb24pICU+JQogICAgc3VtbWFyaXNlICggY29uZmlybWVkX3RvdGFsID0gc3VtICggQ29uZmlybWVkLCBuYS5ybT1UKSwgCiAgICAgICAgICAgICAgICBkZWF0aHNfdG90YWwgPSBzdW0gKERlYXRocywgbmEucm09VCkgLCAKICAgICAgICAgICAgICAgIGVzdF9wb3B1bGF0aW9uID0gc3VtICggZXN0X3BvcHVsYXRpb24gLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgIGFwcHJveF9sYXQgPSBtZWRpYW4gKCBMYXQsIG5hLnJtPVQpLCAKICAgICAgICAgICAgICAgIGFwcHJveF9sb25nID0gbWVkaWFuIChMb25nXywgbmEucm09VCkpCiAgICAgICAgICAgICAgCiAgICAgICAgICAgIAogIAogIAoKCmBgYAoKCldhbnQgdG8gc2VlIGEgY2hhcnQsIHdpdGggdGhlIDE5NiBjb3VudHJpZXMgbGlzdGVkIGJ5IHRoZSBudW1iZXIgb2YgQ09WSUQgY2FzZXMgdGhleSBoYXZlPyBJJ2xsIHVzZSBhbm90aGVyIHBhY2thZ2UgdG8gbWFrZSBpdCBsb29rIGEgbGl0dGxlIG5pY2VyLCBhbmQgdG8gY3JlYXRlIGEgc29ydGFibGUsIHNlYXJjaGFibGUgdGFibGUuIFRoYXQgcGFja2FnZSBpcyBjYWxsZWQgYHJlYWN0YWJsZWAKCmBgYHtyIG1ha2VfdGFibGVfY291bnRzfQoKamh1X2NvdmlkX2NvdW50MiAlPiUKICAgcmVhY3RhYmxlICggIAogICAgICBzZWFyY2hhYmxlPVRSVUUsIAogICAgICBmaWx0ZXJhYmxlPUZBTFNFLAogICAgICAjY29tcGFjdD1UUlVFLCAKICAgICAgdGhlbWU9cmVhY3RhYmxlVGhlbWUoY29sb3I9ImRhcmsgZ3JheSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT1saXN0KGZvbnRTaXplPSI4NSUiKSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgKSAsIAogICAgICAgZGVmYXVsdENvbERlZiA9IGNvbERlZiAoIGZvcm1hdCA9IGNvbEZvcm1hdChzZXBhcmF0b3JzPVRSVUUsIGRpZ2l0cz0wKSwgbWluV2lkdGg9NzUpCiAgICkKCgoKCgoKYGBgCgoKSWYgd2Ugd2FudGVkIHRvIGZpbmQgb3V0IHdoYXQgcGVyY2VudGFnZSBvZiB0aGUgdG90YWwgd29ybGR3aWRlIGNhc2VzIGVhY2ggY291bnRyeSBoYWQsIHdlIGNhbiBkbyB0aGF0IHdpdGggb3VyIGRhdGEgdGhlIHNhbWUgd2F5OiAKCgpgYGB7ciBwY3RfY2FzZXN9CgpqaHVfY292aWRfY291bnQyICU+JQogIG11dGF0ZSAoIHBjdF9vZl9jYXNlcyA9IGNvbmZpcm1lZF90b3RhbCAvIHN1bSAoIGNvbmZpcm1lZF90b3RhbCkgKiAxMDAgLCAKICAgICAgICAgICBwY3Rfb2ZfcG9wICA9IGVzdF9wb3B1bGF0aW9uIC8gc3VtIChlc3RfcG9wdWxhdGlvbikgKiAxMDAgKSAlPiUKICBzZWxlY3QgKENvdW50cnlfUmVnaW9uLCBwY3Rfb2ZfY2FzZXMsIHBjdF9vZl9wb3ApICU+JQogIGFycmFuZ2UgKCBkZXNjIChwY3Rfb2ZfY2FzZXMpKSAKICAKCgpgYGAKCk5vdyBsZXQncyBnZXQgdGhlIHJhdGUgcGVyIDEwMCwwMDAgcmVzaWRlbnRzOiAKCgpgYGB7ciBjb3VudHJ5X3JhdGVzfQoKamh1X3JhdGVzIDwtIAogIGpodV9jb3ZpZF9jb3VudDIgICU+JQogIG11dGF0ZSAoIHJhdGVfcGVyXzEwMGsgPSBjb25maXJtZWRfdG90YWwgLyBlc3RfcG9wdWxhdGlvbiAqIDEwMDAwMCwgCiAgICAgICAgICAgZGVhdGhfcmF0ZSAgICA9IGRlYXRoc190b3RhbCAvIGNvbmZpcm1lZF90b3RhbCAqIDEwMCkgCgoKCmBgYAoKCiMjIENoYXJ0IG9mIDIwIGxhcmdlc3QgY291bnRyaWVzCgpIZXJlJ3Mgb25lIHdheSB0byBjaGFydCB0aGUgZGVhdGggcmF0ZXMgZnJvbSBDT1ZJRCBmb3IgdGhlIDIwIGxhcmdlc3QgY291bnRyaWVzIGluIHRoZSBkYXRhc2V0OiAKCmBgYHtyIGNoYXJ0XzIwfQoKamh1X3JhdGVzICU+JQogIHNsaWNlX21heCAoIG49MjAsIGVzdF9wb3B1bGF0aW9uKSAgJT4lCiAgICBnZ3Bsb3QgKCBhZXMgKCB4PXJlb3JkZXIoQ291bnRyeV9SZWdpb24sZGVhdGhfcmF0ZSksICB5PWRlYXRoX3JhdGUsICkgKSArCiAgICAgZ2VvbV9jb2wgKGZpbGw9ImJsdWUiKSArCiAgICAgeWxpbSAoIDAsIDgpICsgCiAgICAgY29vcmRfZmxpcCgpICAgICsKICAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgIGxhYnMoIHkgPSAiUGN0IG9mIGNhc2VzIGVuZGluZyBpbiBkZWF0aCIsIHg9IiIpCiAgICAgCiAgICAgCgpgYGAKCgoKIyMgV29ybGQgbWFwIAoKTm93IHdlJ2xsIHVzZSBhIHBhY2thZ2UgdGhhdCBzb21lIG9mIHlvdSBtYXkgYmUgYSBsaXR0bGUgZmFtaWxpYXIgd2l0aCAtIGl0J3MgYW4gUiB2ZXJzaW9uIG9mIHRoZSBsZWFmbGV0KCkgamF2YXNjcmlwdCBsaWJyYXJ5OiAKCgpgYGB7ciBsZWFmbGV0X21hcCwgd2FybmluZz1GQUxTRX0KCmxpYnJhcnkobGVhZmxldCkKCgpsZWFmbGV0ICggamh1X3JhdGVzKSAlPiUgCiAgICAgYWRkVGlsZXMgKCkgJT4lCiAgICAgc2V0VmlldyAoIGxuZz0gMCwgbGF0PTAsIHpvb20gPSAxKSAlPiUgCiAgICAgYWRkQ2lyY2xlcyAoIGxuZz1+YXBwcm94X2xvbmcsIGxhdD1+YXBwcm94X2xhdCwgd2VpZ2h0PTEsIAogICAgICAgICAgICAgICAgICByYWRpdXMgPSBzcXJ0KGpodV9yYXRlcyRyYXRlX3Blcl8xMDBrKSo0MDAwLCAKICAgICAgICAgICAgICAgICAgcG9wdXA9IHBhc3RlKGpodV9yYXRlcyRDb3VudHJ5X1JlZ2lvbiwgcm91bmQoamh1X3JhdGVzJHJhdGVfcGVyXzEwMGssIDApLCBzZXA9IjogIikgIAogICAgICkKICAgICAKCgoKYGBgCgoKCg==