diff --git a/.drone.yml b/.drone.yml index 40c656c7..8dfa8f57 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,52 +1,7 @@ --- kind: pipeline type: docker -name: mpn:latest - -platform: - os: linux - arch: amd64 - -steps: -- name: pull - image: omerxx/drone-ecr-auth - commands: - - $(aws ecr get-login --no-include-email --region us-east-1) - - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:latest - - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-3.6:latest - volumes: - - name: docker.sock - path: /var/run/docker.sock - -- name: "Check package: R 4.0" - pull: never - image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:latest - commands: - - R -s -e 'devtools::install_deps(upgrade = '"'"'never'"'"')' - - R -s -e 'devtools::check(env_vars = c("NOT_CRAN" = "true"))' - - -- name: "Check package: R 3.6" - pull: never - image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-3.6:latest - commands: - - R -s -e 'devtools::install_deps(upgrade = '"'"'never'"'"')' - - R -s -e 'devtools::check(env_vars = c("NOT_CRAN" = "true"))' - -volumes: -- name: docker.sock - host: - path: /var/run/docker.sock - -trigger: - event: - exclude: - - promote - ---- -kind: pipeline -type: docker -name: mpn:oldest +name: cran-latest platform: os: linux @@ -55,114 +10,50 @@ platform: steps: - name: pull image: omerxx/drone-ecr-auth - commands: + commands: - $(aws ecr get-login --no-include-email --region us-east-1) - - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:2020-06-08 - - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-3.6:2020-06-08 + - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-3.6:cran-latest + - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:cran-latest + - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.1:cran-latest volumes: - name: docker.sock path: /var/run/docker.sock -- name: "Check package: R 4.0" - pull: never - image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:2020-06-08 - commands: - - R -s -e 'devtools::install_deps(upgrade = '"'"'never'"'"')' - - R -s -e 'devtools::check(env_vars = c("NOT_CRAN" = "true"))' - - name: "Check package: R 3.6" pull: never - image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-3.6:2020-06-08 + image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-3.6:cran-latest commands: - - R -s -e 'devtools::install_deps(upgrade = '"'"'never'"'"')' + - R -s -e 'devtools::install_deps(upgrade = '"'"'always'"'"', dependencies=TRUE)' - R -s -e 'devtools::check(env_vars = c("NOT_CRAN" = "true"))' - -volumes: -- name: docker.sock - host: - path: /var/run/docker.sock - -trigger: - event: - exclude: - - promote - ---- -kind: pipeline -type: docker -name: cran-latest - -platform: - os: linux - arch: amd64 - -steps: -- name: pull - image: omerxx/drone-ecr-auth - commands: - - $(aws ecr get-login --no-include-email --region us-east-1) - - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:cran-latest - - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-3.6:cran-latest + environment: + USER: drone volumes: - - name: docker.sock - path: /var/run/docker.sock + - name: cache + path: /ephemeral - name: "Check package: R 4.0" pull: never image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:cran-latest commands: - - R -s -e 'devtools::install_deps(upgrade = '"'"'always'"'"')' - - R -s -e 'devtools::load_all(); sessioninfo::session_info()' - - R -s -e 'devtools::check(env_vars = c("NOT_CRAN" = "true"))' - -- name: "Check package: R 3.6" - pull: never - image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-3.6:cran-latest - commands: - - R -s -e 'devtools::install_deps(upgrade = '"'"'always'"'"')' - - R -s -e 'devtools::load_all(); sessioninfo::session_info()' + - R -s -e 'devtools::install_deps(upgrade = '"'"'always'"'"', dependencies=TRUE)' - R -s -e 'devtools::check(env_vars = c("NOT_CRAN" = "true"))' - -volumes: -- name: docker.sock - host: - path: /var/run/docker.sock - -trigger: - event: - exclude: - - promote - ---- -kind: pipeline -type: docker -name: coverage - -platform: - os: linux - arch: amd64 - -steps: -- name: pull - image: omerxx/drone-ecr-auth - commands: - - $(aws ecr get-login --no-include-email --region us-east-1) - - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:latest + environment: + USER: drone volumes: - - name: docker.sock - path: /var/run/docker.sock - + - name: cache + path: /ephemeral -- name: Code coverage +- name: "Check package: R 4.1" pull: never - image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:latest + image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.1:cran-latest commands: - - R -s -e 'devtools::install_deps(upgrade = '"'"'never'"'"')' - - R -s -e 'covr::codecov()' + - R -s -e 'devtools::install_deps(upgrade = '"'"'always'"'"', dependencies=TRUE)' + - R -s -e 'devtools::check(env_vars = c("NOT_CRAN" = "true"))' environment: - CODECOV_TOKEN: - from_secret: CODECOV_TOKEN - NOT_CRAN: true + USER: drone + volumes: + - name: cache + path: /ephemeral volumes: - name: docker.sock @@ -176,13 +67,10 @@ trigger: exclude: - promote -depends_on: -- mpn:latest -- cran-latest --- kind: pipeline type: docker -name: release +name: pmtables-release platform: os: linux @@ -193,21 +81,20 @@ steps: image: omerxx/drone-ecr-auth commands: - $(aws ecr get-login --no-include-email --region us-east-1) - - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:latest + - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.1:latest volumes: - name: docker.sock path: /var/run/docker.sock - name: Build package pull: never - image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:latest + image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.1:latest commands: - git config --global user.email drone@metrumrg.com - git config --global user.name Drony - git fetch --tags + - R -s -e 'devtools::install_deps(upgrade = '"'"'always'"'"', dependencies=TRUE)' - R -s -e 'pkgpub::create_tagged_repo(.dir = '"'"'/ephemeral'"'"')' - environment: - NOT_CRAN: true volumes: - name: cache path: /ephemeral @@ -248,6 +135,4 @@ trigger: - tag depends_on: -- mpn:latest -- mpn:oldest - cran-latest diff --git a/DESCRIPTION b/DESCRIPTION index 17929aba..681368ae 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: pmtables Type: Package Title: Tables for Pharmacometrics -Version: 0.4.0 +Version: 0.4.1 Authors@R: c( person(given = "Kyle", @@ -36,7 +36,7 @@ Suggests: testthat, yaml, fs, texPreview Encoding: UTF-8 Language: en-US LazyData: true -RoxygenNote: 7.1.1 +RoxygenNote: 7.1.2 Roxygen: list(markdown = TRUE) VignetteBuilder: knitr Collate: diff --git a/NEWS.md b/NEWS.md index acd44af0..3fc6311f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,19 @@ +# pmtables 0.4.1 + +- `colgroup()` (and `st_span()`) gains an `align` argument to position + the spanner title on left or right in addition to the center (default) + #260, #261. + +- `rowpanel()` (and `st_panel()`) gains `jut`argument to push non-panel + table contents to the right relative to the panel header row so that + contents under the panel header are indented #251, #253. + +- Panel header rows are now modified so that the header row stays + with the first non-header row for longtable output #252, #253. + +- Consistent `BQL` / `BLQ` handling for column titles and table + notes for `pt_data_inventory()` #254, #255. + # pmtables 0.4.0 - Add `cols_omit` option to omit column header data (#213) @@ -11,7 +27,7 @@ - Add `hline` argument to `rowgroup()` constructor to make the horizontal line above the panel data optional (#215) -- Refactor `pt_data_inventory()` to calculate percent BLQ using denomninator +- Refactor `pt_data_inventory()` to calculate percent BLQ using denominator that is the sum of the number of observations BLQ and non-BLQ / non-missing (#221, #222) diff --git a/R/AAAA.R b/R/AAAA.R index ff3755af..2ac50004 100644 --- a/R/AAAA.R +++ b/R/AAAA.R @@ -26,6 +26,7 @@ NULL # GLOBAL object .internal <- new.env(parent = emptyenv()) +.internal$marker.panel <- "%--pmtables-insert-panel" .onLoad <- function(libname, pkgname) { st_reset_knit_deps() diff --git a/R/data_inventory_table.R b/R/data_inventory_table.R index 4a7c7f77..305124fb 100644 --- a/R/data_inventory_table.R +++ b/R/data_inventory_table.R @@ -221,18 +221,18 @@ pt_data_study <- function(data, study_col = "STUDY", panel = study_col, ...) { #' #' @inheritParams pt_cont_long #' -#' @param by the outer grouping variable; may be character or quosure -#' @param panel the panel grouping variable; may be character or quosure -#' @param inner_summary if `TRUE`, then a summary of the inner variable will -#' be provided -#' @param drop_miss if `TRUE`, then `MISS` will be dropped, but only when all -#' `MISS` values are equal to zero -#' @param stacked if `TRUE`, then independent summaries are created by `outer` -#' and included in a single table (see examples) -#' @param dv_col character name of `DV` column -#' @param bq_col character name of `BQL` column; see [find_bq_col()] -#' @param id_col character name of `ID` column -#' @param ... other arguments passed to [data_inventory_chunk()] +#' @param by The outer grouping variable; may be character or quosure. +#' @param panel The panel grouping variable; may be character or quosure. +#' @param inner_summary If `TRUE`, then a summary of the inner variable will +#' be provided. +#' @param drop_miss If `TRUE`, then `MISS` will be dropped, but only when all +#' `MISS` values are equal to zero. +#' @param stacked If `TRUE`, then independent summaries are created by `outer` +#' and included in a single table (see examples). +#' @param dv_col Character name of `DV` column. +#' @param bq_col Character name of `BQL` column; see [find_bq_col()]. +#' @param id_col Character name of `ID` column. +#' @param ... Other arguments passed to [data_inventory_chunk()]. #' #' #' @details @@ -303,7 +303,8 @@ pt_data_inventory <- function(data, by = ".total", panel = by, all_name = "all", dv_col = "DV", bq_col = find_bq_col(data), - id_col = "ID", ...) { + id_col = "ID", + ...) { has_panel <- !missing(panel) panel_data <- as.panel(panel) @@ -375,6 +376,10 @@ pt_data_inventory <- function(data, by = ".total", panel = by, Number.BQL = .data[["NBQL"]] ) + if(bq_col == "BLQ") { + names(ans) <- gsub(".BQL", ".BLQ", names(ans), fixed = TRUE) + } + if(isTRUE(drop_miss)) { ans <- mutate(ans, Number.MISS = NULL) } @@ -382,7 +387,7 @@ pt_data_inventory <- function(data, by = ".total", panel = by, ans <- mutate(ans,.total = NULL) out <- ans - notes <- pt_data_inventory_notes() + notes <- pt_data_inventory_notes(bq = bq_col, drop_bql = drop_bql) if(isTRUE(drop_miss)) notes <- notes[!grepl("MISS", notes)] @@ -421,18 +426,34 @@ pt_data_inventory <- function(data, by = ".total", panel = by, #' Return table notes for pt_data_inventory #' -#' See [pt_data_inventory()]. +#' See [pt_data_inventory()]. The function generates standard table notes for +#' the table. #' -#' @param note_add additional notes to include +#' @param bq Abbreviation for below limit of quantification. +#' @param drop_bql If `TRUE`, the `BQL`/`BLQ` summary is omitted. +#' @param note_add Additional notes to be include. + #' #' @export -pt_data_inventory_notes <- function(note_add = NULL) { +pt_data_inventory_notes <- function(bq = c("BQL", "BLQ"), drop_bql = FALSE, note_add = NULL) { + l2 <- NULL + l3 <- "MISS: missing observations" + if(isFALSE(drop_bql)) { + bq <- match.arg(bq) + if(bq=="BQL") { + l2 <- "BQL: below quantification limit" + } + if(bq=="BLQ") { + l2 <- "BLQ: below limit of quantification" + } + l3 <- paste0(l3, " (non-", bq, ")") + } ans <- note_add ans <- c( ans, "SUBJ: subjects", - "BQL: below quantitation limit", - "MISS: missing observations (not BQL)", + l2, + l3, "OBS: observations" ) ans diff --git a/R/discrete_table.R b/R/discrete_table.R index 0f748336..95bcb900 100644 --- a/R/discrete_table.R +++ b/R/discrete_table.R @@ -85,7 +85,7 @@ cat_data <- function(data, cols, by = ".total", panel = by, ans[["N"]] <- NULL ans <- pivot_wider( ans, - names_from = by, + names_from = all_of(unname(by)), values_from = "summary", names_sep = '_._' ) diff --git a/R/table-long.R b/R/table-long.R index 7328eeaa..a3f14aab 100644 --- a/R/table-long.R +++ b/R/table-long.R @@ -1,11 +1,7 @@ -head <- ' -\\endhead -\\hline -\\multicolumn{}{r}{} -\\endfoot -\\hline -\\endlastfoot -' + +longtable_head <- function(multicol) { + c("\\endhead", "\\hline", multicol, "\\endfoot", "\\hline", "\\endlastfoot") +} ltcaption <- function(macro = "", text = "", short = "", label = "") { if(identical(c(macro, text, short), c("", "", ""))) { @@ -42,6 +38,12 @@ longtable_notes <- function(notes) { #' Create longtable output from an R data frame #' +#' Use this function to allow your table to span multiple pages, with a +#' "to be continued" statement at the bottom of each page. There are important +#' differences between this `longtable` environment and the `tabular` +#' environment that is used to generate tables from [stable()]. See the +#' `details` section for more information. +#' #' @inheritParams tab_notes #' @param data an object to render as a long table; this could be a `data.frame`, #' a `pmtable` object or an `stobject`; when passing in a `data.frame`, the data @@ -57,6 +59,30 @@ longtable_notes <- function(notes) { #' @param lt_cap_label table label for use in latex document #' @param lt_continue longtable continuation message #' +#' @details +#' +#' To create `longtable` output, `pmtables` first passes the data frame +#' through [stable()] and then modifies the output to create a table in +#' `longtable` environment. The `...` arguments to [stable_long()] are passed +#' to [stable()] and can be used to configure the table. One important difference +#' between `tablular` and `longtable` environments is that captions need to +#' get inserted **inside** the `longtable` environment; this is why you see +#' several additional arguments for [stable_long()]. +#' +#' You may have to run `pdflatex` on your `longtable` more than once to get the +#' table to render properly; this is not unexpected behavior for `longtable`. +#' +#' If you have panels in your table, the default is to prevent page breaks +#' right after the panel title row using the `\\*` command in the `longtable` +#' package. This shouldn't need to be changed by the user, but if needed this +#' can be suppressed by adding `nopagebreak = FALSE` when calling [as.panel()] +#' or [rowpanel()]. +#' +#' @return A character vector with the TeX code for the table with `class` +#' attribute set to `stable_long` and `stable`. +#' +#' @examples +#' stable_long(stdata()) #' #' @export stable_long <- function(data, ...) UseMethod("stable_long") @@ -87,11 +113,17 @@ stable_long.data.frame <- function(data, row_space <- gluet("\\renewcommand{\\arraystretch}{}") col_space <- gluet("\\setlength{\\tabcolsep}{pt} ") - nc <- x$nc - head <- gluet(head) + n_col <- x$nc + continued <- gluet("\\multicolumn{}{r}{}") + head <- longtable_head(continued) lt_notes <- longtable_notes(x$mini_notes) + # Put stars on panel rows for long tables + if(!x$panel$null && x$panel$nopagebreak) { + x$tab <- tab_panel_star(x$tab) + } + longtab <- c( x$sizes$font_size$start, row_space, diff --git a/R/table-panel.R b/R/table-panel.R index 9a2bd2a1..2215ed19 100644 --- a/R/table-panel.R +++ b/R/table-panel.R @@ -15,6 +15,14 @@ #' @param it render panel title in italic font face #' @param hline logical indicating whether or not to draw an `hline` above #' the panel row; the first panel row never receives an `hline` +#' @param jut amount (in TeX `ex` units) by which the panel headers are +#' outdented from the first column in the main table and header. Consider +#' using a value of `2` for a clear offset. +#' @param nopagebreak if `TRUE` (the default) then the page will not break +#' immediately after a panel title _when creating a longtable_; this argument +#' does nothing if you are not generating a `longtable`. Set to `FALSE` to +#' prevent the `nopagebreak` command (implemented as `*`) in the longtable +#' output. #' #' @seealso [as.panel()] #' @@ -22,7 +30,8 @@ rowpanel <- function(col = NULL, prefix = "", skip = ".panel.skip.", prefix_name = FALSE, prefix_skip = NULL, duplicates_ok = FALSE, - bold = TRUE, it = FALSE, hline = TRUE) { + bold = TRUE, it = FALSE, hline = TRUE, + jut = 0, nopagebreak = TRUE) { null <- FALSE if(is.null(col)) { col <- NULL @@ -31,10 +40,13 @@ rowpanel <- function(col = NULL, prefix = "", skip = ".panel.skip.", } else { col <- new_names(col) } + assert_that(is.numeric(jut)) + assert_that(is.character(skip)) ans <- list( col = col, prefix = prefix, prefix_name = isTRUE(prefix_name), prefix_skip = prefix_skip, null = null, dup_err = !isTRUE(duplicates_ok), - bold = isTRUE(bold), it = isTRUE(it), skip = skip, hline = isTRUE(hline) + bold = isTRUE(bold), it = isTRUE(it), skip = skip, hline = isTRUE(hline), + jut = jut, nopagebreak = isTRUE(nopagebreak) ) structure(ans, class = "rowpanel") } @@ -109,7 +121,7 @@ panel_by <- function(data, x) { "panel labels are duplicated; ", "please sort the data frame by the panel column ", "or set duplicates_ok to TRUE", - call.=FALSE + call. = FALSE ) } prefix <- rep(prefix, length(lab)) @@ -142,15 +154,16 @@ tab_panel <- function(data, panel, sumrows) { ins$data <- data return(ins) } - require_col(data,panel$col,context = "panel column input name") + require_col(data,panel$col, context = "panel column input name") assert_that( ncol(data) > 1, msg = "must have more than one column to use 'panel' option" ) - paneln <- match(panel$col,names(data)) + paneln <- match(panel$col, names(data)) if(any(is.na(paneln))) { stop("panel column not found: ", squote(panel$col), call.=FALSE) } + data[[paneln]] <- as.character(data[[paneln]]) data[[paneln]] <- replace_na(data[[paneln]],"") # check summary rows if(!is.null(sumrows)) { @@ -171,10 +184,22 @@ tab_panel_insert <- function(tab, panel_insert) { insrt_vec( vec = tab, where = panel_insert$insert_row, - nw = panel_insert$insert_data + nw = tab_panel_mark(panel_insert$insert_data) ) } +tab_panel_marker <- function() { + .internal$marker.panel +} +tab_panel_mark <- function(x) { + paste0(x, tab_panel_marker()) +} - +tab_panel_star <- function(x) { + marker <- tab_panel_marker() + where <- grepl(marker, x, fixed = TRUE) + if(!any(where)) return(x) + x[where] <- sub(marker, paste0("*", marker), x[where], fixed = TRUE) + x +} diff --git a/R/table-span.R b/R/table-span.R index 2fb9f427..c821e8b7 100644 --- a/R/table-span.R +++ b/R/table-span.R @@ -11,15 +11,18 @@ #' @param sep character; the separator used for finding column groupings #' @param title_side which side of the split should be taken as the `title`? #' defaults to left (`.l`) but can also take the right (`.r`) side of the split +#' @param align justify the span title to the center, left or right #' @return an object with class `colgroup` #' @export colgroup <- function(title = NULL, vars = c(), level = 1, sep = ".", - split = FALSE, title_side = c(".l", ".r")) { + split = FALSE, title_side = c(".l", ".r"), + align = c("c", "l", "r")) { if(isTRUE(split)) { ans <- colsplit( title = title, level = level, + align = align, sep = sep, split = TRUE, title_side = title_side @@ -32,6 +35,7 @@ colgroup <- function(title = NULL, vars = c(), level = 1, sep = ".", ans <- list( title = title, level = level, + align = match.arg(align), vars = enquo(vars), split = split, sep = sep, @@ -47,7 +51,7 @@ as.span <- colgroup #' @rdname colgroup #' @export colsplit <- function(sep, level = 1, split = TRUE, title = NULL, - title_side = c(".l", ".r")) { + title_side = c(".l", ".r"), align = c("c", "l", "r")) { assert_that(is.null(title) || is.list(title)) if(is.list(title)) { assert_that(is_named(title)) @@ -59,6 +63,7 @@ colsplit <- function(sep, level = 1, split = TRUE, title = NULL, title = title, level = level, split = split, + align = match.arg(align), sep = sep, gather = FALSE, tagn = tagn @@ -84,28 +89,29 @@ process_colgroup <- function(x,cols) { coln = eval_select(x$vars, data = cols), col = names(cols)[.data[["coln"]]], newcol = col, - title = x$title + title = x$title, + align = x$align ) ans <- mutate(ans, level = x$level) ans } -fill_nospan <- function(span,cols) { +fill_nospan <- function(span, cols) { nc <- length(cols) row_fill <- setdiff(seq(nc), span$coln) - tbl <- tibble( + non_span <- tibble( coln = row_fill, col = cols[.data[["coln"]]], newcol = col, - title = "" + title = "", + align = 'c' # placeholder only ) - ans <- bind_rows(span,tbl) - ans <- arrange(ans,.data[["coln"]]) + ans <- bind_rows(span, non_span) + ans <- arrange(ans, .data[["coln"]]) ans <- mutate( ans, flg = chunk_runs(.data[["title"]]), - align = 'c', - level=span$level[1] + level = span$level[1] ) ans } @@ -122,7 +128,7 @@ combine_spans <- function(..., cols) { all } -find_span_split <- function(cols,xsp) { +find_span_split <- function(cols, xsp) { x <- str_split(cols, fixed(xsp$sep), n = 2) sp <- list( @@ -144,7 +150,7 @@ find_span_split <- function(cols,xsp) { spans <- mutate( spans, tag = ifelse(col == .data[["newcol"]], "", .data[["tag"]]), - align = 'c' + align = ifelse(.data[["tag"]] == "", "c", xsp$align[1]) ) spans <- mutate(spans, flg = chunk_runs(.data[["tag"]])) spans[["tagf"]] <- NULL @@ -191,11 +197,12 @@ find_span_split <- function(cols,xsp) { #' #' @inheritParams stable #' @inheritParams tab_cols -#' @param span_split not implemented at this time; ; see also [st_span_split()] +#' @param span_split a `colsplit` object ; ; see also [st_span_split()] #' @param cols a character vector of column names #' @param span_title_break a character sequence indicating where to split the #' title across multiple lines #' @param ... not used +#' @seealso [colgroup()], [colsplit()], [st_span()], [st_span_split()] #' @export tab_spanners <- function(data, cols = NULL, span = NULL, span_split = NULL, span_title_break = "...", sizes = tab_size(), ...) { @@ -207,15 +214,23 @@ tab_spanners <- function(data, cols = NULL, span = NULL, span_split = NULL, } else { if(is.colgroup(span)) span <- list(span) assert_that(is.list(span)) + assert_that( + all(map_lgl(span, is.colgroup)), + msg = "All objects passed under `span` must have class `colgroup`." + ) span <- map(span, process_colgroup, cols = cols) } - do_span_split <- is.colsplit(span_split) + if(!is.null(span_split)) { + assert_that(is.colsplit(span_split)) + } spans_from_split <- NULL + all_span_tex <- NULL + all_spans <- NULL - if(do_span_split) { - spans <- find_span_split(cols,span_split) + if(is.colsplit(span_split)) { + spans <- find_span_split(cols, span_split) if(isTRUE(spans$any)) { data <- data[,spans$recol] cols <- spans$data$newcol @@ -223,9 +238,6 @@ tab_spanners <- function(data, cols = NULL, span = NULL, span_split = NULL, } } - all_span_tex <- NULL - all_spans <- NULL - if(length(span) > 0 || length(spans_from_split) > 0) { all_spans <- combine_spans(span, spans_from_split, cols = cols) @@ -263,10 +275,11 @@ span_header_box <- function(x, title_break = "...", header_space = -0.5) { nr <- nrow(box) ncols <- map_int(x, ~ nrow(.x)) + align <- map_chr(x, ~ .x$align[1]) for(i in seq_along(ncols)) { length <- ncols[[i]] - box[[i]] <- gluet("\\multicolumn{}{c}{}") + box[[i]] <- gluet("\\multicolumn{}{}{}") } box <- apply(box, MARGIN = 1, FUN = form_tex_cols) diff --git a/R/table-stable.R b/R/table-stable.R index 85905c51..7bb5f962 100644 --- a/R/table-stable.R +++ b/R/table-stable.R @@ -216,17 +216,26 @@ stable.data.frame <- function(data, # add hlines tab <- tab_add_hlines(tab, add_hlines, sumrows) + # indent if paneled + tab <- indent_tex(tab, panel$jut) + # execute panel insertions tab <- tab_panel_insert(tab, panel_insert) + # Table header + head_rows <- form_headrows( + span_data, + cols_tex, + cols_data, + indent = panel$jut + ) + # notes note_data <- tab_notes(notes, escape_fun = escape_fun, ...) row_space <- gluet("\\renewcommand{\\arraystretch}{}") col_space <- gluet("\\setlength{\\tabcolsep}{pt} ") - head_rows <- form_headrows(span_data, cols_tex, cols_data) - out <- c( sizes$font_size$start, col_space, diff --git a/R/table-tabular.R b/R/table-tabular.R index 507a307c..ff2f0f02 100644 --- a/R/table-tabular.R +++ b/R/table-tabular.R @@ -109,7 +109,7 @@ form_open <- function(align) { } # all the rows above top headline for rows -form_headrows <- function(span_data, cols_tex, cols_data) { +form_headrows <- function(span_data, cols_tex, cols_data, indent = 0) { hl1 <- hl2 <- "\\hline" if(cols_data$omit) { cols_tex <- NULL @@ -119,5 +119,10 @@ form_headrows <- function(span_data, cols_tex, cols_data) { hl2 <- NULL } } - c(hl1, span_data$tex, cols_tex, hl2) + ans <- c(span_data$tex, cols_tex) + if(indent > 0) { + ans <- indent_tex(ans, indent) + } + c(hl1, ans, hl2) } + diff --git a/R/table-utils.R b/R/table-utils.R index 09ead159..4e4a06ef 100644 --- a/R/table-utils.R +++ b/R/table-utils.R @@ -165,3 +165,9 @@ paste_units <- function(cols, units) { } cols } + +indent_tex <- function(x, n) { + if(n == 0) return(x) + prefix <- paste0("\\hskip ", n, "ex ") + paste0(prefix, x) +} diff --git a/man/colgroup.Rd b/man/colgroup.Rd index b397ceb5..5603e491 100644 --- a/man/colgroup.Rd +++ b/man/colgroup.Rd @@ -14,7 +14,8 @@ colgroup( level = 1, sep = ".", split = FALSE, - title_side = c(".l", ".r") + title_side = c(".l", ".r"), + align = c("c", "l", "r") ) as.span( @@ -23,7 +24,8 @@ as.span( level = 1, sep = ".", split = FALSE, - title_side = c(".l", ".r") + title_side = c(".l", ".r"), + align = c("c", "l", "r") ) colsplit( @@ -31,7 +33,8 @@ colsplit( level = 1, split = TRUE, title = NULL, - title_side = c(".l", ".r") + title_side = c(".l", ".r"), + align = c("c", "l", "r") ) is.colgroup(x) @@ -54,6 +57,8 @@ splitting columns names on a separator} \item{title_side}{which side of the split should be taken as the \code{title}? defaults to left (\code{.l}) but can also take the right (\code{.r}) side of the split} +\item{align}{justify the span title to the center, left or right} + \item{x}{an R object} } \value{ diff --git a/man/data_inventory_chunk.Rd b/man/data_inventory_chunk.Rd index 1e4841fc..369343db 100644 --- a/man/data_inventory_chunk.Rd +++ b/man/data_inventory_chunk.Rd @@ -22,22 +22,22 @@ data_inventory_chunk( so that \code{data} contains exactly the records to be summarized; pmtables will not add or remove rows prior to summarizing \code{data}} -\item{by}{the outer grouping variable; may be character or quosure} +\item{by}{The outer grouping variable; may be character or quosure.} -\item{panel}{the panel grouping variable; may be character or quosure} +\item{panel}{The panel grouping variable; may be character or quosure.} -\item{stacked}{if \code{TRUE}, then independent summaries are created by \code{outer} -and included in a single table (see examples)} +\item{stacked}{If \code{TRUE}, then independent summaries are created by \code{outer} +and included in a single table (see examples).} \item{tot}{logical indicating if a summary row should be included} \item{all_name}{a name to use for the complete data summary} -\item{dv_col}{character name of \code{DV} column} +\item{dv_col}{Character name of \code{DV} column.} -\item{bq_col}{character name of \code{BQL} column; see \code{\link[=find_bq_col]{find_bq_col()}}} +\item{bq_col}{Character name of \code{BQL} column; see \code{\link[=find_bq_col]{find_bq_col()}}.} -\item{id_col}{character name of \code{ID} column} +\item{id_col}{Character name of \code{ID} column.} \item{...}{used to absorb other arguments; not used} } diff --git a/man/data_inventory_data.Rd b/man/data_inventory_data.Rd index b71c8f3b..569653ee 100644 --- a/man/data_inventory_data.Rd +++ b/man/data_inventory_data.Rd @@ -18,14 +18,14 @@ data_inventory_data( so that \code{data} contains exactly the records to be summarized; pmtables will not add or remove rows prior to summarizing \code{data}} -\item{by}{the outer grouping variable; may be character or quosure} +\item{by}{The outer grouping variable; may be character or quosure.} -\item{panel}{the panel grouping variable; may be character or quosure} +\item{panel}{The panel grouping variable; may be character or quosure.} \item{all_name}{a name to use for the complete data summary} -\item{stacked}{if \code{TRUE}, then independent summaries are created by \code{outer} -and included in a single table (see examples)} +\item{stacked}{If \code{TRUE}, then independent summaries are created by \code{outer} +and included in a single table (see examples).} \item{...}{passed to subsequent summary functions} } diff --git a/man/pt_data_inventory.Rd b/man/pt_data_inventory.Rd index 3b122d8c..20e51269 100644 --- a/man/pt_data_inventory.Rd +++ b/man/pt_data_inventory.Rd @@ -24,31 +24,31 @@ pt_data_inventory( so that \code{data} contains exactly the records to be summarized; pmtables will not add or remove rows prior to summarizing \code{data}} -\item{by}{the outer grouping variable; may be character or quosure} +\item{by}{The outer grouping variable; may be character or quosure.} -\item{panel}{the panel grouping variable; may be character or quosure} +\item{panel}{The panel grouping variable; may be character or quosure.} -\item{inner_summary}{if \code{TRUE}, then a summary of the inner variable will -be provided} +\item{inner_summary}{If \code{TRUE}, then a summary of the inner variable will +be provided.} -\item{drop_miss}{if \code{TRUE}, then \code{MISS} will be dropped, but only when all -\code{MISS} values are equal to zero} +\item{drop_miss}{If \code{TRUE}, then \code{MISS} will be dropped, but only when all +\code{MISS} values are equal to zero.} -\item{stacked}{if \code{TRUE}, then independent summaries are created by \code{outer} -and included in a single table (see examples)} +\item{stacked}{If \code{TRUE}, then independent summaries are created by \code{outer} +and included in a single table (see examples).} \item{table}{a named list to use for renaming columns (see details and examples)} \item{all_name}{a name to use for the complete data summary} -\item{dv_col}{character name of \code{DV} column} +\item{dv_col}{Character name of \code{DV} column.} -\item{bq_col}{character name of \code{BQL} column; see \code{\link[=find_bq_col]{find_bq_col()}}} +\item{bq_col}{Character name of \code{BQL} column; see \code{\link[=find_bq_col]{find_bq_col()}}.} -\item{id_col}{character name of \code{ID} column} +\item{id_col}{Character name of \code{ID} column.} -\item{...}{other arguments passed to \code{\link[=data_inventory_chunk]{data_inventory_chunk()}}} +\item{...}{Other arguments passed to \code{\link[=data_inventory_chunk]{data_inventory_chunk()}}.} } \value{ An object with class \code{pmtable}; see \link{class-pmtable}. diff --git a/man/pt_data_inventory_notes.Rd b/man/pt_data_inventory_notes.Rd index 1dcdc888..e9399669 100644 --- a/man/pt_data_inventory_notes.Rd +++ b/man/pt_data_inventory_notes.Rd @@ -4,11 +4,20 @@ \alias{pt_data_inventory_notes} \title{Return table notes for pt_data_inventory} \usage{ -pt_data_inventory_notes(note_add = NULL) +pt_data_inventory_notes( + bq = c("BQL", "BLQ"), + drop_bql = FALSE, + note_add = NULL +) } \arguments{ -\item{note_add}{additional notes to include} +\item{bq}{Abbreviation for below limit of quantification.} + +\item{drop_bql}{If \code{TRUE}, the \code{BQL}/\code{BLQ} summary is omitted.} + +\item{note_add}{Additional notes to be include.} } \description{ -See \code{\link[=pt_data_inventory]{pt_data_inventory()}}. +See \code{\link[=pt_data_inventory]{pt_data_inventory()}}. The function generates standard table notes for +the table. } diff --git a/man/rowpanel.Rd b/man/rowpanel.Rd index fcb72e4f..da15156e 100644 --- a/man/rowpanel.Rd +++ b/man/rowpanel.Rd @@ -14,7 +14,9 @@ rowpanel( duplicates_ok = FALSE, bold = TRUE, it = FALSE, - hline = TRUE + hline = TRUE, + jut = 0, + nopagebreak = TRUE ) is.rowpanel(x) @@ -42,6 +44,16 @@ panel will have the same header} \item{hline}{logical indicating whether or not to draw an \code{hline} above the panel row; the first panel row never receives an \code{hline}} +\item{jut}{amount (in TeX \code{ex} units) by which the panel headers are +outdented from the first column in the main table and header. Consider +using a value of \code{2} for a clear offset.} + +\item{nopagebreak}{if \code{TRUE} (the default) then the page will not break +immediately after a panel title \emph{when creating a longtable}; this argument +does nothing if you are not generating a \code{longtable}. Set to \code{FALSE} to +prevent the \code{nopagebreak} command (implemented as \code{*}) in the longtable +output.} + \item{x}{an object to test} } \description{ diff --git a/man/stable_long.Rd b/man/stable_long.Rd index f4afeb07..1ddaa95b 100644 --- a/man/stable_long.Rd +++ b/man/stable_long.Rd @@ -50,6 +50,36 @@ lead with \verb{\\\\} - this will be added for you} \item{lt_continue}{longtable continuation message} } +\value{ +A character vector with the TeX code for the table with \code{class} +attribute set to \code{stable_long} and \code{stable}. +} \description{ -Create longtable output from an R data frame +Use this function to allow your table to span multiple pages, with a +"to be continued" statement at the bottom of each page. There are important +differences between this \code{longtable} environment and the \code{tabular} +environment that is used to generate tables from \code{\link[=stable]{stable()}}. See the +\code{details} section for more information. +} +\details{ +To create \code{longtable} output, \code{pmtables} first passes the data frame +through \code{\link[=stable]{stable()}} and then modifies the output to create a table in +\code{longtable} environment. The \code{...} arguments to \code{\link[=stable_long]{stable_long()}} are passed +to \code{\link[=stable]{stable()}} and can be used to configure the table. One important difference +between \code{tablular} and \code{longtable} environments is that captions need to +get inserted \strong{inside} the \code{longtable} environment; this is why you see +several additional arguments for \code{\link[=stable_long]{stable_long()}}. + +You may have to run \code{pdflatex} on your \code{longtable} more than once to get the +table to render properly; this is not unexpected behavior for \code{longtable}. + +If you have panels in your table, the default is to prevent page breaks +right after the panel title row using the \verb{\\\\*} command in the \code{longtable} +package. This shouldn't need to be changed by the user, but if needed this +can be suppressed by adding \code{nopagebreak = FALSE} when calling \code{\link[=as.panel]{as.panel()}} +or \code{\link[=rowpanel]{rowpanel()}}. +} +\examples{ +stable_long(stdata()) + } diff --git a/man/tab_spanners.Rd b/man/tab_spanners.Rd index 2f332e76..db965477 100644 --- a/man/tab_spanners.Rd +++ b/man/tab_spanners.Rd @@ -24,7 +24,7 @@ see also \code{\link[=st_new]{st_new()}}} \item{span}{a list of objects created with \code{\link[=colgroup]{colgroup()}}; ; see also \code{\link[=st_span]{st_span()}}} -\item{span_split}{not implemented at this time; ; see also \code{\link[=st_span_split]{st_span_split()}}} +\item{span_split}{a \code{colsplit} object ; ; see also \code{\link[=st_span_split]{st_span_split()}}} \item{span_title_break}{a character sequence indicating where to split the title across multiple lines} @@ -36,3 +36,6 @@ title across multiple lines} \description{ Create groups of columns with spanners } +\seealso{ +\code{\link[=colgroup]{colgroup()}}, \code{\link[=colsplit]{colsplit()}}, \code{\link[=st_span]{st_span()}}, \code{\link[=st_span_split]{st_span_split()}} +} diff --git a/tests/testthat/test-inventory-table.R b/tests/testthat/test-inventory-table.R index 93bc520b..323d7425 100644 --- a/tests/testthat/test-inventory-table.R +++ b/tests/testthat/test-inventory-table.R @@ -62,7 +62,7 @@ test_that("inventory table - different BQL cols", { data2 <- rename(data1, BLQ = BQL) ans1 <- pt_data_inventory(data1) ans2 <- pt_data_inventory(data2) - expect_identical(ans1,ans2) + expect_false(identical(ans1,ans2)) }) test_that("inventory table - no bq col", { @@ -125,3 +125,28 @@ test_that("inventory table - missing / non-missing", { ans <- pmtables:::n_non_missing(dv, bql) expect_equal(ans, 8) }) + +test_that("handle BQL and BLQ inventory table", { + + data1 <- pmt_first + data2 <- dplyr::rename(data1, BLQ = BQL) + data3 <- dplyr::mutate(data2, BLQ = NULL, BQL = NULL) + + tab1 <- pt_data_inventory(data1, panel = "STUDYf") + tab2 <- pt_data_inventory(data2, panel = "STUDYf") + tab3 <- pt_data_inventory(data3, panel = "STUDYf") + + expect_equal(names(tab1$data)[5], "Number.BQL") + expect_equal(names(tab2$data)[5], "Number.BLQ") + expect_false(any(grepl("BLQ|BQL", names(tab3$data)))) + + expect_length(tab1$notes, 4) + expect_length(tab2$notes, 4) + expect_length(tab3$notes, 3) + + expect_equal(tab1$notes[2], "BQL: below quantification limit") + expect_equal(tab2$notes[2], "BLQ: below limit of quantification") + expect_equal(tab1$notes[3], "MISS: missing observations (non-BQL)" ) + expect_equal(tab2$notes[3], "MISS: missing observations (non-BLQ)") + expect_equal(tab3$notes[2], "MISS: missing observations") +}) diff --git a/tests/testthat/test-panel.R b/tests/testthat/test-panel.R index afd5698e..98d9a7c5 100644 --- a/tests/testthat/test-panel.R +++ b/tests/testthat/test-panel.R @@ -82,3 +82,50 @@ test_that("omit hline from panel", { expect_match(tab1[where], "\\hline", fixed = TRUE) expect_false(grepl("\\hline", tab2[where], fixed = TRUE)) }) + +test_that("nopagebreak for panels in longtable", { + ans <- stable_long(stdata(), panel = "STUDY") + inserted <- grep(pmtables:::.internal$marker.panel, ans, fixed = TRUE) + check <- grep("DEMO", ans, fixed = TRUE) + expect_identical(inserted, check) + expect_match( + ans[inserted], + "\\\\*", + fixed = TRUE + ) + ans <- stable_long(stdata(), panel = as.panel("STUDY", nopagebreak = FALSE)) + inserted <- grep(pmtables:::.internal$marker.panel, ans, fixed = TRUE) + expect_no_match( + ans[inserted], + "\\\\*", + fixed = TRUE + ) +}) + +test_that("jut de-indents panel rows", { + u <- list(WT = "kg") + ans <- inspect(stdata(), panel = rowpanel("STUDY", jut = 1), units = u) + code <- ans$output + tab <- ans$tab + header <- ans$head_rows + header <- header[-c(1, length(header))] + panels <- grepl("DEMO", tab, fixed = TRUE) + expect_match( + tab[!panels], + "\\hskip 1ex", + fixed = TRUE + ) + expect_no_match( + tab[panels], + "\\hskip 1ex", + fixed = TRUE + ) + expect_match( + header, + "\\hskip 1ex", + fixed = TRUE + ) + indented <- c(header, tab[!panels]) + pick <- code[grep("hskip", code)] + expect_identical(indented, pick) +}) diff --git a/tests/testthat/test-span.R b/tests/testthat/test-span.R index 9714b322..a5c6126d 100644 --- a/tests/testthat/test-span.R +++ b/tests/testthat/test-span.R @@ -80,3 +80,64 @@ test_that("add span_split via st_span", { regexp = "`span_split` is already set and will be replaced" ) }) + +test_that("align spanner - standard", { + data <- stdata() + ans1 <- stable(stdata(), span = colgroup("FOO", AGE:SCR)) + ans2 <- stable(stdata(), span = colgroup("FOO", AGE:SCR, align = 'l')) + check <- which(grepl("FOO", ans1)) + expect_match( + ans1[[check]], + "multicolumn{3}{c}{FOO}", + fixed = TRUE, + all = FALSE + ) + expect_match( + ans2[[check]], + "multicolumn{3}{l}{FOO}", + fixed = TRUE, + all = FALSE + ) + expect_error( + stable(stdata(), span = colgroup("FOO", AGE:SCR, align = 'x')), + regexp='should be one of ' + ) +}) + +test_that("align spanner - multiple", { + sp <- list( + colgroup("LEFT", DOSE:FORM, align = 'l'), + colgroup("CENTER", WT:CRCL), + colgroup("RIGHT", AGE:SCR, align = 'r') + ) + ans <- stable(stdata(), span = sp) + spans <- ans[grepl("multicolumn", ans)] + expect_match( + spans, + "multicolumn{2}{l}{LEFT}", + fixed = TRUE + ) + expect_match( + spans, + "multicolumn{2}{c}{CENTER}", + fixed = TRUE + ) + expect_match( + spans, + "multicolumn{3}{r}{RIGHT}", + fixed = TRUE + ) +}) + +test_that("align spanner - via colsplit", { + data <- rename(stdata(), AAA.CRCL= CRCL, AAA.WT = WT) + ans1 <- stable(data, span_split = colsplit(sep = ".", align = 'r')) + check <- which(grepl("AAA", ans1)) + expect_match( + ans1[[check]], + "multicolumn{2}{r}{AAA}", + fixed = TRUE, + all = FALSE + ) +}) + diff --git a/tests/testthat/test-tab_cols.R b/tests/testthat/test-tab_cols.R index c8c3d5a6..b1ab0638 100644 --- a/tests/testthat/test-tab_cols.R +++ b/tests/testthat/test-tab_cols.R @@ -127,8 +127,8 @@ test_that("cols_omit drops column names - stable", { test_that("cols_omit drops column names - longtable", { a <- stable_long(stdata(), cols_omit = FALSE) b <- stable_long(stdata(), cols_omit = TRUE) - expect_match(a[1:7], "STUDY &", all = FALSE, fixed = TRUE) - expect_match(a[1:7], " & CRCL &", all = FALSE, fixed = TRUE) + expect_match(a[1:20], "STUDY &", all = FALSE, fixed = TRUE) + expect_match(a[1:20], " & CRCL &", all = FALSE, fixed = TRUE) expect_false(any(grepl("STUDY", b))) expect_false(any(grepl("CRCL", b))) }) diff --git a/tests/testthat/validate/cat-long-span.tex b/tests/testthat/validate/cat-long-span.tex index 7e7a5603..276915c8 100644 --- a/tests/testthat/validate/cat-long-span.tex +++ b/tests/testthat/validate/cat-long-span.tex @@ -8,15 +8,15 @@ & 12-DEMO-001 & 12-DEMO-002 & 11-DEMO-005 & 13-DEMO-001 & Summary \\ [-0.52em] & n = 30 & n = 50 & n = 40 & n = 40 & n = 160 \\ \hline -\multicolumn{6}{l}{\textbf{SEXf}}\\ +\multicolumn{6}{l}{\textbf{SEXf}}\\%--pmtables-insert-panel male & 10 (33.3) & 18 (36.0) & 29 (72.5) & 23 (57.5) & 80 (50.0) \\ female & 20 (66.7) & 32 (64.0) & 11 (27.5) & 17 (42.5) & 80 (50.0) \\ -\hline \multicolumn{6}{l}{\textbf{RFf}}\\ +\hline \multicolumn{6}{l}{\textbf{RFf}}\\%--pmtables-insert-panel normal & 30 (100.0) & 50 (100.0) & 10 (25.0) & 40 (100.0) & 130 (81.2) \\ mild & 0 (0.0) & 0 (0.0) & 10 (25.0) & 0 (0.0) & 10 (6.2) \\ moderate & 0 (0.0) & 0 (0.0) & 10 (25.0) & 0 (0.0) & 10 (6.2) \\ severe & 0 (0.0) & 0 (0.0) & 10 (25.0) & 0 (0.0) & 10 (6.2) \\ -\hline \multicolumn{6}{l}{\textbf{FORMf}}\\ +\hline \multicolumn{6}{l}{\textbf{FORMf}}\\%--pmtables-insert-panel tablet & 25 (83.3) & 42 (84.0) & 30 (75.0) & 33 (82.5) & 130 (81.2) \\ capsule & 3 (10.0) & 6 (12.0) & 3 (7.5) & 3 (7.5) & 15 (9.4) \\ troche & 2 (6.7) & 2 (4.0) & 7 (17.5) & 4 (10.0) & 15 (9.4) \\ diff --git a/tests/testthat/validate/cat-wide-by-panel.tex b/tests/testthat/validate/cat-wide-by-panel.tex index 72db8a25..25f4808e 100644 --- a/tests/testthat/validate/cat-wide-by-panel.tex +++ b/tests/testthat/validate/cat-wide-by-panel.tex @@ -8,19 +8,19 @@ \cmidrule(lr){5-8} FORMf & n & male & female & normal & mild & moderate & severe \\ \hline -\multicolumn{8}{l}{\textbf{12-DEMO-001}}\\ +\multicolumn{8}{l}{\textbf{12-DEMO-001}}\\%--pmtables-insert-panel tablet & 25 & 7 (28.0) & 18 (72.0) & 25 (100.0) & 0 (0.0) & 0 (0.0) & 0 (0.0) \\ capsule & 3 & 1 (33.3) & 2 (66.7) & 3 (100.0) & 0 (0.0) & 0 (0.0) & 0 (0.0) \\ troche & 2 & 2 (100.0) & 0 (0.0) & 2 (100.0) & 0 (0.0) & 0 (0.0) & 0 (0.0) \\ -\hline \multicolumn{8}{l}{\textbf{12-DEMO-002}}\\ +\hline \multicolumn{8}{l}{\textbf{12-DEMO-002}}\\%--pmtables-insert-panel tablet & 42 & 16 (38.1) & 26 (61.9) & 42 (100.0) & 0 (0.0) & 0 (0.0) & 0 (0.0) \\ capsule & 6 & 2 (33.3) & 4 (66.7) & 6 (100.0) & 0 (0.0) & 0 (0.0) & 0 (0.0) \\ troche & 2 & 0 (0.0) & 2 (100.0) & 2 (100.0) & 0 (0.0) & 0 (0.0) & 0 (0.0) \\ -\hline \multicolumn{8}{l}{\textbf{11-DEMO-005}}\\ +\hline \multicolumn{8}{l}{\textbf{11-DEMO-005}}\\%--pmtables-insert-panel tablet & 30 & 20 (66.7) & 10 (33.3) & 9 (30.0) & 7 (23.3) & 6 (20.0) & 8 (26.7) \\ capsule & 3 & 3 (100.0) & 0 (0.0) & 0 (0.0) & 2 (66.7) & 0 (0.0) & 1 (33.3) \\ troche & 7 & 6 (85.7) & 1 (14.3) & 1 (14.3) & 1 (14.3) & 4 (57.1) & 1 (14.3) \\ -\hline \multicolumn{8}{l}{\textbf{13-DEMO-001}}\\ +\hline \multicolumn{8}{l}{\textbf{13-DEMO-001}}\\%--pmtables-insert-panel tablet & 33 & 19 (57.6) & 14 (42.4) & 33 (100.0) & 0 (0.0) & 0 (0.0) & 0 (0.0) \\ capsule & 3 & 1 (33.3) & 2 (66.7) & 3 (100.0) & 0 (0.0) & 0 (0.0) & 0 (0.0) \\ troche & 4 & 3 (75.0) & 1 (25.0) & 4 (100.0) & 0 (0.0) & 0 (0.0) & 0 (0.0) \\ diff --git a/tests/testthat/validate/continuous-long-panel.tex b/tests/testthat/validate/continuous-long-panel.tex index 50f67973..b27711bc 100644 --- a/tests/testthat/validate/continuous-long-panel.tex +++ b/tests/testthat/validate/continuous-long-panel.tex @@ -5,23 +5,23 @@ \hline Variable & n & Mean & Median & SD & Min / Max \\ \hline -\multicolumn{6}{l}{\textbf{12-DEMO-001}}\\ +\multicolumn{6}{l}{\textbf{12-DEMO-001}}\\%--pmtables-insert-panel WT (kg) & 29 & 72.2 & 70.0 & 14.3 & 50.9 / 97.2 \\ CRCL (ml/min) & 29 & 106 & 104 & 9.46 & 93.2 / 126 \\ ALB (g/dL) & 29 & 4.28 & 4.08 & 0.474 & 3.56 / 5.15 \\ -\hline \multicolumn{6}{l}{\textbf{12-DEMO-002}}\\ +\hline \multicolumn{6}{l}{\textbf{12-DEMO-002}}\\%--pmtables-insert-panel WT (kg) & 49 & 72.4 & 72.1 & 11.5 & 51.5 / 96.6 \\ CRCL (ml/min) & 49 & 103 & 103 & 8.35 & 90.6 / 121 \\ ALB (g/dL) & 50 & 4.47 & 4.43 & 0.468 & 3.65 / 5.39 \\ -\hline \multicolumn{6}{l}{\textbf{11-DEMO-005}}\\ +\hline \multicolumn{6}{l}{\textbf{11-DEMO-005}}\\%--pmtables-insert-panel WT (kg) & 39 & 68.9 & 65.4 & 14.5 & 43.6 / 92.8 \\ CRCL (ml/min) & 39 & 58.8 & 56.2 & 29.7 & 15.4 / 103 \\ ALB (g/dL) & 39 & 4.41 & 4.44 & 0.537 & 3.51 / 5.39 \\ -\hline \multicolumn{6}{l}{\textbf{13-DEMO-001}}\\ +\hline \multicolumn{6}{l}{\textbf{13-DEMO-001}}\\%--pmtables-insert-panel WT (kg) & 40 & 69.4 & 68.1 & 11.6 & 50.7 / 96.6 \\ CRCL (ml/min) & 37 & 102 & 102 & 8.19 & 90.7 / 119 \\ ALB (g/dL) & 38 & 3.58 & 3.65 & 1.15 & 1.28 / 5.38 \\ -\hline \multicolumn{6}{l}{\textbf{All data}}\\ +\hline \multicolumn{6}{l}{\textbf{All data}}\\%--pmtables-insert-panel WT (kg) & 157 & 70.7 & 70.0 & 12.8 & 43.6 / 97.2 \\ CRCL (ml/min) & 154 & 92.1 & 98.8 & 25.5 & 15.4 / 126 \\ ALB (g/dL) & 156 & 4.20 & 4.32 & 0.793 & 1.28 / 5.39 \\ diff --git a/tests/testthat/validate/inventory-by.tex b/tests/testthat/validate/inventory-by.tex index ce764d56..aa646f6a 100644 --- a/tests/testthat/validate/inventory-by.tex +++ b/tests/testthat/validate/inventory-by.tex @@ -17,8 +17,8 @@ \end{tabular} \begin{tablenotes}[flushleft] \item SUBJ: subjects -\item BQL: below quantitation limit -\item MISS: missing observations (not BQL) +\item BQL: below quantification limit +\item MISS: missing observations (non-BQL) \item OBS: observations \end{tablenotes} \end{threeparttable} diff --git a/tests/testthat/validate/inventory-panel-by.tex b/tests/testthat/validate/inventory-panel-by.tex index 2e81bb6e..5ba96bf3 100644 --- a/tests/testthat/validate/inventory-panel-by.tex +++ b/tests/testthat/validate/inventory-panel-by.tex @@ -9,17 +9,17 @@ \cmidrule(lr){8-9} STUDYf & SUBJ & MISS & OBS & BQL & OBS & BQL & OBS & BQL \\ \hline -\multicolumn{9}{l}{\textbf{tablet}}\\ +\multicolumn{9}{l}{\textbf{tablet}}\\%--pmtables-insert-panel 12-DEMO-001 & 25 & 8 & 356 & 11 & 13.9 & 0.4 & 11.2 & 0.3 \\ 12-DEMO-002 & 42 & 7 & 969 & 32 & 37.7 & 1.2 & 30.5 & 1.0 \\ 11-DEMO-005 & 30 & 9 & 687 & 24 & 26.8 & 0.9 & 21.6 & 0.8 \\ 13-DEMO-001 & 33 & 6 & 479 & 10 & 18.7 & 0.4 & 15.1 & 0.3 \\ -\hline \multicolumn{9}{l}{\textbf{capsule}}\\ +\hline \multicolumn{9}{l}{\textbf{capsule}}\\%--pmtables-insert-panel 12-DEMO-001 & 3 & 0 & 41 & 4 & 13.5 & 1.3 & 1.3 & 0.1 \\ 12-DEMO-002 & 6 & 2 & 137 & 5 & 45.2 & 1.7 & 4.3 & 0.2 \\ 11-DEMO-005 & 3 & 1 & 70 & 1 & 23.1 & 0.3 & 2.2 & 0.0 \\ 13-DEMO-001 & 3 & 0 & 45 & 0 & 14.9 & 0.0 & 1.4 & 0.0 \\ -\hline \multicolumn{9}{l}{\textbf{troche}}\\ +\hline \multicolumn{9}{l}{\textbf{troche}}\\%--pmtables-insert-panel 12-DEMO-001 & 2 & 0 & 30 & 0 & 9.9 & 0.0 & 0.9 & 0.0 \\ 12-DEMO-002 & 2 & 1 & 46 & 1 & 15.1 & 0.3 & 1.4 & 0.0 \\ 11-DEMO-005 & 7 & 0 & 163 & 5 & 53.6 & 1.6 & 5.1 & 0.2 \\ @@ -29,8 +29,8 @@ \end{tabular} \begin{tablenotes}[flushleft] \item SUBJ: subjects -\item BQL: below quantitation limit -\item MISS: missing observations (not BQL) +\item BQL: below quantification limit +\item MISS: missing observations (non-BQL) \item OBS: observations \end{tablenotes} \end{threeparttable} diff --git a/tests/testthat/validate/inventory-stacked.tex b/tests/testthat/validate/inventory-stacked.tex index 7e5bff74..403d61a2 100644 --- a/tests/testthat/validate/inventory-stacked.tex +++ b/tests/testthat/validate/inventory-stacked.tex @@ -8,17 +8,17 @@ \cmidrule(lr){6-7} STUDYf & SUBJ & MISS & OBS & BQL & OBS & BQL \\ \hline -\multicolumn{7}{l}{\textbf{DEMO PK}}\\ +\multicolumn{7}{l}{\textbf{DEMO PK}}\\%--pmtables-insert-panel 12-DEMO-001 & 30 & 8 & 427 & 15 & 13.4 & 0.5 \\ 12-DEMO-002 & 50 & 10 & 1152 & 38 & 36.3 & 1.2 \\ 11-DEMO-005 & 40 & 10 & 920 & 30 & 29.0 & 0.9 \\ 13-DEMO-001 & 40 & 7 & 582 & 11 & 18.3 & 0.3 \\ \hline {\it Group Total} & 160 & 35 & 3081 & 94 & 97.0 & 3.0 \\ -\hline \multicolumn{7}{l}{\textbf{ESTRDIOL}}\\ +\hline \multicolumn{7}{l}{\textbf{ESTRDIOL}}\\%--pmtables-insert-panel 11-DEMO-005 & 40 & 0 & 40 & 0 & 50.6 & 0.0 \\ 13-DEMO-001 & 40 & 1 & 39 & 0 & 49.4 & 0.0 \\ \hline {\it Group Total} & 80 & 1 & 79 & 0 & 100.0 & 0.0 \\ -\hline \multicolumn{7}{l}{\textbf{BMD}}\\ +\hline \multicolumn{7}{l}{\textbf{BMD}}\\%--pmtables-insert-panel 11-DEMO-005 & 40 & 9 & 111 & 0 & 49.1 & 0.0 \\ 13-DEMO-001 & 40 & 5 & 115 & 0 & 50.9 & 0.0 \\ \hline {\it Group Total} & 80 & 14 & 226 & 0 & 100.0 & 0.0 \\ @@ -26,8 +26,8 @@ \end{tabular} \begin{tablenotes}[flushleft] \item SUBJ: subjects -\item BQL: below quantitation limit -\item MISS: missing observations (not BQL) +\item BQL: below quantification limit +\item MISS: missing observations (non-BQL) \item OBS: observations \end{tablenotes} \end{threeparttable} diff --git a/tests/testthat/validate/panel-basic.tex b/tests/testthat/validate/panel-basic.tex index 2cee6852..cf75b438 100644 --- a/tests/testthat/validate/panel-basic.tex +++ b/tests/testthat/validate/panel-basic.tex @@ -5,14 +5,14 @@ \hline DOSE & FORM & N & WT & CRCL & AGE & ALB & SCR \\ \hline -\multicolumn{8}{l}{\textbf{12-DEMO-001}}\\ +\multicolumn{8}{l}{\textbf{12-DEMO-001}}\\%--pmtables-insert-panel 100 mg & tablet & 80 & 71.4 & 104 & 33.7 & 4.20 & 1.06 \\ 150 mg & capsule & 16 & 89.4 & 122 & 24.4 & 4.63 & 1.12 \\ 150 mg & tablet & 48 & 81.7 & 104 & 34.4 & 3.83 & 0.910 \\ 150 mg & troche & 16 & 94.0 & 93.2 & 27.4 & 4.94 & 1.25 \\ 200 mg & tablet & 64 & 67.9 & 100 & 27.5 & 4.25 & 1.10 \\ 200 mg & troche & 16 & 76.6 & 99.2 & 22.8 & 4.54 & 1.15 \\ -\hline \multicolumn{8}{l}{\textbf{12-DEMO-002}}\\ +\hline \multicolumn{8}{l}{\textbf{12-DEMO-002}}\\%--pmtables-insert-panel 100 mg & capsule & 36 & 61.3 & 113 & 38.3 & 4.04 & 1.28 \\ 100 mg & tablet & 324 & 77.6 & 106 & 29.9 & 4.31 & 0.981 \\ 50 mg & capsule & 36 & 74.1 & 112 & 37.1 & 4.44 & 0.900 \\ diff --git a/tests/testthat/validate/panel-prefix.tex b/tests/testthat/validate/panel-prefix.tex index 340e0880..082ee777 100644 --- a/tests/testthat/validate/panel-prefix.tex +++ b/tests/testthat/validate/panel-prefix.tex @@ -5,14 +5,14 @@ \hline DOSE & FORM & N & WT & CRCL & AGE & ALB & SCR \\ \hline -\multicolumn{8}{l}{\textbf{Study: 12-DEMO-001}}\\ +\multicolumn{8}{l}{\textbf{Study: 12-DEMO-001}}\\%--pmtables-insert-panel 100 mg & tablet & 80 & 71.4 & 104 & 33.7 & 4.20 & 1.06 \\ 150 mg & capsule & 16 & 89.4 & 122 & 24.4 & 4.63 & 1.12 \\ 150 mg & tablet & 48 & 81.7 & 104 & 34.4 & 3.83 & 0.910 \\ 150 mg & troche & 16 & 94.0 & 93.2 & 27.4 & 4.94 & 1.25 \\ 200 mg & tablet & 64 & 67.9 & 100 & 27.5 & 4.25 & 1.10 \\ 200 mg & troche & 16 & 76.6 & 99.2 & 22.8 & 4.54 & 1.15 \\ -\hline \multicolumn{8}{l}{\textbf{Study: 12-DEMO-002}}\\ +\hline \multicolumn{8}{l}{\textbf{Study: 12-DEMO-002}}\\%--pmtables-insert-panel 100 mg & capsule & 36 & 61.3 & 113 & 38.3 & 4.04 & 1.28 \\ 100 mg & tablet & 324 & 77.6 & 106 & 29.9 & 4.31 & 0.981 \\ 50 mg & capsule & 36 & 74.1 & 112 & 37.1 & 4.44 & 0.900 \\ diff --git a/tests/testthat/validate/validate.pdf b/tests/testthat/validate/validate.pdf index c4c31e1a..d1ecc045 100644 Binary files a/tests/testthat/validate/validate.pdf and b/tests/testthat/validate/validate.pdf differ