1899

Active shortages

35

New shortages expected this week

7

New discontinuations this week

127

Shortages of Essential Medicines this year

Drugs in shortage
Causes of drug shortages this year
Jul 2019Aug 2019Sep 2019Oct 2019Nov 2019Dec 2019Jan 2020Feb 2020Mar 2020Apr 2020May 2020Jun 2020Jul 2020Aug 2020Sep 2020Oct 2020Nov 2020Dec 2020Jan 2021Feb 2021Mar 2021Apr 2021May 2021Jun 2021Jul 2021Aug 2021Sep 2021Oct 2021Nov 2021Dec 2021Jan 2022Feb 2022Mar 2022Apr 2022May 2022Jun 2022Jul 2022Aug 2022Sep 2022Oct 2022Nov 2022Dec 2022Jan 2023Feb 2023Mar 2023Apr 2023May 2023Jun 2023Jul 2023Aug 2023Sep 2023Oct 2023Nov 2023Dec 2023Jan 2024Feb 2024Mar 2024Apr 2024May 2024Jun 2024Jul 2024Aug 2024Sep 2024Oct 2024Nov 2024Dec 2024Jan 2025Feb 2025Mar 2025Apr 2025May 2025Jun 2025Jul 20250255075100
reasonDemand increaseShipping delayOtherManufacturing distruptionIngredient shortageManufacturing practices% of total
New shortages per week since 2018
Shortage durations
010020030002004006008000100200300010020030001002003000100200300010020030001002003000100200300
Duration (weeks)# of shortages20182019202020212022202320242025
COVID-19 Indicator Medicines

We are tracking the following list of medications (and groups of medications) to assess for COVID-related shortages:

ATC Description
C01CA Adrenergics
H01BA Vasopressin and analogues
M03AB01 Succinylcholine (suxamethonium)
M03AC Muscle relaxants (e.g. paralytics)
N01A Anesthetics (includes opioid, ketamines)
N05CD Benzodiazepines
N05CM18 Dexmedetomidine
C01BD01 Amiodarone
C03CA01 Furosemide
J05AR10 Kaletra (anti-viral)
N02BE01 Acetaminophen (Paracetamol)
P01BA02 Hydroxychloroquine
R01AA04 Phenylephrine (nasal)
R03AC Respiratory Beta Agonists

The following charts shows the availability of COVID-19 indicator medications since November 2019. Only medications (by ATC code) that have had a shortages will be shown.

About this website

Hello. I am Jon Pipitone, resident in Psychiatry at Queen’s University, Kingston, Ontario, Canada. This dashboard is my attempt to show interesting trends and summaries of Canadian drug shortage reports. Code for this website can be found on github. This work is licensed under a Creative Commons Attribution 4.0 International License.

Much of this work has been done in collaboration with Dr. Jacalyn Duffin, who has a her own site dedicated to analysis of the canadian drug shortages: https://canadadrugshortage.com.

We published a peer-reviwed paper in 2018 on this issue:

  • Donelle, Jessy, Jacalyn Duffin, Jonathan Pipitone, and Brian White-Guay. “Assessing Canada’s Drug Shortage Problem.” CD Howe Institute Commentary 515, 2018. https://doi.org/10.2139/ssrn.3192558

I also keep a blog and I sometimes discuss drug shortages.

Questions, comments, requests? Please send email to .

FAQ
  1. Why are there different totals listed under “active shortages” and on the “drugs in shortage” graph?

    In short, it’s because there is missing or incorrect data in some of the shortage reports that means they get excluded from historical counts. I’ve written a blog post about it.

  2. How often is the dashboard updated?

    Daily. It was last updated 2025-04-04 00:17:10.

Data sources
drugshortagescanada.ca

Database of shortage and discontinuation reports. Active from March 2017 to present. Manufacturers are required to report.

Last updated: 2025-04-04 00:17:10

Drug Product Database

Database of historical records of drug products in Canada. Maintained by Health Canada.

Last updated: 2025-04-04 00:17:10

---
title: "Assessing Canada’s Drug Shortage Problem"
output: 
  flexdashboard::flex_dashboard:
    orientation: rows
    source_code: embed
    self_contained: false
---

```{r setup, include=FALSE}
options(tidyverse.quiet=T)
library(flexdashboard)
library(tidyverse)
library(lubridate, warn.conflicts=F)
library(devtools, quietly=T)
library(rdrugshortages)     # devtools::install_github('pipitone/rdrugshortages')
library(kableExtra, warn.conflicts=F)
library(plotly, warn.conflicts=F)

# settings
DATA_DIR = 'data/'  # Load the Health Canada Drug Product Database
DPD_DOWNLOAD = getOption('dpd_download', default=F) # download the latest drug product database dataset
DSC_DOWNLOAD = getOption('dsc_download', default=F) # download latest DSC database
DSC_PATH     = 'data/dsc.csv'
DSC_START    = ymd("2018-01-01", tz="")
FAR_FUTURE   = ymd("2100-12-31", tz="")
FAR_PAST     = origin

theme_set(theme_minimal())
caption = list(labs(caption =  paste(
  paste(
    "source: drugshortagescanada.ca, last updated:", as_date(file.info(DSC_PATH)$ctime)), 
    "source: Health Canada Drug Product Database",
    "jon.pipitone.ca/research/drug-shortages", 
    sep="\n")))

# load data
if (DSC_DOWNLOAD) { 
  dsc = dsc_search()  %>%
    select_if(~ !is.list(.x)) %>%
    write_csv(DSC_PATH)
}
dsc.orig = read_csv(DSC_PATH, trim_ws = T, col_types = cols(
  status = col_factor(),
  drug.company.post_office_box = col_character(),
  hc_en_comments = col_character(), 
  hc_fr_comments = col_character(),
  shortage_reason.en_reason = col_factor()
  )) %>%
  select(-matches("^fr_|\\.fr_|_fr")) %>%
  mutate(
    reason = fct_recode(shortage_reason.en_reason,  
      "Demand increase" = "Demand increase for the drug.", 
      "Manufacturing distruption" = "Disruption of the manufacture of the drug.",
      "Ingredient shortage" = "Shortage of an active ingredient.", 
      "Ingredient shortage" = "Shortage of an inactive ingredient or component.",
      "Manufacturing practices" = "Requirements related to complying with good manufacturing practices.",
      "Other" = "Other (Please describe in comments)",
      "Shipping delay" = "Delay in shipping of the drug." 
    ) 
  ) %>%
  mutate(
    end_date_no_na = coalesce(actual_end_date, FAR_FUTURE))

## Parsual EML
# https://dx.doi.org/10.9778%2Fcmajo.20160122
eml = read_csv('data/cleanmeds.csv', trim_ws=T, col_types = cols()) %>% 
  select(category, medication, ATC)

## DPD
dpd = dpd_load(DATA_DIR, download=DPD_DOWNLOAD)

## Add the DPD ATC number to the DSC data
dsc = inner_join(dpd$drug, dpd$ther, by="DRUG_CODE") %>% 
  select(din = DRUG_IDENTIFICATION_NUMBER, dpd_atc_number = TC_ATC_NUMBER, TC_ATC) %>% 
  distinct(din, .keep_all = TRUE) %>%
  right_join(dsc.orig, by=c("din"))
dsc.shortages = dsc %>% filter(type.label == "shortage")
dsc.discontinuations = dsc %>% filter(type.label == "discontinuance")

this_year = interval(floor_date(now(), "year"), ceiling_date(now(), "year"))
week_start = floor_date(now(), "week")
this_week = interval(week_start, week_start + dweeks(1))
next_week = int_shift(this_week, by = dweeks(1))
```

Shortages
==========

Row {data-height=125}
--------------------------------

### Active shortages
```{r}
valueBox(dsc %>%  filter(status == "active_confirmed") %>% count() %>% pull(), 
         icon="fa-capsules", 
         color = "warning")
```

### New shortages expected this week
```{r}
.shortages_this_week =  dsc.shortages %>% 
  filter(
    status %in% c("active_confirmed", "anticipated_shortage"), 
    coalesce(actual_start_date, anticipated_start_date) %within% this_week)
valueBox(nrow(.shortages_this_week),icon="fa-needle")
```

### New discontinuations this week
```{r}
.active_disconts = dsc.discontinuations  %>% 
  filter(
    status == "discontinued", 
    discontinuation_date %within% this_week)
valueBox(nrow(.active_disconts), icon="fa-ban")
```

### Shortages of Essential Medicines this year
```{r}
.emls_with_shortages = dsc.shortages %>%
  filter(actual_start_date %within% this_year) %>%
  right_join(eml, by=c("dpd_atc_number" = "ATC"))  %>%
  filter(!is.na(din))  %>%
  group_by(category, medication, dpd_atc_number)
valueBox(nrow(.emls_with_shortages), icon="fa-ambulance")
```

Row  
----------------------------------
### Drugs in shortage
```{r}
weeks = tibble(
  week.start = seq(as_date(floor_date(DSC_START, "week")), 
                   as_date(floor_date(now(), "week")), by = "1 week"), 
  i = 1
)
p = dsc.shortages %>%
  mutate(i = 1) %>%
  full_join(weeks, by = "i") %>%
  filter(week.start %within% interval(actual_start_date, end_date_no_na))%>%
  group_by(week.start) %>%
  summarize(n = n()) %>%
  mutate(week = week.start) %>%
  ggplot( aes(x = week, y = n)) + 
  geom_line() + 
  scale_x_date(date_minor_breaks = "1 month", 
               date_labels="%b %Y") + 
  labs(x = NULL, y = NULL)
ggplotly(p)
```


### Causes of drug shortages this year {.no-padding}
```{r, fig.height=4, asp=0.6}
p = dsc.shortages %>%
  transmute(started = as_date(floor_date(actual_start_date, "month")), 
            reason = reason) %>%  
  filter(started >= ymd('2019-10-01'), started < now()) %>%
  count(started, reason) %>%
  group_by(started) %>%
  mutate(percent = round(n / sum(n) * 100, 1)) %>%
  transmute(month = started, percent, reason) %>%
  ggplot(aes(x = month, y = percent, fill=reason)) + 
  geom_bar(stat='identity') + 
  scale_x_date(date_breaks="1 month", 
               date_labels="%b %Y") +
  labs(x=NULL, y="% of total") + 
  theme(legend.position="bottom")
ggplotly(p) %>% layout(legend = list(orientation = 'h', x=0, y=-.1))
```


Row
-----------------------------------------------------------------------

### New shortages per week since 2018
```{r}
p = dsc.shortages %>%
  mutate(week = as_date(floor_date(actual_start_date, "week"))) %>%
  filter(week >= ymd('2018-01-01'), week < now()) %>%
  count(week) %>%
  ggplot(aes(x=week, y=n)) + 
  geom_bar(stat='identity') + 
  scale_x_date(date_minor_breaks = "1 month", 
               date_labels = "%b %Y") +  
  labs(x=NULL, y=NULL)
 ggplotly(p) 
```

### Shortage durations
```{r}
p = dsc.shortages %>% 
  filter(actual_start_date >= ymd("2018-01-01")) %>%
  mutate(duration = (actual_end_date - coalesce(actual_start_date, estimated_end_date))/dweeks(1), 
         month_str=floor_date(actual_start_date, unit="year")) %>%
  filter(!is.na(duration), duration > 0) %>%
  ggplot(aes(x=duration)) +
  geom_histogram(binwidth=4) +
  facet_wrap(~factor(month_str), nrow=1, labeller = as_labeller(function(x) format.Date(x, "%Y"))) + 
  labs(x="Duration (weeks)", y="# of shortages")
ggplotly(p)
```

COVID-19 {data-orientation=columns}
=======================================

```{r}
.dpd_status_ordered = dpd$status %>%
  group_by(DRUG_CODE) %>%
  arrange(HISTORY_DATE, .by_group = T) %>%
  mutate(.curr = 1:n(), .prev = .curr-1, .last = n()) %>%
  ungroup()

# complex join so that LHS has FROM status and RHS has TO status
# Also includes pair when LHS is the most recent status and then RHS is ignored
dpd_status_intervals = inner_join(.dpd_status_ordered, .dpd_status_ordered, by="DRUG_CODE") %>%
  select(-starts_with("CURRENT"), -starts_with("LOT"), -starts_with("EXP")) %>%
  filter(.prev.y == .curr.x | (.curr.x == .last.x & .curr.x == .curr.y)) %>%
  transmute(
    DRUG_CODE = DRUG_CODE, 
    start_date = HISTORY_DATE.x,
    end_date = if_else(.curr.x == .last.x, as_date(FAR_FUTURE), HISTORY_DATE.y), 
    status = STATUS.x)

# Map ATC code to NAME
atc_codes = dpd$ther %>% transmute(dpd_atc_number = TC_ATC_NUMBER, atc_name = TC_ATC) %>% distinct()

# date range to show 
FROM = floor_date(ymd("2019-11-01"), "week")
TO = as_date(now())
BY = "1 week"  # warning: if the FROM-TO range is large (e.g. > 20 years), change BY to something larger, e.g. 3 months

# now, for each month, let's compute the total number of drugs marketed, for each ATC code
dpd.months = tibble(month.date = seq(FROM, TO, by=BY),  n = 1)

dpd_num_marketed_by_atc = dpd_status_intervals %>% mutate(n=1) %>%
  inner_join(dpd.months, by="n") %>%
  filter(month.date %within% interval(start_date, end_date)) %>%
  select(DRUG_CODE, status, month.date) %>%
  inner_join(select(dpd$ther, DRUG_CODE, TC_ATC_NUMBER), by = "DRUG_CODE") %>%
  group_by(month.date, TC_ATC_NUMBER) %>%
  summarize(marketed = sum(status == "MARKETED"))

dsc_num_shortages_by_atc = dsc.shortages %>%
  mutate(n=1) %>%
  inner_join(dpd.months, by="n") %>%
  filter(month.date %within% interval(actual_start_date, end_date_no_na)) %>%
  distinct(month.date, dpd_atc_number, din) %>%
  group_by(month.date, dpd_atc_number) %>%
  summarize(shortages = n())


atc_plot = function(atc) {
  shortages = dsc_num_shortages_by_atc %>%
    filter(str_starts(dpd_atc_number, atc))
  
  marketed = dpd_num_marketed_by_atc  %>%
    filter(str_starts(TC_ATC_NUMBER, atc))
  
  right_join(shortages, marketed, 
             by = c("month.date" = "month.date", "dpd_atc_number" = "TC_ATC_NUMBER")) %>%
    ungroup() %>%
    mutate(shortages = replace_na(shortages, 0)) %>%
    mutate(available = marketed - shortages) %>%
    group_by(month.date) %>%
    summarize(Available = sum(available), Marketed = sum(marketed)) %>%
    pivot_longer(cols = Available:Marketed, names_to = "status", values_to = "count") %>%
    ggplot(aes(x = week, y = count, linetype = fct_rev(status))) +
    geom_line()  + 
    labs(x=NULL, y = "# of DINs", 
         linetype = NULL) + 
    expand_limits(y=0)  + 
    theme(legend.position="bottom")
}

integer_breaks <- function(n = 5, ...) { 
  function(x) { 
    breaks = floor(pretty(x, n, ...))
    names(breaks) = attr(breaks, "labels")
    breaks
  }
}


atc_group_plot = function(atcs) {
  atc = paste(atcs, collapse="|")
  shortages = dsc_num_shortages_by_atc %>%
    filter(str_starts(dpd_atc_number, atc))
  
  marketed = dpd_num_marketed_by_atc  %>%
    filter(str_starts(TC_ATC_NUMBER, atc))
  
  right_join(shortages, marketed, 
             by = c("month.date" = "month.date", "dpd_atc_number" = "TC_ATC_NUMBER")) %>%
    ungroup() %>%
    mutate(shortages = replace_na(shortages, 0)) %>%
    mutate(available = marketed - shortages) %>%
    group_by(month.date, dpd_atc_number) %>%
    summarize(Available = sum(available), Marketed = sum(marketed)) %>%
    ungroup() %>%
    group_by(dpd_atc_number) %>%
    filter(sum(Marketed-Available) > 0, Available > 0) %>%
    mutate(percent_available = Available/max(Available)*100) %>%
    mutate(week = month.date) %>%
    left_join(atc_codes, by="dpd_atc_number") %>%
    mutate(name_and_atc = paste0(atc_name, ' [', dpd_atc_number, ']')) %>%
    ggplot(aes(x = week, y = Available, fill=name_and_atc)) +
    geom_area()  + 
    scale_x_date(date_breaks="2 month", 
                 date_labels="%b %Y", 
                 minor_breaks=NULL) + 
    scale_y_continuous(breaks=integer_breaks(3)) + 
    facet_wrap(~name_and_atc, ncol=2, strip.position = "right", scales="free_y") + 
    theme(legend.position="none", 
          strip.text.y = element_text(angle=0)
          )
}
```


Column  {data-width=150}
------------------------------------- 

### COVID-19 Indicator Medicines
We are tracking the following list of medications (and groups of medications) to
assess for COVID-related shortages:

```{r}
# To add: propofol, rocuronium, cisatracurium, ketamine, midazolam, norepinephrine, phenylephrine, fentanyl
covid_meds = tribble(
  ~ATC,      ~Type,             ~Description,
  "N01A",    "Anesthetic use",  "Anesthetics (includes opioid, ketamines)",  
  "M03AC",   "Anesthetic use",  "Muscle relaxants (e.g. paralytics)", 
  "M03AB01", "Anesthetic use",  "Succinylcholine (suxamethonium)",
  "N05CD",   "Anesthetic use",  "Benzodiazepines", 
  "C01CA",   "Anesthetic use",  "Adrenergics",  
  "N05CM18", "Anesthetic use",  "Dexmedetomidine", 
  "H01BA",   "Anesthetic use",  "Vasopressin and analogues",
  "R03AC",   "Other",           "Respiratory Beta Agonists",  
  "R01AA04", "Other",           "Phenylephrine (nasal)",
  "C03CA01", "Other",           "Furosemide",
  "C01BD01", "Other",	          "Amiodarone",
  "J05AR10", "Other",           "Kaletra (anti-viral)", 
  "P01BA02", "Other",           "Hydroxychloroquine", 
  "N02BE01", "Other",           "Acetaminophen (Paracetamol)",  
)
   
kable(covid_meds %>% arrange(Type, ATC) %>% select(-Type)) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"), 
    full_width=T) 
```

The following charts shows the availability of COVID-19 indicator medications since November 2019. 
Only medications (by ATC code) that have had a shortages will be shown. 

Column {.tabset}
-----------------------------------------------------

### Anesthetic Use {data-padding=30}
```{r}
p = atc_group_plot(filter(covid_meds, Type == "Anesthetic use")$ATC)
p1 = p + 
    geom_vline(xintercept=as.numeric(ymd("2020-01-15")), linetype=1) + 
    geom_vline(xintercept=as.numeric(ymd("2020-03-09")), linetype=1, colour="red") + 
    labs(x=NULL, y = "# DIN available") 
p1_ly = ggplotly(p1) %>% 
  layout(annotations = list(x = 1, y = -0.06, xref="paper", yref="paper", 
                            xanchor="right", yanchor="bottom", xshift=0, yshift=0, 
                            font=list(size=10), showarrow=F,
                            text="Black line indicates first COVID-19 case in canada, red line indicates first death"))  
p1_ly
```

### Other Use {data-padding=30}
```{r}
p = atc_group_plot(filter(covid_meds, Type == "Other")$ATC)
p1 = p + 
    geom_vline(xintercept=as.numeric(ymd("2020-01-15")), linetype=1) + 
    geom_vline(xintercept=as.numeric(ymd("2020-03-09")), linetype=1, colour="red") + 
    labs(x=NULL, y = "# DIN available") 
p1_ly = ggplotly(p1) %>% 
  layout(annotations = list(x = 1, y = -0.06, xref="paper", yref="paper", 
                            xanchor="right", yanchor="bottom", xshift=0, yshift=0, 
                            font=list(size=10), showarrow=F,
                            text="Black line indicates first COVID-19 case in canada, red line indicates first death"))  
p1_ly
```


Search
======

Row 
------------------------------------- 
### Searchable list of shortage reports since January, 2023
```{r}
dsc.shortages %>%
  mutate(start_date = date(coalesce(actual_start_date, anticipated_start_date)),) %>%
  filter(start_date %within% interval(ymd("2023-01-01"), now()+dweeks(4))) %>% 
  arrange(start_date) %>%
  mutate(drug_name = if_else(str_length(en_drug_brand_name) > 30, paste0(substring(en_drug_brand_name, 0, 30), '...'), en_drug_brand_name), 
         company_name = str_to_title(company_name), 
         duration = round((start_date %--% estimated_end_date)/days(1)),
         status = fct_recode(status, 
                             "Active" = "active_confirmed", 
                             "Anticipated" = "anticipated_shortage", 
                             "Avoided" = "avoided_shortage", 
                             "Resolved" = "resolved"),
         link = text_spec(id, link=paste0("https://www.drugshortagescanada.ca/shortage/", id))) %>%
  select(dpd_atc_number, drug_name, drug_strength, company_name, status, start_date, duration, reason, link) %>%
  DT::datatable(escape=F, 
                filter="top",
                rownames=T,
                options = list(bPaginate = FALSE, 
                               autoWidth = T,
                               rowid = F, 
                               search = list(regex = T), 
                               order=list(list(6, "desc"), list(5,"asc"))), 
                colnames = c("ATC", "Drug Name", "Dose", "Company", "Status", "Start", "Duration (days)", "Reason", "View report")) %>%
  DT::formatStyle("start_date", "white-space"="nowrap") %>%
  DT::formatStyle("drug_name", "white-space"="nowrap") %>%
  DT::formatStyle("reason", "white-space"="nowrap") %>%
  DT::formatStyle('status', color = DT::styleEqual(c('Anticipated', 'Active'), c('blue', 'green')))
```

About
=====

Column
------

### About this website

Hello. I am Jon Pipitone, resident in Psychiatry at Queen's University, Kingston,
Ontario, Canada. This dashboard is my attempt to show interesting trends and
summaries of Canadian drug shortage reports. Code for this website can be found
on [github](https://github.com/pipitone/drug-shortages-dashboard). This work is
licensed under a <a rel="license"
href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution
4.0 International License</a>.

Much of this work has been done in collaboration with [Dr. Jacalyn
Duffin](https://jacalynduffin.ca/), who has a her own site dedicated to
analysis of the canadian drug shortages: https://canadadrugshortage.com. 

We published a peer-reviwed paper in 2018 on this issue:

* Donelle, Jessy, Jacalyn Duffin, Jonathan Pipitone, and Brian White-Guay.
“Assessing Canada’s Drug Shortage Problem.” CD Howe Institute Commentary 515, 2018. https://doi.org/10.2139/ssrn.3192558

I also keep [a blog](https://jon.pipitone.ca/blog) and I sometimes discuss drug
shortages.

Questions, comments, requests? Please send email to drugshortages@pipitone.ca.

### FAQ 

1. Why are there different totals listed under "active shortages" and on the "drugs in shortage" graph?

    In short, it's because there is missing or incorrect data in some of the
shortage reports that means they get excluded from historical counts. I've
written [a blog post about it](https://jon.pipitone.ca/blog/2020-04-18-how-many-drug-shortages-are-there/).

1. How often is the dashboard updated? 

    Daily. It was last updated `r now()`. 

Column  {data-width=100}
----------------------------

### Data sources

##### [drugshortagescanada.ca](https://www.drugshortagescanada.ca)
Database of shortage and discontinuation reports.  Active from March 2017 to
present. Manufacturers are required to report.

Last updated: `r now()`

##### [Drug Product Database](https://www.canada.ca/en/health-canada/services/drugs-health-products/drug-products/drug-product-database.html)
Database of historical records of drug products in Canada. Maintained by Health Canada.

Last updated: `r now()`

##### [CLEANMeds Essential Medicines List](https://cleanmeds.ca)