a b/R/utils-graphics.R
1
 
2
#' Theme for ggplot2
3
#' @param base_size Legend title size, all other sizes scaled appropriately 
4
#' to this
5
#' @param base_family Font family
6
#' 
7
#' @import ggplot2
8
#' 
9
theme_bma <- function(
10
  base_size = 10, 
11
  base_family = get_base_family()
12
) {
13
14
  `%+replace%` <- ggplot2::`%+replace%`
15
16
  base_family <- ifelse(is.null(base_family), get_base_family(), base_family)
17
18
  ggplot2::theme_bw(base_size = base_size, base_family = base_family) %+replace%
19
    ggplot2::theme(
20
      text = ggplot2::element_text(family = base_family),
21
      plot.title = ggplot2::element_text(
22
        size = base_size + 6,
23
        margin = margin(0, 0, 13, 0),
24
        hjust = 0,
25
        face = "bold"
26
      ),
27
      plot.title.position = "plot",
28
      plot.subtitle = ggplot2::element_text(
29
        size = base_size + 4,
30
        margin = margin(0, 0, 13, 0),
31
        hjust = 0
32
      ),
33
      axis.text = ggplot2::element_text(size = base_size - 1),
34
      axis.title = ggplot2::element_text(size = base_size + 4),
35
      axis.title.x = ggplot2::element_text(
36
        margin = ggplot2::margin(t = base_size - 1)
37
      ),
38
      axis.title.y = ggplot2::element_text(
39
        margin = ggplot2::margin(l = 0, r = base_size + 1), 
40
        angle = 90
41
      ),
42
      legend.text = ggplot2::element_text(size = base_size),
43
      legend.title = ggplot2::element_text(size = base_size + 2),
44
      strip.background = ggplot2::element_rect(fill = "grey90")
45
    )
46
}
47
48
49
#' Set base font family for ggplot2.
50
#' 
51
#' @return Character string of the name of a postscript font related to 
52
#' Arial if available, otherwise "sans".
53
#' 
54
#' @importFrom grDevices postscriptFonts
55
#' 
56
get_base_family <- function() {
57
  avail_fonts <- tryCatch(
58
    grDevices::postscriptFonts(),
59
    error = function(cond) NULL,
60
    warning = function(cond) NULL
61
  )
62
63
  avail_fontnames <- names(avail_fonts)
64
65
  if (any(grepl("Arial", avail_fontnames))) {
66
    base_family <- avail_fontnames[grep("Arial", avail_fontnames)[1]]
67
  } else {
68
    base_family <- "sans"
69
  }
70
71
  return(base_family)
72
}
73
74
75
#' Generate arm closure summaries for batches
76
#' 
77
#' @param file_prefix Consistent beginning of filenames holding 
78
#' arm closure data. Defaults to `closures`.
79
#' @param run_time Specify a particular instance of `biomkrAccrual()`
80
#' execution using a date-time format `yyyy-mm-dd-hh-mm-ss`. 
81
#' Used to select which files will be summarised.
82
#' @param output_path Directory where the input files are located
83
#' and the output files will be written.
84
#' @param keep_files Save data files and plots generated during the run. 
85
#' Defaults to TRUE.
86
#' 
87
#' @export
88
#' 
89
#' @importFrom stats sd
90
#' @importFrom utils read.csv write.csv
91
#' 
92
##### Nothing is calling this?
93
get_arm_closures <- function(
94
  file_prefix = "closures",
95
  run_time = "2024-08-07-18-35-09",
96
  output_path = "../biomkrAccrual_output_data/",
97
  keep_files = TRUE
98
) {
99
  # What output files do we have?
100
  filenames <- list.files(
101
    output_path, 
102
    pattern = paste0("^", file_prefix, "-", run_time, ".*.csv"), 
103
    full.names = TRUE
104
  )
105
106
  # Read files
107
  closures_ls <- lapply(filenames, read.csv)
108
109
  # Summarise files
110
  summ <- lapply(closures_ls, function(d) summary(d[, -1]))
111
  name_types <- c("1", "2", "mixed", "unbalanced", "multirate", "multimix")
112
  names(summ) <- c(
113
    paste0("gamma_rate_closures_", name_types),
114
    paste0("fixed_rate_closures_", name_types)
115
  )
116
117
  write.csv(
118
    summ, 
119
    paste0(output_path, "arm_closure_summary-", run_time, ".csv")
120
  )
121
122
  # Standard Deviations
123
  sd_mx <- t(sapply(
124
    closures_ls, 
125
    function(a_df) {
126
      sapply(
127
        seq_len(ncol(a_df)),
128
        function(i) stats::sd(a_df[i, ], na.rm = TRUE)
129
      )
130
    }
131
  ))
132
133
  sd_mx <- sd_mx[, -1]
134
135
  colnames(sd_mx) <- paste0("T", 1:3, "_sd")
136
  rownames(sd_mx) <- c(
137
    paste0("gamma_rate_closures_", name_types),
138
    paste0("fixed_rate_closures_", name_types)
139
  )
140
141
  write.csv(
142
    as.data.frame(sd_mx), 
143
    paste0(output_path, "arm_closures_sd", run_time, ".csv")
144
  )
145
146
  return(list(summ, sd_mx))
147
}
148
149
150
#' Plot predicted recruitment from file containing a CSV from
151
#' a single run
152
#' 
153
#' @param file_prefix Consistent beginning of filenames holding 
154
#' arm closure data. Defaults to `accrual`.
155
#' @param plot_prefix Prefix for file name to identify plot type. 
156
#' Defaults to `accrual_plot`.
157
#' @param run_time Specify a particular instance of `biomkrAccrual()`
158
#' execution using a date-time format `yyyy-mm-dd-hh-mm-ss`. 
159
#' Used to select which files will be summarised.
160
#' @param output_path Directory where the output files from the 
161
#' `biomkrAccrual()` instance are located.
162
#' @param figs_path Folder where figures generated during execution
163
#' will be stored; defaults to the `figures` subdirectory in
164
#' `output_path`.
165
#' 
166
#' @export
167
#' 
168
accrual_plot_from_file <- function(
169
  file_prefix = "accrual",
170
  plot_prefix = "accrual-from-file",
171
  run_time = "2024-08-07-18-35-09",
172
  output_path = "../biomkrAccrual_output_data/",
173
  figs_path = paste0(output_path, "figures/")
174
) {
175
  # Validate input
176
177
  checkmate::assert_directory_exists(
178
    file.path(output_path), 
179
    access = "rx"
180
  )
181
182
  input_file <- paste0(
183
    output_path, file_prefix, "-", run_time, ".csv"
184
  )
185
186
  checkmate::assert_file_exists(file.path(input_file))
187
188
  makeifnot_dir(figs_path, min_access = "rwx")
189
190
  accrual_raw_df <- utils::read.csv(file.path(input_file))
191
192
  # Get unique arm identifiers
193
  arm_names <- unique(sapply(
194
    strsplit(names(accrual_raw_df), "\\."), 
195
    getElement,
196
    1
197
  ))
198
199
  # Make dataframe of arm recruitment per week by summing site-arm columns
200
  accrual_df <- as.data.frame(
201
    lapply(
202
      arm_names, 
203
      function(n) rowSums(accrual_raw_df[startsWith(names(accrual_raw_df), n)])
204
    ),
205
    col.names = arm_names
206
  )
207
208
  # Convert to long format of class "accrualplotdata"
209
  accrual_df <- accrual_to_long(accrual_df)
210
211
  # Plot 
212
  p <- plot(
213
    accrual_df, 
214
    plot_prefix = plot_prefix,
215
    run_time = run_time
216
  )
217
218
  print(p)
219
220
  ggplot2::ggsave(
221
    paste0(figs_path, plot_prefix, "-", run_time, ".png"),
222
    plot = p,
223
    width = 12,
224
    height = 8,
225
    dpi = 400
226
  )
227
}
228
229
230
#' Convert accrual data to long format dataframe of class
231
#' `accrualplotformat`.
232
#' 
233
#' @param accrual_df Wide format accrual data.
234
#' 
235
#' @importFrom stats reshape
236
#' 
237
accrual_to_long <- function(accrual_df) {
238
  # Convert to cumulative sums
239
  accrual_df <- cumsum(accrual_df)
240
241
  arm_names <- names(accrual_df)
242
243
  # Add week information
244
  accrual_df$Week <- seq_len(nrow(accrual_df))
245
246
  # Convert to long format
247
  accrual_df <- stats::reshape(
248
    accrual_df,
249
    direction = "long",
250
    varying = list(arm_names),
251
    timevar = "Arm",
252
    times = arm_names,
253
    v.names = "Recruitment",
254
    idvar = "Week"
255
  )
256
257
  accrual_df$Arm <- factor(accrual_df$Arm)
258
259
  # Define an S3 class so we can have a custom plot command
260
  class(accrual_df) <- c("accrualplotdata", class(accrual_df))
261
262
  return(accrual_df)
263
}
264
265
266
#' S3 method to plot predicted recruitment from a long format 
267
#' dataframe of class "accrualplotdata".
268
#' 
269
#' @param data long format dataframe with columns "Week", 
270
#' "Arm" and "Recruitment".
271
#' @param plot_prefix Prefix for file name to identify plot type.
272
#' Defaults to `accrual_plot`.
273
#' @param run_time Specify a particular instance of `biomkrAccrual()`
274
#' execution using a date-time format `yyyy-mm-dd-hh-mm-ss`.
275
#' @param output_path = Directory where the output files from the 
276
#' `biomkrAccrual()` instance are located.
277
#' @param figs_path Folder where figures generated during execution
278
#' will be stored; defaults to the `figures` subdirectory in
279
#' `output_path`.
280
#' @param target_arm_size Number of subjects required for each treatment arm.
281
#' @param target_control Number of subjects required for control arm(s).
282
#' @param target_interim Number of subjects required for treatment arm at 
283
#' interim analysis.
284
#' @param accrual_period Number of weeks in recruitment period.
285
#' @param interim_period Number of weeks to recruit for interim analysis.
286
#' 
287
#' @import ggplot2
288
#' @importFrom grDevices palette.colors
289
#' @importFrom rlang .data
290
#' 
291
plot.accrualplotdata <- function(
292
  data,
293
  plot_prefix = "accrual_plot",
294
  run_time = NULL,
295
  output_path = "../biomkrAccrual_output_data/",
296
  figs_path = paste0(output_path, "figures/"),
297
  target_arm_size = NA_integer_,
298
  target_control = NA_integer_,
299
  target_interim = NA_integer_,
300
  accrual_period = NA_integer_,
301
  interim_period = NA_integer_
302
) {
303
  
304
  accrual_df <- data
305
  arm_names <- levels(accrual_df$Arm)
306
307
  linetypes <- c(
308
    "Interim arm" = 2, "Experimental arm" = 3, "Control arm" = 4,
309
    "Interim accrual" = 5, "Total accrual" = 6
310
  )
311
312
  hline_y <- c(target_interim, target_arm_size, target_control)
313
  vline_x <- c(interim_period, accrual_period)
314
315
  p <- ggplot2::ggplot(
316
    accrual_df, 
317
    ggplot2::aes(
318
      x = .data$Week, 
319
      y = .data$Recruitment, 
320
      group = .data$Arm, 
321
      color = .data$Arm
322
    )
323
  ) +
324
    ggplot2::geom_line(linewidth = 1) +
325
    # Use colourblind friendly Okabe-Ito palette
326
    ggplot2::scale_colour_manual(
327
      values = grDevices::palette.colors(length(arm_names))
328
    ) +
329
    ggplot2::geom_vline(
330
      xintercept = vline_x,
331
      linewidth = 1,
332
      linetype = 2:3,
333
      color = "grey75"
334
    ) +
335
    ggplot2::geom_hline(
336
      yintercept = hline_y,
337
      linewidth = 1,
338
      linetype = 4:6,
339
      color = "grey65"
340
    ) +
341
    ggplot2::labs(
342
      title = "Accrual plot"
343
    ) +
344
    theme_bma(base_size = 16)
345
346
  return(p)
347
}
348
349
350
#' Plot distributions of recruitment to arms at given time.
351
#' 
352
#' @param data Matrix with columns for each recruitment arm, 
353
#' including control.
354
#' @param target Vector of targets for recruitment. First two
355
#' should be those directly relevant to the subject of the graph.
356
#' @param target_names Vector of target names, for labelling.
357
#' @param target The adjust parameter from `ggplot2::geom_density`;
358
#' higher values mean more smoothing. Defaults to 1.
359
#' 
360
#' @importFrom stats reshape
361
#' @import ggplot2
362
#' @importFrom grDevices palette.colors
363
#' 
364
#' @export
365
#' 
366
plot.armtotals <- function(
367
  data,
368
  target,
369
  target_names,
370
  target_week,
371
  adjust = 1
372
) {
373
  data_df <- matrix_to_long(data)
374
375
  # Which of the accrual targets are within the dataset
376
  if (length(target) > 2) {
377
    target_indices <- 
378
      c(1:2, 2 + which(target[-c(1, 2)] <= max(data_df$Recruitment)))
379
  } else {
380
    target_indices <- seq_len(length(target))
381
  }
382
  target <- target[target_indices]
383
  target_names <- target_names[target_indices]
384
385
  p <- ggplot2::ggplot(
386
    data = data_df
387
  ) +
388
    ggplot2::geom_density(
389
      ggplot2::aes(
390
        x = Recruitment, group = Arm, fill = Arm, col = Arm
391
      ),
392
      alpha = 0.4, adjust = 1
393
    ) +
394
    ggplot2::scale_fill_manual(
395
      values = grDevices::palette.colors(length(unique(data_df$Arm))),
396
      aesthetics = c("color", "fill")
397
    ) +
398
    ggplot2::geom_vline(
399
      xintercept = target, 
400
      linetype = "dashed",
401
      linewidth = 1,
402
      colour = "grey65"
403
    ) +
404
    ggplot2::labs(
405
      y = "Probability density",
406
      title = target_names[1],
407
    ) +
408
    theme_bma(base_size = 16)
409
410
  p <- label_vlines(p, target, target_names)
411
412
  return(p)
413
}
414
415
416
#' Adds labels for vlines to accrual plots
417
#' 
418
#' @param p Ggplot object.
419
#' @param target Vector of x axis positions of vlines.
420
#' @param target_names Vector of target names (excluding the
421
#' word `target`).
422
#' @param size Font size (in ggplot measure) for labels; 
423
#' defaults to 6.
424
#' 
425
label_vlines <- function(
426
  p,
427
  target,
428
  target_names,
429
  size = 6
430
) {
431
  # Get height of y axis for this particular plot
432
  label_y <- round(ggplot2::layer_scales(p)$y$range$range[2], 2)
433
434
  # Get x range of plot
435
  xrange <- round(ggplot2::layer_scales(p)$x$range$range, 2)
436
437
  whisker <- diff(xrange) * .2
438
439
  # There's more than one target.  Move labels for the one at the range end
440
441
  # Add labels for vlines
442
  abline_df <- data.frame(
443
    x = target, 
444
    y = label_y * 0.9, 
445
    label = paste(target_names, "\ntarget")
446
  )
447
448
  p <- p +
449
    ggplot2::geom_text(
450
      data = abline_df,
451
      ggplot2::aes(x = x, y = y, label = label),
452
      size = size,
453
      family = get_base_family()
454
    )
455
456
  return(p)
457
}
458
459
460
#' Plot single arm accrual plot
461
#' 
462
#' Bodge, fix later
463
#' 
464
#' 
465
accrual_arm_plot <- function(
466
  data_df,
467
  arm_colours,
468
  treatment_arms,
469
  targets,
470
  plot_id,
471
  i
472
) {
473
  arm_names <- colnames(data_df)
474
475
  if (length(unique(data_df[, i])) == 1) {
476
    # BODGE - don't want to see this but need it to produce graph
477
    arm_col <- "white"
478
    alpha <- 0.0001
479
  } else {
480
    arm_col <- arm_colours[i]
481
    alpha <- 0.4
482
  }
483
484
485
  p <- ggplot2::ggplot(
486
    data = data_df
487
  ) +
488
    ggplot2::geom_density(
489
      ggplot2::aes(x = .data[[arm_names[i]]]),
490
      col = arm_col, fill = arm_col,
491
      alpha = alpha, adjust = 1
492
    ) 
493
  
494
  if (length(unique(data_df[, i])) == 1) {
495
    p <- p +
496
      ggplot2::geom_vline(
497
        xintercept = unique(data_df[, i]),
498
        linewidth = 2,
499
        colour = arm_colours[i],
500
        alpha = 0.4
501
      )
502
  }
503
504
  p <- p + 
505
    ggplot2::geom_vline(
506
      xintercept = ifelse(
507
        treatment_arms[i], 
508
        targets[1], 
509
        targets[2]
510
      ), 
511
      linetype = "dashed",
512
      linewidth = 1,
513
      colour = "grey75"
514
    ) +
515
    ggplot2::labs(
516
      x = paste(
517
        "No. virtual patients recruited at", 
518
        "target week for",
519
        tolower(plot_id)
520
      ),
521
      y = "Probability density",
522
      title = paste(plot_id, "for", arm_names[i]),
523
    ) +
524
    ggplot2::scale_x_continuous(expand = expansion(mult = 0.07)) +
525
    theme_bma(base_size = 16)
526
527
  p <- label_vlines(
528
    p, 
529
    target = ifelse(
530
      treatment_arms[i],
531
      targets[1], 
532
      targets[2]
533
    ),
534
    target_names = ifelse(
535
      treatment_arms[i],
536
      plot_id, 
537
      paste0(plot_id, "\ncontrol")
538
    )
539
  )
540
541
  return(p)
542
}