Visualize COVID-19 case data in a blink of an eye — using R, Shiny & Plotly
By: Meinhard Ploner
Tags: Shiny, Plotly, Covit 19, R, Coronavirus

Set up R & RStudio

First, download the base version of R from https://rstudio.com/products/rstudio/download/#download, start the installer and go through.

Then, download the free version of RStudio Desktop from https://rstudio.com/products/rstudio/download/#download. Start again the installer and complete the steps.

Now install the packages we need. Launch the RStudio application and go to the console (the window which resides usually on the bottom-left). Install the packages by typing:

> install.packages("shiny")

> install.packages("dplyr")

> install.packages("tidyr")

> install.packages("plotly")

Set up the R-Project and its folder structure

In RStudio, click on the drop-down menu “Project: (None)” on the top-right and then select “New Project…”.

Click on “New Directory”, then on “Shiny Web Application”. Now choose an arbitrary new directory name for your project, e.g. “corona-app”. Further, choose a subdirectory where you want your app to be located. Click on the button “Create Project” to create the folder with 2 files:

The project file: corona-app.Rproj (might have another name)

An app template file which we do not need: app.R

Create the User Interface using Shiny

The framework Shiny allows R users to create reactive web applications with minimal coding effort. Small apps need only 2 files: ui.R for the user interface layout, and server.R for the server and rendering logic.

Go to RStudio. On the top-left of the user interface, there is a plus button to create new files. Click on it, and choose “R Script”. Create a file and name it “ui.R” by saving it, and start editing.

First load the packages we need:

library(shiny)

library(plotly)

Then, set up an empty page using the bootstrap scheme (12 columns per row), and create the title using the element titlePanel:

shinyUI(fluidPage(

titlePanel("Case History of the Coronavirus (COVID-19)"),

))

Altogether, the screen is divided into different rows (fluidRow’s). The fluidRow with the controls is composed of three column’s of a width of 4 each, which sums up to 12 again. The plots occupy the whole width (12):

Now add the code for the controls. Insert the following code immediately after the line which defines the titlePanel (before the 2 closing brackets!):

fluidRow(

column(

width=4,

selectizeInput(

"country", label=h5("Country"), choices=NULL, width="100%")

),

column(

width=4,

selectizeInput(

"state", label=h5("State"), choices=NULL, width="100%")

),

column(

width=4,

checkboxGroupInput(

"metrics", label=h5("Selected Metrics"),

choices=c("Confirmed", "Deaths", "Recovered"),

selected=c("Confirmed", "Deaths", "Recovered"),

width="100%")

)

),

As you see, there is a fluidRow having 3 column elements, with each having a width of 4. The first two columns hold the drop-down menu elements (selectizeInput) to let the user select the country and the state.

The third column holds a group of checkbox elements (checkboxGroupInput). This allows the user to choose which metrics to be shown in the two plots.

For the two plots, we use the R package plotly (documentation). The following code lines add one plotly-style plot in each of the next two fluidRow’s:

fluidRow(

plotlyOutput("dailyMetrics")

),

fluidRow(

plotlyOutput("cumulatedMetrics")

)

Add logic to load and preprocess the data

The app needs some logic to download and parse the data. This happens on the server. Create again a new file and name it “server.R” and add these lines to load the respective R packages:

library(dplyr)

library(tidyr)

Then define a baseURL from which we download the case data of the Coronavirus by country and region. The data is located on a server of the Johns Hopkins Center for Systems Science and Engineering (JHU/CSSE). Further, define a font (f1) to be used later, plus a short-hand function to measure how old a file is (to avoid too many reloadings):

baseURL = " https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series f1 = list(family="Courier New, monospace", size=12,

color="rgb(30,30,30)") minutesSinceLastUpdate = function(fileName) {

(as.numeric(as.POSIXlt(Sys.time())) -

as.numeric(file.info(fileName)$ctime)) / 60

}

Now, define the main function to load a specific dataset (e.g. number of confirmed cases by day and country).

loadData = function(fileName, columnName) {

if(!file.exists(fileName) ||

minutesSinceLastUpdate(fileName) > 10) {

data = read.csv(file.path(baseURL, fileName),

check.names=FALSE, stringsAsFactors=FALSE) %>%

select(-Lat, -Long) %>%

pivot_longer(-(1:2), names_to="date", values_to=columnName)%>%

mutate(

date=as.Date(date, format="%m/%d/%y"),

`Province/State`=

if_else(`Province/State` == "", "<all>", `Province/State`)

)

save(data, file=fileName)

} else {

load(file=fileName)

}

return(data)

}

The function first checks if the data is already available, and not older than 10 minutes. If this is not the case, read.csv downloads the file from the web. Geo-coordinates are omitted, then pivot_longer (from package tidyr) converts the data.frame from the wide to the long format. The method mutate parses the dates and inserts the text <all> if no region is available (true for countries other than US, Canada, China and Australia).

This next block is called immediately on app start. As explained above, it loads the data from the web or cache:

allData =

loadData(

"time_series_covid19_confirmed_global.csv", "CumConfirmed") %>%

inner_join(loadData(

"time_series_covid19_deaths_global.csv", "CumDeaths")) %>%

inner_join(loadData(

"time_series_covid19_recovered_global.csv","CumRecovered"))

The code block loads Confirmed Cases, Recovered Cases and Deaths, and joins these 3 data sets to get one big data set.

Update [2020-Mar-27]: the file names were changed to reflect the new data structure of the Johns Hopkins University (see Link).

Add the server logic

The last section covered static code on the server. Now the main part starts, i.e. the reactive server code. All the code which is added in this section resides within the brackets of the server function:

server = function(input, output, session) {

## ...

}

Data

First let us add a reactive function that returns a subset of the overall data, i.e. only a specified country and region. Whenever the user changes the selection, the function is called and the filters update:

data = reactive({

d = allData %>%

filter(`Country/Region` == input$country)

if(input$state != "<all>") {

d = d %>%

filter(`Province/State` == input$state)

} else {

d = d %>%

group_by(date) %>%

summarise_if(is.numeric, sum, na.rm=TRUE)

}

d %>%

mutate(

dateStr = format(date, format="%b %d, %Y"),

NewConfirmed=CumConfirmed - lag(CumConfirmed, default=0),

NewRecovered=CumRecovered - lag(CumRecovered, default=0),

NewDeaths=CumDeaths - lag(CumDeaths, default=0)

)

})

Additionally, the function aggregates the data (using summarise_if) if the user chooses <all> for a country having states (e.g. US). Further, from cumulated statistics, the differences are calculated and saved into new columns, prefixed with “New”.

Dropdown menus

Next the server observes for changes in the country selection. If that happens, the server must update the selectable States. Also, <all> is added as an option in the State dropdown menu:

observeEvent(input$country, {

states = allData %>%

filter(`Country/Region` == input$country) %>%

pull(`Province/State`)

states = c("<all>", sort(unique(states)))

updateSelectInput(session, "state", choices=states,

selected=states[1])

})

The next two lines are not reactive. Instead, they are called at app start. The first line extracts all countries by lexicographical order. The other line selects Italy on the drop-down menu (selectizeInput):

countries = sort(unique(allData$`Country/Region`))



updateSelectInput(session, "country", choices=countries,

selected="Italy")

Plots

For the plots, we use the bar charts of the R package plotly. The following code defines a function to be used for both the plots (New and Cumulated cases). The first part of the function defines the basics of the bar chart (style, axes, legend). The second part loops through the selected metrics (e.g. Recovered, …) and adds the bars. A bit tricky is the composition of the column name using the varPrefix (“New” or “Cum”) and metric.



renderBarPlot = function(varPrefix, legendPrefix, yaxisTitle) {

renderPlotly({

data = data()

plt = data %>%

plot_ly() %>%

config(displayModeBar=FALSE) %>%

layout(

barmode='group',

xaxis=list(

title="", tickangle=-90, type='category',

ticktext=as.list(data$dateStr),

tickvals=as.list(data$date)),

yaxis=list(title=yaxisTitle),

legend=list(x=0.1, y=0.9,bgcolor='rgba(240,240,240,0.5)'),

font=f1

)

for(metric in input$metrics)

plt = plt %>%

add_trace(

x=~date, y=data[[paste0(varPrefix, metric)]],type='bar',

name=paste(legendPrefix, metric, "Cases"),

marker=list(

color=switch(metric,

Deaths='rgb(200,30,30)',

Recovered='rgb(30,200,30)',

Confirmed='rgb(100,140,240)'),

line=list(color='rgb(8,48,107)', width=1.0)

)

)

plt

})

}

The last lines of code use the previously defined function. The two plots are assigned to the UI elements by:

output$dailyMetrics = renderBarPlot(

"New", legendPrefix="New", yaxisTitle="New Cases per Day") output$cumulatedMetrics = renderBarPlot(

"Cum", legendPrefix="Cumulated", yaxisTitle="Cumulated Cases")

Voilà!

That is all, you can run the code by pressing “Run App”, the button on the top-right of the editor window.

GITHUB code

All the code above can be found on GitHub. After all, it’s only two files, plus the project file:

https://github.com/ploner/coronavirus-r

[optional] Upload the application to the cloud

The easiest way to publish the Shiny application to a web server is to use shinyapps. After creating an account on shinyapps, go to RStudio and click on the publish icon on the upper-right corner, then on “Publish Application…”:

Connect with your account, then select the files you need (only ui.R and server.R!):

That’s it! Your application is running on the webserver, and you can access it through the internet.