Table of contents
Background and method
Medicines developed and authorised for cancers in adults are often relevant to be investigated for cancers in children . Investigating potential cancer medicines in children is recommended to be started early and before authorisation .
This time relation is addressed on this page, to allow inspecting changes over the last decades and to further inform policies. In the U.S., requirements for molecularly targeted paediatric cancer investigations apply from mid-2020 and will be tracked in the future.
Information from the EU Clinical Trials Register and ClinicalTrials.Gov was used for this analysis . Paediatric trials were identified based on the protocol-related information that any of the paediatric age groups was to be included in the trial. Information on cancer medicines authorisations was retrieved from the EMA website. To reproduce the analysis, the R code is included below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | ### all code by ralf.herold@gmx.net # latest updates: # - 2019-12-31 to match ctrdata 1.1 package # - 2022-04-23 to match ctrdata 1.9 package ### Options for subsequent chunks # # knitr knitr::opts_chunk$set( dev = "svglite", fig.width = 6, fig.height = 4, out.width = "100%", out.height = "1200px", width = "100%", echo = TRUE, results = "asis", warning = FALSE, message = FALSE, cache = FALSE ) ### Helper functions # start_date_ctgov <- function(x) { # September 30, 2017 -> ok # September 2013 -> September 15, 2013 x <- sub("([a-zA-Z]+) ([0-9]{4})", "\\1 15, \\2", x) return(as.Date(x, format = "%B %d, %Y")) } # age_ctgov <- function(x) { tmp.years <- suppressWarnings(as.numeric(gsub("([0-9]+) Year.?", "\\1", x, ignore.case = TRUE))) / 1 tmp.months <- suppressWarnings(as.numeric(gsub("([0-9]+) Month.?", "\\1", x, ignore.case = TRUE))) / 12 tmp.days <- suppressWarnings(as.numeric(gsub("([0-9]+) Day.?", "\\1", x, ignore.case = TRUE))) / 365.25 tmp.minage <- ifelse(!is.na(tmp.years), tmp.years, ifelse(!is.na(tmp.months), tmp.months, ifelse(!is.na(tmp.days), tmp.days, NA))) return(tmp.minage) } # normalise_string <- function(x) { x <- gsub(",", "", x) x <- gsub("-", " ", x) x <- tolower(x) x <- tools::toTitleCase(x) x <- gsub("[ ]+", " ", x) x <- trimws(x) return(x) } # normalise_number <- function(x) { x <- gsub("<=|<|≤|=|>", "", x) x <- gsub("(^| )[.]", "0.", x) x <- trimws(x) x <- sapply(x, FUN = function(y) { z <- try(as.numeric(y), silent = TRUE) if (class(z) == "try-error") { NA message(z) } else { z } }) return(unname(x)) } # # getannotationelement <- function(x, element = "AS") { # for use with ctrdata # example from annotation.text: # x <- "|AS:tisagenlecleucel| |AS:tisagenlecleucel|" x <- strsplit(x = x, split = "\\|") x <- unlist(x) x <- x[grepl(paste0("^", element, ":"), x)] x <- strsplit(x = x, split = ":") x <- unlist(x) x <- x[!grepl("AS", x)] x <- sort(unique(x)) return(x) } ### Initialise # load libraries library(ggplot2) library(plotly) library(data.table) library(tidyr) # # This library was specially written # by Ralf Herold and is available here: # https://github.com/rfhb/ctrdata library(ctrdata) |
The figure shows all cancer medicines that are centrally authorised in Europe. Open circles indicate the date of the authorisation (for some active substances, there are two marketing authorisations). Closed circles indicate the start of a trial of the respective active substance that included the paediatric population (irrespective of subset, such as adolescents or broader paediatric age ranges). Green lines indicate that this trial was started at least 3 years before the marketing authorisation. A stark marks cancer medicines that are authorised for children (full list is in another post). Whether the trial is part of a paediatric development plan, is not visualised here. Hover over the symbols to see details.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | ### create database connection con <- nodbi::src_mongo( collection = "all_paed_phase1_authorised_cancer_drugs", db = "users" ) # dbQueryHistory(con) # &type=DRUG&age=0&phase=4&phase=0&phase=1&term=vismodegib # get about 170 centrally authorised cancer medicines from # https://paediatricdata.eu/emaepars/ # - ATC code = ^L|^V|^$ # - generic = ["FALSE"] # - biosimilar = ["FALSE"] # - MeSH root term(s)* = Neoplasms|Surgical Procedures, Operative / Therapeutics # # utils::browseURL("https://paediatricdata.eu/emaepars/?_state_id_=04a0fd8118e1b332") # download as "paediatricdata.eu - EPARs.xlsx" capslist <- readxl::read_xlsx( path = "paediatricdata.eu - EPARs.xlsx", sheet = 1, skip = 1 ) # keep relevant medicines capslist <- subset( x = capslist, subset = # TODO include no longer authorised medicines? authorised == "Authorised" & # medicines wrongly not labelled as generic !grepl( pattern = "Topotecan Hospira|Docetaxel Winthrop|Pemetrexed Actavis", x = capslist[["invented name"]]) & # remove CAPs: # - mercaptopurine is not new but kept # - irinotecan is "old" and not suitable for timelines !grepl( pattern = "irinotecan", x = capslist[["active substance"]]) ) # canonically format active substances capslist["active substance"] <- trimws( gsub( pattern = paste0( "hydrochloride|dimaleate|monohydrate|pentasodium", "|dichloride|hydrochloride monohydrate"), replacement = "", x = capslist[["active substance"]] )) # re-write active substances to find the most relevant trials in CTGOV capslist[grepl("Vyxeos", capslist[["invented name"]]), "active substance"] <- "liposome-encapsulated daunorubicin / cytarabine" capslist[grepl("Spectrila", capslist[["invented name"]]), "active substance"] <- "recombinant asparaginase" capslist[grepl("Abraxane", capslist[["invented name"]]), "active substance"] <- "nabpaclitaxel" ### get trials per active substance # which substances to search trials for? activesubstancestodo <- sort(unique(capslist[["active substance"]])) # get active substances already in database # as stored in annotation field if (nrow(dbQueryHistory(con))) { asdone <- dbGetFieldsIntoDf(c("annotation"), con) asdone <- getannotationelement(asdone$annotation, element = "AS") aslast <- dbQueryHistory(con) aslast <- max( as.Date( strptime( aslast["query-timestamp"][,1], format = "%Y-%m-%d %H:%M:%S")), na.rm = TRUE) # keep all but already downloaded active substances activesubstancestodo <- activesubstancestodo[ !(activesubstancestodo %in% asdone)] } # iterate by active substance # as <- activesubstancestodo[3] for (as in activesubstancestodo[149:169]) { message("\n\n\n\n ************** ", as, " **************\n") # get paediatric phase 1 trials ... as <- gsub("[^-\"a-zA-Z0-9 _#+]", " ", as) # ... from ctgov try({ctrLoadQueryIntoDb( paste0("https://clinicaltrials.gov/ct2/results?cond=Cancer&type=Intr&age=0&intr=", as), con = con, annotation.text = paste0("|AS:", as, "|"))}) # ... from euctr assynonyms <- try({ctrFindActiveSubstanceSynonyms(as)}, silent = TRUE) if (inherits("try-error", assynonyms) || (length(assynonyms) > 1)) { assynonyms <- paste0(""", paste0(assynonyms, collapse = "" OR ""), """) } else { assynonyms <- as } assynonyms <- gsub("[^-\"a-zA-Z0-9 _#+]", " ", assynonyms) message("\n = ", assynonyms, "\n") euctrsearch <- paste0( "https://www.clinicaltrialsregister.eu/ctr-search/search?", "age=under-18&phase=phase-one&phase=phase-two&query=", assynonyms) # ctrOpenSearchPagesInBrowser(euctrsearch) retrieved <- try({ctrLoadQueryIntoDb( euctrsearch, con = con, annotation.text = paste0("|AS:", as, "|"))}) } # View(dbQueryHistory(con)) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | # get result set (has both EUCTR and CTGOV fields) result <- dbGetFieldsIntoDf( c("n_date_of_competent_authority_decision", "annotation", "eligibility.minimum_age", "eligibility.maximum_age", "a3_full_title_of_the_trial", "start_date", "condition", "brief_title", "phase", "n_competent_authority_decision"), con = con, stopifnodata = FALSE) # deduplicate trials result <- result[ result[["_id"]] %in% dbFindIdsUniqueTrials( preferregister = "CTGOV", include3rdcountrytrials = FALSE, con = con), ] # keep only authorised trials result <- result[ c(NA, "Authorised") %in% result[["n_competent_authority_decision"]], ] # merge dates result["startdate"] <- dfMergeTwoVariablesRelevel( df = result, colnames = c("n_date_of_competent_authority_decision", "start_date")) # select trials that are reasonably recent result <- result[result$startdate > "1975-01-01", ] # merge study title result$trial_title <- dfMergeTwoVariablesRelevel( df = result, colnames = c("brief_title", "a3_full_title_of_the_trial")) # for ctgov trials better to be strict # NCT00002866 (docetaxel in breast cancer) 16 years min age, therefore result$pediatrictrialidentifier <- ( grepl("child|pediatr|paediatr", result$trial_title, ignore.case = TRUE) & !grepl("breast|prostrat|lung|head and neck|colo", result$trial_title, ignore.case = TRUE) ) | ( !is.na(result$eligibility.minimum_age) & result$eligibility.minimum_age <= 15 * 365.25) | ( !is.na(result$eligibility.minimum_age) & result$eligibility.minimum_age <= 21 * 365.25) # table(result$pediatrictrialidentifier, exclude = NaN) # select presumably pediatric trials result <- subset( result, subset = pediatrictrialidentifier, select = c("startdate", "trial_title", "annotation", "_id")) # rename variables names(result) <- c("trial_start", "trial_title", "annotation", "trial_id") # construct set per active substance resplot <- lapply( X = sort(unique(capslist[["active substance"]])), FUN = function(x) { stt <- result[grepl(paste0("AS:", x), result$annotation), ] if (!nrow(stt)) { stt <- data.frame("active_substance" = x, "trial_start" = NA, "trial_id" = NA, "trial_title" = NA, stringsAsFactors = FALSE) } else { whi <- which(stt[, "trial_start"] == min(stt[, "trial_start"], na.rm = TRUE)) data.frame("active_substance" = x, "trial_start" = as.Date(stt[whi, "trial_start"], origin = "1970-01-01"), "trial_id" = stt[whi, "trial_id"], "trial_title" = stt[whi, "trial_title"], stringsAsFactors = FALSE) } } ) resplot <- do.call(rbind, resplot) # merge caps list and euctr and ctgov trials result2 <- merge(x = resplot, y = capslist, by.x = "active_substance", by.y = "active substance", all.y = TRUE) # keep variables of interest result2 <- result2[ , match(c("active_substance", "authorisation_date", "indication", "trial_start", "trial_title", "trial_id", "paediatric indication 4.1 *"), names(result2))] # identify paediatric authorisations result2$paediatricindication <- grepl("TRUE", result2$`paediatric indication 4.1 *`, ignore.case = TRUE) | (result2$active_substance == "everolimus" & result2$authorisation_date == "2011-09-02") # table(result2$paediatricindication) # table(result2$`paediatric indication 4.1 *`) result2$paediatricindication[result2$active_substance == "nelarabine"] <- TRUE # checks # table(result2$paediatricindication) # table(result2$`paediatric indication 4.1 *`) # table(result2$paediatricindication) # table(result2$paediatricindication, # result2$`paediatric indication 4.1 *`) # shorten active substance result2$active_substance <- substring(result2$active_substance, 1, 16) # create hoverinfo result2$hoverinfo <- sapply( seq(from = 1, to = nrow(result2)), FUN = function(x) paste0( result2[x, "active_substance"], ":<br>", "- authorised on: ", result2[x, "authorisation_date"], "<br>", "- for children: ", ifelse(result2[x, "paediatricindication"], "YES", "no"), "<br>", "- first trial with childen: ", result2[x, "trial_start"], " (", result2[x, "trial_id"], ")<br>", gsub("(.{1,50})(\\s|$)", "\\1\n", result2[x, "trial_title"]))) # plotly colors tmp.difftime <- difftime(result2$authorisation_date, result2$trial_start, units = "days") / 365.25 result2$segmentcolor <- ifelse(0 <= tmp.difftime & tmp.difftime < 3, "suboptimal", ifelse(3 <= tmp.difftime, "early", "late")) # order and factor necessary for plotting correctly result2 <- result2[ order(result2$authorisation_date), ] result2$active_substance <- factor( x = result2$active_substance, levels = unique(result2$active_substance), ordered = TRUE) # vertical line vline <- function(x = 0, color = "blue") { list(type = "line", yref = "paper", line = list(color = color), y0 = 0, y1 = 1, x0 = x, x1 = x)} # plotly p <- plot_ly( data = result2, hoverinfo = "text", text = ~hoverinfo, color = I("black"), width = 1000, height = 2000, symbols = c("circle-open", "star"), colors = c("#00BFC4", "gray", "gray")) %>% add_markers( x = ~authorisation_date, y = ~active_substance, symbol = ~paediatricindication, marker = list(size = 14)) %>% add_markers( x = ~trial_start, y = ~active_substance) %>% add_segments( x = ~trial_start, xend = ~authorisation_date, y = ~active_substance, yend = ~active_substance, color = ~segmentcolor) %>% group_by(active_substance) %>% layout( margin = list(l = 100), font = list(family = "Helvetica", size = 12), # 13 autosize = FALSE, xaxis = list(title = ""), yaxis = list(title = "", dtick = 1, range = list(-1, nrow(result2) * 0.975)), shapes = list(vline(Sys.Date())), title = paste0("Time relation of EU market authorisation for cancer medicine\nand ", "start of first trial with children with cancer")) %>% hide_legend() %>% add_annotations( x = "1990-01-01", y = ~round(nrow(result2) * 0.95), align = "left", text = paste0("o Cancer medicine first authorised\n", "* Authorised for use in children\n on the date of this analysis\n", ". Start of first trial with children\n", "\npaediatricdata.eu ", format(Sys.Date(), format = "%Y-%m-%d")), xanchor = "left", yanchor = "top", showarrow = FALSE) # save save.image() |
1 2 3 4 5 6 7 8 | # read back in saved image data load(".RData") ## produce code # htmltools::browsable(htmltools::tagList(p)) # note: Warnings do not mean absent observations print(htmltools::tagList(p)) |
Finally managed to update this post: The code is now fully compatible with my R package ctrdata version 1.1 (see references). Happy this worked out, even if on the last day of the year.