Donut Chart on Top Causes of DALYs globally

A donut chart, with an inset pie chart on the main causes of DALYs, coloured by different categories, and different causes within each category distinguished by opacity. Labels show percentage contribution and the names of causes.

Our World in Data
Public Health
{donutsk}
Author

Aditya Dahiya

Published

August 1, 2024

The dataset from Our World in Data provides comprehensive information on Disability Adjusted Life Years (DALYs) for various health conditions, including communicable, maternal, neonatal, nutritional diseases, injuries, and non-communicable diseases (NCDs). The data is sourced from the IHME’s Global Burden of Disease (GBD) study, which offers a detailed assessment of global health trends, encompassing death and DALY counts and rates for 371 diseases and injuries. The dataset, which spans from 1990 to 2021, was last updated on May 20, 2024, with the next update expected in May 2028. Detailed data can be retrieved from the Global Burden of Disease’s results tool here.

Figure 1: A donut chart, with an inset pie chart on the main causes of DALYs, coloured by different categories, and different causes within each category distinguished by opacity. Labels show percentage contribution and the names of causes.

How I made these graphics?

Getting the data

Code
# Data Import and Wrangling Tools
library(tidyverse)            # All things tidy
library(owidR)                # Get data from Our World in R

# Final plot tools
library(scales)               # Nice Scales for ggplot2
library(fontawesome)          # Icons display in ggplot2
library(ggtext)               # Markdown text support
library(showtext)             # Display fonts in ggplot2
library(colorspace)           # To lighten and darken colours
library(patchwork)            # Combining plots

library(donutsk)              # Pie & Donut Charts with labels

# search1 <- owidR::owid_search("burden of disease")

df1 <- owid("burden-of-disease-by-cause")

# popdf <- owid("population-with-un-projections")

Visualization Parameters

Code
# Font for titles
font_add_google("Fondamento",
  family = "title_font"
) 

# Font for the caption
font_add_google("Saira Extra Condensed",
  family = "caption_font"
) 

# Font for plot text
font_add_google("PT Sans Narrow",
  family = "body_font"
) 

showtext_auto()

# Colour Palette
set.seed(41)
mypal <- paletteer::paletteer_d("khroma::smoothrainbow")
mypal <- sample(mypal, 25, replace = F)
# Background Colour
bg_col <- "white"
text_col <- "grey20"
text_hil <- "grey30"

# Base Text Size
bts <- 80

plot_title <- "Causes of DALYs Worldwide"

plot_subtitle <- str_wrap("A Donut Chart on the causes of death, disease and disability globally in 2021 (measured by DALYs).", 60)
str_view(plot_subtitle)

# Caption stuff for the plot
sysfonts::font_add(
  family = "Font Awesome 6 Brands",
  regular = here::here("docs", "Font Awesome 6 Brands-Regular-400.otf")
)
github <- "&#xf09b"
github_username <- "aditya-dahiya"
xtwitter <- "&#xe61b"
xtwitter_username <- "@adityadahiyaias"
social_caption_1 <- glue::glue("<span style='font-family:\"Font Awesome 6 Brands\";'>{github};</span> <span style='color: {text_hil}'>{github_username}  </span>")
social_caption_2 <- glue::glue("<span style='font-family:\"Font Awesome 6 Brands\";'>{xtwitter};</span> <span style='color: {text_hil}'>{xtwitter_username}</span>")

plot_caption <- paste0(
  "**Data:** IHME - Global Burden of Disease (GBD) Study  |  ",
  "**Code:** ", 
  social_caption_1, 
  " |  **Graphics:** ", 
  social_caption_2
  )
rm(github, github_username, xtwitter, 
   xtwitter_username, social_caption_1, social_caption_2)

Data Wrangling

Code
df2 <- df1 |> 
  as_tibble() |> 
  pivot_longer(
    cols = -c(year, entity, code),
    names_to = "indicator",
    values_to = "value"
  ) |> 
  mutate(
    indicator = str_remove(
      indicator,
      "Total number of DALYs from "
    ),
    indicator = str_remove(
      indicator,
      "\n"
    )
  ) |>
  mutate(
    indicator = str_to_title(
      indicator
    ),
    indicator = str_replace(
      indicator,
      "Hiv/Aids",
      "HIV / AIDS"
    ),
    indicator = str_replace_all(
      indicator,
      "And",
      "and"
    )
  )

plotdf <- df2 |> 
  filter(year == 2021 & entity == "World") |> 
  mutate(
    indicator = fct(indicator),
    indicator_group = fct_collapse(
      indicator,
    "Lifestyle Disorders" = c("Cardiovascular Diseases", 
                              "Diabetes and Kidney Diseases", 
                              "Digestive Diseases", "Neoplasms", 
                              "Chronic Respiratory Diseases"),
    "Infectious Diseases" = c("Other Infectious Diseases", 
                              "Neglected Tropical Diseases and Malaria", 
                              "Enteric Infections", 
                              "HIV / AIDS and Sexually Transmitted Infections", 
                              "Respiratory Infections and Tuberculosis"),
    "Violence and Injuries" = c("Transport Injuries", 
                                "Self-Harm", 
                                "Interpersonal Violence",
                                "Unintentional Injuries", 
                                "Conflict and Terrorism", 
                                "Exposure To Forces Of Nature"),
    "Mental Health Disorders" = c("Neurological Disorders",
                                  "Mental Disorders", 
                                  "Substance Use Disorders"),
    "Others" = c("Musculoskeletal Disorders", 
                 "Maternal Disorders", 
                 "Nutritional Deficiencies", 
                 "Neonatal Disorders", 
                 "Skin and Subcutaneous Diseases", 
                 "Other Non-Communicable Diseases")
  )) |> 
  select(entity, year, indicator_group, indicator, value) |> 
  donutsk::packing(value = value, level = indicator_group) |> 
  mutate(perc_outer = 100 * value / sum(value)) |> 
  group_by(indicator_group) |> 
  mutate(perc_inner = sum(perc_outer)) |> 
  ungroup()

labelsdf <- plotdf |> 
  distinct(indicator_group, perc_inner) |> 
  mutate(
    ymax = cumsum(perc_inner),
    ymin = ymax - perc_inner,
    x_var = case_when(
      indicator_group == "Violence and Injuries" ~ 1.4,
      indicator_group == "Mental Health Disorders" ~ 1.4,
      .default = 1.3
    )
  ) |> 
  select(-perc_inner) |> 
  pivot_longer(
    cols = c(ymax, ymin),
    values_to = "y_var",
    names_to = NULL
  ) |> 
  mutate(
    y_var = y_var / 100
  )

Visualization

Code
g <- plotdf |>
  ggplot(
    mapping = aes(
      value = value,
      fill = indicator_group
    )
  ) +

  # Internal donut represents major categories of causes of DALYs
  geom_donut_int(
    r_int = 0, 
    col = bg_col, 
    linewidth = 0.75
  ) +
  
  # External donut represents detailed individual causes
  geom_donut_ext(
    mapping = aes(
      opacity = indicator
    ), 
    col = bg_col, 
    linewidth = 0.5, 
    show.legend = F
  ) +
  
  # Text annotations for internal donut
  geom_text_int(
    mapping = aes(
      label = paste0(
        round(perc_inner, 1),
        " %"
        )
      ),
    size = bts / 4,
    colour = bg_col,
    hjust = 0.5,
    r = 0.55,
    show.legend = F,
    family = "body_font",
    fontface = "bold"
  ) +
  
  geom_text_int(
    mapping = aes(
      label = str_wrap(as.character(indicator_group), 10)
      ),
    size = bts / 2,
    colour = text_hil,
    hjust = 0.5,
    lineheight = 0.25,
    r = 1.2,
    show.legend = F,
    family = "body_font",
    fontface = "bold"
  ) +

  # Label annotations for internal donut
  geom_text_ext(
    mapping = aes(
      colour = indicator_group, 
      label = paste0(
        str_wrap(indicator, 10), " (",
        round(perc_outer, 1), 
        " %)"
        )
      ),
    
    hjust = "outward",
    vjust = "outward",
    size = bts / 5,
    layout = circle(thinner = T), 
    show.legend = F,
    lineheight = 0.25,
    family = "caption_font"
  ) +
  
  # Link label annotations to outer donut chart
  geom_pin(
    mapping = aes(colour = indicator_group),
    size = 1, 
    linewidth = .25, 
    show.legend = F, 
    cut = 0, 
    layout = circle(thinner = T), 
    r = 1.75
  ) +
  # geomtextpath::geom_textpath(
  #   data = labelsdf,
  #   mapping = aes(
  #     x = x_var, 
  #     y = y_var,
  #     colour = indicator_group,
  #     label = str_wrap(indicator_group, 1),
  #     value = NULL,
  #     fill = NULL
  #   ),
  #   spacing = -200,
  #   text_only = TRUE,
  #   text_smoothing = 0,
  #   lineheight = 0.25,
  #   family = "caption_font",
  #   size = bts/6
  # ) + 
  # Scales and Coordinates
  paletteer::scale_colour_paletteer_d("wesanderson::Darjeeling1") +
  paletteer::scale_fill_paletteer_d("wesanderson::Darjeeling1") +
  coord_radial(
    theta = "y", 
    expand = F, 
    clip = "off"
    ) +
  xlim(0, 3.5) +
  
  # Labels and Themes
  labs(
    title = plot_title,
    caption = plot_caption,
    subtitle = plot_subtitle
  ) +
  
  theme_void(
    base_family = "body_font",
    base_size = bts
  ) +
  theme(
    legend.position = "none", 
    plot.title.position = "plot",
    plot.title = element_text(
      colour = text_hil,
      family = "title_font",
      size = 3 * bts,
      hjust = 0.5,
      margin = margin(10,0,0,0, "mm")
    ),
    plot.subtitle = element_text(
      colour = text_hil,
      family = "title_font",
      size = 1.5 * bts,
      hjust = 0.5,
      margin = margin(5,0,-70,0, "mm"),
      lineheight = 0.25
    ),
    plot.caption = element_textbox(
      colour = text_hil,
      family = "caption_font",
      hjust = 0.5,
      margin = margin(-70,0,0,0, "mm")
    ),
    plot.margin = margin(10,-70,10,-70, "mm"),
    panel.border = element_blank()
  )

Save a thumbnail

Code
ggsave(
  filename = here::here("data_vizs", "owid_daly_causes_donut.png"),
  plot = g,
  height = 500,
  width = 400,
  units = "mm",
  bg = bg_col
)

library(magick)
# Saving a thumbnail for the webpage
image_read(here::here("data_vizs", 
                      "owid_daly_causes_donut.png")) |> 
  image_resize(geometry = "400") |> 
  image_write(here::here("data_vizs", "thumbnails", 
                         "owid_daly_causes_donut.png"))

Other Trial and Error Code

Code
# Trying a sample with geom_textpath
# ggplot() +
#   geom_point(
#     data = labelsdf,
#     mapping = aes(
#       x = x_var,
#       y = y_var,
#       colour = indicator_group,
#       value = NULL,
#       fill = NULL
#     )
#   ) +
#   geom_line(
#     data = labelsdf,
#     mapping = aes(
#       x = x_var,
#       y = y_var,
#       colour = indicator_group,
#       value = NULL,
#       fill = NULL
#     )
#   ) +
#   
#   # Adding a line on inside of outer donut
#   geomtextpath::geom_textpath(
#     data = labelsdf,
#     mapping = aes(
#       x = x_var, 
#       y = y_var,
#       colour = indicator_group,
#       label = indicator_group,
#       value = NULL,
#       fill = NULL
#     ),
#     text_only = TRUE,
#     text_smoothing = 0,
#     family = "caption_font",
#     size = bts/3
#   ) +
#   coord_radial(theta = "y", expand = F, clip = "off")