# Program Name: tsfecg04# Prep Environmentlibrary(envsetup)library(tern)library(dplyr)library(rtables)library(junco)library(rlang)# Define script level parameters:tblid <-"TSFECG04"fileid <- tblidtitles <-get_titles_from_file(input_path ='../../_data/', tblid)string_map <- default_str_mappopfl <-"SAFFL"trtvar <-"TRT01A"ctrl_grp <-"Placebo"demogvars <-c("SEX", "AGEGR1", "RACE", "ETHNIC", "AGE")ad_domain <-"ADEG"## selection of QTC parametersselparamcd <-c("QTCFAG", "QTCBAG", "QTCS", "QTCLAG")# initial read of dataadeg_complete <- pharmaverseadamjnj::adeg### available QTC parameters in studyselparamcd <-intersect(selparamcd, unique(adeg_complete$PARAMCD))catvar <-"AVALCAT1"# Process Data:adsl <- pharmaverseadamjnj::adsl %>%filter(!!sym(popfl) =="Y") %>%select(STUDYID, USUBJID, all_of(c(trtvar, popfl, demogvars)))adsl <- adsl %>%mutate(colspan_trt =factor(ifelse(!!sym(trtvar) == ctrl_grp, " ", "Active Study Agent"),levels =c("Active Study Agent", " ") ) )## to ensure the same order as on other outputstrt_order <-as.character((unique( adsl %>%select("colspan_trt", all_of(trtvar))) %>%arrange(colspan_trt, !!sym(trtvar)))[[trtvar]])adsl[[trtvar]] <-factor(as.character(adsl[[trtvar]]), levels = trt_order)adeg <- adeg_complete %>%filter(PARAMCD %in% selparamcd) %>%### Maximum On-treatment### note: by filter ANL03FL, this table is restricted to On-treatment values, per definition of ANL03FL### therefor, no need to add ONTRTFL in filter### if derivation of ANL03FL is not restricted to ONTRTFL records, adding ONTRTFL here will not give the correct answer either### as mixing worst with other period is not giving the proper selection !!!filter(ANL03FL =="Y") %>%select( USUBJID, ONTRTFL, TRTEMFL, PARAM, PARAMCD, AVISITN, AVISIT, AVAL, BASE, CHG, AVALCAT1, BASECAT1, ONTRTFL, TRTEMFL, ANL01FL, ANL02FL, ANL03FL ) %>%inner_join(., adsl)adeg$AVISIT <-factor("Maximum Corrected QT Interval")## if also over time is needed, append these to above dataset## see ecg05 as examplecheck1 <- adeg %>%group_by(TRT01A, PARAMCD, AVISIT) %>%summarize(n =n_distinct(USUBJID))## add variable for column split headeradeg$BASECAT1_header <-"Baseline Corrected QT Interval"adeg$BASECAT1_header2 <-" "## first column N should not appear under Baseline column spanadeg$BASECAT1_header3 <-" "## extra to allow for additional topleft material###AVALCAT1_levels <-levels(adeg$AVALCAT1)## add extra level N to Basecat1adeg <- adeg %>%mutate(BASECAT1 =factor(as.character(BASECAT1), levels =c("N", AVALCAT1_levels)) )## trick for alt_counts_df to work with col splitting# add BASECAT1 to adsl, all assign to extra level N (column will be used for N counts)adslx <- adsl %>%mutate(BASECAT1 ="N") %>%mutate(BASECAT1 =factor(BASECAT1, levels =c("N", AVALCAT1_levels)))adslx$BASECAT1_header <-"Baseline Corrected QT Interval"adslx$BASECAT1_header2 <-" "adslx$BASECAT1_header3 <-" "## extra to allow for additional topleft material# Define layout and build table:lyt <-basic_table(show_colcounts =FALSE) %>%split_cols_by("BASECAT1_header3") %>%## to ensure N column is not under the Baseline column span headersplit_cols_by("BASECAT1_header2") %>%split_cols_by("BASECAT1", split_fun =keep_split_levels("N")) %>%## restart column split (Nested = False)## Combined levels will be made, and the N column should not appearsplit_cols_by("BASECAT1_header", nested =FALSE) %>%split_cols_by("BASECAT1",split_fun =make_split_fun(pre =list(rm_levels(excl ="N")),post =list(add_overall_facet("TOTAL", "Total") ) ) ) %>%#### replace split_rows and summarize by single analyze call### a_freq_j only works due to### special arguments can do the trick : denomf = adslx & .stats = count_unique### we want counts of treatment group coming from adsl, not from input dataset, therefor, countsource = altdfanalyze(vars = trtvar,afun = a_freq_j,extra_args =list(restr_columns ="N",.stats ="count_unique",countsource ="altdf",extrablankline =TRUE ),indent_mod =-1L ) %>%# ## main part of tablesplit_rows_by("PARAM",nested =FALSE,label_pos ="topleft",child_labels ="visible",split_label ="QTc Interval",split_fun = drop_split_levels,section_div =" " ) %>%split_rows_by( trtvar,label_pos ="topleft",indent_mod =-1L,child_labels ="hidden",split_label ="Treatment Group",section_div =" " ) %>%### a_freq_j### the special statistic "n_rowdf" option does the trick here of getting the proper value for the N columnsummarize_row_groups( trtvar,cfun = a_freq_j,extra_args =list(.stats ="n_rowdf",restr_columns =c("N") ) ) %>%split_rows_by("AVISIT",label_pos ="hidden",indent_mod =1L,split_label =" ",child_labels ="visible",section_div =" " ) %>%## add extra level TOTAL using new_levels, rather than earlier technique## advantage for denominator derivation -- n_rowdf can be used, if we'd like to present fraction as well## switch .stats to count_unique_denom_fraction or count_unique_fractionanalyze("AVALCAT1",afun = a_freq_j,extra_args =list(.stats ="count_unique",denom ="n_rowdf",new_levels =list(c("Total"), list(AVALCAT1_levels)),new_levels_after =TRUE,.indent_mods =1L,restr_columns =c(toupper(AVALCAT1_levels),"TOTAL" ) ) ) %>%append_topleft(" Criteria, n")result <-build_table(lyt, adeg, alt_counts_df = adslx)# Add titles and footnotes:result <-set_titles(result, titles)# Convert to tbl file and output table# the default column-widths had issues with 2 columns appear too close# retrieve the default column widths and update the latter columnsfontspec <-font_spec("Times", 9L, 1.2)col_gap <-7Llabel_width_ins <-2colwidths <-def_colwidths( result, fontspec,col_gap = col_gap,label_width_ins = label_width_ins)# adjust the column-widths to have the same length for columns 3 - 7 (<= 450, ...., Total)acolwidths <- colwidthsacolwidths[3:length(acolwidths)] <-12tt_to_tlgrtf(string_map = string_map, tt = result, file = fileid, colwidths = acolwidths)
TSFECG04:Shift From Baseline to Maximum On-treatment Corrected QT Interval; Safety Analysis Set (Study jjcs - core)
QTc Interval
Treatment Group
Baseline Corrected QT Interval
Criteria, n
N
≤450
>450 to ≤480
>480 to ≤500
>500
Total
Xanomeline High Dose
53
Xanomeline Low Dose
73
Placebo
59
QTcB Interval, Aggregate (msec)
Xanomeline High Dose
53
Maximum Corrected QT Interval
≤450
31
2
3
5
41
>450 to ≤480
4
0
2
0
6
>480 to ≤500
1
0
0
1
2
>500
1
2
0
1
4
Total
37
4
5
7
53
Xanomeline Low Dose
53
Maximum Corrected QT Interval
≤450
31
3
2
2
38
>450 to ≤480
2
1
0
1
4
>480 to ≤500
4
1
0
1
6
>500
5
0
0
0
5
Total
42
5
2
4
53
Placebo
56
Maximum Corrected QT Interval
≤450
40
4
1
2
47
>450 to ≤480
2
0
1
1
4
>480 to ≤500
2
0
0
0
2
>500
0
0
2
1
3
Total
44
4
4
4
56
QTcF Interval, Aggregate (msec)
Xanomeline High Dose
53
Maximum Corrected QT Interval
≤450
33
2
4
3
42
>450 to ≤480
3
0
0
0
3
>480 to ≤500
6
0
0
0
6
>500
2
0
0
0
2
Total
44
2
4
3
53
Xanomeline Low Dose
53
Maximum Corrected QT Interval
≤450
34
2
0
4
40
>450 to ≤480
3
0
0
1
4
>480 to ≤500
4
0
0
0
4
>500
4
0
1
0
5
Total
45
2
1
5
53
Placebo
56
Maximum Corrected QT Interval
≤450
29
6
5
3
43
>450 to ≤480
2
0
0
0
2
>480 to ≤500
4
1
0
0
5
>500
5
1
0
0
6
Total
40
8
5
3
56
Note: On-treatment is defined as QTc interval values obtained after the first dose and within [30 days] following treatment discontinuation.
Note: N is the number of subjects with non-missing values for the ECG parameter at baseline and at least 1 postbaseline visit.