TL Catalog
  1. Tables
  2. Electrocardiograms
  3. TSFECG01A
  • 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. Electrocardiograms
  3. TSFECG01A

TSFECG01A

Mean Change From Baseline for ECG Data Over Time by Subgroup


Output

  • Preview
Code
# Program Name:              tsfecg01

# Prep Environment

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

# Define script level parameters:

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

popfl <- "SAFFL"
trtvar <- "TRT01A"
ctrl_grp <- "Placebo"
subgrpvar <- "AGEGR1"
subgrplbl <- "Age: %s years"

page_by <- FALSE # Set page_by TRUE/FALSE if you (do not) wish to start a new page after a new subgroup
indent_adj <- -1L
if (page_by) {
  indent_adj <- 0L
}

# 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


selparamcd <- c("EGHRMN", "PRAG", "QRSAG", "QTC", "QTCBAG", "QTCFAG", "RRAG")
selvisit <- c(
  "Baseline",
  "Month 1",
  "Month 3",
  "Month 6",
  "Month 9",
  "Month 12",
  "Month 15",
  "Month 18",
  "Month 24"
)

# For template output to reduce output, only limited timepoints and parameters are selected
# Remove for actual study code
selparamcd <- c("EGHRMN", "PRAG")
# see further, an alternative method to identify all non-unscheduled visits based upon data
selvisit <- c("Baseline", "Month 1", "Month 3")

# Process Data:

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


msubgrp <- adsl %>%
  group_by(across(all_of(c(trtvar, subgrpvar)))) %>%
  summarize(count = n())

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))


adeg00 <- pharmaverseadamjnj::adeg %>%
  select(
    USUBJID,
    AVISITN,
    AVISIT,
    PARAMCD,
    PARAM,
    AVAL,
    BASE,
    CHG,
    starts_with("ANL"),
    ABLFL,
    APOBLFL
    # ,EGSTAT
  ) %>%
  mutate(inegdata = "Y") %>%
  inner_join(adsl)

# selection of all non-unscheduled visits from data
visits <- adeg00 %>%
  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

## 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 = 3 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 = 3, the resulting decimal value for parameter x is 3

## 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)

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

filtered_adeg_00 <- adeg00 %>%
  filter(PARAMCD %in% selparamcd) %>%
  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_adeg_00 %>%
  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_adeg_00x <- adeg00 %>%
    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_adeg_00 <- filtered_adeg_00x %>%
    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 adeg on this timepoint: ignoring anl01fl/anl02fl/param
filtered_adeg_timepoints <- unique(
  adeg00 %>%
    filter(AVISIT %in% selvisit) %>%
    select(USUBJID, AVISITN, AVISIT, inegdata)
) %>%
  inner_join(adsl)

params <- unique(filtered_adeg_00 %>% select(PARAMCD, PARAM))

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

### identify subjects in filtered_adeg_timepoints and not in filtered_adeg

extra_sub <- anti_join(filtered_adeg_timepoints, filtered_adeg_00) %>%
  mutate(extra_sub = "Y")

attr(extra_sub$extra_sub, "label") <- "Extra Subject step 1"

### add these extra_sub dataframes to
### this will ensure we still meet the one record per subject per timepoint
### note that we can no longer use length(x) can be used for the denominator derivation inside summarize_aval_chg_diff function
filtered_adeg <- bind_rows(filtered_adeg_00, extra_sub) %>%
  arrange(USUBJID, PARAM, AVISITN)

#### Only In case we want the subgroup N to come from ADSL, and not just from adeg

### also add adsl subjects that have no vs data --- for subgroup counts from adsl

adeg_timepoints_subgroups <-
  adsl %>%
  select(USUBJID) %>%
  # define factor PARAMCD/AVISIT with one category, all levels we need
  mutate(
    PARAMCD = factor(selparamcd[1], levels = selparamcd),
    AVISIT = factor(selvisit[1], levels = selvisit)
  ) %>%
  # expand dataset to show all levels
  tidyr::complete(., USUBJID, PARAMCD, AVISIT)


extra_sub2 <-
  anti_join(
    adeg_timepoints_subgroups,
    filtered_adeg_00 %>% select(USUBJID, AVISITN, AVISIT, PARAMCD, PARAM)
  ) %>%
  left_join(., unique(adeg00 %>% select(AVISITN, AVISIT, PARAMCD, PARAM))) %>%
  anti_join(., extra_sub) %>%
  inner_join(adsl) %>%
  mutate(extra_sub2 = "Y")

attr(extra_sub2$extra_sub2, "label") <- "Extra Subject step 2"

### add these extra_sub dataframe as well
### this will ensure we still meet the one record per subject per timepoint
### However, by adding also subjects without data in vs, we can no longer use length(x) for the denominator derivation inside summarize_aval_chg_diff function
filtered_adeg <- bind_rows(filtered_adeg, extra_sub2) %>%
  arrange(USUBJID, PARAM, AVISITN)


filtered_adeg <- filtered_adeg %>%
  inner_join(eg_precision, by = "PARAMCD")

### important: previous actions lost the label of variables
### in order to be able to use obj_label(filtered_adeg$PARAM) in layout, need to redefine the label

## do these 2 manually, as these are not available on adeg00
attr(filtered_adeg$extra_sub, "label") <- "Extra Subject step 1"
attr(filtered_adeg$extra_sub2, "label") <- "Extra Subject step 2"

filtered_adeg <- var_relabel_list(filtered_adeg, var_labels(adeg00, fill = T))


#### Note : variable inegdata will be used to differentiate records from subjects in vsdata at timepoint and subjects not in vsdata (only in adsl)
#### is.na(filtered_adeg$inegdata) corresponds to filtered_adeg$extra_sub2 == "Y"

## check1 <- filtered_adeg %>% filter(extra_sub2 == "Y") %>% select(USUBJID,inegdata,extra_sub2,PARAMCD,AVISIT,AVAL)
## check2 <- filtered_adeg %>% filter(is.na(inegdata)) %>% select(USUBJID,inegdata,extra_sub2,PARAMCD,AVISIT,AVAL)

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)

# Define layout and build table:

summ_vars <- list(arm = trtvar, covariates = NULL)
ref_path <- c("colspan_trt", " ", trtvar, ctrl_grp)
multivars <- c("AVAL", "AVAL", "CHG")

extra_args_3col <- list(
  format_na_str = rep("NA", 3),
  d = "decimal",
  variables = summ_vars,
  ref_path = ref_path,
  ancova = ancova,
  comp_btw_group = comp_btw_group,
  indatavar = "inegdata",
  multivars = multivars
)

lyt_0 <- 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(
    subgrpvar,
    label_pos = "hidden",
    section_div = " ",
    split_fun = drop_split_levels,
    page_by = page_by
  ) %>%
  ### just show number of subjects in current level of subgrpvar
  ### only show this number in the first AVAL column
  summarize_row_groups(
    var = subgrpvar,
    cfun = a_freq_j,
    extra_args = list(
      label_fstr = subgrplbl,
      extrablankline = TRUE,
      restr_columns = "AVAL",
      .stats = c("n_altdf"),
      riskdiff = FALSE,
      denom_by = subgrpvar
    )
  ) %>%
  split_rows_by(
    "PARAM",
    label_pos = "topleft",
    split_label = obj_label(filtered_adeg$PARAM),
    section_div = " ",
    split_fun = drop_split_levels,
    indent_mod = indent_adj
  ) %>%
  ## 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(" "))


## layout as specified in shell
lyt <- lyt_0 %>%
  ### 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
  )

result <- build_table(lyt, filtered_adeg, alt_counts_df = adsl)

# Post-Processing:

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)

# Add titles and footnotes:

result <- set_titles(result, titles)

# Convert to tbl file and output table

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

TSFECG01a: Mean Change From Baseline for ECG Data Over Time by [Subgroup]; 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

Parameter

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)

Age: ≥18 to <65 years

8

5

7

ECG Mean Heart Rate
 (beats/min)

Baseline

8/8 (100.0%)

362.2 (261.5, 463.0)

5/5 (100.0%)

345.6 (90.1, 601.1)

7/7 (100.0%)

307.4 (226.6, 388.2)

Month 1

8/8 (100.0%)

296.6 (155.9, 437.3)

-65.6 (-242.5, 111.3)

5/5 (100.0%)

323.0 (131.7, 514.3)

-22.6 (-339.7, 294.5)

7/7 (100.0%)

297.7 (112.2, 483.2)

-9.7 (-169.1, 149.7)

-55.9 (-270.3, 158.5)

-12.9 (-328.1, 302.3)

Month 3

8/8 (100.0%)

217.0 (58.2, 375.8)

-145.2 (-375.3, 84.8)

5/5 (100.0%)

197.6 (-87.5, 482.7)

-148.0 (-413.0, 117.0)

7/7 (100.0%)

279.3 (113.1, 445.4)

-28.1 (-203.2, 146.9)

-117.1 (-379.3, 145.1)

-119.9 (-394.6, 154.8)

PR Interval, Aggregate (msec)

Baseline

8/8 (100.0%)

292.1 (119.9, 464.4)

5/5 (100.0%)

302.8 (68.8, 536.8)

7/7 (100.0%)

277.1 (126.5, 427.8)

Month 1

8/8 (100.0%)

325.8 (208.7, 442.8)

33.6 (-181.5, 248.7)

5/5 (100.0%)

256.0 (83.3, 428.7)

-46.8 (-404.7, 311.1)

7/7 (100.0%)

274.4 (77.1, 471.8)

-2.7 (-176.6, 171.2)

36.3 (-213.7, 286.4)

-44.1 (-398.8, 310.6)

Month 3

8/8 (100.0%)

358.2 (187.7, 528.8)

66.1 (-146.9, 279.2)

5/5 (100.0%)

307.4 (70.3, 544.5)

4.6 (-457.1, 466.3)

7/7 (100.0%)

269.7 (124.5, 414.9)

-7.4 (-199.9, 185.0)

73.6 (-184.9, 332.0)

12.0 (-441.9, 466.0)

Age: ≥65 to <75 years

16

17

19

ECG Mean Heart Rate
 (beats/min)

Baseline

16/16 (100.0%)

311.6 (212.6, 410.7)

17/17 (100.0%)

367.2 (270.8, 463.7)

19/19 (100.0%)

212.2 (129.9, 294.4)

Month 1

16/16 (100.0%)

283.4 (193.8, 373.0)

-28.2 (-180.9, 124.6)

17/17 (100.0%)

383.9 (294.6, 473.3)

16.7 (-126.1, 159.6)

19/19 (100.0%)

212.4 (133.5, 291.2)

0.2 (-98.5, 99.0)

-28.4 (-204.4, 147.6)

16.5 (-151.5, 184.5)

Month 3

16/16 (100.0%)

403.5 (317.5, 489.5)

91.9 (-39.6, 223.4)

10/10 (100.0%)

259.2 (123.8, 394.6)

-119.8 (-326.3, 86.7)

19/19 (100.0%)

289.6 (203.6, 375.7)

77.5 (-33.5, 188.5)

14.4 (-151.2, 180.0)

-197.3 (-421.8, 27.3)

PR Interval, Aggregate (msec)

Baseline

16/16 (100.0%)

282.4 (190.0, 374.9)

17/17 (100.0%)

362.1 (295.1, 429.0)

19/19 (100.0%)

292.6 (204.9, 380.3)

Month 1

16/16 (100.0%)

348.7 (265.8, 431.5)

66.2 (-68.8, 201.3)

17/17 (100.0%)

249.1 (146.1, 352.0)

-113.0 (-240.4, 14.4)

19/19 (100.0%)

329.1 (249.6, 408.6)

36.5 (-56.9, 129.8)

29.8 (-128.8, 188.4)

-149.5 (-302.1, 3.1)

Month 3

16/16 (100.0%)

354.0 (269.7, 438.3)

71.6 (-26.6, 169.8)

10/10 (100.0%)

304.9 (189.4, 420.4)

-59.3 (-190.7, 72.1)

19/19 (100.0%)

301.0 (226.8, 375.2)

8.4 (-106.0, 122.8)

63.2 (-82.0, 208.3)

-67.7 (-232.4, 97.1)

Age: ≥75 years

29

51

33

ECG Mean Heart Rate
 (beats/min)

Baseline

29/29 (100.0%)

308.6 (253.1, 364.2)

51/51 (100.0%)

309.2 (258.7, 359.6)

33/33 (100.0%)

279.7 (214.2, 345.3)

Month 1

29/29 (100.0%)

205.1 (133.0, 277.2)

-103.5 (-183.8, -23.3)

49/49 (100.0%)

245.2 (192.5, 297.9)

-69.4 (-141.4, 2.5)

32/32 (100.0%)

309.8 (246.5, 373.2)

28.9 (-61.9, 119.7)

-132.4 (-251.1, -13.7)

-98.3 (-212.3, 15.7)

Month 3

29/29 (100.0%)

277.5 (199.0, 356.0)

-31.1 (-134.4, 72.2)

38/38 (100.0%)

327.8 (271.8, 383.9)

21.4 (-61.2, 104.1)

30/30 (100.0%)

322.8 (256.1, 389.6)

36.8 (-65.5, 139.1)

-67.9 (-210.1, 74.3)

-15.4 (-144.5, 113.8)

PR Interval, Aggregate (msec)

Baseline

29/29 (100.0%)

319.6 (252.2, 386.9)

51/51 (100.0%)

345.6 (293.7, 397.5)

33/33 (100.0%)

319.9 (255.7, 384.1)

Month 1

29/29 (100.0%)

282.3 (217.2, 347.4)

-37.2 (-138.7, 64.2)

49/49 (100.0%)

304.8 (257.0, 352.7)

-44.2 (-121.0, 32.6)

32/32 (100.0%)

232.7 (179.0, 286.5)

-79.1 (-171.8, 13.6)

41.9 (-92.7, 176.4)

34.9 (-83.5, 153.4)

Month 3

29/29 (100.0%)

262.1 (196.4, 327.8)

-57.4 (-148.9, 34.0)

38/38 (100.0%)

289.6 (233.8, 345.4)

-64.8 (-152.3, 22.7)

30/30 (100.0%)

212.0 (150.8, 273.2)

-101.9 (-190.8, -13.0)

44.5 (-80.3, 169.2)

37.1 (-85.2, 159.4)

Download RTF file

TSFECG01
TSFECG02
Source Code
---
title: TSFECG01A
subtitle: Mean Change From Baseline for ECG Data Over Time by Subgroup
---

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

{{< 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:              tsfecg01

# Prep Environment

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

# Define script level parameters:

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

popfl <- "SAFFL"
trtvar <- "TRT01A"
ctrl_grp <- "Placebo"
subgrpvar <- "AGEGR1"
subgrplbl <- "Age: %s years"

page_by <- FALSE # Set page_by TRUE/FALSE if you (do not) wish to start a new page after a new subgroup
indent_adj <- -1L
if (page_by) {
  indent_adj <- 0L
}

# 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


selparamcd <- c("EGHRMN", "PRAG", "QRSAG", "QTC", "QTCBAG", "QTCFAG", "RRAG")
selvisit <- c(
  "Baseline",
  "Month 1",
  "Month 3",
  "Month 6",
  "Month 9",
  "Month 12",
  "Month 15",
  "Month 18",
  "Month 24"
)

# For template output to reduce output, only limited timepoints and parameters are selected
# Remove for actual study code
selparamcd <- c("EGHRMN", "PRAG")
# see further, an alternative method to identify all non-unscheduled visits based upon data
selvisit <- c("Baseline", "Month 1", "Month 3")

# Process Data:

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


msubgrp <- adsl %>%
  group_by(across(all_of(c(trtvar, subgrpvar)))) %>%
  summarize(count = n())

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))


adeg00 <- pharmaverseadamjnj::adeg %>%
  select(
    USUBJID,
    AVISITN,
    AVISIT,
    PARAMCD,
    PARAM,
    AVAL,
    BASE,
    CHG,
    starts_with("ANL"),
    ABLFL,
    APOBLFL
    # ,EGSTAT
  ) %>%
  mutate(inegdata = "Y") %>%
  inner_join(adsl)

# selection of all non-unscheduled visits from data
visits <- adeg00 %>%
  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

## 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 = 3 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 = 3, the resulting decimal value for parameter x is 3

## 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)

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

filtered_adeg_00 <- adeg00 %>%
  filter(PARAMCD %in% selparamcd) %>%
  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_adeg_00 %>%
  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_adeg_00x <- adeg00 %>%
    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_adeg_00 <- filtered_adeg_00x %>%
    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 adeg on this timepoint: ignoring anl01fl/anl02fl/param
filtered_adeg_timepoints <- unique(
  adeg00 %>%
    filter(AVISIT %in% selvisit) %>%
    select(USUBJID, AVISITN, AVISIT, inegdata)
) %>%
  inner_join(adsl)

params <- unique(filtered_adeg_00 %>% select(PARAMCD, PARAM))

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

### identify subjects in filtered_adeg_timepoints and not in filtered_adeg

extra_sub <- anti_join(filtered_adeg_timepoints, filtered_adeg_00) %>%
  mutate(extra_sub = "Y")

attr(extra_sub$extra_sub, "label") <- "Extra Subject step 1"

### add these extra_sub dataframes to
### this will ensure we still meet the one record per subject per timepoint
### note that we can no longer use length(x) can be used for the denominator derivation inside summarize_aval_chg_diff function
filtered_adeg <- bind_rows(filtered_adeg_00, extra_sub) %>%
  arrange(USUBJID, PARAM, AVISITN)

#### Only In case we want the subgroup N to come from ADSL, and not just from adeg

### also add adsl subjects that have no vs data --- for subgroup counts from adsl

adeg_timepoints_subgroups <-
  adsl %>%
  select(USUBJID) %>%
  # define factor PARAMCD/AVISIT with one category, all levels we need
  mutate(
    PARAMCD = factor(selparamcd[1], levels = selparamcd),
    AVISIT = factor(selvisit[1], levels = selvisit)
  ) %>%
  # expand dataset to show all levels
  tidyr::complete(., USUBJID, PARAMCD, AVISIT)


extra_sub2 <-
  anti_join(
    adeg_timepoints_subgroups,
    filtered_adeg_00 %>% select(USUBJID, AVISITN, AVISIT, PARAMCD, PARAM)
  ) %>%
  left_join(., unique(adeg00 %>% select(AVISITN, AVISIT, PARAMCD, PARAM))) %>%
  anti_join(., extra_sub) %>%
  inner_join(adsl) %>%
  mutate(extra_sub2 = "Y")

attr(extra_sub2$extra_sub2, "label") <- "Extra Subject step 2"

### add these extra_sub dataframe as well
### this will ensure we still meet the one record per subject per timepoint
### However, by adding also subjects without data in vs, we can no longer use length(x) for the denominator derivation inside summarize_aval_chg_diff function
filtered_adeg <- bind_rows(filtered_adeg, extra_sub2) %>%
  arrange(USUBJID, PARAM, AVISITN)


filtered_adeg <- filtered_adeg %>%
  inner_join(eg_precision, by = "PARAMCD")

### important: previous actions lost the label of variables
### in order to be able to use obj_label(filtered_adeg$PARAM) in layout, need to redefine the label

## do these 2 manually, as these are not available on adeg00
attr(filtered_adeg$extra_sub, "label") <- "Extra Subject step 1"
attr(filtered_adeg$extra_sub2, "label") <- "Extra Subject step 2"

filtered_adeg <- var_relabel_list(filtered_adeg, var_labels(adeg00, fill = T))


#### Note : variable inegdata will be used to differentiate records from subjects in vsdata at timepoint and subjects not in vsdata (only in adsl)
#### is.na(filtered_adeg$inegdata) corresponds to filtered_adeg$extra_sub2 == "Y"

## check1 <- filtered_adeg %>% filter(extra_sub2 == "Y") %>% select(USUBJID,inegdata,extra_sub2,PARAMCD,AVISIT,AVAL)
## check2 <- filtered_adeg %>% filter(is.na(inegdata)) %>% select(USUBJID,inegdata,extra_sub2,PARAMCD,AVISIT,AVAL)

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)

# Define layout and build table:

summ_vars <- list(arm = trtvar, covariates = NULL)
ref_path <- c("colspan_trt", " ", trtvar, ctrl_grp)
multivars <- c("AVAL", "AVAL", "CHG")

extra_args_3col <- list(
  format_na_str = rep("NA", 3),
  d = "decimal",
  variables = summ_vars,
  ref_path = ref_path,
  ancova = ancova,
  comp_btw_group = comp_btw_group,
  indatavar = "inegdata",
  multivars = multivars
)

lyt_0 <- 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(
    subgrpvar,
    label_pos = "hidden",
    section_div = " ",
    split_fun = drop_split_levels,
    page_by = page_by
  ) %>%
  ### just show number of subjects in current level of subgrpvar
  ### only show this number in the first AVAL column
  summarize_row_groups(
    var = subgrpvar,
    cfun = a_freq_j,
    extra_args = list(
      label_fstr = subgrplbl,
      extrablankline = TRUE,
      restr_columns = "AVAL",
      .stats = c("n_altdf"),
      riskdiff = FALSE,
      denom_by = subgrpvar
    )
  ) %>%
  split_rows_by(
    "PARAM",
    label_pos = "topleft",
    split_label = obj_label(filtered_adeg$PARAM),
    section_div = " ",
    split_fun = drop_split_levels,
    indent_mod = indent_adj
  ) %>%
  ## 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(" "))


## layout as specified in shell
lyt <- lyt_0 %>%
  ### 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
  )

result <- build_table(lyt, filtered_adeg, alt_counts_df = adsl)

# Post-Processing:

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)

# Add titles and footnotes:

result <- set_titles(result, titles)

# Convert to tbl file and output table

tt_to_tlgrtf(string_map = string_map, tt = 
  result,
  file = fileid,
  nosplitin = list(cols = c(trtvar, "rrisk_header")),
  orientation = "landscape"
)
```
```{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