Updated April 29, 2020

Introduction

I have been asked by several co-workers and others if I have looked at the data for COVID-19. This document is an attempt to look at that data at a fairly high level.

The data for this writeup is now coming from the Johns Hopkins data set.
https://github.com/CSSEGISandData/COVID-19

Note: RPubs doens’t seem to allow URLs. You will need to copy/pase the url into your browser.

The data is updated almost daily.

Loading the Data

The following loads the COVID-19 data.

rm(list=ls())
library(ggplot2)
library(data.table)
library(RCurl)

df_confirmed <- read.csv('time_series_covid_19_confirmed.csv')

Reshape the Data

The data in the dataset is a little difficult to work with. The next several code chunks arrange the data in such a way to make it easier to graph.

Fill in Empty Country Names

The dataset contains some countries without state or province names. In those cases, we’d like the state name to just be the same as the country name.


fill_missing_province <- function(df) {
  df[,1] <- as.character(df[,1])
  df[,2] <- as.character(df[,2])
  head(df_confirmed)
  for(i in seq(1,nrow(df))){
    if(as.character(df[i,1])==""){
      df[i,1] <- df[i,2]
    }
  }
  df[,1] <- as.factor(df[,1])
  df[,2] <- as.factor(df[,2])
  return(df)
}

df_confirmed <- fill_missing_province(df_confirmed)
head(df_confirmed)

Transpose the Data

We want the data to eventually be in a tibble (tall skinny table) to make it easier to graph.

# Rename rows...
extract_location <- function(df, cname, pname){
  country <- subset(df, df$Country.Region==cname)
  head(country)
  dfct <- data.frame(t(subset(country, country$Province.State==pname)))
  dfct <- tail(dfct, length(dfct)-5)
  names(dfct) <- c("count")
  dfct$count <- as.numeric(as.character(dfct$count))
  dfct$location = rep(paste(cname,"-",pname), nrow(dfct))
  dfct$sequence = seq(1,nrow(dfct))
  names(dfct)<- c('count','location','sequence')
  dfct <- dfct[c('sequence','location','count')]
  dfct$count <- as.integer(dfct$count)
  row.names(dfct) <- seq(1,nrow(dfct))
  return(dfct)
}

head(extract_location(df_confirmed, "US", "US"))

Shuffle the Data

Countries found their first case of the virus on different days. This code shuffles the data so that we can explore the growth curve for each country starting on the first day a case was reported in that location. This is their “day 0”.


shift_up <- function(df, column){
  idx = which(names(df)==column)
  while(df[1,idx]==0){
    first_row <- df[1,]
    for(i in seq(1, nrow(df)-1)){
      df[i,] <- df[i+1,]
    }
    df[nrow(df),] <- first_row
  }
  return(df)
}

test_df <- data.frame(as.integer(), as.character())
test_df <- rbind(test_df,data.frame(count=0,stuff='zero'))
test_df <- rbind(test_df,data.frame(count=0,stuff='zero'))
test_df <- rbind(test_df,data.frame(count=1,stuff='one'))
test_df <- rbind(test_df,data.frame(count=2,stuff='two'))
test_df <- rbind(test_df,data.frame(count=3,stuff='three'))
names(test_df) <- c('count','stuff')
test_df
shift_up(test_df, 'count')
NA

Remove Empty Values

In order to make the graphs cleaner, we delete all days that have a zero count value. This will prevent our trendlines from dropping to zero if we don’t have data.


remove_zeros <- function(df) {
  new_df = subset(df, df$count>0)
  return(new_df)
}

test_df <- data.frame(as.integer(), as.character())
test_df <- rbind(test_df,data.frame(count=0,stuff='zero'))
test_df <- rbind(test_df,data.frame(count=0,stuff='zero'))
test_df <- rbind(test_df,data.frame(count=1,stuff='one'))
test_df <- rbind(test_df,data.frame(count=2,stuff='two'))
test_df <- rbind(test_df,data.frame(count=3,stuff='three'))
names(test_df) <- c('count','stuff')
test_df
shift_df <- shift_up(test_df, 'count')
shift_df
clean_df <- remove_zeros(shift_df)
clean_df

Compute the Delta Column

Rather than calculating cumulative values, which never decline, I have opted to go for deltas. In other words, the number of cases that have changed since the previous day.


test_df <- data.frame(as.integer(), as.character())
test_df <- rbind(test_df,data.frame(count=5,stuff='zero'))
test_df <- rbind(test_df,data.frame(count=45,stuff='zero'))
test_df <- rbind(test_df,data.frame(count=112,stuff='one'))
test_df <- rbind(test_df,data.frame(count=209,stuff='two'))
test_df <- rbind(test_df,data.frame(count=783,stuff='three'))
names(test_df) <- c('count','stuff')

compute_delta <- function(df, colname){
  delta = data.frame(as.integer())
  delta <- rbind(delta,delta=0)
  c = which(names(df)==colname)
  for (r in seq(2,nrow(df))){
      delta <- rbind(delta, delta=(df[r,c] - df[r-1,c]))
  }
  names(delta) <- c('delta')
  df$delta <- delta$delta
  return(df)
}

test_df <- compute_delta(test_df, 'count')
test_df
get_location <- function(df_full, country, province) {
  df <- shift_up(extract_location(df_full, country,province), "count")
  df$sequence <- seq(1,nrow(df))
  df$count <- as.numeric(df$count)
  df <- remove_zeros(df)
  # Compute delta column
  df <- compute_delta(df, 'count')
  return(df)
}

Graphs

We will start with a set of places: United States, Iran, France, Spain, and Hubei, China, Taiwan and South Korea. This code creates a single dataframe that we can explore with various transforms of the data.


all_locations = data.frame(as.integer(), as.character(), as.integer(), as.integer())
names(all_locations) <- c("sequence", "location","count", "delta")

us <- get_location(df_confirmed, "US", "US")
iran <- get_location(df_confirmed, "Iran","Iran")
italy <- get_location(df_confirmed, "Italy", "Italy")
france <- get_location(df_confirmed, "France","France")
spain <- get_location(df_confirmed, "Spain","Spain")
china <- get_location(df_confirmed, "China","Hubei")
taiwan <- get_location(df_confirmed, "Taiwan*", "Taiwan*")
skorea <- get_location(df_confirmed, "Korea, South","Korea, South")

all_locations <- rbind(all_locations, us)
all_locations <- rbind(all_locations, iran)
all_locations <- rbind(all_locations, italy)
all_locations <- rbind(all_locations, france)
all_locations <- rbind(all_locations, spain)
all_locations <- rbind(all_locations, china)
all_locations <- rbind(all_locations, taiwan)
all_locations <- rbind(all_locations, skorea)

Raw Counts

Our first graph will be of confirmed case counts in the different areas of interest. Here we are graphing the raw number of new reported cases by days since “day 0”.


p <- ggplot(all_locations, aes(x=sequence, y=delta, colour=location, linetype=location)) +
  geom_line() +
  ggtitle("COVID19 New Cases")+xlab("Days After First Case")+ylab("New Cases")
p

On this graph, we see something interesting. several of the countries doesn’t seem to appear. Actually, you have to look really really close. Due to the scale, several of the lines hug the x-axis.

Square Root of Counts

In the next graph, we will plot the square root of the number of new cases for each of the countries. By doing so, we will be able to more easily see the smaller numbers.

p <- ggplot(all_locations, aes(x=sequence, y=sqrt(delta), colour=location, linetype=location)) +
  geom_line()+ggtitle("COVID-19 New Cases (Sqrt)")+xlab("Days After First Case")+ylab("New Cases sqrt(x)")
p

Here we see several of the countries just start to appears. The square root transform is not sufficient to really get a sense of the pattern. To do that we will move to a log scale.

Log Scale

In the next graph, we transform the number of cases by a natural log.

all_locations$logscale <- log(all_locations$delta)
NaNs produced
all_locations[all_locations<0] <-0
p <- ggplot(all_locations, aes(x=sequence, y=logscale, colour=location,  linetype=location)) +
  geom_line() + ggtitle("COVID-19 New Cases Natural Log")+xlab("Days After First Case")+ylab("New Cases ln(x)")
p

write.csv(all_locations, "all_locations.csv", row.names = FALSE)

A Few Words About the Data

In data science and statistics we frequently talk about sample bias. This data is a perfect example of that phenomenon. Only those that appear sick are tested for the virus Also, different countries and regions have differing inventory of test kits. Clearly, those areas that are performing more tests are going to find more people with the virus. The site https://ourworldindata.org/covid-testing has several graphs that display total tests by country and test per capita by country.

We are not looking at actual new cases in the graphs that I displayed, but a much smaller subset.

The virus takes 1-14 days before symptoms start to appear. The most common incubation period is around 5 days. (https://www.who.int/news-room/q-a-detail/q-a-coronaviruses) While it is possible to pass the virus on before being contageous, the virus is mainly spread by people that are symptomatic. (https://www.cdc.gov/coronavirus/2019-ncov/prepare/transmission.html)

Conclusion

COVID-19 presents an unprecident challenge to our society. It is a serious situation due to several factors including the risk it poses to the elderly and those with pre-existing health issues as well as the overall strain that it could pose to our healthcare system. A COVID-19 patient taking up a room in a hospital is one less bed that hospital has for people with other urgent healthcare needs.

While we should take this situation seriously, we should not panic. The US Centers for Disease Control, World Health Organizaton and many state and local departments of health and publich safety have valueable resources for us.

I have made every attempt to ensure that the numbers and data presented in this writeup are accurate based on the data that I could find. That said, it makes a great deal of sense to confirm any data you see on the internet with other sources such as the CDC and WHO.

Lastly, for more information, the CDC is a good place to start:

https://www.cdc.gov/coronavirus/2019-ncov/symptoms-testing/share-facts.html?CDC_AA_refVal=https%3A%2F%2Fwww.cdc.gov%2Fcoronavirus%2F2019-ncov%2Fabout%2Fshare-facts.html

Thanks, and stay healthy, and #flattenthecurve everyone.

LS0tDQp0aXRsZTogIkNPVklELTE5IEFuYWx5c2lzIg0KYXV0aG9yOiAgTWlsZXMgUi4gUG9ydGVyDQpkYXRlOiBNYXJjaCAyMCwgMjAyMA0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KKlVwZGF0ZWQgQXByaWwgMjksIDIwMjAqDQoNCiMgSW50cm9kdWN0aW9uDQoNCkkgaGF2ZSBiZWVuIGFza2VkIGJ5IHNldmVyYWwgY28td29ya2VycyBhbmQgb3RoZXJzIGlmIEkgaGF2ZSBsb29rZWQgYXQgdGhlIGRhdGEgZm9yIENPVklELTE5LiAgVGhpcyBkb2N1bWVudCBpcyBhbiBhdHRlbXB0IHRvIGxvb2sgYXQgdGhhdCBkYXRhIGF0IGEgZmFpcmx5IGhpZ2ggbGV2ZWwuDQoNClRoZSBkYXRhIGZvciB0aGlzIHdyaXRldXAgaXMgbm93IGNvbWluZyBmcm9tIHRoZSBKb2hucyBIb3BraW5zIGRhdGEgc2V0Lg0KPHByZT4NCmh0dHBzOi8vZ2l0aHViLmNvbS9DU1NFR0lTYW5kRGF0YS9DT1ZJRC0xOQ0KPC9wcmU+DQpOb3RlOiAgUlB1YnMgZG9lbnMndCBzZWVtIHRvIGFsbG93IFVSTHMuICBZb3Ugd2lsbCBuZWVkIHRvIGNvcHkvcGFzZSB0aGUgdXJsIGludG8geW91ciBicm93c2VyLg0KDQoNClRoZSBkYXRhIGlzIHVwZGF0ZWQgYWxtb3N0IGRhaWx5LiAgDQoNCiMgTG9hZGluZyB0aGUgRGF0YQ0KDQpUaGUgZm9sbG93aW5nIGxvYWRzIHRoZSBDT1ZJRC0xOSBkYXRhLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0Kcm0obGlzdD1scygpKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShSQ3VybCkNCg0KZGZfY29uZmlybWVkIDwtIHJlYWQuY3N2KCd0aW1lX3Nlcmllc19jb3ZpZF8xOV9jb25maXJtZWQuY3N2JykNCmBgYA0KDQojIFJlc2hhcGUgdGhlIERhdGENCg0KVGhlIGRhdGEgaW4gdGhlIGRhdGFzZXQgaXMgYSBsaXR0bGUgZGlmZmljdWx0IHRvIHdvcmsgd2l0aC4gIFRoZSBuZXh0IHNldmVyYWwgY29kZSBjaHVua3MgYXJyYW5nZSB0aGUgZGF0YSBpbiBzdWNoIGEgd2F5IHRvIG1ha2UgaXQgZWFzaWVyIHRvIGdyYXBoLg0KDQojIyBGaWxsIGluIEVtcHR5IENvdW50cnkgTmFtZXMNCg0KVGhlIGRhdGFzZXQgY29udGFpbnMgc29tZSBjb3VudHJpZXMgd2l0aG91dCBzdGF0ZSBvciBwcm92aW5jZSBuYW1lcy4gIEluIHRob3NlIGNhc2VzLCB3ZSdkIGxpa2UgdGhlIHN0YXRlIG5hbWUgdG8ganVzdCBiZSB0aGUgc2FtZSBhcyB0aGUgY291bnRyeSBuYW1lLg0KDQpgYGB7cn0NCg0KZmlsbF9taXNzaW5nX3Byb3ZpbmNlIDwtIGZ1bmN0aW9uKGRmKSB7DQogIGRmWywxXSA8LSBhcy5jaGFyYWN0ZXIoZGZbLDFdKQ0KICBkZlssMl0gPC0gYXMuY2hhcmFjdGVyKGRmWywyXSkNCiAgaGVhZChkZl9jb25maXJtZWQpDQogIGZvcihpIGluIHNlcSgxLG5yb3coZGYpKSl7DQogICAgaWYoYXMuY2hhcmFjdGVyKGRmW2ksMV0pPT0iIil7DQogICAgICBkZltpLDFdIDwtIGRmW2ksMl0NCiAgICB9DQogIH0NCiAgZGZbLDFdIDwtIGFzLmZhY3RvcihkZlssMV0pDQogIGRmWywyXSA8LSBhcy5mYWN0b3IoZGZbLDJdKQ0KICByZXR1cm4oZGYpDQp9DQoNCmRmX2NvbmZpcm1lZCA8LSBmaWxsX21pc3NpbmdfcHJvdmluY2UoZGZfY29uZmlybWVkKQ0KaGVhZChkZl9jb25maXJtZWQpDQpgYGANCg0KIyMgVHJhbnNwb3NlIHRoZSBEYXRhDQoNCldlIHdhbnQgdGhlIGRhdGEgdG8gZXZlbnR1YWxseSBiZSBpbiBhIHRpYmJsZSAodGFsbCBza2lubnkgdGFibGUpIHRvIG1ha2UgaXQgZWFzaWVyIHRvIGdyYXBoLg0KDQpgYGB7cn0NCiMgUmVuYW1lIHJvd3MuLi4NCmV4dHJhY3RfbG9jYXRpb24gPC0gZnVuY3Rpb24oZGYsIGNuYW1lLCBwbmFtZSl7DQogIGNvdW50cnkgPC0gc3Vic2V0KGRmLCBkZiRDb3VudHJ5LlJlZ2lvbj09Y25hbWUpDQogIGhlYWQoY291bnRyeSkNCiAgZGZjdCA8LSBkYXRhLmZyYW1lKHQoc3Vic2V0KGNvdW50cnksIGNvdW50cnkkUHJvdmluY2UuU3RhdGU9PXBuYW1lKSkpDQogIGRmY3QgPC0gdGFpbChkZmN0LCBsZW5ndGgoZGZjdCktNSkNCiAgbmFtZXMoZGZjdCkgPC0gYygiY291bnQiKQ0KICBkZmN0JGNvdW50IDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGRmY3QkY291bnQpKQ0KICBkZmN0JGxvY2F0aW9uID0gcmVwKHBhc3RlKGNuYW1lLCItIixwbmFtZSksIG5yb3coZGZjdCkpDQogIGRmY3Qkc2VxdWVuY2UgPSBzZXEoMSxucm93KGRmY3QpKQ0KICBuYW1lcyhkZmN0KTwtIGMoJ2NvdW50JywnbG9jYXRpb24nLCdzZXF1ZW5jZScpDQogIGRmY3QgPC0gZGZjdFtjKCdzZXF1ZW5jZScsJ2xvY2F0aW9uJywnY291bnQnKV0NCiAgZGZjdCRjb3VudCA8LSBhcy5pbnRlZ2VyKGRmY3QkY291bnQpDQogIHJvdy5uYW1lcyhkZmN0KSA8LSBzZXEoMSxucm93KGRmY3QpKQ0KICByZXR1cm4oZGZjdCkNCn0NCg0KaGVhZChleHRyYWN0X2xvY2F0aW9uKGRmX2NvbmZpcm1lZCwgIlVTIiwgIlVTIikpDQpgYGANCg0KIyBTaHVmZmxlIHRoZSBEYXRhDQoNCkNvdW50cmllcyBmb3VuZCB0aGVpciBmaXJzdCBjYXNlIG9mIHRoZSB2aXJ1cyBvbiBkaWZmZXJlbnQgZGF5cy4gIFRoaXMgY29kZSBzaHVmZmxlcyB0aGUgZGF0YSBzbyB0aGF0IHdlIGNhbiBleHBsb3JlIHRoZSBncm93dGggY3VydmUgZm9yIGVhY2ggY291bnRyeSBzdGFydGluZyBvbiB0aGUgZmlyc3QgZGF5IGEgY2FzZSB3YXMgcmVwb3J0ZWQgaW4gdGhhdCBsb2NhdGlvbi4gIFRoaXMgaXMgdGhlaXIgImRheSAwIi4NCg0KYGBge3J9DQoNCnNoaWZ0X3VwIDwtIGZ1bmN0aW9uKGRmLCBjb2x1bW4pew0KICBpZHggPSB3aGljaChuYW1lcyhkZik9PWNvbHVtbikNCiAgd2hpbGUoZGZbMSxpZHhdPT0wKXsNCiAgICBmaXJzdF9yb3cgPC0gZGZbMSxdDQogICAgZm9yKGkgaW4gc2VxKDEsIG5yb3coZGYpLTEpKXsNCiAgICAgIGRmW2ksXSA8LSBkZltpKzEsXQ0KICAgIH0NCiAgICBkZltucm93KGRmKSxdIDwtIGZpcnN0X3Jvdw0KICB9DQogIHJldHVybihkZikNCn0NCg0KdGVzdF9kZiA8LSBkYXRhLmZyYW1lKGFzLmludGVnZXIoKSwgYXMuY2hhcmFjdGVyKCkpDQp0ZXN0X2RmIDwtIHJiaW5kKHRlc3RfZGYsZGF0YS5mcmFtZShjb3VudD0wLHN0dWZmPSd6ZXJvJykpDQp0ZXN0X2RmIDwtIHJiaW5kKHRlc3RfZGYsZGF0YS5mcmFtZShjb3VudD0wLHN0dWZmPSd6ZXJvJykpDQp0ZXN0X2RmIDwtIHJiaW5kKHRlc3RfZGYsZGF0YS5mcmFtZShjb3VudD0xLHN0dWZmPSdvbmUnKSkNCnRlc3RfZGYgPC0gcmJpbmQodGVzdF9kZixkYXRhLmZyYW1lKGNvdW50PTIsc3R1ZmY9J3R3bycpKQ0KdGVzdF9kZiA8LSByYmluZCh0ZXN0X2RmLGRhdGEuZnJhbWUoY291bnQ9MyxzdHVmZj0ndGhyZWUnKSkNCm5hbWVzKHRlc3RfZGYpIDwtIGMoJ2NvdW50Jywnc3R1ZmYnKQ0KdGVzdF9kZg0Kc2hpZnRfdXAodGVzdF9kZiwgJ2NvdW50JykNCg0KYGBgDQoNCiMjIFJlbW92ZSBFbXB0eSBWYWx1ZXMNCg0KSW4gb3JkZXIgdG8gbWFrZSB0aGUgZ3JhcGhzIGNsZWFuZXIsIHdlIGRlbGV0ZSBhbGwgZGF5cyB0aGF0IGhhdmUgYSB6ZXJvIGNvdW50IHZhbHVlLiAgVGhpcyB3aWxsIHByZXZlbnQgb3VyIHRyZW5kbGluZXMgZnJvbSBkcm9wcGluZyB0byB6ZXJvIGlmIHdlIGRvbid0IGhhdmUgZGF0YS4NCg0KYGBge3J9DQoNCnJlbW92ZV96ZXJvcyA8LSBmdW5jdGlvbihkZikgew0KICBuZXdfZGYgPSBzdWJzZXQoZGYsIGRmJGNvdW50PjApDQogIHJldHVybihuZXdfZGYpDQp9DQoNCnRlc3RfZGYgPC0gZGF0YS5mcmFtZShhcy5pbnRlZ2VyKCksIGFzLmNoYXJhY3RlcigpKQ0KdGVzdF9kZiA8LSByYmluZCh0ZXN0X2RmLGRhdGEuZnJhbWUoY291bnQ9MCxzdHVmZj0nemVybycpKQ0KdGVzdF9kZiA8LSByYmluZCh0ZXN0X2RmLGRhdGEuZnJhbWUoY291bnQ9MCxzdHVmZj0nemVybycpKQ0KdGVzdF9kZiA8LSByYmluZCh0ZXN0X2RmLGRhdGEuZnJhbWUoY291bnQ9MSxzdHVmZj0nb25lJykpDQp0ZXN0X2RmIDwtIHJiaW5kKHRlc3RfZGYsZGF0YS5mcmFtZShjb3VudD0yLHN0dWZmPSd0d28nKSkNCnRlc3RfZGYgPC0gcmJpbmQodGVzdF9kZixkYXRhLmZyYW1lKGNvdW50PTMsc3R1ZmY9J3RocmVlJykpDQpuYW1lcyh0ZXN0X2RmKSA8LSBjKCdjb3VudCcsJ3N0dWZmJykNCnRlc3RfZGYNCnNoaWZ0X2RmIDwtIHNoaWZ0X3VwKHRlc3RfZGYsICdjb3VudCcpDQpzaGlmdF9kZg0KY2xlYW5fZGYgPC0gcmVtb3ZlX3plcm9zKHNoaWZ0X2RmKQ0KY2xlYW5fZGYNCmBgYA0KDQojIyBDb21wdXRlIHRoZSBEZWx0YSBDb2x1bW4NCg0KUmF0aGVyIHRoYW4gY2FsY3VsYXRpbmcgY3VtdWxhdGl2ZSB2YWx1ZXMsIHdoaWNoIG5ldmVyIGRlY2xpbmUsIEkgaGF2ZSBvcHRlZCB0byBnbyBmb3IgZGVsdGFzLiAgSW4gb3RoZXIgd29yZHMsIHRoZSBudW1iZXIgb2YgY2FzZXMgdGhhdCBoYXZlIGNoYW5nZWQgc2luY2UgdGhlIHByZXZpb3VzIGRheS4gIA0KDQpgYGB7cn0NCg0KdGVzdF9kZiA8LSBkYXRhLmZyYW1lKGFzLmludGVnZXIoKSwgYXMuY2hhcmFjdGVyKCkpDQp0ZXN0X2RmIDwtIHJiaW5kKHRlc3RfZGYsZGF0YS5mcmFtZShjb3VudD01LHN0dWZmPSd6ZXJvJykpDQp0ZXN0X2RmIDwtIHJiaW5kKHRlc3RfZGYsZGF0YS5mcmFtZShjb3VudD00NSxzdHVmZj0nemVybycpKQ0KdGVzdF9kZiA8LSByYmluZCh0ZXN0X2RmLGRhdGEuZnJhbWUoY291bnQ9MTEyLHN0dWZmPSdvbmUnKSkNCnRlc3RfZGYgPC0gcmJpbmQodGVzdF9kZixkYXRhLmZyYW1lKGNvdW50PTIwOSxzdHVmZj0ndHdvJykpDQp0ZXN0X2RmIDwtIHJiaW5kKHRlc3RfZGYsZGF0YS5mcmFtZShjb3VudD03ODMsc3R1ZmY9J3RocmVlJykpDQpuYW1lcyh0ZXN0X2RmKSA8LSBjKCdjb3VudCcsJ3N0dWZmJykNCg0KY29tcHV0ZV9kZWx0YSA8LSBmdW5jdGlvbihkZiwgY29sbmFtZSl7DQogIGRlbHRhID0gZGF0YS5mcmFtZShhcy5pbnRlZ2VyKCkpDQogIGRlbHRhIDwtIHJiaW5kKGRlbHRhLGRlbHRhPTApDQogIGMgPSB3aGljaChuYW1lcyhkZik9PWNvbG5hbWUpDQogIGZvciAociBpbiBzZXEoMixucm93KGRmKSkpew0KICAgICAgZGVsdGEgPC0gcmJpbmQoZGVsdGEsIGRlbHRhPShkZltyLGNdIC0gZGZbci0xLGNdKSkNCiAgfQ0KICBuYW1lcyhkZWx0YSkgPC0gYygnZGVsdGEnKQ0KICBkZiRkZWx0YSA8LSBkZWx0YSRkZWx0YQ0KICByZXR1cm4oZGYpDQp9DQoNCnRlc3RfZGYgPC0gY29tcHV0ZV9kZWx0YSh0ZXN0X2RmLCAnY291bnQnKQ0KdGVzdF9kZg0KYGBgDQogIA0KYGBge3J9DQpnZXRfbG9jYXRpb24gPC0gZnVuY3Rpb24oZGZfZnVsbCwgY291bnRyeSwgcHJvdmluY2UpIHsNCiAgZGYgPC0gc2hpZnRfdXAoZXh0cmFjdF9sb2NhdGlvbihkZl9mdWxsLCBjb3VudHJ5LHByb3ZpbmNlKSwgImNvdW50IikNCiAgZGYkc2VxdWVuY2UgPC0gc2VxKDEsbnJvdyhkZikpDQogIGRmJGNvdW50IDwtIGFzLm51bWVyaWMoZGYkY291bnQpDQogIGRmIDwtIHJlbW92ZV96ZXJvcyhkZikNCiAgIyBDb21wdXRlIGRlbHRhIGNvbHVtbg0KICBkZiA8LSBjb21wdXRlX2RlbHRhKGRmLCAnY291bnQnKQ0KICByZXR1cm4oZGYpDQp9DQoNCmBgYA0KDQojIEdyYXBocw0KDQpXZSB3aWxsIHN0YXJ0IHdpdGggYSBzZXQgb2YgcGxhY2VzOiAgVW5pdGVkIFN0YXRlcywgSXJhbiwgRnJhbmNlLCBTcGFpbiwgYW5kIEh1YmVpLCBDaGluYSwgVGFpd2FuIGFuZCBTb3V0aCBLb3JlYS4gIFRoaXMgY29kZSBjcmVhdGVzIGEgc2luZ2xlIGRhdGFmcmFtZSB0aGF0IHdlIGNhbiBleHBsb3JlIHdpdGggdmFyaW91cyB0cmFuc2Zvcm1zIG9mIHRoZSBkYXRhLg0KDQpgYGB7cn0NCg0KYWxsX2xvY2F0aW9ucyA9IGRhdGEuZnJhbWUoYXMuaW50ZWdlcigpLCBhcy5jaGFyYWN0ZXIoKSwgYXMuaW50ZWdlcigpLCBhcy5pbnRlZ2VyKCkpDQpuYW1lcyhhbGxfbG9jYXRpb25zKSA8LSBjKCJzZXF1ZW5jZSIsICJsb2NhdGlvbiIsImNvdW50IiwgImRlbHRhIikNCg0KdXMgPC0gZ2V0X2xvY2F0aW9uKGRmX2NvbmZpcm1lZCwgIlVTIiwgIlVTIikNCmlyYW4gPC0gZ2V0X2xvY2F0aW9uKGRmX2NvbmZpcm1lZCwgIklyYW4iLCJJcmFuIikNCml0YWx5IDwtIGdldF9sb2NhdGlvbihkZl9jb25maXJtZWQsICJJdGFseSIsICJJdGFseSIpDQpmcmFuY2UgPC0gZ2V0X2xvY2F0aW9uKGRmX2NvbmZpcm1lZCwgIkZyYW5jZSIsIkZyYW5jZSIpDQpzcGFpbiA8LSBnZXRfbG9jYXRpb24oZGZfY29uZmlybWVkLCAiU3BhaW4iLCJTcGFpbiIpDQpjaGluYSA8LSBnZXRfbG9jYXRpb24oZGZfY29uZmlybWVkLCAiQ2hpbmEiLCJIdWJlaSIpDQp0YWl3YW4gPC0gZ2V0X2xvY2F0aW9uKGRmX2NvbmZpcm1lZCwgIlRhaXdhbioiLCAiVGFpd2FuKiIpDQpza29yZWEgPC0gZ2V0X2xvY2F0aW9uKGRmX2NvbmZpcm1lZCwgIktvcmVhLCBTb3V0aCIsIktvcmVhLCBTb3V0aCIpDQoNCmFsbF9sb2NhdGlvbnMgPC0gcmJpbmQoYWxsX2xvY2F0aW9ucywgdXMpDQphbGxfbG9jYXRpb25zIDwtIHJiaW5kKGFsbF9sb2NhdGlvbnMsIGlyYW4pDQphbGxfbG9jYXRpb25zIDwtIHJiaW5kKGFsbF9sb2NhdGlvbnMsIGl0YWx5KQ0KYWxsX2xvY2F0aW9ucyA8LSByYmluZChhbGxfbG9jYXRpb25zLCBmcmFuY2UpDQphbGxfbG9jYXRpb25zIDwtIHJiaW5kKGFsbF9sb2NhdGlvbnMsIHNwYWluKQ0KYWxsX2xvY2F0aW9ucyA8LSByYmluZChhbGxfbG9jYXRpb25zLCBjaGluYSkNCmFsbF9sb2NhdGlvbnMgPC0gcmJpbmQoYWxsX2xvY2F0aW9ucywgdGFpd2FuKQ0KYWxsX2xvY2F0aW9ucyA8LSByYmluZChhbGxfbG9jYXRpb25zLCBza29yZWEpDQoNCmBgYA0KDQojIyBSYXcgQ291bnRzDQoNCk91ciBmaXJzdCBncmFwaCB3aWxsIGJlIG9mIGNvbmZpcm1lZCBjYXNlIGNvdW50cyBpbiB0aGUgZGlmZmVyZW50IGFyZWFzIG9mIGludGVyZXN0LiAgSGVyZSB3ZSBhcmUgZ3JhcGhpbmcgdGhlIHJhdyBudW1iZXIgb2YgbmV3IHJlcG9ydGVkIGNhc2VzIGJ5IGRheXMgc2luY2UgImRheSAwIi4NCg0KYGBge3J9DQoNCnAgPC0gZ2dwbG90KGFsbF9sb2NhdGlvbnMsIGFlcyh4PXNlcXVlbmNlLCB5PWRlbHRhLCBjb2xvdXI9bG9jYXRpb24sIGxpbmV0eXBlPWxvY2F0aW9uKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIGdndGl0bGUoIkNPVklEMTkgTmV3IENhc2VzIikreGxhYigiRGF5cyBBZnRlciBGaXJzdCBDYXNlIikreWxhYigiTmV3IENhc2VzIikNCnANCg0KYGBgDQoNCk9uIHRoaXMgZ3JhcGgsIHdlIHNlZSBzb21ldGhpbmcgaW50ZXJlc3RpbmcuICBzZXZlcmFsIG9mIHRoZSBjb3VudHJpZXMgZG9lc24ndCBzZWVtIHRvIGFwcGVhci4gIEFjdHVhbGx5LCB5b3UgaGF2ZSB0byBsb29rIHJlYWxseSByZWFsbHkgY2xvc2UuICBEdWUgdG8gdGhlIHNjYWxlLCBzZXZlcmFsIG9mIHRoZSBsaW5lcyBodWcgdGhlIHgtYXhpcy4NCg0KIyMgU3F1YXJlIFJvb3Qgb2YgQ291bnRzDQoNCkluIHRoZSBuZXh0IGdyYXBoLCB3ZSB3aWxsIHBsb3QgdGhlIHNxdWFyZSByb290IG9mIHRoZSBudW1iZXIgb2YgbmV3IGNhc2VzIGZvciBlYWNoIG9mIHRoZSBjb3VudHJpZXMuICBCeSBkb2luZyBzbywgd2Ugd2lsbCBiZSBhYmxlIHRvIG1vcmUgZWFzaWx5IHNlZSB0aGUgc21hbGxlciBudW1iZXJzLg0KDQpgYGB7cn0NCnAgPC0gZ2dwbG90KGFsbF9sb2NhdGlvbnMsIGFlcyh4PXNlcXVlbmNlLCB5PXNxcnQoZGVsdGEpLCBjb2xvdXI9bG9jYXRpb24sIGxpbmV0eXBlPWxvY2F0aW9uKSkgKw0KICBnZW9tX2xpbmUoKStnZ3RpdGxlKCJDT1ZJRC0xOSBOZXcgQ2FzZXMgKFNxcnQpIikreGxhYigiRGF5cyBBZnRlciBGaXJzdCBDYXNlIikreWxhYigiTmV3IENhc2VzIHNxcnQoeCkiKQ0KcA0KDQpgYGANCg0KSGVyZSB3ZSBzZWUgc2V2ZXJhbCBvZiB0aGUgY291bnRyaWVzIGp1c3Qgc3RhcnQgdG8gYXBwZWFycy4gIFRoZSBzcXVhcmUgcm9vdCB0cmFuc2Zvcm0gaXMgbm90IHN1ZmZpY2llbnQgdG8gcmVhbGx5IGdldCBhIHNlbnNlIG9mIHRoZSBwYXR0ZXJuLiAgVG8gZG8gdGhhdCB3ZSB3aWxsIG1vdmUgdG8gYSBsb2cgc2NhbGUuDQoNCiMjIExvZyBTY2FsZQ0KDQpJbiB0aGUgbmV4dCBncmFwaCwgd2UgdHJhbnNmb3JtIHRoZSBudW1iZXIgb2YgY2FzZXMgYnkgYSBuYXR1cmFsIGxvZy4NCg0KYGBge3J9DQphbGxfbG9jYXRpb25zJGxvZ3NjYWxlIDwtIGxvZyhhbGxfbG9jYXRpb25zJGRlbHRhKQ0KYWxsX2xvY2F0aW9uc1thbGxfbG9jYXRpb25zPDBdIDwtMA0KcCA8LSBnZ3Bsb3QoYWxsX2xvY2F0aW9ucywgYWVzKHg9c2VxdWVuY2UsIHk9bG9nc2NhbGUsIGNvbG91cj1sb2NhdGlvbiwgIGxpbmV0eXBlPWxvY2F0aW9uKSkgKw0KICBnZW9tX2xpbmUoKSArIGdndGl0bGUoIkNPVklELTE5IE5ldyBDYXNlcyBOYXR1cmFsIExvZyIpK3hsYWIoIkRheXMgQWZ0ZXIgRmlyc3QgQ2FzZSIpK3lsYWIoIk5ldyBDYXNlcyBsbih4KSIpDQpwDQoNCmBgYA0KDQpgYGB7cn0NCndyaXRlLmNzdihhbGxfbG9jYXRpb25zLCAiYWxsX2xvY2F0aW9ucy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQojIEEgRmV3IFdvcmRzIEFib3V0IHRoZSBEYXRhDQoNCkluIGRhdGEgc2NpZW5jZSBhbmQgc3RhdGlzdGljcyB3ZSBmcmVxdWVudGx5IHRhbGsgYWJvdXQgc2FtcGxlIGJpYXMuICBUaGlzIGRhdGEgaXMgYSBwZXJmZWN0IGV4YW1wbGUgb2YgdGhhdCBwaGVub21lbm9uLiAgT25seSB0aG9zZSB0aGF0IGFwcGVhciBzaWNrIGFyZSB0ZXN0ZWQgZm9yIHRoZSB2aXJ1cyAgQWxzbywgZGlmZmVyZW50IGNvdW50cmllcyBhbmQgcmVnaW9ucyBoYXZlIGRpZmZlcmluZyBpbnZlbnRvcnkgb2YgdGVzdCBraXRzLiAgQ2xlYXJseSwgdGhvc2UgYXJlYXMgdGhhdCBhcmUgcGVyZm9ybWluZyBtb3JlIHRlc3RzIGFyZSBnb2luZyB0byBmaW5kIG1vcmUgcGVvcGxlIHdpdGggdGhlIHZpcnVzLiBUaGUgc2l0ZSAgaHR0cHM6Ly9vdXJ3b3JsZGluZGF0YS5vcmcvY292aWQtdGVzdGluZyBoYXMgc2V2ZXJhbCBncmFwaHMgdGhhdCBkaXNwbGF5IHRvdGFsIHRlc3RzIGJ5IGNvdW50cnkgYW5kIHRlc3QgcGVyIGNhcGl0YSBieSBjb3VudHJ5Lg0KDQpXZSBhcmUgbm90IGxvb2tpbmcgYXQgYWN0dWFsIG5ldyBjYXNlcyBpbiB0aGUgZ3JhcGhzIHRoYXQgSSBkaXNwbGF5ZWQsIGJ1dCBhIG11Y2ggc21hbGxlciBzdWJzZXQuDQoNClRoZSB2aXJ1cyB0YWtlcyAxLTE0IGRheXMgYmVmb3JlIHN5bXB0b21zIHN0YXJ0IHRvIGFwcGVhci4gIFRoZSBtb3N0IGNvbW1vbiBpbmN1YmF0aW9uIHBlcmlvZCBpcyBhcm91bmQgNSBkYXlzLiAgKGh0dHBzOi8vd3d3Lndoby5pbnQvbmV3cy1yb29tL3EtYS1kZXRhaWwvcS1hLWNvcm9uYXZpcnVzZXMpICBXaGlsZSBpdCBpcyBwb3NzaWJsZSB0byBwYXNzIHRoZSB2aXJ1cyBvbiBiZWZvcmUgYmVpbmcgY29udGFnZW91cywgdGhlIHZpcnVzIGlzIG1haW5seSBzcHJlYWQgYnkgcGVvcGxlIHRoYXQgYXJlIHN5bXB0b21hdGljLiAgKGh0dHBzOi8vd3d3LmNkYy5nb3YvY29yb25hdmlydXMvMjAxOS1uY292L3ByZXBhcmUvdHJhbnNtaXNzaW9uLmh0bWwpICAgICANCg0KIyBDb25jbHVzaW9uDQoNCkNPVklELTE5IHByZXNlbnRzIGFuIHVucHJlY2lkZW50IGNoYWxsZW5nZSB0byBvdXIgc29jaWV0eS4gIEl0IGlzIGEgc2VyaW91cyBzaXR1YXRpb24gZHVlIHRvIHNldmVyYWwgZmFjdG9ycyBpbmNsdWRpbmcgdGhlIHJpc2sgaXQgcG9zZXMgdG8gdGhlIGVsZGVybHkgYW5kIHRob3NlIHdpdGggcHJlLWV4aXN0aW5nIGhlYWx0aCBpc3N1ZXMgYXMgd2VsbCBhcyB0aGUgb3ZlcmFsbCBzdHJhaW4gdGhhdCBpdCBjb3VsZCBwb3NlIHRvIG91ciBoZWFsdGhjYXJlIHN5c3RlbS4gIEEgQ09WSUQtMTkgcGF0aWVudCB0YWtpbmcgdXAgYSByb29tIGluIGEgaG9zcGl0YWwgaXMgb25lIGxlc3MgYmVkIHRoYXQgaG9zcGl0YWwgaGFzIGZvciBwZW9wbGUgd2l0aCBvdGhlciB1cmdlbnQgaGVhbHRoY2FyZSBuZWVkcy4gIA0KDQpXaGlsZSB3ZSBzaG91bGQgdGFrZSB0aGlzIHNpdHVhdGlvbiBzZXJpb3VzbHksIHdlIHNob3VsZCBub3QgcGFuaWMuICBUaGUgVVMgQ2VudGVycyBmb3IgRGlzZWFzZSBDb250cm9sLCBXb3JsZCBIZWFsdGggT3JnYW5pemF0b24gYW5kIG1hbnkgc3RhdGUgYW5kIGxvY2FsIGRlcGFydG1lbnRzIG9mIGhlYWx0aCBhbmQgcHVibGljaCBzYWZldHkgaGF2ZSB2YWx1ZWFibGUgcmVzb3VyY2VzIGZvciB1cy4NCg0KSSBoYXZlIG1hZGUgZXZlcnkgYXR0ZW1wdCB0byBlbnN1cmUgdGhhdCB0aGUgbnVtYmVycyBhbmQgZGF0YSBwcmVzZW50ZWQgaW4gdGhpcyB3cml0ZXVwIGFyZSBhY2N1cmF0ZSBiYXNlZCBvbiB0aGUgZGF0YSB0aGF0IEkgY291bGQgZmluZC4gIFRoYXQgc2FpZCwgaXQgbWFrZXMgYSBncmVhdCBkZWFsIG9mIHNlbnNlIHRvIGNvbmZpcm0gYW55IGRhdGEgeW91IHNlZSBvbiB0aGUgaW50ZXJuZXQgd2l0aCBvdGhlciBzb3VyY2VzIHN1Y2ggYXMgdGhlIENEQyBhbmQgV0hPLg0KDQpMYXN0bHksIGZvciBtb3JlIGluZm9ybWF0aW9uLCB0aGUgQ0RDIGlzIGEgZ29vZCBwbGFjZSB0byBzdGFydDoNCg0KaHR0cHM6Ly93d3cuY2RjLmdvdi9jb3JvbmF2aXJ1cy8yMDE5LW5jb3Yvc3ltcHRvbXMtdGVzdGluZy9zaGFyZS1mYWN0cy5odG1sP0NEQ19BQV9yZWZWYWw9aHR0cHMlM0ElMkYlMkZ3d3cuY2RjLmdvdiUyRmNvcm9uYXZpcnVzJTJGMjAxOS1uY292JTJGYWJvdXQlMkZzaGFyZS1mYWN0cy5odG1sDQoNClRoYW5rcywgYW5kIHN0YXkgaGVhbHRoeSwgYW5kICNmbGF0dGVudGhlY3VydmUgZXZlcnlvbmUuDQoNCg0K