This workshop will build upon “Introduction to R for Data Analysis”,
and assume you have a basic understanding of R, reading in data, and
subsetting data. We will use the dplyr package for data
manipulation and the ggplot2 package to create customised
visualisation.
This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see http://rmarkdown.rstudio.com.
When you click the Knit button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. Embed R code chunk can be run by pressing the green triangle in the top right of the code chunk. You can also run all code chunks in the document by clicking the Run button in the toolbar above.
Set your working directory to the folder where the data and this file
is located. This can be done in RStudio by Session > Set Working
Directory > To Source File Location. Alternatively, you can use the
setwd() function in R.
Your folder should contain: -
childhood_24month_immunisation_2015_2026.csv - the data we
will use in this workshop - resbaz_ggplot_2026.Rmd - this R
Markdown file
For data analysis and exploration. For yourself or the team
For data communication and dissemination. For an audience
For art. For yourself and an audience (https://www.data-to-art.com/)
The New Zealand government has set a target of 95% immunisation coverage for 24-month-old children by 2030. The data we will explore is from the Health New Zealand - Te Whatu Ora, covering the years 2015 to 2026. https://www.healthnz.govt.nz/about-us/health-data/data-sets-and-collections/immunisation-data-and-statistics/immunisation-coverage
“Health NZ is working towards a goal of 95% of tamariki aged 24-months to be fully immunised by 2030, and an interim target of 84% by June 2025.”
Select a health question to answer:
The “GG” in ggplot2 stands for “Grammar of Graphics”. It is a
powerful and flexible package for creating graphics in R. The package
allows you to build plots layer by layer, making it easy to customise
and enhance your visualisations. The ggplot2 package is
part of the tidyverse package, which also includes dplyr,
tidyr, readr, and other packages for data
manipulation and visualisation.
# start a line with a hash (#) for a comment inside a code chunk
# if you don't already have tidyverse, run the line of code below by
# removing the hash then run by pressing the green triangle in the top right of the code chunk
# install.packages("tidyverse")
library(tidyverse)
## Warning: package 'ggplot2' was built under R version 4.3.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.2 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# option 2,
# if you don't already have tidyverse and tidyverse is taking too long to download
# we will only require these four packages ggplot2, dplyr, tidyr, readr
# install.packages(c("ggplot2", "dplyr", "tidyr", "readr"))
#
# library(ggplot2)
# library(dplyr)
# library(tidyr)
# library(readr)
If the data is not found, please check the data in in the same folder as this Rmd file, and check that the working directory is set correctly. Instructions above.
# Load the data
child_imm_raw <- read_csv("./childhood_24month_immunisation_2015_2026.csv")
## Rows: 1260 Columns: 6
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (3): district_health_boards, region, ethnicity
## dbl (3): year, num_eligible, fully_immunised_for_age
##
## ℹ 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.
# show the first 6 rows of data
print(head(child_imm_raw))
## # A tibble: 6 × 6
## year district_health_boards region ethnicity num_eligible
## <dbl> <chr> <chr> <chr> <dbl>
## 1 2015 Auckland Northern Total 6003
## 2 2015 Auckland Northern Maori 737
## 3 2015 Auckland Northern Pacific 1087
## 4 2015 Auckland Northern Asian 1793
## 5 2015 Auckland Northern European or Other 2386
## 6 2015 Bay of Plenty Te Manawa Taki Total 3002
## # ℹ 1 more variable: fully_immunised_for_age <dbl>
# shape of the data (number of rows and columns)
# should be 1260 rows and 6 columns
print(dim(child_imm_raw))
## [1] 1260 6
# print unique district health boards
# should be 21, i.e. 20 districts and "National"
print(child_imm_raw |> select (district_health_boards) |> unique())
## # A tibble: 21 × 1
## district_health_boards
## <chr>
## 1 Auckland
## 2 Bay of Plenty
## 3 Canterbury
## 4 Capital and Coast
## 5 Counties Manukau
## 6 Hawkes Bay
## 7 Hutt Valley
## 8 Lakes
## 9 MidCentral
## 10 National
## # ℹ 11 more rows
Let’s demonstrate how to build up a plot in ggplot2. We will plot the immunisation coverage of 24-month-old children in each district health board in 2020.
perc_fully_immunised_for_age
which is the percentage of children fully immunised for their age group.
This is done by dividing the number of fully immunised children
(fully_immunised_for_age) by the number of eligible
children (num_eligible)Fill in the blanks to select the desired data, there should be 20 rows of data and 7 columns in the resulting data frame.
# fill in the blanks below
# create the percentage column here, we can reuse child_imm later
child_imm <- child_imm_raw |>
mutate(perc_fully_immunised_for_age = fully_immunised_for_age / num_eligible)
# create a new data frame child_imm_2020_dhb for this plot
child_imm_2020_dhb <- child_imm |>
filter(ethnicity == "Total", year == 2020, district_health_boards != "National")
# this should have 20 rows and 7 columns
print(child_imm_2020_dhb)
## # A tibble: 20 × 7
## year district_health_boards region ethnicity num_eligible
## <dbl> <chr> <chr> <chr> <dbl>
## 1 2020 Auckland Northern Total 5249
## 2 2020 Bay of Plenty Te Manawa Taki Total 3179
## 3 2020 Canterbury Te Waipounamu Total 6416
## 4 2020 Capital and Coast Central Total 3242
## 5 2020 Counties Manukau Northern Total 8313
## 6 2020 Hawkes Bay Central Total 2154
## 7 2020 Hutt Valley Central Total 2035
## 8 2020 Lakes Te Manawa Taki Total 1590
## 9 2020 MidCentral Central Total 2243
## 10 2020 Nelson Marlborough Te Waipounamu Total 1586
## 11 2020 Northland Northern Total 2365
## 12 2020 South Canterbury Te Waipounamu Total 625
## 13 2020 Southern Te Waipounamu Total 3383
## 14 2020 Tairawhiti Te Manawa Taki Total 724
## 15 2020 Taranaki Te Manawa Taki Total 1682
## 16 2020 Waikato Te Manawa Taki Total 5620
## 17 2020 Wairarapa Central Total 535
## 18 2020 Waitemata Northern Total 7655
## 19 2020 West Coast Te Waipounamu Total 334
## 20 2020 Whanganui Central Total 841
## # ℹ 2 more variables: fully_immunised_for_age <dbl>,
## # perc_fully_immunised_for_age <dbl>
ggplot2 uses a layered approach to building plots. See: https://ggplot2.tidyverse.org/articles/ggplot2.html
The basic structure of a ggplot2 plot is:
# ggplot(<data>,
# aes(<mapping>)
# ) +
# <geom_function>() +
# <theme_function>()
Notice how layers are added with the + operator. The
aes() function is used to map variables in the data to
aesthetics of the plot, such as x and y axes, colour, size, etc. The
geom_function() is used to specify the type of plot (e.g.,
geom_bar(), geom_line(),
geom_point(), etc.). The theme_function() is
used to customise the appearance of the plot.
ggplot(child_imm_2020_dhb,
aes(
x = perc_fully_immunised_for_age,
y = fct_reorder(district_health_boards, num_eligible),
fill = -num_eligible,
color = region
)) +
geom_col() +
coord_cartesian(xlim = c(0.75,1)) +
labs(
title = "2020 immunisation data",
x = "Coverage (%)",
y = "District"
)
ggplot(child_imm_2020_dhb) # data only
ggplot(child_imm_2020_dhb,
aes(x = district_health_boards,
y = perc_fully_immunised_for_age) # added aesthetics, but no geometry
)
ggplot(child_imm_2020_dhb,
aes(x = district_health_boards,
y = perc_fully_immunised_for_age)
) +
geom_col() +# this adds a column plot layer
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Replacing the labels with plain English and adding a title will
improve readability. To avoid the x-axis labels overlapping, we can
rotate the labels by 45 degrees using the theme() function.
We can also set the y-axis to go from 0% to 100%.
ggplot(child_imm_2020_dhb,
aes(x = district_health_boards,
y = perc_fully_immunised_for_age)
) +
geom_col() +
# NEW LINES:
labs(title = "Immunisation Coverage of 24-month-old Children in 2020", # title at the top of the plot
x = "District Health Boards", # x-axis label
y = "Percentage Fully Immunised") + # y-axis label
scale_y_continuous(labels = scales::percent_format()) + # this line formats the y-axis labels as percentages
coord_cartesian(ylim = c(0,1)) + # this line sets the y-axis limits to 0% to 100%
theme(axis.text.x = element_text(angle = 45, hjust = 1)) # this rotates the x-axis labels by 45 degrees
Alternatively, we can place the districts on the y-axis and the immunisation coverage on the x-axis
ggplot(child_imm_2020_dhb,
# NEW LINES: swapped x and y
aes(x = perc_fully_immunised_for_age,
y = district_health_boards)
) +
geom_col() +
# NEW LINES:
labs(title = "Immunisation Coverage of 24-month-old Children in 2020",
x = "Percentage Fully Immunised",
y = "District Health Boards") +
scale_x_continuous(labels = scales::percent_format()) +
coord_cartesian(xlim = c(0,1))
You may also choose to focus on the range 75% to 100%
ggplot(child_imm_2020_dhb,
aes(x = district_health_boards,
y = perc_fully_immunised_for_age)
) +
geom_col() +
labs(title = "Immunisation Coverage of 24-month-old Children in 2020",
x = "District Health Boards",
y = "Percentage Fully Immunised") +
scale_y_continuous(labels = scales::percent_format()) + # this line formats the y-axis labels as percentages
# NEW LINE:
coord_cartesian(ylim = c(0.75,1)) + # this line sets the y-axis limits to 75% to 100%
theme(axis.text.x = element_text(angle = 45, hjust = 1))
We can make the plot more informative by colouring the bars with the
region. We can do this by mapping the region variable to
the fill aesthetic in the aes() function.
ggplot(child_imm_2020_dhb,
aes(x = district_health_boards,
y = perc_fully_immunised_for_age,
# NEW LINE:
fill = region) # mapping the fill aesthetic to region
) +
geom_col() +
labs(title = "Immunisation Coverage of 24-month-old Children in 2020",
x = "District Health Boards",
y = "Percentage Fully Immunised") +
scale_y_continuous(labels = scales::percent_format()) +
coord_cartesian(ylim = c(0.75,1)) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
To bring further clarity, we can order the bars in ascending order of percentage fully immunised
ggplot(child_imm_2020_dhb,
# NEW LINE:
aes(x = fct_reorder(district_health_boards, perc_fully_immunised_for_age),
y = perc_fully_immunised_for_age,
fill = region)
) +
geom_col() +
labs(title = "Immunisation Coverage of 24-month-old Children in 2020",
x = "District Health Boards",
y = "Percentage Fully Immunised") +
scale_y_continuous(labels = scales::percent_format()) +
coord_cartesian(ylim = c(0.75,1)) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Explore data for your chosen question.
geom_point())You may also try other geometries, such as geom_line()
or geom_col(), and use different aesthetics to map the data
to the plot, such as colour, fill,
size, shape, linetype, etc. (just
remember to adjust the dataframe appropriately to include the correct
rows and columns you want to plot).
# fill in the blanks below
# define a new dataframe to use for this plot
child_imm_year <- child_imm |>
filter(
district_health_boards == "National", # filter 1
ethnicity == "Total" # filter 2
)
p <- ggplot(child_imm_year, # data
aes(x = year, # mapping x axis
y = perc_fully_immunised_for_age) # mapping y axis
) +
geom_line() + # geom layer
scale_x_continuous(breaks=2015:2026)
print(p)
library(plotly)
## Warning: package 'plotly' was built under R version 4.3.3
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
ggplotly(p)
Pick out a few data points or patterns to focus your storytelling.
Main “characters” data points are the center of your story, they will experience conflict and resolution, and they may interact with the world or other characters in the story.
Secondary “characters” data points (rivals, antagonist, best friend) that interact with the main characters, they enhance the story and provide context or contrast to the main characters. In a graph, these could be additional data points or variables.
(option 1a) might choose to measure against the government targets 84% (by June 2025), 87% (by June 2026), and 95% (by 2030)
(option 1b) might choose to characterise the year-on-year difference and the improvements since 2024
(option 1c) might choose to include data from each region to show the differences in immunisation coverage
(option 2) might choose to characterise the Tairawhiti region, which has the second lowest immunisation coverage in 2026 but was average in 2015
(option 3) might choose to focus on Maori children, who historically had lower immunisation coverage than non-Maori children.
The message is the main point you want to convey to your audience. It should be clear, concise, and supported by the data. This could be added as text in a title, subtitle, or as an annotation in the plot.
The conflict is the main point of interest in your data. This could be a trend (e.g. general growth), a pattern (e.g. sudden drop), or a specific data point (e.g. anomaly) that stands out. It should be something that captures the attention of the audience and makes them want to learn more.
The resolution is the conclusion or insight, such as additional data that gives more insights to the conflict, or actionable insights.
Choose something to highlight within your data.
In one sentence, summarise the message you want to convey. Make this your title or subtitle.
Enhance and build upon your previous plot to support this message
with your graph. You can use the annotate() function to add
text annotate("text",...) or arrows
annotate("segement",...) to your plot to highlight specific
data points or patterns. Adding additional geometries with colour and
other aesthetics can also help to you focus the attention of the
reader.
# fill in the blanks below
# define a new dataframe to use for this plot
child_imm_year <- child_imm |>
filter(
district_health_boards == "National", # filter 1
ethnicity == "Total" # filter 2
)
ggplot(child_imm_year, # data
aes(x = year, # mapping x axis
y = perc_fully_immunised_for_age) # mapping y axis
) +
geom_line() + # geom layer
annotate("segment",
x = 2018, xend = 2018,
y = 0.9, yend = 0.85,
arrow = arrow()) +
annotate("text",
x = 2024,
y = 0.78,
label = "some notes") +
geom_hline(aes(yintercept = 0.85),
colour="blue"
) +
geom_vline(aes(xintercept = 2024), colour = "green") +
scale_x_continuous(breaks=2015:2026)
# write your answer here
# write your answer here
child_imm_year <- child_imm |>
filter(district_health_boards == "National",
ethnicity == "Total")
perc_2026 = child_imm_year |> filter(year == 2026) |> pull(perc_fully_immunised_for_age)
ggplot(child_imm_year,
aes(x = year, y = perc_fully_immunised_for_age)
) +
geom_point() +
geom_hline(yintercept = 0.95, linetype = "dashed", color = "blue") +
annotate("text",
x = 2024, y = 0.95,
label = "95% target coverage 2030",
colour = "blue",
hjust = 1, vjust = 1.1, size = 4) +
geom_hline(yintercept = 0.84, linetype = "dashed", color = "darkgreen") +
annotate("text",
x = 2020, y = 0.84,
label = "84% target coverage June 2025",
colour = "darkgreen",
hjust = 1, vjust = 1.1, size = 4) +
annotate("text",
x = 2026, y = perc_2026,
label = paste0(round(perc_2026*100,1), "%"),
colour = "black",
hjust = 0.5, vjust = 2, size = 4) +
scale_x_continuous(breaks = seq(2015, 2026, 1)) +
scale_y_continuous(labels = scales::percent_format(), limits = c(0.6,1)) +
labs(title = "Immunisation Coverage of 24-month-old Children in New Zealand",
subtitle = "The national average has almost reached interim target, some districts have met the milestone",
x = "Year",
y = "Percentage Fully Immunised",
caption = "Data: Ministry of Health NZ") +
theme_minimal() +
theme(legend.position = "none", # remove the legend
panel.grid.major.x = element_blank(), # remove x axis grid lines
panel.grid.minor.x = element_blank())
# write your answer here
child_imm_year <- child_imm |>
filter( ethnicity == "Total")
ggplot(child_imm_year,
aes(x = year, y = perc_fully_immunised_for_age, colour = district_health_boards)
) +
geom_line(aes(group = district_health_boards), colour = "grey") +
geom_line(data=child_imm_year|>filter(district_health_boards=="National"),
# aes(x = year, y = perc_fully_immunised_for_age), # this line is not needed as we are using the same aes() as the main plot
linewidth=3) +
annotate("text",
x = 2015, y = 0.65,
label = "National coverage is highlighted in bold",
hjust = 0, vjust = 1, size = 4) +
geom_hline(yintercept = 0.95, linetype = "dashed", color = "grey") +
annotate("text",
x = 2030, y = 0.95,
label = "95% target coverage 2030",
colour = "grey",
hjust = 1, vjust = 1.1, size = 4) +
scale_x_continuous(breaks = seq(2015, 2030, 1), limits = c(2015,2030)) +
scale_y_continuous(labels = scales::percent_format()) +
labs(title = "Immunisation Coverage of 24-month-old Children in New Zealand",
subtitle = "A steady progress towards the national 95% target in most districts since the target set in 2024",
x = "Year",
y = "Percentage Fully Immunised",
caption = "Data: Ministry of Health NZ") +
theme_minimal() +
theme(legend.position = "none", # remove the legend
panel.grid.major.x = element_blank(), # remove x axis grid lines
panel.grid.minor.x = element_blank())
child_imm_year <- child_imm |>
filter(ethnicity == "Total") |>
group_by(year) |>
mutate(rank_desc = min_rank(desc(perc_fully_immunised_for_age))) |>
ungroup() |>
filter(district_health_boards %in% c("National", "Tairawhiti"))
library(gganimate)
ggplot(child_imm_year,
aes(x = year, y = perc_fully_immunised_for_age, colour = district_health_boards)
) +
geom_line() +
scale_x_continuous(breaks = seq(2015, 2026, 1), limits = c(2015,2026.5)) +
scale_y_continuous(labels = scales::percent_format()) +
geom_text(data = child_imm_year |> filter(district_health_boards == "Tairawhiti"),
aes(x = year,
y = perc_fully_immunised_for_age,
label = paste0("Top ", rank_desc)),
hjust = 0, vjust = 0, size = 3) +
labs(title = "Immunisation Coverage of 24-month-old Children in New Zealand",
subtitle = "Tairāwhiti experienced a sharper drop in 2017-2024 than the rest of NZ",
x = "Year",
y = "Percentage Fully Immunised") +
theme(legend.title=element_blank()) +
transition_states(
year,
transition_length = 2,
state_length = 1) +
transition_reveal(year) +
ease_aes('linear')
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?