################################### # SHINY Explorer - global.R ################################### #===== LOAD PACKAGES ----------------------------------------------------------------------------------------------------------- pkg <- c( 'bsplus', 'Cairo', 'colourpicker', 'data.table', 'DT', 'dygraphs', 'fst', 'ggplot2', 'ggiraph', 'ggrepel', 'ggthemes', 'htmltools', 'leaflet', 'mapview', 'plyr', 'RColorBrewer', 'rgdal', 'RMySQL', 'rpivotTable', 'rvest', 'scales', 'shiny', 'shinycssloaders', 'shinyDND', 'shinyjs', 'shinyjqui', 'shinythemes', 'shinyWidgets', 'sp', 'tidyr', 'xts' # 'circlize', 'extrafont', 'GGally', 'ggmap', 'ggspatial', 'ggparallel', 'tmap', ) invisible( lapply(pkg, require, character.only = TRUE) ) # for(conn in dbListConnections(MySQL())) dbDisconnect(conn) #===== GENERAL OPTIONS ---------------------------------------------------------------------------------------------------------- options(spinner.color = '#e5001a', spinner.size = 1, spinner.type = 4) #===== LOAD DATA ---------------------------------------------------------------------------------------------------------------- # From fst shared rep dataset <- read.fst(paste0(data.path, audit, '_dt.fst'), as.data.table = TRUE ) # The following recoding is necessary until a new version of the fst package fixes the bug with dates converted into integers dataset[, `:=`( date.day = as.Date(date.day, origin = '1970-01-01'), date.week = as.Date(date.week, origin = '1970-01-01'), date.month = as.Date(date.month, origin = '1970-01-01'), date.quarter = as.Date(date.quarter, origin = '1970-01-01') )] if(has_consultants){ dt_cons <- read.fst(paste0(data.path, audit, '_pc.fst'), as.data.table = TRUE ) consultants <- read.fst(paste0(data.path, 'consultants.fst'), as.data.table = TRUE ) } # From databases db_conn <- dbConnect(MySQL(), group = 'shiny', dbname = audit) # dataset <- suppressWarnings(data.table(dbReadTable(db_conn, 'shinyexp_dataset'), key = 'HSP_id' ) ) lookups <- suppressWarnings(data.table(dbGetQuery(db_conn, 'SELECT * FROM lookups WHERE domain_id != 9') ) ) vars <- suppressWarnings(data.table(dbReadTable(db_conn, 'vars') ) ) metrics <- suppressWarnings(data.table(dbReadTable(db_conn, 'metrics') ) ) calendar <- suppressWarnings(data.table(dbReadTable(db_conn, 'calendar') ) ) load.tabs <- suppressWarnings(data.table(dbReadTable(db_conn, 'tabs'), key = 'name' ) ) # dt_cons <- suppressWarnings(data.table(dbReadTable(db_conn, 'procedures_consultants'), key = 'proc_id' ) ) completeness <- suppressWarnings(data.table(dbGetQuery(db_conn, 'SELECT item, filter_by FROM completeness WHERE is_active') ) ) dbDisconnect(db_conn) db_conn <- dbConnect(MySQL(), group = 'shiny', dbname = 'common') areas <- suppressWarnings(data.table(dbReadTable(db_conn, 'locations') ) ) centres <- suppressWarnings(data.table(dbReadTable(db_conn, 'centres'), key = 'HSP_id') ) font.table <- suppressWarnings(data.table(dbGetQuery(db_conn, 'SELECT DISTINCT family FROM fonts WHERE is_active ORDER BY family') ) ) maptiles <- suppressWarnings(data.table(dbGetQuery(db_conn, 'SELECT CONCAT(provider, ".", name) AS name, url, attribution FROM maptiles WHERE require_reg = 0 ORDER BY name' ))) # consultants <- suppressWarnings(data.table(dbReadTable(db_conn, 'consultants'), key = 'consultant_id' ) ) dbDisconnect(db_conn) hospitals <- centres[get(audit) == 1] #===== FUNCTIONS ---------------------------------------------------------------------------------------------------------------- # ifelse(x1, x2, NA) ifna <- function(x1, x2) ifelse(x1, x2, NA) # Create a list of variables from specifying one or more "type" (type could be: LGC, CAT, NUM, GEO, TMS) build_uiV <- function(tp){ ui.list <- as.list(vars[nature %in% tp & is_active == 1][order(ordering)][, lookup_id]) names(ui.list) <- vars[nature %in% tp & is_active == 1][order(ordering)][, description] return(ui.list) } # Create a list of reference variables from specifying a "subtab" and a (optional) type build_uiY <- function(tb, tp = 1:4){ ui.list <- as.list(metrics[get(tb) == 1 & type %in% tp ][order(ordering)][, label]) return(ui.list) } # Create a list of locations from specifying a "hierarchy" build_uiG <- function(h){ y <- unique(hospitals[, .( get(h), get(paste0(h, '_id')) )][!(V1 %in% labels.del)][order(V1)]) ui.list <- as.list(y[, V2]) names(ui.list) <- y[, V1] return(ui.list) } # Create a list of values from specifying a variable build_uiF <- function(v){ y <- lookups[domain_id == v, .(lookup_id, description ) ][order(lookup_id)][!(description %in% labels.del)] ui.list <- as.list(y[, lookup_id]) names(ui.list) <- y[, description] return(ui.list) } # Create a combobox of locations for a given tab 'tb', specifying child 'lw' and parent 'lb' area codes, and a parent area id 'ipt' build_uiGeo <- function(tb, hg, lw, ipt){ hg.id <- paste(hg, '_id', sep = '') lw.id <- paste(lw, '_id', sep = '') inp.obj <- paste('cbo_', tb, '_', hg, sep = '') ui.list <- c('TOTAL') if(ipt != 'TOTAL'){ ui.list <- as.list(unique(hospitals[get(hg.id) == ipt][order(get(lw))][, get(lw.id)])) names(ui.list) <- unique(hospitals[get(hg.id) == ipt][order(get(lw))][, get(lw)]) } if(length(ui.list) > 1) ui.list <- c('TOTAL', ui.list) return(ui.list) } # Build the title for most substabs build.title <- function(var.Y, is.Y.ref = TRUE, var.X, var.X2 = NA, fnl.area = NA, has.pct = FALSE, var.G1, var.G2 = 'NONE', var.F, val.F){ ttl <- if(is.Y.ref){ paste( metrics[label == var.Y, title], ifelse(has.pct, '(%)', '')) } else { clear.label(var.Y) } ttl <- paste( ttl, 'by', var.X ) if(!is.na(var.X2)) ttl <- paste(ttl, 'and', var.X2) if(!is.na(fnl.area)) ttl <- paste(ttl, 'in', fnl.area) if(var.G1 != 'NONE') ttl <- paste0( ttl, '
', 'Grouped by ', clear.label(var.G1), if(var.G2 != 'NONE'){ paste(' and', clear.label(var.G2)) } ) if(var.F != 'NONE'){ if(val.F != 'NONE') ttl <- paste0( ttl, '
', 'Filtered by ', clear.label(var.F), ' = ', lookups[domain_id == gsub('X', '', var.F) & lookup_id == val.F, description], '' ) } trimws(gsub(' ', ' ', ttl)) } # Return filter and function for a metric, according to "normal" or "percentage" behaviour mtc.parsed <- function(mtc, pct = FALSE){ if(!pct){ F <- metrics[label == eval(mtc), filter_by] M <- metrics[label == eval(mtc), mutate_as] } else { F <- metrics[label == eval(mtc), filter_pct] M <- metrics[label == eval(mtc), mutate_pct] } return( parse(text = c(F, M) ) ) } # Given a categorical var id (without the leading "X") and one or some of its ids, return the corresponding label(s) get.Xlabel <- function(x.id, x.val = 1){ x.id <- as.numeric(gsub('X', '', x.id)) lookups[domain_id == x.id & lookup_id %in% x.val, description] } # Renames the dataset for pivot table mydt.rename <- function(dt){ nm <- names(dt)[substr(names(dt), 1, 1) == 'X'] nm <- c('HSP_id', 'HSP', 'CCG', 'LAT', 'NHSR', 'CCR', 'CTRY', paste('datec.', c('day', 'week', 'month', 'quarter', 'year'), sep = ''), nm) dt <- dt[, nm, with = FALSE] m1 <- as.data.table(names(dt), key = 'V1') m2 <- data.table(lookups[domain_id <= 1, .( old = paste('X', lookup_id, sep = ''), new = description ) ], key = 'old') names(dt) <- m2[m1][is.na(new), new := old][, new] setnames(dt, c('HSP_id','HSP', 'CCG', 'LAT', 'NHSR', 'CCR', 'CTRY', 'datec.day', 'datec.week', 'datec.month', 'datec.quarter', 'datec.year'), c('Hospital_code','Hospital', 'Comm. Group', 'Area Team', 'NHS Region', 'Comm. Region', 'Country', 'day', 'week', 'month', 'quarter', 'year') ) dt } # Returns a renamed dataset after specifying columns and optionally records to retain get.dt.renamed <- function(columns, records = NA){ columns <- c('HSP_id', 'HSP', 'datec.day', columns[substr(columns, 1, 1) == 'X']) if(is.na(records)){ dt <- dataset[, columns, with = FALSE] } else { dt <- dataset[HSP_id %in% records, columns, with = FALSE] } m1 <- as.data.table(names(dt), key = 'V1') m2 <- data.table(lookups[domain_id <= 1, .( old = paste('X', lookup_id, sep = ''), new = description ) ], key = 'old') names(dt) <- m2[m1][is.na(new), new := old][, new] dt[, Hospital := paste(HSP_id, '-', HSP)] dt[, `:=`(HSP_id = NULL, HSP = NULL)] setnames(dt, 'datec.day', 'day') setcolorder(dt, c('Hospital', setdiff(names(dt), 'Hospital'))) dt[order(Hospital, -day)] } # Renames the exporting dataset for easier reading dt.for.export <- function(dt){ fld2save <- c('date.day', 'date.hour', 'datec.month', 'datec.quarter', 'date.year', 'HSP_id', 'HSP', 'CCG', 'LAT', 'NHSR', 'CCR', 'CTRY') fld2name <- c('day', 'hour', 'month', 'quarter', 'year', 'hospital_code', 'hospital', 'Comm. Group', 'Area Team', 'NHS Region', 'Comm. Region', 'Country') for(idx in 1:length(names(dt))){ if(substr(names(dt)[idx], 1, 1) == 'X'){ fld2save <- c(fld2save, names(dt)[idx]) fld2name <- c(fld2name, lookups[lookup_id == substr(names(dt)[idx], 2, nchar(names(dt)[idx])), description ]) } } dt <- dt[, fld2save, with = FALSE] setnames(dt, fld2save, fld2name) dt } # Clean the filename, keep only alphanum, dash and underline filename.clean <- function(fn){ fn <- gsub('[ \\.]', '-', fn) gsub('[^[:alnum:]_-]', '', fn) } # Clean the title of the plot for exporting plot.title.clean <- function(fn){ fn <- gsub('
', '\n', fn) fn <- gsub('<(.*)>', '', fn) fn } # Trim a dataset based on the values of (usually) a slider input wrt some quantity shrink.dataset <- function(dt, field.from, quant.obj){ setnames(dt, field.from, 'quantity') limInfQuant <- as.numeric(quant.obj)[1]/100 limSupQuant <- as.numeric(quant.obj)[2]/100 if(limInfQuant > 0 | limSupQuant < 1) dt <- dt[ quantity >= quantile(quantity, limInfQuant, na.rm = TRUE) & quantity <= quantile(quantity, limSupQuant, na.rm = TRUE) ] setnames(dt, 'quantity', field.from) return(dt) } # delete number of item in questionaire from label clear.label <- function(lbl){ lbl <- vars[lookup_id == eval(lbl), description] if(grepl('^[[:digit:]]', lbl)) return( substr(lbl, regexpr(' ', lbl) + 1, nchar(lbl)) ) lbl } # return the correct expression for the ggplot mapping argument get.list.aes <- function(lst.aes) { if(!length(lst.aes)) return(NULL) result <- 'aes(' for(idx in 1:length(lst.aes)) result <- paste0(result, names(lst.aes[idx]), ' = ', lst.aes[[idx]], ', ') result <- paste0(substr(result, 1, nchar(result) - 2), ')') eval(parse(text = result)) } # convert a ggplot into its corresponding interactive plot from ggiraph extension gg.to.ggiraph <- function(p, sel.type = 'single', gg.width = 0.8){ ggiraph( code = {print(p)}, width = gg.width, zoom_max = 1, selection_type = sel.type, # selected_css = "", tooltip_offx = 20, tooltip_offy = -10, hover_css = "fill:red;cursor:pointer;r:4pt;opacity-value:0.5;", tooltip_extra_css= "background-color:wheat;color:gray20;border-radius:10px;padding:3pt;", tooltip_opacity = 0.9, pointsize = 12 ) } # calculate both funnel plot limits based on given "total" probability and maximum effect size get.funnel.limits <- function(plevel, maxn, theta1, theta2 = NULL){ ord.magn <- ifelse(nchar(maxn) <= 4, 1, nchar(maxn) - 3) my.funnel <- data.table(x = seq(1, pretty(maxn)[2], 10^(ord.magn - 1) ) ) plevel.inf <- (1 - plevel)/2 plevel.sup <- plevel + plevel.inf if(!is.null(theta2)){ my.funnel[, liminf := qnorm(plevel.inf, theta1, theta2/sqrt(x)) ] my.funnel[, limsup := qnorm(plevel.sup, theta1, theta2/sqrt(x)) ] } else { my.funnel[, nu := qbinom(plevel.inf, x, theta1) ] my.funnel[, num := ( pbinom(nu, x, theta1) - plevel.inf ) ] my.funnel[, den := ( pbinom(nu, x, theta1) - pbinom(nu - 1, x, theta1) ) ] my.funnel[, alpha := num / den ] my.funnel[, liminf := (nu - alpha) / x ] my.funnel[, nu := qbinom(plevel.sup, x, theta1) ] my.funnel[, num := ( pbinom(nu, x, theta1) - plevel.sup ) ] my.funnel[, den := ( pbinom(nu, x, theta1) - pbinom(nu - 1, x, theta1) ) ] my.funnel[, alpha := num / den ] my.funnel[, limsup := (nu - alpha) / x ] } return(my.funnel[, .(x, liminf, limsup) ]) } # recode multiple fields as ordered factors, using themselves or the lookup table as reference for the labels/levels recode.factors <- function(dt, flds, lkps = FALSE){ for(fn in flds){ if(lkps){ lk <- sub('X', '', fn) lvls <- lookups[domain_id == lk, lookup_id ] lbls <- lookups[domain_id == lk, description ] } else { lvls <- sort(unique(dt[[fn]])) lvls <- lvls[!(lvls %in% c(NA))] lbls <- lvls } dt[, (fn) := factor(dt[[fn]], levels = lvls, labels = lbls, ordered = TRUE) ] } } # get long name of day/month starting from 3-chars name get.longname <- function(shortname){ longname <- lookups[domain_id == 200091 & lookup_id == lookups[domain_id == 200092 & description == shortname, lookup_id], description] if(length(longname) > 0) return(longname) longname <- lookups[domain_id == 200093 & lookup_id == lookups[domain_id == 200094 & description == shortname, lookup_id], description] if(length(longname) > 0) return(longname) shortname } vget.longname <- Vectorize(get.longname) # get the summary dataset for the different tabs V1 get.y.tms <- function(dt, var.Y, var.X = NA, grp1 = NA, grp2 = NA, tt = NULL, pct = FALSE, flt.var = NA, flt.val = NA){ # retrieve formulas for calculate the metric mtc.def <- mtc.parsed(var.Y, pct) # apply the filter that define the metric y <- dt[ eval(mtc.def[1]) ] # if needed, determine the independent var X if(!is.na(var.X)) y[, X := get(var.X) ][!is.na(X)] # if needed, filter over the specified variable if(!is.na(flt.var)){ if(flt.val != 'NONE'){ flt.lbl <- lookups[domain_id == gsub('X', '', flt.var) & lookup_id == flt.val, description] y <- y[ get(flt.var) == flt.lbl ] } } # calculate the metrics if(is.na(grp1)){ # ungrouped case y <- y[, .( Y = eval(mtc.def[2]) ), X ][, G := 1] } else { if(is.na(grp2)){ # grouped case, one grouping var y <- y[, G := get(grp1) ][!(G %in% labels.del)][, .( Y = eval(mtc.def[2]) ), .(X, G) ] } else { # grouped case, two grouping vars y <- y[, G1 := get(grp1) ][!(G1 %in% labels.del)][, G2 := get(grp2) ][!(G2 %in% labels.del)] y <- y[, .( Y = eval(mtc.def[2]) ), .(X, G1, G2) ][, G := 1] } } # if needed, calculate the tooltip if(!is.null(tt)) y[, ttip := paste0( if(!is.na(tt[1])){ paste0('', tt[1], '
') }, tt[2], ': ', X, '
', if(!is.na(grp1)){ if(is.na(grp2)){ paste0(clear.label(grp1), ': ', G, '
') } else { paste0(clear.label(grp1), ': ', G1, '
', clear.label(grp2), ': ', G2, '
') } }, metrics[label == var.Y, title], ': ', if(pct) { paste0(100*Y, '%') } else {Y}, '' )] return(y) } # return the summary dataset to display in the "table" subtab get.dt.tbl <- function(dt, tblY, lblX, fld.to.order = NA, ord.desc = FALSE, is.time = FALSE, col.bars = '#80cdc1', col.fonts = 'black', pal.scale = 'Spectrum', n.cols = 7, reverse = FALSE, flt.var = NA, flt.val = NA, grp.var = NA, grp.type = NA, grp.stype = NA ){ ## A1- DETERMINE THE MAIN DATASET ----------------------------------------------------------------------- # Tables display following columns: # - type = 1 (quantities) : 'Count', 'Quota' # - type = 2 (measure) : 'Count', 'Quota', 'Percent', 'Index' # - type >= 3 (metrics) : 'Value', 'Index' # filtering if(!is.na(flt.var)){ if(!is.na(flt.val)){ flt.lbl <- lookups[domain_id == gsub('X', '', flt.var) & lookup_id == flt.val, description] dt <- dt[ get(flt.var) == flt.lbl ] } } # query the type of the variable var.type <- min(3, metrics[label == tblY, type]) # query filter and formula for counting (type = 1) and metrics (type >= 3) YF <- metrics[label == tblY, filter_by] YM <- metrics[label == tblY, mutate_as] # filter the dataset for the main table y <- dt[eval( parse(text = YF) )] # calculate and store total if metric if(var.type == 3) yt <- y[, eval( parse(text = YM) ) ] # calculate counting or metric if(is.time){ y <- y[ , .( m = eval( parse(text = YM) ) ), X ][order(-X)] } else { y <- y[ , .( m = eval( parse(text = YM) ) ), .( X = as.character(X) ) ] } if(var.type == 3){ # calculate Index if metric y[, yt := yt][, Index := round(m / yt, 4)][, yt := NULL] } else { # calculate and store total if counting yt <- y[, sum(m)] y[, Quota := round(m / sum(m), 4)] } # define row for the total df.total <- data.frame('TOTAL', yt, NA) if(var.type == 2){ # calculataion percentages as above if measure (type = 2) YFP <- metrics[label == tblY, filter_pct] YMP <- metrics[label == tblY, mutate_pct] yp <- dt[eval( parse(text = YFP) )] ytp <- yp[, eval( parse(text = YMP) ) ] yp <- yp[ , .( m = eval( parse(text = YMP) ) ), .( X = as.character(X) ) ] yp[, ytp := ytp][, Index := round(m / ytp, 4)][, ytp := NULL] setkey(y, 'X') setkey(yp, 'X') y <- y[yp] df.total <- data.frame('TOTAL', yt, NA, ytp, NA) } # order table according to user choices: variable + type setnames(y, c(lblX, tbl.headings[[var.type]])) if(!is.na(fld.to.order)) y <- y[order(get(fld.to.order), decreasing = ord.desc)] # add total row at the top y <- rbindlist(list( df.total, y) ) # rename columns setnames(y, c(lblX, tbl.headings[[var.type]])) ## A2- IF REQUESTED, DETERMINE AND ADD TO THE ABOVE THE GROUP DATASET ----------------------------------- # Tables display following columns: # - type = 1 (quantities): # - 'Count' # - 'Quota': vs Total National, vs Grouped Item # - type = 2 (measure) : 'Count', 'Quota', 'Percent', 'Index' # - 'Count' # - 'Quota': vs Total National, vs Grouped Item # - 'Percentage': vs Total National, vs Grouped Item # - 'Index' (vs Grouped Items only) # - type >= 3 (metrics)'', '' # - 'Value' # - 'Index' (vs Grouped Items only) if(!is.na(grp.var)){ dt[, G := get(grp.var) ] yg <- dt[!(G %in% labels.del)][eval( parse(text = YF) )] if(var.type == 1){ yg <- yg[, .( m = eval( parse(text = YM) ) ), .( X = as.character(X), G ) ] yg <- dcast.data.table(yg, X~G, value.var = 'm', fill = 0) yn <- names(yg) yg <- rbindlist( list( data.frame('TOTAL', yg[, lapply(.SD, sum), .SDcols = 2:ncol(yg)]), yg) ) names(yg) <- yn y <- merge(y, yg, by.x = lblX, by.y = 'X') if(grp.type == 'Quota'){ y[, T := rowSums(y[,4:ncol(y), with = FALSE])] yq <- if(grp.stype == 'R'){ y[, lapply(.SD, function(x) round(x/T, 4)), .SDcols = 4:ncol(y)] } else if(grp.stype == 'C'){ y[, lapply(.SD, function(x) round(x/x[1], 4)), .SDcols = 4:ncol(y)] } else { y[, lapply(.SD, function(x) round(x/sum(T[2:.N]), 4)), .SDcols = 4:ncol(y)] } yq[, T := NULL] if(grp.stype == 'C') yq[1, names(yq) := NA] y <- cbind(y[, 1:3], yq) } } else if(var.type == 2){ } else { ygt <- t(yg[, .( m = eval( parse(text = YM) ) ), G ][, m]) yg <- yg[, .( m = eval( parse(text = YM) ) ), .( X = as.character(X), G ) ] yg <- dcast.data.table(yg, X~G, value.var = 'm', fill = NA) yn <- names(yg) yg <- rbindlist( list( data.frame('TOTAL', ygt), yg) ) names(yg) <- yn y <- merge(y, yg, by.x = lblX, by.y = 'X') if(grp.type == 'Index'){ if(grp.stype == 'R'){ yq <- y[, lapply(.SD, function(x) round(x/Value, 4)), .SDcols = 4:ncol(y)] } else if(grp.stype == 'C'){ yq <- y[, lapply(.SD, function(x) round(x/x[1], 4)), .SDcols = 4:ncol(y)] yq[1, names(yq) := NA] } y <- cbind(y[, 1:3], yq) } } } ## B- BUILD THE TABLE ----------------------------------------------------------------------------------- t <- datatable(y, rownames = FALSE, selection = 'none', class = 'cell-border stripe hover nowrap', extensions = c('Buttons', 'FixedColumns', 'Scroller'), options = list( scrollX = TRUE, scrollY = 400, scroller = TRUE, buttons = c('copy', 'csv', 'excel', 'pdf', 'print'), fixedColumns = list(leftColumns = 1), ordering = FALSE, deferRender = TRUE, dom = 'Btip' ) ) ## C- STYLE THE DATATABLE ------------------------------------------------------------------------------- if('Counting' %in% names(y)){ t <- t %>% formatCurrency('Counting', '', digits = 0) } if('Quota' %in% names(y)){ t <- t %>% formatPercentage('Quota', digits = 2) t <- t %>% formatStyle('Quota', color = col.fonts, background = styleColorBar(y[, Quota], col.bars ), backgroundSize = '90% 70%', backgroundRepeat = 'no-repeat', backgroundPosition = 'center' ) } if('Percentage' %in% names(y)){ t <- t %>% formatPercentage('Percentage', digits = 2) t <- t %>% formatStyle('Percentage', color = col.fonts, background = styleColorBar(y[, Percentage], col.bars ), backgroundSize = '90% 70%', backgroundRepeat = 'no-repeat', backgroundPosition = 'center' ) } if('Value' %in% names(y)){ t <- t %>% formatCurrency('Value', '', digits = 2) t <- t %>% formatStyle('Value', color = col.fonts, background = styleColorBar(y[, Value], col.bars ), backgroundSize = '90% 70%', backgroundRepeat = 'no-repeat', backgroundPosition = 'center' ) } if('Index' %in% names(y)){ t <- t %>% formatCurrency('Index', '', digits = 3) pal <- brewer.pal(n.cols + 1, pal.scale) if(reverse) pal <- rev(pal) t <- t %>% formatStyle('Index', backgroundColor = styleInterval( quantile(y$Index, prob = seq(0, 1, length = n.cols + 1), na.rm = TRUE)[2:(n.cols + 1)], pal ) ) } if(!is.na(grp.var)){ tg.names <- names(y[, 4:ncol(y)]) tg.range <- y[2:nrow(y), 4:ncol(y)] if(var.type == 1){ if(grp.type == 'Counting'){ t <- t %>% formatCurrency(tg.names, '', digits = 0) # if(grp.stype == 'T'){ # t <- t %>% formatStyle(tg.names, # background = styleColorBar(as.matrix(tg.range), 'lightblue'), # backgroundSize = '90% 80%', # backgroundRepeat = 'no-repeat', # backgroundPosition = 'center' # ) # } } else { t <- t %>% formatPercentage(tg.names, digits = 2) } } else if(var.type == 2){ } else { t <- t %>% formatCurrency(tg.names, '', digits = 2 + as.numeric(grp.type == 'Index')) } } # format TOTAL first row as bigger font size, bold typeface. t <- t %>% formatStyle(lblX, target = 'row', backgroundColor = styleEqual('TOTAL', 'black' ), color = styleEqual('TOTAL', 'white' ), fontWeight = styleEqual('TOTAL', 'bold'), fontSize = styleEqual('TOTAL', '130%' ) ) ## RETURN ----------------------------------------------------------------------------------------------- t } # returns the summary dataset for the BARPLOT as a 2-elements list: 1st the dataset, 2nd the national total get.dt.brp <- function(dt, var.Y, var.X = NA, grp1 = NA, grp2 = NA, tt = NULL, pct = FALSE, flt.var = NA, flt.val = NA, show.NA = FALSE, ordering = 1){ # retrieve formulas for calculate the metric mtc.def <- mtc.parsed(var.Y, pct) # apply the filter that define the metric y <- dt[ eval(mtc.def[1]) ] # if needed, determine the independent var X if(!is.na(var.X)) y[, X := get(var.X)][!is.na(X)] # y[, X := as.character(X)] # if needed, filter over the specified variable if(!is.na(flt.var)){ if(flt.val != 'NONE'){ flt.lbl <- lookups[domain_id == gsub('X', '', flt.var) & lookup_id == flt.val, description] y <- y[ get(flt.var) == flt.lbl ] } } # calculate the metrics if(is.na(grp1)){ # ungrouped case yt <- as.numeric(y[, .( Y = eval(mtc.def[2]) ) ]) y <- y[, .( Y = eval(mtc.def[2]) ), X ][, G := 1] } else { if(is.na(grp2)){ # grouped case, one grouping var y <- y[, G := get(grp1) ][!(G %in% labels.del)] yt <- y[, .( Y = eval(mtc.def[2]) ), G ] y <- y[, .( Y = eval(mtc.def[2]) ), .(X, G) ] if(show.NA) y <- setDT(complete(y, X, nesting(G), fill = list(Y = 0) ) ) } else { # grouped case, two grouping vars y <- y[, G1 := get(grp1) ][!(G1 %in% labels.del)][, G2 := get(grp2) ][!(G2 %in% labels.del)] yt <- y[, .( Y = eval(mtc.def[2]) ), .(G1, G2) ] y <- y[, .( Y = eval(mtc.def[2]) ), .(X, G1, G2) ][, G := 1] } } # if needed, calculate the tooltip if(!is.null(tt)) y[, ttip := paste0( if(!is.na(tt[1])){ paste0('', tt[1], '
') }, tt[2], ': ', X, '
', if(!is.na(grp1)){ if(is.na(grp2)){ paste0(clear.label(grp1), ': ', G, '
') } else { paste0(clear.label(grp1), ': ', G1, '
', clear.label(grp2), ': ', G2, '
') } }, metrics[label == var.Y, title], ': ', if(pct) { paste0(100*Y, '%') } else {Y}, '' )] # cancel factor on X y[, X := as.character(X)] # order bars y <- switch(ordering, '1' = y[order(X)], '2' = y[order(-X)], '3' = y[order(Y)], '4' = y[order(-Y)] ) return( list(y, yt, ytg = NA, ytg2 = NA) ) } # returns the summary dataset for the BOXPLOT as a 2-elements list: 1st the dataset, 2nd the outliers get.dt.bxp <- function(dt, var.Y, var.X = NA, grp1 = NA, grp2 = NA, tt = NULL, flt.var = NA, flt.val = NA){ cols = c('X') y0 <- dt[, Y := get(var.Y) ] if(!is.na(var.X)) y0[, X := get(var.X)][!is.na(X)] if(!is.na(grp1)){ y0 <- y0[, G := get(grp1) ][!(G %in% labels.del)] cols = c(cols, 'G') if(!is.na(grp2)){ y0 <- y0[, G2 := get(grp2) ][!(G2 %in% labels.del)] cols = c(cols, 'G2') } } # if needed, filter over the specified variable if(!is.na(flt.var)){ if(flt.val != 'NONE'){ flt.lbl <- lookups[domain_id == gsub('X', '', flt.var) & lookup_id == flt.val, description] y0 <- y0[ get(flt.var) == flt.lbl ] } } y <- y0[, .SD, .SDcols = c(cols, 'Y')] setkeyv(y, cols) yt <- y[, .(V0 = sum(is.na(Y))), cols] y <- y[!(Y %in% labels.del)] yt <- yt[y[, .( V1 = .N, V2 = median(Y), V3 = quantile(Y)[2], V4 = quantile(Y)[4], V5 = IQR(Y), V6 = paste(range(Y), collapse = '-'), V11 = round(mean(Y), 2), V12 = round(sd(Y), 2) ), cols ]] yt <- yt[y[, .(V7 = sum( Y < (quantile(Y)[2] - IQR(Y) * 1.5) | Y > (quantile(Y)[4] + IQR(Y) * 1.5) )), cols ]] yt[, ttip := as.factor(paste0( tt, ': ', X, '
', if(!is.na(grp1)) paste0(clear.label(grp1), ': ', G, '
'), if(!is.na(grp2)) paste0(clear.label(grp2), ': ', G2, '
'), 'N. Procedures: ', format(V1, big.mark = ','), '

', toupper(clear.label(var.Y)), '
    ', '
  • Completeness: ', ifelse(V0 > 0, paste0('', paste0(round(100*(1-V0/(V0+V1)), 2), '%'), ''), '100%'), '
    ', '
  • Median: ', V2, '
    ', '
  • 1st quartile: ', V3, '
    ', '
  • 3rd quartile: ', V4, '
    ', '
  • IQR: ', V5, '
    ', '
  • Range: ', V6, '
    ', '
  • N. Outliers: ', ifelse(V7 > 0, paste0('', V7, ''), '--'), '
    ', '
  • Mean: ', V11, '
    ', '
  • St Dev: ', V12, '
' ))] y <- y[yt[, c(cols, 'ttip'), with = FALSE]] # outliers y0[, D := datec.day] y.out <- y0[!(Y %in% labels.del)][Y < (quantile(Y)[2] - IQR(Y) * 1.5) | Y > (quantile(Y)[4] + IQR(Y) * 1.5)][, c(cols, 'D', 'Y'), with = FALSE] y.out[, ttip := paste0( tt, ': ', X, '
', if(!is.na(grp1)) paste0(clear.label(grp1), ': ', G, '
'), if(!is.na(grp2)) paste0(clear.label(grp2), ': ', G2, '
'), clear.label(var.Y), ': ', Y, '
', 'Date of Procedure: ', D )] # cancel factor on X y[, X := as.character(X)] return( list(y, y.out) ) } # returns the summary dataset for the HEATMAPS get.dt.hmp <- function(dt, var.Y, var.X = NA, var.X2 = NA, grp1 = NA, grp2 = NA, tt = NULL, pct = FALSE, flt.var = NA, flt.val = NA, mtc.rescale = NA, ordering = 1, show.NA = FALSE){ # retrieve formulas for calculate the metric mtc.def <- mtc.parsed(var.Y, pct) # apply the filter that define the metric y <- dt[ eval(mtc.def[1]) ] # if needed, determine the independent vars X and X2 if(!is.na(var.X)) y[, X := get(var.X)][!is.na(X)] if(!is.na(var.X2)) y[, X2 := get(var.X2)][!is.na(X2)] # if needed, filter over the specified variable if(!is.na(flt.var)){ if(flt.val != 'NONE'){ flt.lbl <- lookups[domain_id == gsub('X', '', flt.var) & lookup_id == flt.val, description] y <- y[ get(flt.var) == flt.lbl ] } } # calculate the metrics if(is.na(grp1)){ # ungrouped case y <- y[, .( Y = eval(mtc.def[2]) ), .(X, X2) ] if(show.NA) y <- setDT(complete(y, X, nesting(X2)) ) } else { if(is.na(grp2)){ # grouped case, one grouping var y <- y[, G := get(grp1) ][!(G %in% labels.del)] y <- y[, .( Y = eval(mtc.def[2]) ), .(X, X2, G) ] if(show.NA) y <- setDT(complete(y, X, nesting(X2, G) ) ) } else { # grouped case, two grouping vars y <- y[, G1 := get(grp1) ][!(G1 %in% labels.del)] y <- y[, G2 := get(grp2) ][!(G2 %in% labels.del)] y <- y[, .( Y = eval(mtc.def[2]) ), .(X, X2, G1, G2) ][, G := 1] if(show.NA) y <- setDT(complete(y, X, nesting(X2, G1, G2) ) ) } } # calculate the tooltip if(length(tt)){ y[, ttip := paste0( tt[1], ': ', X, '
', tt[2], ': ', X2, '
', if(!is.na(grp1)){ if(is.na(grp2)){ paste0(clear.label(grp1), ': ', G, '
') } else { paste0(clear.label(grp1), ': ', G1, '
', clear.label(grp2), ': ', G2, '
') } }, metrics[label == var.Y, title], ': ', if(pct) { paste0(100*Y, '%') } else {Y}, '' )] } # Rescaling along X-geo or X2-time if(!is.na(mtc.rescale)) y <- setDT( ddply(y, .(get(mtc.rescale)), transform, Y = round(rescale(Y) * 100, 0) ) ) if(ordering){ # cancel factor on X y[, X := as.character(X)] # order bars y <- switch(ordering, '1' = y[order(X)], '2' = y[order(-X)], '3' = y[order(Y)], '4' = y[order(-Y)] ) } return( list(y, yt = NA, ytg = NA, ytg2 = NA) ) } # return only the X-items with positions indicated by vals trim.dt.X <- function(dt, vals){ if(length(vals) == 0) vals <- c(1, length(unique(dt$X))) y <- dt[X %in% unique(dt$X)[vals[1]:vals[2] ] ] y$X <- factor(y$X, levels = unique(y$X)) return(y) } # return only the X-items with positions indicated by vals trim.dt.X.bxp <- function(dt, vals, ordering){ if(length(vals) == 0) vals <- c(1, length(unique(dt$X))) yo <- switch(ordering, '1' = unique(dt[, .(X)])[order(X)][vals[1]:vals[2] ], '2' = unique(dt[, .(X)])[order(-X)][vals[1]:vals[2] ], '3' = dt[, median(Y), X][order(V1), .(X)][vals[1]:vals[2] ], '4' = dt[, median(Y), X][order(-V1), .(X)][vals[1]:vals[2] ] ) yo <- unlist(yo) y <- dt[X %in% yo] return( list(y, yo) ) } # embellish the dataset to be downloaded from the user as CSV file dt.csv.output <- function(dt){ y <- copy(dt) y[, `:=`(ttip = NULL, data_id = NULL, clk = NULL)] } #===== OPTIONAL TABS ------------------------------------------------------------------------------------------------------------ # if(load.tabs['consultants', status]){ } # if(load.tabs['patients', status]){ } # Build Completeness table, with all different areas and timespans if(load.tabs['completeness', status]){ cmp.tbl <- data.table() for(itm in 1:nrow(completeness)){ W = parse(text = paste0(completeness[itm, filter_by])) cmp.tbl <- rbindlist(list( cmp.tbl, cbind( item = completeness[itm, item], dataset[, .( count = sum( eval(W) ), total = .N ), .(HSP_id, year = date.year, quarter = date.quarter, month = date.month) ][order(HSP_id, month)] ) )) } setkey(cmp.tbl, 'HSP_id') setkey(hospitals, 'HSP_id') cmp.tbl <- cmp.tbl[hospitals[, .(HSP_id, CCG_id, CTRY_id) ] ] } #===== VARIABLES/LABELS ---------------------------------------------------------------------------------------------------------------- # List for the GEOGRAPHY combo box locations <- c( 'Country' = 'CTRY', 'Comm. Region' = 'CCR', 'NHS Region' = 'NHSR', 'Area Team' = 'LAT', 'Comm. Group' = 'CCG', 'Hospital' = 'HSP' ) # List for the TIME REFERENCE single period in heatmaps hospitals timeref <- c( 'Year' = 'datec.year', 'Quarter' = 'datec.quarter', 'Month of Year' = 'month_st', 'Day of Month' = 'day_nid', 'Day of Week' = 'day_st', 'Daypart' = 'daypart', 'Hour of Day' = 'date.hour' ) # List for the TIME REFERENCE single period in timeseries timeper <- c( 'Year' = 'datec.year', 'Quarter' = 'datec.quarter', 'Month' = 'datec.month', 'Week' = 'datec.week', 'Day' = 'datec.day' ) # List for the DATES double combo date.range <- c( start = max(dataset$date.day) - 365*3 - 1, min = min(dataset$date.day), max = max(dataset$date.day) ) # Values to delete from labels labels.del <- c( 'Unknown', 'Not Applicable', 'Not measured', 'Not known', 'Not specified', NA, '' ) # List of Function to apply to numeric measures fun.measures <- c( 'Mean' = 'mean', 'Minimum' = 'min', 'Maximum' = 'max', 'Interquartile Range' = 'IQR', 'Standard Deviation' = 'sd' ) # Default color/palette pal.default <- c('col' = 'steelblue3', 'cat' = 'Dark2', 'seq' = 'YlGnBu', 'div' = 'RdBu', 'na' = 'grey62') # List of palettes to be used with ColourBrewer package: lst.palette <- list( 'SEQUENTIAL' = c( # ordinal data where (usually) low is less important and high is more important 'Blues' = 'Blues', 'Blue-Green' = 'BuGn', 'Blue-Purple' = 'BuPu', 'Green-Blue' = 'GnBu', 'Greens' = 'Greens', 'Greys' = 'Greys', 'Oranges' = 'Oranges', 'Orange-Red' = 'OrRd', 'Purple-Blue' = 'PuBu', 'Purple-Blue-Green' = 'PuBuGn', 'Purple-Red' = 'PuRd', 'Purples' = 'Purples', 'Red-Purple' = 'RdPu', 'Reds' = 'Reds', 'Yellow-Green' = 'YlGn', 'Yellow-Green-Blue' = 'YlGnBu', 'Yellow-Orange-Brown' = 'YlOrBr', 'Yellow-Orange-Red' = 'YlOrRd' ), 'DIVERGING' = c( # ordinal data where both low and high are important (i.e. deviation from some reference "average" point) 'Brown-Blue-Green' = 'BrBG', 'Pink-Blue-Green' = 'PiYG', 'Purple-Red-Green' = 'PRGn', 'Orange-Purple' = 'PuOr', 'Red-Blue' = 'RdBu', 'Red-Grey' = 'RdGy', 'Red-Yellow-Blue' = 'RdYlBu', 'Red-Yellow-Green' = 'RdYlGn', 'Spectral' = 'Spectral' ), 'QUALITATIVE' = c( # categorical/nominal data where there is no logical order 'Accent' = 'Accent', 'Dark2' = 'Dark2', 'Paired' = 'Paired', 'Pastel1' = 'Pastel1', 'Pastel2' = 'Pastel2', 'Set1' = 'Set1', 'Set2' = 'Set2', 'Set3' = 'Set3' ) ) # list of labels for download buttons btndwn.text <- c('Save Dataset as CSV', 'Save Chart as PNG', 'Save Static Map as PNG', 'Save Interactive Map as HTML', 'Save Table as CSV') # list of options for charts point.shapes <- c('circle' = 21, 'square' = 22, 'diamond' = 23, 'triangle up' = 24, 'triangle down' = 25) line.types <- c('dashed', 'dotted', 'solid', 'dotdash', 'longdash', 'twodash') face.types <- c('plain', 'bold', 'italic', 'bold.italic') val.lbl.pos <- list( 'Inside' = list('Vertical' = c(0.5, 1.5), 'Horizontal' = c( 1.2, 0.2) ), 'Outside' = list('Vertical' = c(0.4, -0.3), 'Horizontal' = c(-0.2, 0.2) ) ) lbl.format <- function(y, type, is.pct = FALSE){ if(type == 1){ format(y, big.mark = ',', nsmall = 0) } else if(type == 2){ if(is.pct){ paste0(format(round(100 * y, 2), nsmall = 2), '%') } else { format(y, big.mark = ',', nsmall = 0) } } else { format(y, nsmall = 2) } } # default probabilities and colors for funnel plots limits funnel.defaults <- list( 'prob.tot' = c('90%' = 0.9, '95%' = 0.95, '98.5%' = 0.985, '99%' = 0.99, '99.8%' = 0.998, '99.9%' = 0.999), 'prob.sel' = c(0.998, 0.95), 'col.tot' = c('green', 'red', 'blue', 'black', 'cyan', 'magenta', 'yellow', 'gray'), 'col.sel' = c('green', 'red'), 'type.tot' = line.types, 'type.sel' = c('solid', 'dotted') ) # table headings tbl.headings <- list( 'cnt' = c('Counting', 'Quota'), 'mtc' = c('Counting', 'Quota', 'Percentage', 'Index'), 'msr' = c('Value', 'Index') ) # table ordering variables tbl.orders <- list( 'cnt' = c('Counting'), 'mtc' = c('Counting', 'Percentage'), 'msr' = c('Value') ) # plot caption plt.caption <- paste0('@', year(Sys.Date()), ' ATG') #===== STYLES ---------------------------------------------------------------------------------------------------------------- # add text at the left of the upper navbar navbarPageWithText <- function(..., text) { navbar <- navbarPage(...) textEl <- tags$p(class = "navbar-text", text) navbar[[3]][[1]]$children[[1]] <- htmltools::tagAppendChild( navbar[[3]][[1]]$children[[1]], textEl) navbar } # return correct spacing for axis labels rotation lbl.plt.rotation = function(angle, position = 'x'){ positions = list(x = 0, y = 90, top = 180, right = 270) rads = (angle - positions[[ position ]]) * pi / 180 hjust = 0.5 * (1 - sin(rads)) vjust = 0.5 * (1 + cos(rads)) element_text(angle = angle, vjust = vjust, hjust = hjust) } # global style for ggplot charts my.ggtheme <- function(g, xaxis.draw = FALSE, yaxis.draw = FALSE, axis.draw = FALSE, ticks.draw = FALSE, axis.colour = 'black', axis.size = 0.1, hgrid.draw = FALSE, vgrid.draw = FALSE, grids.colour = 'black', grids.size = 0.1, grids.type = 'dotted', labels.rotation = c(45, 0), labels.rotate = FALSE, bkg.colour = 'white', font.size = 6, ttl.font.size.mult = 1.2, ttl.face = 'bold', legend.pos = 'bottom', plot.border = FALSE, font.family = 'Arial' ){ g <- g + theme( text = element_text(family = font.family), plot.title = element_text(hjust = 0, size = rel(1.2) ), # hjust: 0-left, 0.5-center, 1-right plot.background = element_blank(), plot.margin = unit(c(1, 0.5, 0, 0.5), 'lines'), # space around the plot as in: TOP, RIGHT, BOTTOM, RIGHT plot.caption = element_text(size = 8, face = 'italic'), axis.line = element_blank(), axis.ticks = element_blank(), axis.text = element_text(size = font.size, color = axis.colour), axis.text.x = element_text(angle = labels.rotation[1], hjust = 1), # vjust = 0.5), axis.text.y = element_text(angle = labels.rotation[2]), # , hjust = , vjust = ), axis.title = element_text(size = font.size * (1 + ttl.font.size.mult), face = ttl.face), axis.title.x = element_text(vjust = -0.3), axis.title.y = element_text(vjust = 0.8, margin = margin(0, 10, 0, 0) ), legend.text = element_text(size = 6), legend.title = element_text(size = 8), legend.title.align = 1, legend.position = legend.pos, legend.background = element_blank(), legend.spacing = unit(0, 'cm'), # legend.key = element_blank(), legend.key.size = unit(0.2, 'cm'), legend.key.height = unit(0.4, 'cm'), legend.key.width = unit(1, 'cm'), panel.background = element_rect(fill = bkg.colour, colour = bkg.colour), panel.border = element_blank(), panel.grid = element_blank(), panel.spacing.x = unit(3, 'lines'), panel.spacing.y = unit(2, 'lines'), strip.text = element_text(hjust = 0.5, size = font.size * (1 + ttl.font.size.mult), face = ttl.face), strip.background = element_blank() ) if(plot.border) g <- g + theme( panel.border = element_rect(colour = axis.colour, size = axis.size, fill = NA) ) if(axis.draw){ g <- g + theme( axis.line = element_line(color = axis.colour, size = axis.size ) ) } else { if(xaxis.draw) g <- g + theme( axis.line.x = element_line(color = axis.colour, size = axis.size ) ) if(yaxis.draw) g <- g + theme( axis.line.y = element_line(color = axis.colour, size = axis.size ) ) } if(ticks.draw) g <- g + theme( axis.ticks = element_line(color = axis.colour, size = axis.size ) ) if(hgrid.draw & vgrid.draw){ g <- g + theme( panel.grid.major = element_line(colour = grids.colour, size = grids.size, linetype = grids.type ) ) } else{ if(vgrid.draw) g <- g + theme( panel.grid.major.x = element_line(colour = grids.colour, size = grids.size, linetype = grids.type ) ) if(hgrid.draw) g <- g + theme( panel.grid.major.y = element_line(colour = grids.colour, size = grids.size, linetype = grids.type ) ) } if(labels.rotate){ g <- g + theme( axis.text.x = element_text(hjust = 1, angle = 45 ) ) } return(g) } #===== MAPS ---------------------------------------------------------------------------------------------------------------- proj.wgs <- '+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0' UK_centre <- c(-2.421976, 53.825564) UK_bounds <- c(-8.3, 49.9, 1.8, 59.0 ) # List for the MAPTILES combo box tiles.list <- as.list(maptiles[, url]) names(tiles.list) <- maptiles[, name] tile.ini <- tiles.list$CartoDB.Positron loca.map <- c('CCG', 'LAT', 'NHSR', 'CCR', 'CTRY') loca.ini <- 'LAT' pal.ini <- 'Blues' # c("#CDC673", "#90EE90", "#20B2AA") # icons for hospitals: green = nhs, red = private hsp.icons <- awesomeIcons( icon = 'h-square', library = 'fa', squareMarker = TRUE, markerColor = sapply(centres$type, function(x) if(x == 1){ "lightgreen" } else { "lightred" }), iconColor = 'white' ) # list of classification methods, to be used with classInt and ColorBrewer packages class.methods <- c( # 'Fixed' = 'fixed', # need an additional argument fixedBreaks that lists the n+1 values to be used 'Equal Intervals' = 'equal', # the range of the variable is divided into n part of equal space 'Quantiles' = 'quantile', # each class contains (more or less) the same amount of values # 'Pretty Integers' = 'pretty', # sequence of about ‘n+1’ equally spaced ‘round’ values which cover the range of the values in ‘x’. The values are chosen so that they are 1, 2 or 5 times a power of 10. 'Natural Breaks' = 'jenks', # seeks to reduce the variance within classes and maximize the variance between classes 'Hierarchical Cluster' = 'hclust', # Cluster with short distance 'K-means Cluster' = 'kmeans' # Cluster with low variance and similar size ) # Read boundaries as shapefiles from files in www directory # boundaries <- lapply(loca.map, function(x) readOGR(shp.path, x)) # names(boundaries) <- loca.map # for(m in loca.map){ # boundaries[[m]] <- merge(boundaries[[m]], areas[, .(ons_id, nhs_id, name)], by.x = 'id', by.y = 'ons_id') # boundaries[[m]] <- merge(boundaries[[m]], centres[get(audit) == 1, .(H = .N), .(ons_id = get(paste0(m, '_ons')))], by.x = 'id', by.y = 'ons_id') # } # Read boundaries as unique list from rds shared rep boundaries <- readRDS(paste0(data.path, 'boundaries.rds')) for(m in loca.map){ boundaries[[m]] <- merge(boundaries[[m]], centres[get(audit) == 1, .(H = .N), .(ons_id = get(paste0(m, '_ons')))], by.x = 'id', by.y = 'ons_id') } # Determines the text intervals for the colours in the map legend get.legend.colnames <- function(bnd, mtc.type, lbl.brks, ncols) { if(mtc.type == 1){ lbl.brks <- format(round(lbl.brks, 0), big.mark = ',') } else if(mtc.type == 2){ lbl.brks <- format(round(100*lbl.brks, 2), nsmall = 2) } else { lbl.brks <- format(round(lbl.brks, 1), nsmall = 1) } lbl.text <- sapply(2:ncols, function(x) paste0( lbl.brks[x-1], ' \u2264 n < ', lbl.brks[x], ' (', length(bnd$Y[bnd$Y >= as.numeric(gsub(',', '', lbl.brks[x-1])) & bnd$Y < as.numeric(gsub(',', '', lbl.brks[x])) ] ), ')' ) ) lbl.text <- c(lbl.text, paste0( lbl.brks[ncols], ' \u2264 n \u2264 ', lbl.brks[ncols + 1], ' (', length(bnd$Y[bnd$Y >= as.numeric(gsub(',', '', lbl.brks[ncols])) & bnd$Y <= as.numeric(gsub(',', '', lbl.brks[ncols + 1])) ] ), ')' ) ) }