TL Catalog
  1. Tables
  2. Clinical Laboratory Evaluation
  3. TSFLAB01
  • Introduction

  • Index

  • Tables
    • Adverse Events
      • TSFAE01A
      • TSFAE01B
      • TSFAE02
      • TSFAE02A
      • TSFAE03
      • TSFAE03A
      • TSFAE04
      • TSFAE04A
      • TSFAE05
      • TSFAE05A
      • TSFAE06A
      • TSFAE06B
      • TSFAE07A
      • TSFAE07B
      • TSFAE08
      • TSFAE09
      • TSFAE10
      • TSFAE11
      • TSFAE12
      • TSFAE13
      • TSFAE14
      • TSFAE15
      • TSFAE16
      • TSFAE17A
      • TSFAE17B
      • TSFAE17C
      • TSFAE17D
      • TSFAE19A
      • TSFAE19B
      • TSFAE19C
      • TSFAE19D
      • TSFAE20A
      • TSFAE20B
      • TSFAE20C
      • TSFAE21A
      • TSFAE21B
      • TSFAE21C
      • TSFAE21D
      • TSFAE22A
      • TSFAE22B
      • TSFAE22C
      • TSFAE23A
      • TSFAE23B
      • TSFAE23C
      • TSFAE23D
      • TSFAE24A
      • TSFAE24B
      • TSFAE24C
      • TSFAE24D
      • TSFAE24F
      • TSFDTH01
    • Clinical Laboratory Evaluation
      • TSFLAB01
      • TSFLAB01A
      • TSFLAB02
      • TSFLAB02A
      • TSFLAB02B
      • TSFLAB03
      • TSFLAB03A
      • TSFLAB04A
      • TSFLAB04B
      • TSFLAB05
      • TSFLAB06
      • TSFLAB07
    • Demographic
      • TSIDEM01
      • TSIDEM02
      • TSIMH01
    • Disposition of Subjects
      • TSIDS01
      • TSIDS02
      • TSIDS02A
    • Electrocardiograms
      • TSFECG01
      • TSFECG01A
      • TSFECG02
      • TSFECG03
      • TSFECG04
      • TSFECG05
    • Exposure
      • TSIEX01
      • TSIEX02
      • TSIEX03
      • TSIEX04
      • TSIEX06
      • TSIEX07
      • TSIEX08
      • TSIEX09
      • TSIEX10
      • TSIEX11
    • Pharmacokinetics
      • TPK01A
      • TPK01B
      • TPK02
      • TPK03
    • Prior and Concomitant Therapies
      • TSICM01
      • TSICM02
      • TSICM03
      • TSICM04
      • TSICM05
      • TSICM06
      • TSICM07
      • TSICM08
    • Vital Signs and Physical Findings
      • TSFVIT01
      • TSFVIT01A
      • TSFVIT02
      • TSFVIT03
      • TSFVIT04
      • TSFVIT05
      • TSFVIT06
  • Listings
    • Adverse Events
      • LSFAE01
      • LSFAE02
      • LSFAE03
      • LSFAE04
      • LSFAE05
      • LSFAE06A
      • LSFAE06B
      • LSFDTH01
    • Clinical Laboratory Evaluation
      • LSFLAB01
    • Demographic
      • LSIDEM01
      • LSIDEM02
      • LSIMH01
    • Disposition of Subjects
      • LSIDS01
      • LSIDS02
      • LSIDS03
      • LSIDS04
      • LSIDS05
    • Electrocardiograms
      • LSFECG01
      • LSFECG02
    • Exposure
      • LSIEX01
      • LSIEX02
      • LSIEX03
    • Prior and Concomitant Therapies
      • LSICM01
    • Vital Signs and Physical Findings
      • LSFVIT01
      • LSFVIT02

  • Reproducibility

  • Changelog

On this page

  • Output
  • Edit this page
  • Report an issue
  1. Tables
  2. Clinical Laboratory Evaluation
  3. TSFLAB01

TSFLAB01

Mean Change From Baseline for Laboratory Category Laboratory Data Over Time


Output

  • Preview
Code
# Program Name:              tsflab01

# Prep Environment

library(envsetup)
library(tern)
library(dplyr)
library(rtables)
library(junco)

# Define script level parameters:

tblid <- "TSFLAB01"
titles <- get_titles_from_file(input_path = '../../_data/', tblid)
string_map <- default_str_map
fileid <- tblid

popfl <- "SAFFL"
trtvar <- "TRT01A"
ctrl_grp <- "Placebo"


# Note on ancova parameter
# when ancova = TRUE
# ancova model will be used to calculate all mean/mean change columns
# not just those from the Difference column
# model specification
summ_vars <- list(arm = trtvar, covariates = NULL)

# when ancova = FALSE, all mean/mean change columns will be from descriptive stats
# for the difference column descriptive stats will be based upon two-sample t-test
ancova <- FALSE


comp_btw_group <- TRUE


## For analysis on SI units: use adlb dataset
## For analysis on Conventional units: use adlbc dataset -- shell is in conventional units

ad_domain <- "ADLB"

# see further, an alternative method to identify all non-unscheduled visits based upon data
selvisit <- c("Screening", "Baseline", "Cycle 02", "Cycle 03", "Cycle 04")


### note : this shell covers multiple tables depending on parcat3 selections

## allowed PARCAT3 selections

# parcat3sel <- "General chemistry"
# parcat3sel <- "Kidney function"
# parcat3sel <- "Liver biochemistry"
# parcat3sel <- "Lipids"
#
# ### Hematology (HM) : has 3 subcategories that should be included on one table
# parcat3sel <- c("Complete blood count","WBC differential","Coagulation studies")

# per DPS specifications, the output identifier should include the abbreviation for the category

# 1. Present laboratory tests using separate outputs for each category as follows:
#   General chemistry (GC): Sodium, Potassium, Chloride, Bicarbonate, Urea Nitrogen, Glucose, Calcium, Magnesium, Phosphate, Protein, Albumin, Creatine Kinase, Amylase, Lipase
#   Kidney function (KF): Creatinine, GFR from Creatinine
#   Liver biochemistry (LV): Alkaline Phosphatase, Alanine Aminotransferase, Aspartate Aminotransferase, Bilirubin, Prothrombin Intl. Normalized Ratio, Gamma Glutamyl Transferase
#   Lipids (LP): Cholesterol, HDL Cholesterol, LDL Cholesterol, Triglycerides
#   Hematology (HM):  Subcategory rows should be included for Complete Blood Count, White Blood Cell Differential and for Coagulation Studies
#     Complete blood count: Leukocytes, Hemoglobin, Platelets;
#     WBC differential: Lymphocytes, Neutrophils, Eosinophils;
#     Coagulation studies: Prothrombin Time, Activated Partial Thromboplastin Time.

# The output identifier should include the abbreviation for the laboratory category (eg, TSFLAB02GC for General Chemistry)

# In current template program, only 1 version is created, without the proper abbreviation appended
# The reason for this is that TSFLAB02GC is not included in the DPS system - only the core version TSFLAB02

get_abbreviation <- function(parcat3sel) {
  parcat3sel <- toupper(parcat3sel)
  abbr <- NULL
  if (length(parcat3sel) == 1) {
    if (parcat3sel == toupper("General chemistry")) {
      abbr <- "GC"
    }
    # the following line should be removed for a true study, global jjcs standards in DPS system does not include the abbreviation
    if (parcat3sel == toupper("General chemistry")) {
      abbr <- ""
    }
    #
    if (parcat3sel == toupper("Kidney function")) {
      abbr <- "KF"
    }
    if (parcat3sel == toupper("Liver biochemistry")) {
      abbr <- "LV"
    }
    if (parcat3sel == toupper("Lipids")) abbr <- "LP"
  }
  if (length(parcat3sel) > 1) {
    if (
      all(
        parcat3sel %in%
          toupper(c(
            "Complete blood count",
            "WBC differential",
            "Coagulation studies"
          ))
      )
    ) {
      abbr <- "HM"
    }
  }

  if (is.null(abbr)) {
    message("Incorrect specification of parcat3sel")
  }

  abbr
}

get_tblid <- function(tblid, parcat3sel, method = c("after", "inbetween")) {
  abbr <- get_abbreviation(parcat3sel)

  method <- match.arg(method)
  # when inbetween, the abbreviation will be added prior to the number part of the table identifier
  # when after (default), the abbreviation will be added at the end of the table identifier

  x <- 0
  if (method == "inbetween") {
    x <- regexpr(pattern = "[0-9]", tblid)[1]
  }

  if (x > 0) {
    tblid1 <- substr(tblid, 1, x - 1)
    tblid2 <- substring(tblid, x)
    tblid_new <- paste0(tblid1, abbr, tblid2)
  } else {
    tblid_new <- paste0(tblid, abbr)
  }

  return(tblid_new)
}

## parcat3 options :
# current data: Liver biochemistry, General chemistry, Lipids, Kidney function, Complete blood count, WBC differential
# according shell: General chemistry, Kidney function, Liver biochemistry, Lipids, Hematology

## not in shell: Complete blood count, WBC differential
## not in data:  Hematology

availparcat3 <- c(
  "General chemistry",
  "Kidney function",
  "Liver biochemistry",
  "Lipids",
  "Complete blood count",
  "WBC differential",
  ""
)

# Process Data:

adsl <- pharmaverseadamjnj::adsl %>%
  filter(.data[[popfl]] == "Y") %>%
  select(
    STUDYID,
    USUBJID,
    all_of(c(popfl, trtvar)),
    SEX,
    AGEGR1,
    RACE,
    ETHNIC,
    AGE
  )

adsl$colspan_trt <- factor(
  ifelse(adsl[[trtvar]] == ctrl_grp, " ", "Active Study Agent"),
  levels = c("Active Study Agent", " ")
)

adsl$rrisk_header <- "Difference in Mean Change (95% CI)"
adsl$rrisk_label <- paste(adsl[[trtvar]], paste("vs", ctrl_grp))


colspan_trt_map <- create_colspan_map(
  adsl,
  non_active_grp = ctrl_grp,
  non_active_grp_span_lbl = " ",
  active_grp_span_lbl = "Active Study Agent",
  colspan_var = "colspan_trt",
  trt_var = trtvar
)

ref_path <- c("colspan_trt", " ", trtvar, ctrl_grp)

adlb_complete <- pharmaverseadamjnj::adlb

# selection of all non-unscheduled visits from data
visits <- adlb_complete %>%
  select(AVISIT) %>%
  filter(!grepl("UNSCHEDULED", toupper(AVISIT)))

visits$AVISIT <- droplevels(visits$AVISIT)
selvisit_data <- levels(visits$AVISIT)

### if preferred to get it from data, rather than hardcoded list of visits
# selvisit <- selvisit_data

adlb00 <- adlb_complete %>%
  select(
    USUBJID,
    AVISITN,
    AVISIT,
    starts_with("PAR"),
    AVAL,
    BASE,
    CHG,
    PCHG,
    starts_with("ANL"),
    ABLFL,
    APOBLFL
  ) %>%
  inner_join(adsl) %>%
  relocate(USUBJID, PARAMCD, AVISIT, ANL02FL, ABLFL, APOBLFL)

parcat <- unique(adlb00 %>% select(starts_with("PARCAT"), PARAMCD, PARAM))

## retrieve the precision of AVAL on the input dataset
## review outcome and make updates manually if needed
## the precision variable will be used for the parameter-based formats in layout

## decimal = 4 is a cap in this derivation: if decimal precision of variable > decimal, the result will end up as decimal
## eg if AVAL has precision of 6 for parameter x, and decimal = 4, the resulting decimal value for parameter x is 4

## note that precision is on the raw values, as we are presenting mean/ci, and extra digit will be added
## eg precision = 2 will result in mean/ci format xx.xxx (xx.xxx, xx.xxx)

lb_precision <- tidytlg:::make_precision_data(
  df = adlb00,
  decimal = 3,
  precisionby = "PARAMCD",
  precisionon = "AVAL"
)

### data preparation

filtered_adlb <- adlb00 %>%
  filter(AVISIT %in% selvisit) %>%
  ### unique record per timepoint:
  filter(ANL02FL == "Y" & (ABLFL == "Y" | APOBLFL == "Y"))

#### perform check on unique record per subject/param/timepoint
check_unique <- filtered_adlb %>%
  group_by(USUBJID, PARAMCD, AVISIT) %>%
  mutate(n_recsub = n()) %>%
  filter(n_recsub > 1)

if (nrow(check_unique) > 0) {
  stop(
    "Your input dataset needs extra attention, as some subjects have more than one record per parameter/visit"
  )
  ### you will run into issues with fraction portion in count_denom_fraction, as count > denom, and fraction > 1 if you don't adjust your input dataset

  # Possible extra derivation - just to ensure program can run without issues
  ### Study team is responsible for adding this derivation onto ADaM dataset and ensure proper derivation rule for ANL02FL is implemented !!!!!!!!!!
  filtered_adlbx <- adlb00 %>%
    filter(PARAMCD %in% selparamcd) %>%
    filter(AVISIT %in% selvisit) %>%
    ### unique record per timepoint:
    filter(ANL02FL == "Y" & (ABLFL == "Y" | APOBLFL == "Y")) %>%
    group_by(USUBJID, PARAM, AVISIT) %>%
    mutate(n_sub = n()) %>%
    arrange(USUBJID, PARAM, AVISIT, ADT) %>%
    mutate(i = vctrs::vec_group_id(ADT)) %>%
    mutate(
      ANL02FL = case_when(
        n_sub == 1 ~ "Y",
        i == 1 ~ "Y"
      )
    ) %>%
    select(-c(i, n_sub)) %>%
    ungroup()

  filtered_adlb <- filtered_adlbx %>%
    filter(PARAMCD %in% selparamcd) %>%
    filter(AVISIT %in% selvisit) %>%
    ### unique record per timepoint:
    filter(ANL02FL == "Y" & (ABLFL == "Y" | APOBLFL == "Y"))

  ## now your data should contain 1 record per subject per parameter
}


### for denominator per timepoint: all records from adlb on this timepoint: ignoring anl01fl/anl02fl/param
filtered_adlb_timepoints <- unique(
  adlb00 %>%
    filter(AVISIT %in% selvisit) %>%
    select(USUBJID, AVISITN, AVISIT)
) %>%
  inner_join(adsl)

# Define layout and build table:

# Core function to produce shell for specific parcat3 selection

build_result_parcat3 <- function(
  df = filtered_adlb,
  df_timepoints = filtered_adlb_timepoints,
  df_orig = adlb00,
  PARCAT3sel = NULL,
  .adsl = adsl,
  tblid,
  save2rtf = TRUE,
  .summ_vars = summ_vars,
  .trtvar = trtvar,
  .ref_path = ref_path
) {
  tblidx <- get_tblid(tblid, PARCAT3sel)
  titles2 <- get_titles_from_file(input_path = '../../_data/', tblidx)

  .ctrl_grp <- utils::tail(.ref_path, n = 1)

  multivars <- c("AVAL", "AVAL", "CHG")
  extra_args_3col <- list(
    format_na_str = rep("NA", 3),
    d = "decimal",
    variables = .summ_vars,
    ancova = ancova,
    comp_btw_group = comp_btw_group,
    ref_path = .ref_path,
    multivars = multivars
  )

  if (!is.null(PARCAT3sel)) {
    df <- df %>%
      filter(PARCAT3 %in% PARCAT3sel)
  }

  ### continue with data preparation
  params <- unique(df %>% select(PARAMCD, PARAM))

  df_timepoints <- df_timepoints %>%
    mutate(dummy_join = 1) %>%
    full_join(
      params %>% mutate(dummy_join = 1),
      relationship = "many-to-many"
    ) %>%
    select(-dummy_join)

  ### identify subjects in df_timepoints and not in df

  extra_sub <- anti_join(df_timepoints, df)

  ### only add these extra_sub to
  ### this will ensure we still meet the one record per subject per timepoint
  ### this will ensure length(x) can be used for the denominator derivation inside summarize_aval_chg_diff function

  df <- bind_rows(df, extra_sub) %>%
    arrange(USUBJID, PARAM, AVISITN)

  df <- df %>%
    inner_join(lb_precision, by = "PARAMCD")

  ### important: previous actions lost the label of variables
  ### in order to be able to use obj_label(filtered_adlb$PARAM) in layout, need to redefine the label
  df <- var_relabel_list(df, var_labels(df_orig, fill = T))

  ################################################################################
  # Define layout and build table:
  ################################################################################

  lyt <- basic_table(show_colcounts = FALSE, colcount_format = "N=xx") %>%
    ### first columns
    split_cols_by(
      "colspan_trt",
      split_fun = trim_levels_to_map(map = colspan_trt_map)
    ) %>%
    split_cols_by(.trtvar, show_colcounts = TRUE, colcount_format = "N=xx") %>%
    split_rows_by(
      "PARAM",
      label_pos = "topleft",
      split_label = "Laboratory Test",
      section_div = " ",
      split_fun = drop_split_levels
    ) %>%
    ## note the child_labels = hidden for AVISIT, these labels will be taken care off by
    ## applying function summarize_aval_chg_diff further in the layout
    split_rows_by(
      "AVISIT",
      label_pos = "topleft",
      split_label = "Study Visit",
      split_fun = drop_split_levels,
      child_labels = "hidden"
    ) %>%
    ## set up a 3 column split
    split_cols_by_multivar(
      multivars,
      varlabels = c(
        "n/N (%)",
        "Mean (95% CI)",
        "Mean Change From Baseline (95% CI)"
      )
    ) %>%
    ### restart for the rrisk_header columns - note the nested = FALSE option
    ### also note the child_labels = "hidden" in both PARAM and AVISIT
    split_cols_by("rrisk_header", nested = FALSE) %>%
    split_cols_by(
      .trtvar,
      split_fun = remove_split_levels(.ctrl_grp),
      labels_var = "rrisk_label",
      show_colcounts = TRUE,
      colcount_format = "N=xx"
    ) %>%
    ### difference columns : just 1 column & analysis needs to be done on change
    split_cols_by_multivar(multivars[3], varlabels = c(" ")) %>%
    ### the variable passed here in analyze is not used (STUDYID), it is a dummy var passing,
    ### the function summarize_aval_chg_diff grabs the required vars from cols_by_multivar calls
    analyze(
      "STUDYID",
      afun = a_summarize_aval_chg_diff_j,
      extra_args = extra_args_3col
    )

  if (nrow(df) > 0) {
    result <- build_table(lyt, df, alt_counts_df = .adsl)

    ################################################################################
    # Post-Processing:
    # - Prune table to remove when n = 0 in all columns
    # - Remove the N=xx column headers for the difference vs PBO columns
    ################################################################################

    ### alhtough this is not really likely to occur in real data, this is a problem in the current synthetic data
    ### also here, try to remove this issue

    # rps_result <- row_paths_summary(result)

    ### below code is based upon tern pruning function has_count_in_any_col, with updates to internal function h_row_first_values for the 3 column - format we are using here

    my_has_count_in_any_col <- function(atleast, ...) {
      checkmate::assert_count(atleast)
      CombinationFunction(function(table_row) {
        row_counts <- my_h_row_counts(table_row, ...)
        ### small update compared to tern::has_count_in_any_col
        ## > vs >=
        any(row_counts > atleast)
      })
    }

    my_h_row_counts <-
      function(table_row, col_names = NULL, col_indices = NULL) {
        ## no updates compared to tern::h_row_counts, beyond using the customized my_h_row_first_values function
        counts <- my_h_row_first_values(table_row, col_names, col_indices)
        checkmate::assert_integerish(counts)
        counts
      }

    my_h_row_first_values <- function(
      table_row,
      col_names = NULL,
      col_indices = NULL
    ) {
      col_indices <- tern:::check_names_indices(
        table_row,
        col_names,
        col_indices
      )
      checkmate::assert_integerish(col_indices)
      checkmate::assert_subset(col_indices, seq_len(ncol(table_row)))

      # Main values are extracted
      row_vals <- row_values(table_row)[col_indices]

      ### specific updates to current situation -- 3 column layout, I want to grab the information from the n/N column, which is in first analysis of AVAL
      specific_cols <- names(row_vals)
      specific_cols <- specific_cols[stringr::str_ends(specific_cols, "AVAL")]

      row_vals <- row_vals[specific_cols]

      # Main return
      vapply(
        row_vals,
        function(rv) {
          if (is.null(rv)) {
            NA_real_
          } else {
            rv[1L]
          }
        },
        FUN.VALUE = numeric(1)
      )
    }

    more_than_0 <- my_has_count_in_any_col(atleast = 0)

    ## seem to work ok, not clear why it goes through each row twice?
    result <- prune_table(result, keep_rows(more_than_0))

    ## Remove the N=xx column headers for the difference vs PBO columns
    remove_col_count2 <- function(result, string = paste("vs", ctrl_grp)) {
      mcdf <- make_col_df(result, visible_only = FALSE)
      mcdfsel <- mcdf %>%
        filter(stringr::str_detect(toupper(label), toupper(string))) %>%
        pull(path)

      for (i in seq_along(mcdfsel)) {
        facet_colcount(result, mcdfsel[[i]]) <- NA
      }

      return(result)
    }

    result <- remove_col_count2(result)
  } else {
    result <- NULL
    message(paste0(
      "Parcat3 [",
      PARCAT3sel,
      "] is not present on input dataset"
    ))
    return(result)
  }

  ################################################################################
  # Set title
  ################################################################################

  result <- set_titles(result, titles2)

  if (save2rtf) {
    ################################################################################
    # Convert to tbl file and output table
    ################################################################################
    ### add the proper abbreviation to the tblid, and add opath path
fileid <- tblid

    tt_to_tlgrtf(string_map = string_map, tt = 
      result,
      file = fileid,
      orientation = "landscape",
      nosplitin = list(cols = c(trtvar, "rrisk_header"))
    )
  }

  return(result)
}

# Apply core function to all specified levels of parcat3 selection

### note : the same core tblid (TSFLAB01) will be used for all, inside the core function build_result_parcat3 the proper abbreviation will be added

### titles will not be retrieved for these, as the table identifiers are not in the DPS system
### study teams will have to ensure all versions that are needed are included in DPS system
result1 <- build_result_parcat3(
  PARCAT3sel = "Liver biochemistry",
  tblid = tblid,
  save2rtf = TRUE
)
result2 <- build_result_parcat3(
  PARCAT3sel = "Kidney function",
  tblid = tblid,
  save2rtf = TRUE
)
result3 <- build_result_parcat3(
  PARCAT3sel = "Lipids",
  tblid = tblid,
  save2rtf = TRUE
)

result4 <- build_result_parcat3(
  PARCAT3sel = c(
    "Complete blood count",
    "WBC differential",
    "Coagulation studies"
  ),
  tblid = tblid,
  save2rtf = TRUE
)

### if a certain category is not present, no rtf will be generated

result <- build_result_parcat3(PARCAT3sel = "General chemistry", tblid = tblid)

TSFLAB01: Mean Change From Baseline for [Laboratory Category] Laboratory Data Over Time; Safety Analysis Set (Study jjcs - core)

Active Study Agent

Difference in Mean Change (95% CI)

Xanomeline High Dose

Xanomeline Low Dose

Placebo

Xanomeline High Dose vs Placebo

Xanomeline Low Dose vs Placebo

Laboratory Test

N=53

N=73

N=59

Study Visit

n/N (%)

Mean (95% CI)

Mean Change From Baseline (95% CI)

n/N (%)

Mean (95% CI)

Mean Change From Baseline (95% CI)

n/N (%)

Mean (95% CI)

Mean Change From Baseline (95% CI)

Calcium (mmol/L)

Baseline

52/53 (98.1%)

2.3007 (2.2724, 2.3290)

68/72 (94.4%)

2.2968 (2.2691, 2.3246)

55/59 (93.2%)

2.3090 (2.2863, 2.3317)

Cycle 02

29/53 (54.7%)

2.2876 (2.2419, 2.3334)

-0.0120 (-0.0500, 0.0259)

31/67 (46.3%)

2.2922 (2.2438, 2.3406)

-0.0217 (-0.0655, 0.0221)

33/58 (56.9%)

2.2674 (2.2342, 2.3006)

-0.0295 (-0.0634, 0.0045)

0.0174 (-0.0324, 0.0673)

0.0078 (-0.0466, 0.0621)

Cycle 03

21/53 (39.6%)

2.2954 (2.2425, 2.3483)

-0.0095 (-0.0609, 0.0419)

21/52 (40.4%)

2.2811 (2.2404, 2.3219)

-0.0238 (-0.0574, 0.0098)

24/56 (42.9%)

2.2621 (2.2299, 2.2944)

-0.0322 (-0.0626, -0.0019)

0.0227 (-0.0356, 0.0810)

0.0085 (-0.0355, 0.0524)

Cycle 04

24/50 (48.0%)

2.2652 (2.2315, 2.2990)

-0.0353 (-0.0800, 0.0093)

25/47 (53.2%)

2.2824 (2.2360, 2.3289)

0.0010 (-0.0341, 0.0361)

24/51 (47.1%)

2.2819 (2.2374, 2.3263)

-0.0187 (-0.0681, 0.0306)

-0.0166 (-0.0814, 0.0482)

0.0197 (-0.0394, 0.0788)

Glucose (mmol/L)

Baseline

50/53 (94.3%)

5.2945 (4.9610, 5.6281)

70/72 (97.2%)

5.4106 (5.1498, 5.6715)

57/59 (96.6%)

5.3124 (5.0333, 5.5915)

Cycle 02

27/53 (50.9%)

6.7290 (5.0413, 8.4168)

1.0650 (-0.0754, 2.2054)

30/67 (44.8%)

5.3937 (4.9428, 5.8446)

0.0703 (-0.4232, 0.5638)

31/58 (53.4%)

5.4471 (4.9675, 5.9268)

0.0376 (-0.4162, 0.4914)

1.0274 (-0.1869, 2.2416)

0.0327 (-0.6238, 0.6892)

Cycle 03

27/53 (50.9%)

6.2644 (5.3223, 7.2065)

0.8162 (0.2610, 1.3715)

30/52 (57.7%)

5.4030 (4.9699, 5.8360)

-0.0259 (-0.3714, 0.3196)

22/56 (39.3%)

5.3365 (4.7634, 5.9096)

-0.0454 (-0.7575, 0.6667)

0.8616 (-0.0185, 1.7417)

0.0195 (-0.7591, 0.7981)

Cycle 04

23/50 (46.0%)

6.6081 (4.7846, 8.4317)

1.0137 (-0.4353, 2.4626)

21/47 (44.7%)

5.2972 (4.9553, 5.6392)

-0.2220 (-0.6998, 0.2557)

21/51 (41.2%)

5.5378 (4.6876, 6.3880)

0.0529 (-0.5992, 0.7050)

0.9608 (-0.6016, 2.5232)

-0.2749 (-1.0604, 0.5106)

Potassium (mmol/L)

Baseline

51/53 (96.2%)

4.27 (4.17, 4.37)

68/72 (94.4%)

4.32 (4.24, 4.41)

57/59 (96.6%)

4.26 (4.14, 4.37)

Cycle 02

29/53 (54.7%)

4.32 (4.17, 4.47)

0.14 (-0.00, 0.28)

31/67 (46.3%)

4.25 (4.09, 4.41)

-0.05 (-0.22, 0.13)

30/58 (51.7%)

4.28 (4.13, 4.43)

0.04 (-0.11, 0.20)

0.10 (-0.11, 0.31)

-0.09 (-0.32, 0.14)

Cycle 03

25/53 (47.2%)

4.22 (4.07, 4.38)

-0.22 (-0.40, -0.05)

24/52 (46.2%)

4.35 (4.14, 4.56)

-0.01 (-0.15, 0.13)

28/56 (50.0%)

4.14 (3.97, 4.31)

-0.10 (-0.27, 0.07)

-0.12 (-0.36, 0.12)

0.09 (-0.12, 0.31)

Cycle 04

24/50 (48.0%)

4.12 (3.98, 4.25)

-0.20 (-0.36, -0.03)

24/47 (51.1%)

4.25 (4.06, 4.45)

-0.03 (-0.23, 0.18)

28/51 (54.9%)

4.27 (4.13, 4.41)

0.01 (-0.15, 0.17)

-0.21 (-0.43, 0.02)

-0.04 (-0.29, 0.22)

Sodium (mmol/L)

Baseline

52/53 (98.1%)

139.9 (139.2, 140.6)

69/72 (95.8%)

139.8 (139.1, 140.4)

57/59 (96.6%)

140.7 (140.0, 141.4)

Cycle 02

22/53 (41.5%)

138.7 (137.5, 139.9)

-1.3 (-2.7, 0.2)

35/67 (52.2%)

139.6 (138.6, 140.6)

0.4 (-0.7, 1.5)

31/58 (53.4%)

140.4 (139.2, 141.6)

0.5 (-0.8, 1.8)

-1.8 (-3.7, 0.2)

-0.1 (-1.7, 1.6)

Cycle 03

26/53 (49.1%)

139.5 (138.5, 140.6)

-0.3 (-1.6, 0.9)

24/52 (46.2%)

140.7 (139.6, 141.7)

0.8 (-0.4, 1.9)

26/56 (46.4%)

139.7 (138.6, 140.7)

-1.0 (-2.3, 0.4)

0.6 (-1.2, 2.4)

1.7 (-0.0, 3.4)

Cycle 04

26/50 (52.0%)

140.5 (139.3, 141.7)

0.8 (-0.4, 2.0)

20/47 (42.6%)

140.3 (139.4, 141.3)

0.8 (-0.2, 1.7)

22/51 (43.1%)

140.3 (139.4, 141.2)

-0.8 (-2.2, 0.6)

1.6 (-0.2, 3.4)

1.5 (-0.1, 3.2)

Download RTF file

TSFDTH01
TSFLAB01A
Source Code
---
title: TSFLAB01
subtitle: Mean Change From Baseline for Laboratory Category Laboratory Data Over Time
---

------------------------------------------------------------------------

{{< include ../../_utils/envir_hook.qmd >}}

```{r setup, echo = FALSE, warning = FALSE, message = FALSE}
options(docx.add_datetime = FALSE, tidytlg.add_datetime = FALSE)
envsetup_config_name <- "default"

# Path to the combined config file
envsetup_file_path <- file.path("../..", "envsetup.yml")

Sys.setenv(ENVSETUP_ENVIRON = '')
library(envsetup)
loaded_config <- config::get(config = envsetup_config_name, file = envsetup_file_path)
envsetup::rprofile(loaded_config)


dpscomp <- compound
dpspdr <- paste(protocol,dbrelease,rpteff,sep="__")

aptcomp <- compound
aptpdr <- paste(protocol,dbrelease,rpteff,sep="__")

###### Study specific updates (formerly in envre)

dpscomp <- "standards"
dpspdr <- "jjcs__NULL__jjcs - core"

apt <- FALSE
library(junco)
default_str_map <- rbind(default_str_map, c("&ctcae", "5.0"))

```

## Output

:::: panel-tabset
## {{< fa regular file-lines sm fw >}} Preview

```{r variant1, results='hide', warning = FALSE, message = FALSE}

# Program Name:              tsflab01

# Prep Environment

library(envsetup)
library(tern)
library(dplyr)
library(rtables)
library(junco)

# Define script level parameters:

tblid <- "TSFLAB01"
titles <- get_titles_from_file(input_path = '../../_data/', tblid)
string_map <- default_str_map
fileid <- tblid

popfl <- "SAFFL"
trtvar <- "TRT01A"
ctrl_grp <- "Placebo"


# Note on ancova parameter
# when ancova = TRUE
# ancova model will be used to calculate all mean/mean change columns
# not just those from the Difference column
# model specification
summ_vars <- list(arm = trtvar, covariates = NULL)

# when ancova = FALSE, all mean/mean change columns will be from descriptive stats
# for the difference column descriptive stats will be based upon two-sample t-test
ancova <- FALSE


comp_btw_group <- TRUE


## For analysis on SI units: use adlb dataset
## For analysis on Conventional units: use adlbc dataset -- shell is in conventional units

ad_domain <- "ADLB"

# see further, an alternative method to identify all non-unscheduled visits based upon data
selvisit <- c("Screening", "Baseline", "Cycle 02", "Cycle 03", "Cycle 04")


### note : this shell covers multiple tables depending on parcat3 selections

## allowed PARCAT3 selections

# parcat3sel <- "General chemistry"
# parcat3sel <- "Kidney function"
# parcat3sel <- "Liver biochemistry"
# parcat3sel <- "Lipids"
#
# ### Hematology (HM) : has 3 subcategories that should be included on one table
# parcat3sel <- c("Complete blood count","WBC differential","Coagulation studies")

# per DPS specifications, the output identifier should include the abbreviation for the category

# 1. Present laboratory tests using separate outputs for each category as follows:
#   General chemistry (GC): Sodium, Potassium, Chloride, Bicarbonate, Urea Nitrogen, Glucose, Calcium, Magnesium, Phosphate, Protein, Albumin, Creatine Kinase, Amylase, Lipase
#   Kidney function (KF): Creatinine, GFR from Creatinine
#   Liver biochemistry (LV): Alkaline Phosphatase, Alanine Aminotransferase, Aspartate Aminotransferase, Bilirubin, Prothrombin Intl. Normalized Ratio, Gamma Glutamyl Transferase
#   Lipids (LP): Cholesterol, HDL Cholesterol, LDL Cholesterol, Triglycerides
#   Hematology (HM):  Subcategory rows should be included for Complete Blood Count, White Blood Cell Differential and for Coagulation Studies
#     Complete blood count: Leukocytes, Hemoglobin, Platelets;
#     WBC differential: Lymphocytes, Neutrophils, Eosinophils;
#     Coagulation studies: Prothrombin Time, Activated Partial Thromboplastin Time.

# The output identifier should include the abbreviation for the laboratory category (eg, TSFLAB02GC for General Chemistry)

# In current template program, only 1 version is created, without the proper abbreviation appended
# The reason for this is that TSFLAB02GC is not included in the DPS system - only the core version TSFLAB02

get_abbreviation <- function(parcat3sel) {
  parcat3sel <- toupper(parcat3sel)
  abbr <- NULL
  if (length(parcat3sel) == 1) {
    if (parcat3sel == toupper("General chemistry")) {
      abbr <- "GC"
    }
    # the following line should be removed for a true study, global jjcs standards in DPS system does not include the abbreviation
    if (parcat3sel == toupper("General chemistry")) {
      abbr <- ""
    }
    #
    if (parcat3sel == toupper("Kidney function")) {
      abbr <- "KF"
    }
    if (parcat3sel == toupper("Liver biochemistry")) {
      abbr <- "LV"
    }
    if (parcat3sel == toupper("Lipids")) abbr <- "LP"
  }
  if (length(parcat3sel) > 1) {
    if (
      all(
        parcat3sel %in%
          toupper(c(
            "Complete blood count",
            "WBC differential",
            "Coagulation studies"
          ))
      )
    ) {
      abbr <- "HM"
    }
  }

  if (is.null(abbr)) {
    message("Incorrect specification of parcat3sel")
  }

  abbr
}

get_tblid <- function(tblid, parcat3sel, method = c("after", "inbetween")) {
  abbr <- get_abbreviation(parcat3sel)

  method <- match.arg(method)
  # when inbetween, the abbreviation will be added prior to the number part of the table identifier
  # when after (default), the abbreviation will be added at the end of the table identifier

  x <- 0
  if (method == "inbetween") {
    x <- regexpr(pattern = "[0-9]", tblid)[1]
  }

  if (x > 0) {
    tblid1 <- substr(tblid, 1, x - 1)
    tblid2 <- substring(tblid, x)
    tblid_new <- paste0(tblid1, abbr, tblid2)
  } else {
    tblid_new <- paste0(tblid, abbr)
  }

  return(tblid_new)
}

## parcat3 options :
# current data: Liver biochemistry, General chemistry, Lipids, Kidney function, Complete blood count, WBC differential
# according shell: General chemistry, Kidney function, Liver biochemistry, Lipids, Hematology

## not in shell: Complete blood count, WBC differential
## not in data:  Hematology

availparcat3 <- c(
  "General chemistry",
  "Kidney function",
  "Liver biochemistry",
  "Lipids",
  "Complete blood count",
  "WBC differential",
  ""
)

# Process Data:

adsl <- pharmaverseadamjnj::adsl %>%
  filter(.data[[popfl]] == "Y") %>%
  select(
    STUDYID,
    USUBJID,
    all_of(c(popfl, trtvar)),
    SEX,
    AGEGR1,
    RACE,
    ETHNIC,
    AGE
  )

adsl$colspan_trt <- factor(
  ifelse(adsl[[trtvar]] == ctrl_grp, " ", "Active Study Agent"),
  levels = c("Active Study Agent", " ")
)

adsl$rrisk_header <- "Difference in Mean Change (95% CI)"
adsl$rrisk_label <- paste(adsl[[trtvar]], paste("vs", ctrl_grp))


colspan_trt_map <- create_colspan_map(
  adsl,
  non_active_grp = ctrl_grp,
  non_active_grp_span_lbl = " ",
  active_grp_span_lbl = "Active Study Agent",
  colspan_var = "colspan_trt",
  trt_var = trtvar
)

ref_path <- c("colspan_trt", " ", trtvar, ctrl_grp)

adlb_complete <- pharmaverseadamjnj::adlb

# selection of all non-unscheduled visits from data
visits <- adlb_complete %>%
  select(AVISIT) %>%
  filter(!grepl("UNSCHEDULED", toupper(AVISIT)))

visits$AVISIT <- droplevels(visits$AVISIT)
selvisit_data <- levels(visits$AVISIT)

### if preferred to get it from data, rather than hardcoded list of visits
# selvisit <- selvisit_data

adlb00 <- adlb_complete %>%
  select(
    USUBJID,
    AVISITN,
    AVISIT,
    starts_with("PAR"),
    AVAL,
    BASE,
    CHG,
    PCHG,
    starts_with("ANL"),
    ABLFL,
    APOBLFL
  ) %>%
  inner_join(adsl) %>%
  relocate(USUBJID, PARAMCD, AVISIT, ANL02FL, ABLFL, APOBLFL)

parcat <- unique(adlb00 %>% select(starts_with("PARCAT"), PARAMCD, PARAM))

## retrieve the precision of AVAL on the input dataset
## review outcome and make updates manually if needed
## the precision variable will be used for the parameter-based formats in layout

## decimal = 4 is a cap in this derivation: if decimal precision of variable > decimal, the result will end up as decimal
## eg if AVAL has precision of 6 for parameter x, and decimal = 4, the resulting decimal value for parameter x is 4

## note that precision is on the raw values, as we are presenting mean/ci, and extra digit will be added
## eg precision = 2 will result in mean/ci format xx.xxx (xx.xxx, xx.xxx)

lb_precision <- tidytlg:::make_precision_data(
  df = adlb00,
  decimal = 3,
  precisionby = "PARAMCD",
  precisionon = "AVAL"
)

### data preparation

filtered_adlb <- adlb00 %>%
  filter(AVISIT %in% selvisit) %>%
  ### unique record per timepoint:
  filter(ANL02FL == "Y" & (ABLFL == "Y" | APOBLFL == "Y"))

#### perform check on unique record per subject/param/timepoint
check_unique <- filtered_adlb %>%
  group_by(USUBJID, PARAMCD, AVISIT) %>%
  mutate(n_recsub = n()) %>%
  filter(n_recsub > 1)

if (nrow(check_unique) > 0) {
  stop(
    "Your input dataset needs extra attention, as some subjects have more than one record per parameter/visit"
  )
  ### you will run into issues with fraction portion in count_denom_fraction, as count > denom, and fraction > 1 if you don't adjust your input dataset

  # Possible extra derivation - just to ensure program can run without issues
  ### Study team is responsible for adding this derivation onto ADaM dataset and ensure proper derivation rule for ANL02FL is implemented !!!!!!!!!!
  filtered_adlbx <- adlb00 %>%
    filter(PARAMCD %in% selparamcd) %>%
    filter(AVISIT %in% selvisit) %>%
    ### unique record per timepoint:
    filter(ANL02FL == "Y" & (ABLFL == "Y" | APOBLFL == "Y")) %>%
    group_by(USUBJID, PARAM, AVISIT) %>%
    mutate(n_sub = n()) %>%
    arrange(USUBJID, PARAM, AVISIT, ADT) %>%
    mutate(i = vctrs::vec_group_id(ADT)) %>%
    mutate(
      ANL02FL = case_when(
        n_sub == 1 ~ "Y",
        i == 1 ~ "Y"
      )
    ) %>%
    select(-c(i, n_sub)) %>%
    ungroup()

  filtered_adlb <- filtered_adlbx %>%
    filter(PARAMCD %in% selparamcd) %>%
    filter(AVISIT %in% selvisit) %>%
    ### unique record per timepoint:
    filter(ANL02FL == "Y" & (ABLFL == "Y" | APOBLFL == "Y"))

  ## now your data should contain 1 record per subject per parameter
}


### for denominator per timepoint: all records from adlb on this timepoint: ignoring anl01fl/anl02fl/param
filtered_adlb_timepoints <- unique(
  adlb00 %>%
    filter(AVISIT %in% selvisit) %>%
    select(USUBJID, AVISITN, AVISIT)
) %>%
  inner_join(adsl)

# Define layout and build table:

# Core function to produce shell for specific parcat3 selection

build_result_parcat3 <- function(
  df = filtered_adlb,
  df_timepoints = filtered_adlb_timepoints,
  df_orig = adlb00,
  PARCAT3sel = NULL,
  .adsl = adsl,
  tblid,
  save2rtf = TRUE,
  .summ_vars = summ_vars,
  .trtvar = trtvar,
  .ref_path = ref_path
) {
  tblidx <- get_tblid(tblid, PARCAT3sel)
  titles2 <- get_titles_from_file(input_path = '../../_data/', tblidx)

  .ctrl_grp <- utils::tail(.ref_path, n = 1)

  multivars <- c("AVAL", "AVAL", "CHG")
  extra_args_3col <- list(
    format_na_str = rep("NA", 3),
    d = "decimal",
    variables = .summ_vars,
    ancova = ancova,
    comp_btw_group = comp_btw_group,
    ref_path = .ref_path,
    multivars = multivars
  )

  if (!is.null(PARCAT3sel)) {
    df <- df %>%
      filter(PARCAT3 %in% PARCAT3sel)
  }

  ### continue with data preparation
  params <- unique(df %>% select(PARAMCD, PARAM))

  df_timepoints <- df_timepoints %>%
    mutate(dummy_join = 1) %>%
    full_join(
      params %>% mutate(dummy_join = 1),
      relationship = "many-to-many"
    ) %>%
    select(-dummy_join)

  ### identify subjects in df_timepoints and not in df

  extra_sub <- anti_join(df_timepoints, df)

  ### only add these extra_sub to
  ### this will ensure we still meet the one record per subject per timepoint
  ### this will ensure length(x) can be used for the denominator derivation inside summarize_aval_chg_diff function

  df <- bind_rows(df, extra_sub) %>%
    arrange(USUBJID, PARAM, AVISITN)

  df <- df %>%
    inner_join(lb_precision, by = "PARAMCD")

  ### important: previous actions lost the label of variables
  ### in order to be able to use obj_label(filtered_adlb$PARAM) in layout, need to redefine the label
  df <- var_relabel_list(df, var_labels(df_orig, fill = T))

  ################################################################################
  # Define layout and build table:
  ################################################################################

  lyt <- basic_table(show_colcounts = FALSE, colcount_format = "N=xx") %>%
    ### first columns
    split_cols_by(
      "colspan_trt",
      split_fun = trim_levels_to_map(map = colspan_trt_map)
    ) %>%
    split_cols_by(.trtvar, show_colcounts = TRUE, colcount_format = "N=xx") %>%
    split_rows_by(
      "PARAM",
      label_pos = "topleft",
      split_label = "Laboratory Test",
      section_div = " ",
      split_fun = drop_split_levels
    ) %>%
    ## note the child_labels = hidden for AVISIT, these labels will be taken care off by
    ## applying function summarize_aval_chg_diff further in the layout
    split_rows_by(
      "AVISIT",
      label_pos = "topleft",
      split_label = "Study Visit",
      split_fun = drop_split_levels,
      child_labels = "hidden"
    ) %>%
    ## set up a 3 column split
    split_cols_by_multivar(
      multivars,
      varlabels = c(
        "n/N (%)",
        "Mean (95% CI)",
        "Mean Change From Baseline (95% CI)"
      )
    ) %>%
    ### restart for the rrisk_header columns - note the nested = FALSE option
    ### also note the child_labels = "hidden" in both PARAM and AVISIT
    split_cols_by("rrisk_header", nested = FALSE) %>%
    split_cols_by(
      .trtvar,
      split_fun = remove_split_levels(.ctrl_grp),
      labels_var = "rrisk_label",
      show_colcounts = TRUE,
      colcount_format = "N=xx"
    ) %>%
    ### difference columns : just 1 column & analysis needs to be done on change
    split_cols_by_multivar(multivars[3], varlabels = c(" ")) %>%
    ### the variable passed here in analyze is not used (STUDYID), it is a dummy var passing,
    ### the function summarize_aval_chg_diff grabs the required vars from cols_by_multivar calls
    analyze(
      "STUDYID",
      afun = a_summarize_aval_chg_diff_j,
      extra_args = extra_args_3col
    )

  if (nrow(df) > 0) {
    result <- build_table(lyt, df, alt_counts_df = .adsl)

    ################################################################################
    # Post-Processing:
    # - Prune table to remove when n = 0 in all columns
    # - Remove the N=xx column headers for the difference vs PBO columns
    ################################################################################

    ### alhtough this is not really likely to occur in real data, this is a problem in the current synthetic data
    ### also here, try to remove this issue

    # rps_result <- row_paths_summary(result)

    ### below code is based upon tern pruning function has_count_in_any_col, with updates to internal function h_row_first_values for the 3 column - format we are using here

    my_has_count_in_any_col <- function(atleast, ...) {
      checkmate::assert_count(atleast)
      CombinationFunction(function(table_row) {
        row_counts <- my_h_row_counts(table_row, ...)
        ### small update compared to tern::has_count_in_any_col
        ## > vs >=
        any(row_counts > atleast)
      })
    }

    my_h_row_counts <-
      function(table_row, col_names = NULL, col_indices = NULL) {
        ## no updates compared to tern::h_row_counts, beyond using the customized my_h_row_first_values function
        counts <- my_h_row_first_values(table_row, col_names, col_indices)
        checkmate::assert_integerish(counts)
        counts
      }

    my_h_row_first_values <- function(
      table_row,
      col_names = NULL,
      col_indices = NULL
    ) {
      col_indices <- tern:::check_names_indices(
        table_row,
        col_names,
        col_indices
      )
      checkmate::assert_integerish(col_indices)
      checkmate::assert_subset(col_indices, seq_len(ncol(table_row)))

      # Main values are extracted
      row_vals <- row_values(table_row)[col_indices]

      ### specific updates to current situation -- 3 column layout, I want to grab the information from the n/N column, which is in first analysis of AVAL
      specific_cols <- names(row_vals)
      specific_cols <- specific_cols[stringr::str_ends(specific_cols, "AVAL")]

      row_vals <- row_vals[specific_cols]

      # Main return
      vapply(
        row_vals,
        function(rv) {
          if (is.null(rv)) {
            NA_real_
          } else {
            rv[1L]
          }
        },
        FUN.VALUE = numeric(1)
      )
    }

    more_than_0 <- my_has_count_in_any_col(atleast = 0)

    ## seem to work ok, not clear why it goes through each row twice?
    result <- prune_table(result, keep_rows(more_than_0))

    ## Remove the N=xx column headers for the difference vs PBO columns
    remove_col_count2 <- function(result, string = paste("vs", ctrl_grp)) {
      mcdf <- make_col_df(result, visible_only = FALSE)
      mcdfsel <- mcdf %>%
        filter(stringr::str_detect(toupper(label), toupper(string))) %>%
        pull(path)

      for (i in seq_along(mcdfsel)) {
        facet_colcount(result, mcdfsel[[i]]) <- NA
      }

      return(result)
    }

    result <- remove_col_count2(result)
  } else {
    result <- NULL
    message(paste0(
      "Parcat3 [",
      PARCAT3sel,
      "] is not present on input dataset"
    ))
    return(result)
  }

  ################################################################################
  # Set title
  ################################################################################

  result <- set_titles(result, titles2)

  if (save2rtf) {
    ################################################################################
    # Convert to tbl file and output table
    ################################################################################
    ### add the proper abbreviation to the tblid, and add opath path
fileid <- tblid

    tt_to_tlgrtf(string_map = string_map, tt = 
      result,
      file = fileid,
      orientation = "landscape",
      nosplitin = list(cols = c(trtvar, "rrisk_header"))
    )
  }

  return(result)
}

# Apply core function to all specified levels of parcat3 selection

### note : the same core tblid (TSFLAB01) will be used for all, inside the core function build_result_parcat3 the proper abbreviation will be added

### titles will not be retrieved for these, as the table identifiers are not in the DPS system
### study teams will have to ensure all versions that are needed are included in DPS system
result1 <- build_result_parcat3(
  PARCAT3sel = "Liver biochemistry",
  tblid = tblid,
  save2rtf = TRUE
)
result2 <- build_result_parcat3(
  PARCAT3sel = "Kidney function",
  tblid = tblid,
  save2rtf = TRUE
)
result3 <- build_result_parcat3(
  PARCAT3sel = "Lipids",
  tblid = tblid,
  save2rtf = TRUE
)

result4 <- build_result_parcat3(
  PARCAT3sel = c(
    "Complete blood count",
    "WBC differential",
    "Coagulation studies"
  ),
  tblid = tblid,
  save2rtf = TRUE
)

### if a certain category is not present, no rtf will be generated

result <- build_result_parcat3(PARCAT3sel = "General chemistry", tblid = tblid)
```
```{r result1, echo=FALSE, message=FALSE, warning=FALSE, test = list(result_v1 = "result")}
tt_to_flextable_j(result, tblid, string_map = string_map) 
```

[Download RTF file](`r paste0(tolower(tblid), '.rtf')`)
::::

Made with ❤️ by the J&J Team

  • Edit this page
  • Report an issue
Cookie Preferences