Readings

Required:


Additional resources:


Announcements

  • Assignment 4 due this Friday


Today’s learning objectives

The goal for today’s class is to return to ggplot to get more practice with using this package for data visualization, cover a few additional aspects of its functionality, and integrate the dplyr data wrangling skills we learned last week to further customize our plots.

By the end of this class, you should be able to:

  • Use different geometric objects and aesthetics to explore various plot types in ggplot
  • Chain together steps for data wrangling (dplyr) and plotting (ggplot)

If we have time, we will wrap up with a brief discussion of good coding practices.


Acknowledgements: Today’s lecture borrows from several excellent resources including the R for Excel users course by Julia Stewart Lowndes and Allison Horst and Chapter 3 of Grolemund and Wickham’s R for Data Science.



Recap on the Grammar of Graphics implemented in ggplot

This overview is borrowed from the STAT545 course at UBC


You can think of the grammar of graphics as a systematic approach for describing the components of a graph. It has seven components (the ones in bold are required to be specifed explicitly in ggplot2):

  • Data
    • Exactly as it sounds: the data that you’re feeding into a plot.
  • Aesthetic mappings
    • This is a specification of how you will connect variables (columns) from your data to a visual dimension. These visual dimensions are called “aesthetics”, and can be (for example) horizontal positioning, vertical positioning, size, color, shape, etc.
  • Geometric objects
    • This is a specification of what object will actually be drawn on the plot. This could be a point, a line, a bar, etc.
  • Scales
    • This is a specification of how a variable is mapped to its aesthetic. Will it be mapped linearly? On a log scale? Something else?
  • Statistical transformations
    • This is a specification of whether and how the data are combined/transformed before being plotted. For example, in a bar chart, data are transformed into their frequencies; in a box-plot, data are transformed to a five-number summary.
  • Coordinate system
    • This is a specification of how the position aesthetics (x and y) are depicted on the plot. For example, rectangular/cartesian, or polar coordinates.
  • Facet
    • This is a specification of data variables that partition the data into smaller “sub plots”, or panels.

These components are like parameters of statistical graphics, defining the “space” of statistical graphics. In theory, there is a one-to-one mapping between a plot and its grammar components, making this a useful way to specify graphics.


Getting started

We’ll return to exploring the Coronavirus dataset. I’ll try to focus on typical beginner’s errors along the way so we can get used to trouble-shooting together.


First, let’s load our packages and read in our Coronavirus dataset. You can choose to either work in an R-script or a Quarto document today.

suppressPackageStartupMessages(library(tidyverse))
suppressPackageStartupMessages(library(knitr))

coronavirus <- read_csv('https://raw.githubusercontent.com/RamiKrispin/coronavirus/master/csv/coronavirus.csv')



Combining dplyr and ggplot

Let’s start by calculating the total number of reported cases of each type across the entire dataset. Take a minute to try this for yourself, then you can look at our approach.


click to see our approach
total_cases <- coronavirus |> 
  group_by(type) |>
  summarize(cases = sum(cases))

kable(total_cases)  # kable() just provides a nice output for the table
type cases
confirmed 662136356
death 7507951
recovery 0


Now, let’s plot the history of the daily counts of new confirmed cases worldwide

# We first have to summarize the data, then plot those summary statistics
# Note that we can pipe dplyr output directly into ggplot
coronavirus |>  
  filter(type == "confirmed") |> 
  group_by(date) |> 
  summarize(total_cases = sum(cases)) |> 
  ggplot(mapping = aes(x = date, y = total_cases)) +
  geom_line()


If we want to play around with different geoms, we can store dplyr data processing steps and initiation of the ggplot as object gg_base so that we don’t need to retype it each time

gg_base <- coronavirus |>  
  filter(type == "confirmed") |> 
  group_by(date) |> 
  summarize(total_cases = sum(cases)) |> 
  ggplot(mapping = aes(x = date, y = total_cases)) 

Then when we want to draw the plot, we can just call that object and specify the geom

gg_base +
  geom_line()



Try these

gg_base +
  geom_point()

gg_base +
  geom_col(color = "red")

gg_base +
  geom_area()



Customizing plots

First, a quick reminder on how we can customize some aesthetics (e.g. colors, styles, axis labels, etc.) of our graphs based on non-variable values.

We can change the aesthetics of elements in a ggplot graph by adding arguments within the layer where that element is created. Some common arguments we’ll use first are:

  • color =: update point or line colors
  • fill =: update fill color for objects with areas
  • linetype =: update the line type (dashed, long dash, etc.)
  • linewidth =: update the the width of lines
  • shape =: update the point style
  • size =: update the size of points
  • alpha =: update element opacity (1 = opaque, 0 = transparent)

There are many overviews of the different arguments to linetype, shape etc on the internet, e.g. here.


Building on our first line graph, let’s update the line color to “purple” and make the line type “dashed”:

gg_base +
  geom_line(
    color = "purple",
    linetype = "dashed"
  )

How do we know which color names ggplot will recognize? If you google “R colors ggplot2” you’ll find a lot of good resources. Here’s one: SAPE ggplot2 colors quick reference guide

Now let’s update the point, style and size of points on our previous scatterplot graph using color =, size =, alpha =, and shape = (see ?pch for the different point styles, which can be further customized).

gg_base + 
  geom_point(color = "purple",
             shape = 17,
             size = 4,
             alpha = 0.5)



Mapping variables onto aesthetics

In the examples above, we have customized aesthetics based on constants that we input as arguments (e.g., the color / style / size isn’t changing based on a variable characteristic or value). Often, however, we do want the aesthetics of a graph to depend on a variable. To do that (as we’ve discussed earlier), we’ll map variables onto graph aesthetics, meaning we’ll change how an element on the graph looks based on a variable characteristic (usually, character or value).

When we want to customize a graph element based on a variable’s characteristic or value, add the argument within aes() in the appropriate geom_*() layer. In short, if updating aesthetics based on a variable, make sure to put that argument inside of aes().

Example: Create a ggplot scatterplot graph where the size and color of the points change based on the number of cases, and make all points the same level of opacity (alpha = 0.5). Notice the aes() around the size = and color = arguments.

Note: this is just for illustration of the functionality only - we are showing the same information in redundant ways, which is typically not helpful or necessary. Avoid excessive / overcomplicated aesthetic mapping in data visualization.

gg_base + 
  geom_point(
    aes(size = total_cases,
        color = total_cases),
    alpha = 0.5
  )

In the example above, notice that the two arguments that do depend on variables are within aes(), but since alpha = 0.5 doesn’t depend on a variable then it is outside the aes() but still within the geom_point() layer.

When we map a variable to an aesthetic, ggplot2 uses a scale to translate values of that variable into visual properties such as colors, sizes, or shapes. It also automatically creates a legend that shows how values are represented.



Changing the color scale

When we map a variable to color inside aes(), ggplot2 automatically chooses a color scale for us. We can change the colors that are used by adding a scale_color_*() function from the scales package to our plot.

For this section, we will use functions from the scales and RColorBrewer packages. Before continuing, install these packages if you don’t already have them installed:

install.packages("scales")
install.packages("RColorBrewer")

You only need to install a package once on your computer.

The important distinction is:

  • aes(color = class) determines what the colors represent
  • scale_color_*() determines what those colors look like

The type of color scale we use depends on whether the variable mapped to color is categorical or continuous.


Color palettes for categorical variables

For a categorical variable, we can choose from a number of predefined palettes. One option is to use the ColorBrewer palettes:

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = class)) +
  geom_point() +
  scale_color_brewer(palette = "Set2")

You can display all of the available ColorBrewer palettes by running:

RColorBrewer::display.brewer.all()

Here we use RColorBrewer:: to tell R explicitly that the display.brewer.all() function comes from the RColorBrewer package.


Choosing colors manually

We can also specify exactly which colors we want using scale_color_manual():

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = class)) +
  geom_point() +
  scale_color_manual(values = c("steelblue", "tomato", "darkgreen",
                                "goldenrod", "purple", "skyblue", "pink"))

R recognizes many named colors, such as "steelblue", "tomato", and "goldenrod". The function colors() gives you a list of all the named colors available in R.

The scales package that we installed above provides a useful function for viewing these colors:

scales::show_col(colors())

# You can adjust the size of font with cex_label
scales::show_col(colors(), cex_label = 0.3)

This displays the colors visually, which makes it much easier to choose colors than looking through the names returned by colors().


Color palettes for continuous variables

If the variable mapped to color is continuous rather than categorical, we need a continuous color scale.

One option is to use one of the built-in viridis color scales:

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = cty)) +
  geom_point() +
  scale_color_viridis_c()

The _c at the end of scale_color_viridis_c() indicates that this is a continuous color scale.

We can also create our own continuous color gradient by specifying the colors at the low and high ends of the scale:

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = cty)) +
  geom_point() +
  scale_color_gradient(low = "lightblue", high = "darkblue")

Here, observations with lower values of cty are shown in light blue, observations with higher values are shown in dark blue, and ggplot2 creates the intermediate colors between them.

We can use any R color names or hexadecimal color codes for low and high. For example:

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = cty)) +
  geom_point() +
  scale_color_gradient(low = "#FDE725", high = "#440154")

If there is a meaningful midpoint in the data, we can instead create a three-color gradient using scale_color_gradient2():

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = cty)) +
  geom_point() +
  scale_color_gradient2(
    low = "blue",
    mid = "white",
    high = "red",
    midpoint = 20
  )

This type of diverging color scale is particularly useful when values above and below a meaningful reference point should be visually distinguished, such as positive versus negative change.

When choosing a color scale, remember that colors are not simply decorative. The palette should help communicate the structure of the variable being represented.

The same principles apply when you map a variable to fill instead of color. In that case, use the corresponding scale_fill_*() functions, such as scale_fill_brewer(), scale_fill_manual(), or scale_fill_viridis_c().



ggplot2 complete themes

While every element of a ggplot graph is manually customizable, there are also built-in themes (theme_*()) that you can add to your ggplot code to make some major headway before making smaller tweaks manually.

We talked about this briefly a few classes ago, but let’s explore a little further. Here are a few to try today (but also notice all the options that appear as we start typing theme_ into our ggplot graph code!):

  • theme_light()
  • theme_minimal()
  • theme_bw()

Also, check out more examples by scrolling down here. Pick one that you like and update your previous plot.


Here, let’s update our previous graph with theme_minimal():

gg_base + 
  geom_point(
    aes(size = total_cases,
        color = total_cases),
    alpha = 0.5
  ) +
  theme_minimal()


You can play around with other themes - see an overview and instructions for how to customize themes here and here.


We could for example color the background of the legend

gg_base + 
  geom_point(
    aes(size = total_cases,
        color = total_cases),
    alpha = 0.5) +
  theme_minimal() +
  theme(legend.background = element_rect(
    fill = "lemonchiffon", 
    color = "grey50", 
    linewidth = 1
  ))


Or we can remove the legend

gg_base + 
  geom_point(
    aes(size = total_cases,
        color = total_cases),
    alpha = 0.5) +
  theme_minimal() +
  theme(legend.position = "none")


In addition to the themes built into ggplot, there are lots packages that implement additional fun themes, see some examples here



Updating axis labels and titles

Use labs() to update axis labels, and add a title and/or subtitle to your ggplot graph.

gg_base +
  geom_line(linetype = "dotted") +
  theme_bw() +
  labs(
    x = "Date",
    y = "Total confirmed cases",
    title = str_c("Daily counts of new Coronavirus cases recorded per ", max(coronavirus$date)),
    subtitle = "Global sums")


Note: If you want to change the formatting of axis values (for example, to display large numbers with commas rather than scientific notation), you can use formatting functions from the scales package and add + scale_y_continuous(labels = scales::label_comma()) (see more examples in the ggplot2 documentation on axes and label formatting).



Now, let’s split the case counts out by country


Your turn

Take a minute to think about how you would generate a plot with a separate line showing the daily reports of new confirmed cases in each country.


Here is some code we might try. Why does that not work?

coronavirus |>  
  filter(type == "confirmed") |> 
  group_by(date) |> 
  summarize(total_cases = sum(cases)) |> 
  ggplot() +
  geom_line(mapping = aes(x = date, y = total_cases, color = country))
# We have summarized out the country details (only one total count per day)


We’ll have to group by both country and date. But why does this not work?

coronavirus |>  
  filter(type == "confirmed") |> 
  group_by(country, date) |> 
  summarize(total_cases = sum(cases)) |> 
  ggplot(mapping = aes(x = date, y = total_cases)) +
  geom_line()
# Even though we have grouped the dataframe by country, that dplyr grouping does not get carried into ggplot because we're not mapping country to an aesthetic


Now let’s make ggplot group the data too by mapping country onto an aesthetic

coronavirus |>  
  filter(type == "confirmed") |> 
  group_by(country, date) |> 
  summarize(total_cases = sum(cases)) |> 
  ggplot(mapping = aes(x = date, y = total_cases, color = country)) +
  geom_line()


It looks like this is doing what we want, but it does not display well. There are too many countries! We could play around with the layout parameters to be able to see this plot. But let’s instead subset to only show the 5 countries with the highest total counts of confirmed cases.

top5_countries <- coronavirus |> 
  filter(type == "confirmed") |>
  group_by(country) |>
  summarize(total_cases = sum(cases)) |>
  arrange(desc(total_cases)) |> 
  head(5) |> 
  pull(country)


Now let’s try to plot the daily counts of new cases just for those countries

coronavirus |>  
  filter(type == "confirmed", country %in% top5_countries) |> 
  group_by(country, date) |> 
  summarize(total_cases = sum(cases)) |>  # Need this summarize because some countries have data broken down by Province.State
  ggplot(mapping = aes(x = date, y = total_cases, color = country)) +
  geom_line()


Much better! But it looks messy because there are large large negative values reported for certain days. Why could that be? In a real data analysis, we would want to account for that, but for display purposes, let’s just remove those rows from our dataset for now.

coronavirus |>  
  filter(type == "confirmed", country %in% top5_countries, cases >= 0) |> 
  group_by(country, date) |> 
  summarize(total_cases = sum(cases)) |>  # Need this summarize because some countries have data broken down by Province.State
  ggplot(mapping = aes(x = date, y = total_cases, color = country)) +
  geom_line()


We can also make a separate panel for each country

coronavirus |>  
  filter(type == "confirmed", country %in% top5_countries, cases >= 0) |> 
  group_by(country, date) |> 
  summarize(total_cases = sum(cases)) |> 
  ggplot(mapping = aes(x = date, y = total_cases)) +
  geom_line() +
  facet_wrap(~country)


Now let’s plot the cumulative sum of cases for each of those countries instead

coronavirus |>  
  filter(type == "confirmed", country %in% top5_countries, cases >= 0) |>
  group_by(country) |> 
  arrange(date) |> 
  mutate(cum_count = cumsum(cases)) |> 
  ggplot(mapping = aes(x = date, y = cum_count, color = country)) +
  geom_line()



Bar charts

Another common plot type are bar charts. There are two types of bar charts in ggplot: geom_bar() and geom_col(). geom_bar() makes the height of the bar proportional to the number of records in each group (or if the weight aesthetic is supplied, the sum of the weights). If you want the heights of the bars to represent values in the data, use geom_col() instead.

Since our dataset reports counts of cases, let’s first start with geom_col() Let’s compare the total number of cases in each of the top 5 countries with the highest total counts

coronavirus |> 
  filter(type == "confirmed", country %in% top5_countries, cases >= 0) |>
  group_by(country) |>
  summarize(cases = sum(cases)) |>
  ggplot() +
  geom_col(aes(x = country, y = cases), color = "black")


Next, let’s make a stacked barplot that also shows the total number of deaths in each country. First we’ll need to compute the total counts for each type of case, then we can use the fill aesthetic to create the stacked bars

coronavirus |> 
  filter(type == "confirmed" | type == "death", country %in% top5_countries, cases >= 0) |>
  group_by(country, type) |>
  summarize(cases = sum(cases)) |>
  ggplot() +
  geom_col(aes(x = country, y = cases, fill = type))


We may want to flip this around

coronavirus |> 
  filter(type == "confirmed" | type == "death", country %in% top5_countries, cases >= 0) |>
  group_by(country, type) |>
  summarize(cases = sum(cases)) |>
  ggplot() +
  geom_col(aes(x = country, y = cases, fill = type)) +
  coord_flip()

This is useful because it puts the proportions in relation to the total daily counts. But it can be hard to compare proportions. We can make all bars the same height with ‘position adjustment’

coronavirus |> 
  filter(type == "confirmed" | type == "death", country %in% top5_countries, cases >= 0) |>
  group_by(country, type) |>
  summarize(cases = sum(cases)) |>
  ggplot() +
  geom_col(aes(x = country, y = cases, fill = type), position = "fill") +
  coord_flip()

While looking at the proportions of case types is helpful for comparing patterns, it doesn’t allow for comparison of the magnitude of case counts. We can do this better by scaling the bars by the raw case counts (not mapping a variable to the fill aesthetic), as we did above.


We can also get the bars for the different types of cases each day stacked next to each other with another position adjustment option

coronavirus |> 
  filter(type == "confirmed" | type == "death", country %in% top5_countries, cases >= 0) |>
  group_by(country, type) |>
  summarize(cases = sum(cases)) |>
  ggplot() +
  geom_col(aes(x = country, y = cases, fill = type), position = "dodge")



Now, let’s explore a different question. Let’s compare for how many days each of the top five countries have had > 100,000 new confirmed cases. For this we will need to count rows within grouped variables, so we’ll use the geom_bar()

coronavirus |> 
  filter(type == "confirmed", country %in% top5_countries, cases >= 0) |>
  group_by(date, country) |> 
  summarize(cases = sum(cases)) |> 
  filter(cases >100000) |> 
  ggplot() +
  geom_bar(aes(country))


Alternatively, we could also pre-compute these counts and plot them with geom_col() like above, but sometimes it’s convenient to have ggplot compute the counts of observations directly with geom_bar()

coronavirus |> 
  filter(type == "confirmed", country %in% top5_countries, cases >= 0) |>
  group_by(date, country) |> 
  summarize(cases = sum(cases)) |> 
  filter(cases >100000) |> 
  # note here that we need to change the grouping to now just be by country
  group_by(country) |>   
  summarize(count = n()) |> 
  ggplot() +
  geom_col(aes(country, y = count))



Plotting with labels

To explore different types of x-y scatterplots, let’s return to the vaccination data we also looked at during the last lecture. Let’s first load it back in:

vacc <- read_csv("https://raw.githubusercontent.com/RamiKrispin/coronavirus/main/csv/covid19_vaccine.csv")


Remember, this dataset shows cumulate counts, so let’s grab the counts from the most recent date included in the dataset 2023-03-09. We’ll also remove rows with missing data for variables we need (this is typically not necessary as those lines will automatically be excluded from plots, but we do it here because some of those rows have extreme values for other variables)

# create a "total to date" subset of the data
vacc_ttd <- vacc |> 
  filter(date == max(date), !is.na(people_at_least_one_dose), !is.na(population))

# Let's look at our new tibble so make sure it makes sense
vacc_ttd
## # A tibble: 191 × 15
##    date       country_region      continent_name continent_code combined_key    
##    <date>     <chr>               <chr>          <chr>          <chr>           
##  1 2023-03-09 Afghanistan         Asia           AS             Afghanistan     
##  2 2023-03-09 Albania             Europe         EU             Albania         
##  3 2023-03-09 Algeria             Africa         AF             Algeria         
##  4 2023-03-09 Andorra             Europe         EU             Andorra         
##  5 2023-03-09 Angola              Africa         AF             Angola          
##  6 2023-03-09 Antigua and Barbuda North America  <NA>           Antigua and Bar…
##  7 2023-03-09 Argentina           South America  SA             Argentina       
##  8 2023-03-09 Armenia             Asia           AS             Armenia         
##  9 2023-03-09 Australia           Oceania        OC             Australia       
## 10 2023-03-09 Austria             Europe         EU             Austria         
## # ℹ 181 more rows
## # ℹ 10 more variables: doses_admin <dbl>, people_at_least_one_dose <dbl>,
## #   population <dbl>, uid <dbl>, iso2 <chr>, iso3 <chr>, code3 <dbl>,
## #   fips <lgl>, lat <dbl>, long <dbl>


We can start by just making a simple plot of the number of fully vaccinated people vs. the total population size in different countries

vacc_ttd |> 
  ggplot() +
  geom_point(mapping = aes(population, people_at_least_one_dose))


That’s nice, but it would be useful to know which country is represented by each dot. geom_label() is our tool for that.

vacc_ttd |> 
  ggplot() +
  geom_label(mapping = aes(population, people_at_least_one_dose, label = country_region))


Let’s do a few things to make this easier to read

We can remove countries with a small population sizes, and log transform the population sizes

vacc_ttd |> 
  filter(population > 50000000) |> 
  ggplot() +
  geom_label(mapping = aes(log(population), people_at_least_one_dose, label = country_region))


Or, we can zoom in on the part of the plot where almost all countries other than China and India are clustered together. We can do this in a few different ways. One is to just filter out rows with large population size (e.g. countries with population sizes over 500 million).

vacc_ttd |> 
  filter(population < 5*10^8) |> 
  ggplot() +
  geom_label(mapping = aes(population, people_at_least_one_dose, label = country_region))


Or we can just zoom in by adjusting what parts of the axes to display

vacc_ttd |> 
  ggplot() +
  geom_label(mapping = aes(population, people_at_least_one_dose, label = country_region)) +
  coord_cartesian(xlim = c(0,4*10^8), ylim = c(0, 3*10^8))


There are lots of other ways we can adjust label plotting. See e.g. here or a more comprehensive overview in the book chapter 8 of the book “ggplot2: Elegant Graphics for Data Analysis”



Bonus: Spatial plotting

Map of newly reported cases

With some additional packages, we can also plot geographical patterns on a map. We can, for example, show which countries have new counts of >5000 new confirmed cases on the most recent day from this dataset and scale the points with case counts.

library("rnaturalearth") # install.packages("rnaturalearth")
library("rnaturalearthdata") # install.packages("rnaturalearthdata")
library("rgeos") #install.packages("rgeos")
world <- ne_countries(scale = "medium", returnclass = "sf")
filter(coronavirus, date == max(coronavirus$date), type == "confirmed", cases > 5000) |>
  ggplot() +
  geom_sf(data = world) +
  geom_point(aes(x=long, y=lat, size=cases), color="red", fill="red", alpha=0.5, shape=21) 



Exercises

Let’s put together the data wrangling and visualization skills we have practiced in this lesson. For each exercise, try to write the code yourself before looking back at the examples above.

2. Change the color scale

Take the plot you created above and change the colors used to distinguish the three countries.

Try one of the following:

  • use a ColorBrewer palette with scale_color_brewer()
  • specify your own three colors with scale_color_manual()

What part of your code determines what the colors represent, and what part determines which colors are displayed?

3. One plot or multiple panels?

Take your plot from Exercise 1 and make a second version in which each country is displayed in its own panel using facet_wrap().

Compare the two versions.

Which plot makes it easier to compare the countries at the same point in time? Which makes it easier to see the pattern within each country?

4. Cases and deaths

Choose five countries and calculate the total number of reported confirmed cases and deaths for each country across the dataset.

Make a bar plot showing these values.

Experiment with at least two of these options:

  • stacked bars
  • side-by-side bars using position = "dodge"
  • proportional bars using position = "fill"
  • horizontal bars using coord_flip()

What different information is emphasized by the different versions of the plot?

5. Make the plot communicate better

Choose one of the plots you created above and improve its presentation.

Make at least four changes. For example, you could:

  • change the colors
  • change the theme
  • change the axis labels
  • add a title or subtitle
  • change the axis number formatting
  • change the legend position
  • change line width, point size, or transparency
  • facet the plot

There is no single correct answer. Your goal is to make deliberate choices that make the pattern you want to communicate easier to see.

6. Your turn

Now come up with one question of your own that can be explored using the Coronavirus dataset.

Before writing any code, write down:

  1. What question are you trying to answer?
  2. Which variables do you need?
  3. What data wrangling will you need to do before plotting?
  4. What type of plot would communicate the answer effectively?

Then create your plot.



Some example plots:


Trend in cumulative case count

## linear scale
group_by(coronavirus, date, type) |>
  summarize(cases = sum(cases)) |> 
  group_by(type) |>
  mutate(cases=cumsum(cases)) |>
  ggplot() +
  geom_line(aes(x=date, y=cases, color=type))

## log scale
group_by(coronavirus, date, type) |>
  summarize(cases = sum(cases)) |> 
  group_by(type) |>
  mutate(cases=cumsum(cases)) |>
  ggplot() +
  geom_line(aes(x=date, y=log(cases), color=type))
## Warning in log(cases): NaNs produced


Trend in cumulative case count in the 5 most infected countries

## linear scale
filter(coronavirus, country %in% top5_countries) |>
  group_by(country, date, type) |>
  summarise(cases = sum(cases)) |> 
  group_by(country, type) |>
  mutate(cases=cumsum(cases)) |>
  ggplot() +
  geom_line(aes(x=date, y=cases/1000, color=type)) +
  ylab("cumulative count (in thousand)") +
  facet_wrap(~country, scales = "free_y")


Trend in cumulative death rate

group_by(coronavirus, date, type) |>
  summarise(cases = sum(cases)) |> 
  group_by(type) |>
  mutate(cases=cumsum(cases)) |>
  pivot_wider(names_from = type, values_from = cases) |>
  mutate(death_rate = death/confirmed) |>
  ggplot(aes(x=date, y=death_rate)) +
  geom_line()


Trend in cumulative death rate in 5 most infected countries

filter(coronavirus, country %in% top5_countries) |>
  group_by(country, date, type) |>
  summarise(cases = sum(cases)) |> 
  group_by(country, type) |>
  mutate(cases=cumsum(cases)) |>
  pivot_wider(names_from = type, values_from = cases) |>
  mutate(death_rate = death/confirmed) |>
  ggplot(aes(x=date, y=death_rate)) +
  geom_line() +
  facet_wrap(~country, scales = "free_y")
## Warning: Removed 50 rows containing missing values or values outside the scale range
## (`geom_line()`).


Countries that had their first reported coronavirus case in January

filter(coronavirus, type=="confirmed", cases>0) |>
  group_by(country, date) |>
  summarise() |>
  group_by(country) |>
  filter(date==min(date)) |>
  ungroup() |>
  arrange(date) |>
  mutate(rank=row_number(date)) |>
  filter(months(date)=="January", lubridate::year(date)==2020) |>
  ggplot(aes(x=date, y=rank)) +
  geom_label(aes(label=country)) +
  theme_minimal()