TL Catalog
  1. Tables
  2. Vital Signs and Physical Findings
  3. TSFVIT01A
  • 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. Vital Signs and Physical Findings
  3. TSFVIT01A

TSFVIT01A

Mean Change From Baseline in Blood Pressure Over Time by Subgroup


Output

  • Preview
Code
# Program Name:              tsfvit01a

# Prep environment:

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

# Define script level parameters:

tblid <- "TSFVIT01a"
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 <- 0L
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("DIABP", "SYSBP")
# to Update the timepoints to what is relevant for your study
# For the trial used for the shell program, the screening timepoint is the timepoint considered as baseline
# see further, an alternative method to identify all non-unscheduled visits based upon data
selvisit <- c("Screening", "Cycle 02", "Cycle 03", "Cycle 04")

### as in dataset, order is important for later processing
### not automated, hard coded approach for ease of reading
### ideally the datasets already contain the appropriate case, to ensure units are in proper case
sel_param <- c(
  "Systolic Blood Pressure (mmHg)",
  "Diastolic Blood Pressure (mmHg)"
)
sel_param_case <- c(
  "Systolic blood pressure (mmHg)",
  "Diastolic blood pressure (mmHg)"
)

# Process Data:

adsl <- pharmaverseadamjnj::adsl %>%
  filter(.data[[popfl]] == "Y") %>%
  select(
    STUDYID,
    USUBJID,
    all_of(c(popfl, trtvar, subgrpvar)),
    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)

advs00 <- pharmaverseadamjnj::advs %>%
  select(
    USUBJID,
    AVISITN,
    AVISIT,
    PARAMCD,
    PARAM,
    AVAL,
    BASE,
    CHG,
    starts_with("ANL"),
    ABLFL,
    APOBLFL,
    VSSTAT
  ) %>%
  mutate(invsdata = "Y") %>%
  inner_join(adsl)

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

vs_precision <- tidytlg:::make_precision_data(
  df = advs00,
  decimal = 4,
  precisionby = "PARAMCD",
  precisionon = "AVAL"
)

### data preparation

filtered_advs_00 <- advs00 %>%
  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_advs_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_advs_00x <- advs00 %>%
    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_advs_00 <- filtered_advs_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 advs on this timepoint: ignoring anl01fl/anl02fl/param
filtered_advs_timepoints <- unique(
  advs00 %>%
    filter(AVISIT %in% selvisit) %>%
    select(USUBJID, AVISITN, AVISIT, invsdata)
) %>%
  inner_join(adsl)

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

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

### identify subjects in filtered_advs_timepoints and not in filtered_advs

extra_sub <- anti_join(filtered_advs_timepoints, filtered_advs_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_advs <- bind_rows(filtered_advs_00, extra_sub) %>%
  arrange(USUBJID, PARAM, AVISITN)

filtered_advs <- filtered_advs %>%
  inner_join(vs_precision, by = "PARAMCD")

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

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

advs_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(
    advs_timepoints_subgroups,
    filtered_advs_00 %>% select(USUBJID, AVISITN, AVISIT, PARAMCD, PARAM)
  ) %>%
  left_join(., unique(advs00 %>% 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_advs <- bind_rows(filtered_advs, extra_sub2) %>%
  arrange(USUBJID, PARAM, AVISITN)

### important: previous actions lost the label of variables

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

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


filtered_advs$PARAM <- factor(
  as.character(filtered_advs$PARAM),
  levels = sel_param,
  labels = sel_param_case
)


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

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

# Define layout and build table:

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,
  multivars = multivars,
  indatavar = "invsdata"
)

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(
    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 = "Blood Pressure",
    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(c("CHG"), 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
  )

result <- build_table(lyt, filtered_advs, 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"
)

TSFVIT01a: Mean Change From Baseline in Blood Pressure 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

Blood Pressure

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

Systolic blood pressure
 (mmHg)

Screening

8/8 (100.0%)

127.37500 (112.23883, 142.51117)

5/5 (100.0%)

134.20000 (119.12402, 149.27598)

7/7 (100.0%)

133.42857 (118.20191, 148.65524)

Cycle 02

8/8 (100.0%)

128.58333 (119.89675, 137.26992)

1.20833 (-11.68998, 14.10664)

5/5 (100.0%)

135.13333 (121.48883, 148.77783)

0.93333 (-15.30722, 17.17389)

7/7 (100.0%)

133.71429 (117.62009, 149.80848)

0.28571 (-8.77489, 9.34632)

0.92262 (-13.44558, 15.29082)

0.64762 (-15.68080, 16.97604)

Cycle 03

8/8 (100.0%)

122.04167 (110.93616, 133.14717)

-5.33333 (-21.55806, 10.89140)

5/5 (100.0%)

143.66667 (130.18509, 157.14824)

9.46667 (-5.73097, 24.66430)

7/7 (100.0%)

133.38095 (116.27531, 150.48660)

-0.04762 (-3.26336, 3.16813)

-5.28571 (-21.58015, 11.00872)

9.51429 (-5.49428, 24.52286)

Cycle 04

8/8 (100.0%)

123.12500 (115.85818, 130.39182)

-4.25000 (-21.93755, 13.43755)

3/3 (100.0%)

126.77778 (80.73118, 172.82438)

-2.55556 (-30.19708, 25.08597)

5/5 (100.0%)

129.20000 (120.67451, 137.72549)

-0.73333 (-20.91308, 19.44641)

-3.51667 (-26.65366, 19.62032)

-1.82222 (-25.84820, 22.20375)

Diastolic blood pressure
 (mmHg)

Screening

8/8 (100.0%)

78.83333 (72.67723, 84.98944)

5/5 (100.0%)

84.26667 (72.01181, 96.52153)

7/7 (100.0%)

76.57143 (64.96169, 88.18117)

Cycle 02

8/8 (100.0%)

78.58333 (72.77448, 84.39219)

-0.25000 (-6.99885, 6.49885)

5/5 (100.0%)

86.66667 (75.87379, 97.45955)

2.40000 (-4.25704, 9.05704)

7/7 (100.0%)

76.80952 (66.10387, 87.51518)

0.23810 (-7.24309, 7.71928)

-0.48810 (-9.54353, 8.56734)

2.16190 (-6.49718, 10.82099)

Cycle 03

8/8 (100.0%)

77.08333 (68.41464, 85.75202)

-1.75000 (-11.08542, 7.58542)

5/5 (100.0%)

78.86667 (69.47588, 88.25746)

-5.40000 (-16.28927, 5.48927)

7/7 (100.0%)

75.61905 (59.61763, 91.62046)

-0.95238 (-7.54373, 5.63896)

-0.79762 (-11.21097, 9.61573)

-4.44762 (-15.53590, 6.64066)

Cycle 04

8/8 (100.0%)

77.16667 (72.03466, 82.29867)

-1.66667 (-9.65639, 6.32306)

3/3 (100.0%)

73.11111 (51.49716, 94.72507)

-4.33333 (-15.53493, 6.86826)

5/5 (100.0%)

74.26667 (53.96842, 94.56491)

-2.06667 (-15.88830, 11.75496)

0.40000 (-13.59849, 14.39849)

-2.26667 (-16.22660, 11.69327)

Age: ≥65 to <75 years

16

17

19

Systolic blood pressure
 (mmHg)

Screening

16/16 (100.0%)

134.91667 (127.98187, 141.85146)

17/17 (100.0%)

131.41176 (122.61808, 140.20545)

19/19 (100.0%)

130.73684 (120.77229, 140.70139)

Cycle 02

16/16 (100.0%)

132.47917 (125.54787, 139.41047)

-2.43750 (-9.39870, 4.52370)

17/17 (100.0%)

132.54902 (123.93730, 141.16074)

1.13725 (-3.59953, 5.87404)

19/19 (100.0%)

125.15789 (116.80566, 133.51013)

-5.57895 (-12.62079, 1.46289)

3.14145 (-6.38129, 12.66419)

6.71620 (-1.50252, 14.93493)

Cycle 03

16/16 (100.0%)

131.72917 (125.68929, 137.76905)

-3.18750 (-9.69885, 3.32385)

10/10 (100.0%)

132.40000 (123.24324, 141.55676)

3.90000 (-1.69903, 9.49903)

19/19 (100.0%)

126.66667 (118.88144, 134.45189)

-4.07018 (-10.31295, 2.17260)

0.88268 (-7.79244, 9.55779)

7.97018 (0.02552, 15.91483)

Cycle 04

15/15 (100.0%)

132.95556 (125.34652, 140.56459)

-3.82222 (-11.21911, 3.57466)

9/9 (100.0%)

135.70370 (128.27055, 143.13686)

6.74074 (1.81423, 11.66725)

19/19 (100.0%)

132.82456 (121.70851, 143.94061)

2.08772 (-2.28040, 6.45584)

-5.90994 (-14.22874, 2.40886)

4.65302 (-1.53462, 10.84066)

Diastolic blood pressure
 (mmHg)

Screening

16/16 (100.0%)

77.04167 (73.06948, 81.01385)

17/17 (100.0%)

74.13725 (70.12248, 78.15203)

19/19 (100.0%)

77.33333 (71.84962, 82.81705)

Cycle 02

16/16 (100.0%)

79.60417 (74.66634, 84.54199)

2.56250 (-1.25020, 6.37520)

17/17 (100.0%)

78.31373 (72.85982, 83.76763)

4.17647 (0.53878, 7.81416)

19/19 (100.0%)

74.05263 (70.58225, 77.52301)

-3.28070 (-8.25552, 1.69412)

5.84320 (-0.20218, 11.88859)

7.45717 (1.50022, 13.41413)

Cycle 03

16/16 (100.0%)

78.60417 (73.92755, 83.28079)

1.56250 (-3.73593, 6.86093)

10/10 (100.0%)

78.76667 (71.30019, 86.23314)

4.03333 (-1.01306, 9.07973)

19/19 (100.0%)

73.85965 (69.69989, 78.01941)

-3.47368 (-8.62748, 1.68011)

5.03618 (-2.07219, 12.14456)

7.50702 (0.68322, 14.33081)

Cycle 04

15/15 (100.0%)

74.91111 (68.41783, 81.40439)

-2.44444 (-6.48849, 1.59960)

9/9 (100.0%)

78.85185 (74.76610, 82.93760)

3.96296 (-0.00734, 7.93327)

19/19 (100.0%)

75.70175 (71.23526, 80.16825)

-1.63158 (-4.93802, 1.67486)

-0.81287 (-5.83409, 4.20835)

5.59454 (0.73739, 10.45169)

Age: ≥75 years

29

51

33

Systolic blood pressure
 (mmHg)

Screening

29/29 (100.0%)

143.06897 (136.39381, 149.74412)

51/51 (100.0%)

141.62092 (136.44665, 146.79518)

33/33 (100.0%)

139.15152 (133.35446, 144.94857)

Cycle 02

29/29 (100.0%)

132.16092 (125.57485, 138.74699)

-10.90805 (-16.78895, -5.02714)

49/49 (100.0%)

136.29932 (131.13591, 141.46273)

-4.97959 (-9.07262, -0.88656)

32/32 (100.0%)

134.59375 (128.37103, 140.81647)

-4.40625 (-8.77864, -0.03386)

-6.50180 (-13.68843, 0.68484)

-0.57334 (-6.46467, 5.31799)

Cycle 03

29/29 (100.0%)

132.59770 (126.15586, 139.03955)

-10.47126 (-15.74485, -5.19768)

38/38 (100.0%)

138.23684 (131.80445, 144.66924)

-2.45614 (-7.80728, 2.89499)

30/30 (100.0%)

133.84444 (126.72385, 140.96503)

-4.96667 (-10.64927, 0.71594)

-5.50460 (-13.09021, 2.08101)

2.51053 (-5.14732, 10.16837)

Cycle 04

28/28 (100.0%)

130.28571 (124.11158, 136.45985)

-13.10714 (-18.37720, -7.83709)

35/35 (100.0%)

136.58095 (129.40991, 143.75199)

-2.42857 (-6.65424, 1.79710)

27/27 (100.0%)

131.24691 (124.82089, 137.67294)

-6.18519 (-11.63114, -0.73923)

-6.92196 (-14.32368, 0.47977)

3.75661 (-2.99972, 10.51295)

Diastolic blood pressure
 (mmHg)

Screening

29/29 (100.0%)

80.73563 (76.43408, 85.03719)

51/51 (100.0%)

76.70588 (74.11246, 79.29930)

33/33 (100.0%)

75.57576 (72.12017, 79.03135)

Cycle 02

29/29 (100.0%)

75.32184 (71.63105, 79.01263)

-5.41379 (-9.35848, -1.46911)

49/49 (100.0%)

75.17007 (72.50558, 77.83456)

-1.37415 (-3.38731, 0.63901)

32/32 (100.0%)

72.95833 (68.91098, 77.00568)

-2.60417 (-6.04063, 0.83230)

-2.80963 (-7.93341, 2.31415)

1.23002 (-2.70204, 5.16208)

Cycle 03

29/29 (100.0%)

74.31034 (71.05598, 77.56471)

-6.42529 (-9.94658, -2.90400)

38/38 (100.0%)

76.08772 (73.07524, 79.10020)

-0.05263 (-2.60383, 2.49856)

30/30 (100.0%)

74.26667 (69.88594, 78.64739)

-0.86667 (-4.07027, 2.33693)

-5.55862 (-10.21687, -0.90037)

0.81404 (-3.20707, 4.83514)

Cycle 04

28/28 (100.0%)

75.00000 (71.32004, 78.67996)

-5.97619 (-9.52099, -2.43139)

35/35 (100.0%)

74.41905 (71.11597, 77.72212)

-1.34286 (-3.41130, 0.72559)

27/27 (100.0%)

73.39506 (69.55708, 77.23304)

-2.46914 (-5.76351, 0.82524)

-3.50705 (-8.23403, 1.21993)

1.12628 (-2.69639, 4.94895)

Download RTF file

TSFVIT01
TSFVIT02
Source Code
---
title: TSFVIT01A
subtitle: Mean Change From Baseline in Blood Pressure 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:              tsfvit01a

# Prep environment:

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

# Define script level parameters:

tblid <- "TSFVIT01a"
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 <- 0L
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("DIABP", "SYSBP")
# to Update the timepoints to what is relevant for your study
# For the trial used for the shell program, the screening timepoint is the timepoint considered as baseline
# see further, an alternative method to identify all non-unscheduled visits based upon data
selvisit <- c("Screening", "Cycle 02", "Cycle 03", "Cycle 04")

### as in dataset, order is important for later processing
### not automated, hard coded approach for ease of reading
### ideally the datasets already contain the appropriate case, to ensure units are in proper case
sel_param <- c(
  "Systolic Blood Pressure (mmHg)",
  "Diastolic Blood Pressure (mmHg)"
)
sel_param_case <- c(
  "Systolic blood pressure (mmHg)",
  "Diastolic blood pressure (mmHg)"
)

# Process Data:

adsl <- pharmaverseadamjnj::adsl %>%
  filter(.data[[popfl]] == "Y") %>%
  select(
    STUDYID,
    USUBJID,
    all_of(c(popfl, trtvar, subgrpvar)),
    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)

advs00 <- pharmaverseadamjnj::advs %>%
  select(
    USUBJID,
    AVISITN,
    AVISIT,
    PARAMCD,
    PARAM,
    AVAL,
    BASE,
    CHG,
    starts_with("ANL"),
    ABLFL,
    APOBLFL,
    VSSTAT
  ) %>%
  mutate(invsdata = "Y") %>%
  inner_join(adsl)

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

vs_precision <- tidytlg:::make_precision_data(
  df = advs00,
  decimal = 4,
  precisionby = "PARAMCD",
  precisionon = "AVAL"
)

### data preparation

filtered_advs_00 <- advs00 %>%
  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_advs_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_advs_00x <- advs00 %>%
    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_advs_00 <- filtered_advs_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 advs on this timepoint: ignoring anl01fl/anl02fl/param
filtered_advs_timepoints <- unique(
  advs00 %>%
    filter(AVISIT %in% selvisit) %>%
    select(USUBJID, AVISITN, AVISIT, invsdata)
) %>%
  inner_join(adsl)

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

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

### identify subjects in filtered_advs_timepoints and not in filtered_advs

extra_sub <- anti_join(filtered_advs_timepoints, filtered_advs_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_advs <- bind_rows(filtered_advs_00, extra_sub) %>%
  arrange(USUBJID, PARAM, AVISITN)

filtered_advs <- filtered_advs %>%
  inner_join(vs_precision, by = "PARAMCD")

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

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

advs_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(
    advs_timepoints_subgroups,
    filtered_advs_00 %>% select(USUBJID, AVISITN, AVISIT, PARAMCD, PARAM)
  ) %>%
  left_join(., unique(advs00 %>% 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_advs <- bind_rows(filtered_advs, extra_sub2) %>%
  arrange(USUBJID, PARAM, AVISITN)

### important: previous actions lost the label of variables

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

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


filtered_advs$PARAM <- factor(
  as.character(filtered_advs$PARAM),
  levels = sel_param,
  labels = sel_param_case
)


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

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

# Define layout and build table:

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,
  multivars = multivars,
  indatavar = "invsdata"
)

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(
    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 = "Blood Pressure",
    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(c("CHG"), 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
  )

result <- build_table(lyt, filtered_advs, 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