inTextSummaryTable
package library(clinUtils)
library(tools)# toTitleCase
library(plyr) # for ddply, rbind.fill
library(pander) # for session info
library(inTextSummaryTable)
The package is demonstrated with a subset of the ADaM datasets from the CDISC Pilot 01 dataset, available in the clinUtils
package.
# load example data
library(clinUtils)
# load example data
data(dataADaMCDISCP01)
dataAll <- dataADaMCDISCP01
labelVars <- attr(dataAll, "labelVars")
Typical in-text table for the CSR are included in the following sections.
Please note that the table content e.g. variables, statistics of interest depends strongly on the study at hand and personal preferences.
# data of interest
dataDM <- dataAll$ADSL
varDMFL <- grep("FL$", colnames(dataDM), value = TRUE)
varDMFLLabel <- sub(" Flag$", "", labelVars[varDMFL])
getSummaryStatisticsTable(
data = dataDM,
var = varDMFL, varFlag = varDMFL, varGeneralLab = "Analysis Set, N",
varLab = varDMFLLabel,
stats = getStats("n (%)"),
colVar = "TRT01P",
labelVars = labelVars,
colTotalInclude = TRUE, colTotalLab = "All subjects",
varInclude0 = TRUE,
title = toTitleCase("Table: subject disposition"),
file = file.path("tables_CSR", "Table_subjectDisposition.docx")
)
Table: Subject Disposition | ||||
---|---|---|---|---|
Analysis Set, N | Placebo | Xanomeline High Dose | Xanomeline Low Dose | All subjects |
Safety Population | 2 (100) | 3 (100) | 2 (100) | 7 (100) |
Intent-to-Treat Population | 2 (100) | 3 (100) | 2 (100) | 7 (100) |
Efficacy Population | 1 (50.0) | 3 (100) | 2 (100) | 6 (85.7) |
Completers of Week 8 Population | 1 (50.0) | 3 (100) | 2 (100) | 6 (85.7) |
Completers of Week 16 Population | 1 (50.0) | 1 (33.3) | 1 (50.0) | 3 (42.9) |
Completers of Week 24 Population | 1 (50.0) | 1 (33.3) | 1 (50.0) | 3 (42.9) |
Did the Subject Discontinue the Study? | 2 (100) | 2 (66.7) | 1 (50.0) | 5 (71.4) |
Discontinued due to AE? | 0 | 1 (33.3) | 0 | 1 (14.3) |
Subject Died? | 2 (100) | 0 | 1 (50.0) | 3 (42.9) |
# data of interest
dataDM <- subset(dataAll$ADSL, SAFFL == "Y")
# variables of interest
# Note: if available: ethnicity is included
varsDM <- c(
"SEX", "AGE", "AGEGR1",
"RACE", "ETHNIC",
"HEIGHTBL", "WEIGHTBL",
"BMIBL", "BMIBLGR1"
)
# Sort variables according to corresponding numeric variable
dataDM$AGEGR1 <- with(dataDM, reorder(AGEGR1, AGEGR1N))
dataDM$RACE <- with(dataDM, reorder(RACE, RACEN))
dataDM$TRT01P <- with(dataDM, reorder(TRT01P, TRT01PN))
## Define set of statistics of interest:
statsDM <- getStatsData(
data = dataDM, var = varsDM,
# different for continuous and categorical variable
type = c(cont = "median (range)", cat = "n (%)"),
# for categorical variable, statistic name (here: 'n (%)')
# should not be included in the table
args = list(cat = list(includeName = FALSE))
)
## create the table:
getSummaryStatisticsTable(
data = dataDM,
# variables to summarize
var = varsDM,
varGeneralLab = "Parameter",
# column
colVar = "TRT01P", colTotalInclude = TRUE, colTotalLab = "All subjects",
# statistics
stats = statsDM,
statsGeneralLab = "",
labelVars = labelVars,
# if only one category, should be included in separated row (e.g. RACE: White)
rowAutoMerge = FALSE,
rowInclude0 = FALSE, emptyValue = 0,
title = toTitleCase("Table: Demographic Data (safety Analysis Set)"),
file = file.path("tables_CSR", "Table_demographicData.docx")
)
Table: Demographic Data (Safety Analysis Set) | ||||
---|---|---|---|---|
Parameter | Placebo | Xanomeline Low Dose | Xanomeline High Dose | All subjects |
Variable group | ||||
Sex | ||||
F | 1 (50.0) | 2 (100) | 2 (66.7) | 5 (71.4) |
M | 1 (50.0) | 0 | 1 (33.3) | 2 (28.6) |
Age | ||||
Median (range) | 82.0 (75,89) | 78.0 (76,80) | 69.0 (57,74) | 75.0 (57,89) |
Pooled Age Group 1 | ||||
<65 | 0 | 0 | 1 (33.3) | 1 (14.3) |
65-80 | 1 (50.0) | 2 (100) | 2 (66.7) | 5 (71.4) |
>80 | 1 (50.0) | 0 | 0 | 1 (14.3) |
Race | ||||
WHITE | 2 (100) | 2 (100) | 2 (66.7) | 6 (85.7) |
BLACK OR AFRICAN AMERICAN | 0 | 0 | 1 (33.3) | 1 (14.3) |
Ethnicity | ||||
NOT HISPANIC OR LATINO | 2 (100) | 2 (100) | 3 (100) | 7 (100) |
Baseline Height (cm) | ||||
Median (range) | 167.65 (157.5,177.8) | 155.55 (151.1,160.0) | 158.80 (154.9,175.3) | 158.80 (151.1,177.8) |
Baseline Weight (kg) | ||||
Median (range) | 59.65 (47.2,72.1) | 54.45 (45.4,63.5) | 66.70 (51.7,87.1) | 63.50 (45.4,87.1) |
Baseline BMI (kg/m^2) | ||||
Median (range) | 20.90 (19.0,22.8) | 22.75 (17.7,27.8) | 27.80 (20.5,28.3) | 22.80 (17.7,28.3) |
Pooled Baseline BMI Group 1 | ||||
25-<30 | 0 | 1 (50.0) | 2 (66.7) | 3 (42.9) |
<25 | 2 (100) | 1 (50.0) | 1 (33.3) | 4 (57.1) |
Please note that the content of the table strongly depends on the study.
# data of interest
dataBDC <- subset(dataAll$ADSL, SAFFL == "Y")
# create table
getSummaryStatisticsTable(
data = dataBDC,
var = c("DURDIS", "EDUCLVL"), varGeneralLab = "Parameter",
colVar = "TRT01P", colTotalInclude = TRUE, colTotalLab = "All subjects",
stats = getStats("median\n(range)"), statsGeneralLab = "",
rowAutoMerge = FALSE,
labelVars = labelVars,
title = toTitleCase("Table: Baseline Disease Characteristics (safety analysis set)"),
file = file.path("tables_CSR", "Table_BaselineCharacteristics.docx")
)
Table: Baseline Disease Characteristics (Safety Analysis Set) | ||||
---|---|---|---|---|
Parameter | Placebo | Xanomeline High Dose | Xanomeline Low Dose | All subjects |
Duration of Disease (Months) | 20.65 | 31.4 | 35.6 | 31.4 |
Years of Education | 13 | 15 | 12 | 12 |
dataCM <- subset(dataAll$ADCM, SAFFL == "Y")
# sort variable according to corresponding numeric variables
dataCM$TRTA <- with(dataCM, reorder(TRTA, TRTAN))
# Terms should be in lower-case
dataCM$CMDECOD <- simpleCap(tolower(dataCM$CMDECOD))
dataCM$CMCLAS <- simpleCap(tolower(dataCM$CMCLAS))
getSummaryStatisticsTable(
data = dataCM,
colVar = "TRTA", colTotalInclude = TRUE, colTotalLab = "All subjects",
rowVar = c("CMCLAS", "CMDECOD"),
# include total across generic terms and across ATC4 classes
rowVarTotalInclude = c("CMCLAS", "CMDECOD"),
rowTotalLab = "Any prior and concomitant medication",
stats = getStats("n (%)"),
# sort rows based on counts of subjects in the total column
rowOrder = "total",
labelVars = labelVars,
emptyValue = 0,
title = toTitleCase(paste("Prior and concomitant therapies",
"by medication class and generic term (safety analyis set)"
)),
file = file.path("tables_CSR", "Table_CM.docx")
)
Prior and Concomitant Therapies by Medication Class and Generic Term (Safety Analyis Set) | |||
---|---|---|---|
Medication Class | Xanomeline Low Dose | Xanomeline High Dose | All subjects |
Standardized Medication Name | |||
Any prior and concomitant medication | 2 (100) | 1 (100) | 3 (100) |
Systemic hormonal preparations, excl. | 2 (100) | 1 (100) | 3 (100) |
Hydrocortisone | 2 (100) | 1 (100) | 3 (100) |
Uncoded | 2 (100) | 1 (100) | 3 (100) |
Uncoded | 2 (100) | 1 (100) | 3 (100) |
Respiratory system | 1 (50.0) | 0 | 1 (33.3) |
Salbutamol sulfate | 1 (50.0) | 0 | 1 (33.3) |
The example dataset has has two primary endpoints:
ADQSADAS
datasetADQSCIBC
dataset dataAdasCog11 <- subset(dataAll$ADQSADAS, PARAMCD == "ACTOT")
dataCIBIC <- subset(dataAll$ADQSCIBC, PARAMCD == "CIBICVAL")
dataEfficacy <- plyr::rbind.fill(dataAdasCog11, dataCIBIC)
dataEfficacy$TRTP <- with(dataEfficacy, reorder(TRTP, TRTPN))
dataEfficacy$AVISIT <- with(dataEfficacy, reorder(AVISIT, AVISITN))
stats <- getStatsData(
data = dataEfficacy,
var = c("AVAL", "CHG"),
type = c("n", "mean (se)", "median (range)")
)
getSummaryStatisticsTable(
data = dataEfficacy,
rowVar = "PARAM",
colVar = c("TRTP", "AVISIT"),
var = c("AVAL", "CHG"),
stats = stats,
labelVars = labelVars,
title = paste("Table: efficacy endpoints",
toTitleCase("actual value and changes from baseline per time point"
)),
file = file.path("tables_CSR", "Table_efficacy.docx")
)
Table: efficacy endpoints Actual Value and Changes from Baseline per Time Point | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
Parameter | Placebo | Xanomeline Low Dose | Xanomeline High Dose | |||||||||
Variable | ||||||||||||
Statistic | Baseline | Week 8 | Week 16 | Week 24 | Baseline | Week 8 | Week 16 | Week 24 | Baseline | Week 8 | Week 16 | Week 24 |
Adas-Cog(11) Subscore | ||||||||||||
Analysis Value | ||||||||||||
n | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 |
Mean (SE) | 14.0 (6.00) | 16.5 (3.50) | 17.5 (2.50) | 17.5 (2.50) | 32.0 (23.00) | 37.0 (25.00) | 38.5 (23.50) | 37.0 (25.00) | 15.7 (6.36) | 16.7 (6.36) | 15.3 (5.21) | 17.7 (5.36) |
Median (range) | 14.0 (8,20) | 16.5 (13,20) | 17.5 (15,20) | 17.5 (15,20) | 32.0 (9,55) | 37.0 (12,62) | 38.5 (15,62) | 37.0 (12,62) | 15.0 (5,27) | 16.0 (6,28) | 16.0 (6,24) | 22.0 (7,24) |
Change from Baseline | ||||||||||||
n | - | 2 | 2 | 2 | - | 2 | 2 | 2 | - | 3 | 3 | 3 |
Mean (SE) | - | 2.5 (2.50) | 3.5 (3.50) | 3.5 (3.50) | - | 5.0 (2.00) | 6.5 (0.50) | 5.0 (2.00) | - | 1.0 (0.00) | -0.3 (1.33) | 2.0 (2.89) |
Median (range) | - | 2.5 (0,5) | 3.5 (0,7) | 3.5 (0,7) | - | 5.0 (3,7) | 6.5 (6,7) | 5.0 (3,7) | - | 1.0 (1,1) | 1.0 (-3,1) | 2.0 (-3,7) |
CIBIC Score | ||||||||||||
Analysis Value | ||||||||||||
n | - | 1 | 1 | 1 | - | 2 | 2 | 2 | - | 2 | 3 | 3 |
Mean (SE) | - | 6.0 ( NA) | 5.0 ( NA) | 5.0 ( NA) | - | 5.0 (0.00) | 4.5 (0.50) | 4.5 (0.50) | - | 4.5 (0.50) | 4.0 (0.00) | 4.3 (0.33) |
Median (range) | - | 6.0 (6,6) | 5.0 (5,5) | 5.0 (5,5) | - | 5.0 (5,5) | 4.5 (4,5) | 4.5 (4,5) | - | 4.5 (4,5) | 4.0 (4,4) | 4.0 (4,5) |
## data of interest: safety analysis set and treatment-emergent
dataTEAE <- subset(dataAll$ADAE, SAFFL == "Y" & TRTEMFL == "Y")
# order treatment and severity categories
dataTEAE$TRTA <- with(dataTEAE, reorder(TRTA, TRTAN))
## data considered for the total
dataTotalAE <- subset(dataAll$ADSL, SAFFL == "Y")
dataTotalAE$TRTA <- with(dataTotalAE, reorder(TRT01A, TRT01AN))
# TEAE with worst intensity
# build worst-case scenario
dataTEAE$AESEV <- factor(dataTEAE$AESEV, levels = c("MILD", "MODERATE", "SEVERE"))
dataTEAE$AESEVN <- as.numeric(dataTEAE$AESEV)
dataTEAE <- ddply(dataTEAE, c("USUBJID", "TRTA"), function(x)
cbind.data.frame(x,
WORSTINT = with(x, ifelse(AESEVN == max(AESEVN), as.character(AESEV), NA_character_))
))
dataTEAE$WORSTINT <- factor(dataTEAE$WORSTINT, levels = levels(dataTEAE$AESEV))
## specify labels for each variable:
varsAE <- c("TRTEMFL", "AESER", "AESDTH", "AEREL")
# create the table
getSummaryStatisticsTable(
data = dataTEAE,
colVar = "TRTA",
# define variables to compute statistics on
var = c("TRTEMFL", "AESER", "WORSTINT", "AESDTH", "AEREL"),
varFlag = c("TRTEMFL", "AESER", "AESDTH"),
varLab = c(TRTEMFL = "Treatment-Emergent", WORSTINT = "Worst-case severity:"),
varGeneralLab = "Subjects with, n(%):",
# force the inclusion of lines for variable without count:
varInclude0 = TRUE,
# include the total for the worst-case scenario
varTotalInclude = "WORSTINT",
# statistics:
stats = getStats('n (%)'),
emptyValue = "0",
labelVars = labelVars,
# dataset used for the total in the header column (and for percentage as default)
dataTotal = dataTotalAE,
# title/export
title = toTitleCase("Table: Summary Table of Treatment-emergent Adverse Events (safety analysis set)"),
file = file.path("tables_CSR", "Table_TEAE_summary.docx")
)
Table: Summary Table of Treatment-Emergent Adverse Events (Safety Analysis Set) | |||
---|---|---|---|
Subjects with, n(%): | Placebo | Xanomeline Low Dose | Xanomeline High Dose |
Variable group | |||
Treatment-Emergent | 2 (100) | 2 (100) | 3 (100) |
Serious Event | 0 | 0 | 1 (33.3) |
Worst-case severity: | 2 (100) | 2 (100) | 3 (100) |
MILD | 0 | 0 | 0 |
MODERATE | 0 | 1 (50.0) | 1 (33.3) |
SEVERE | 2 (100) | 1 (50.0) | 2 (66.7) |
Results in Death | 2 (100) | 1 (50.0) | 0 |
Causality | |||
NONE | 1 (50.0) | 2 (100) | 3 (100) |
POSSIBLE | 1 (50.0) | 1 (50.0) | 2 (66.7) |
PROBABLE | 0 | 1 (50.0) | 3 (100) |
REMOTE | 0 | 0 | 1 (33.3) |
dataTEAE <- subset(dataAll$ADAE, SAFFL == "Y" & TRTEMFL == "Y")
# order treatment and severity categories
dataTEAE$TRTA <- with(dataTEAE, reorder(TRTA, TRTAN))
## data considered for the total
dataTotalAE <- subset(dataAll$ADSL, SAFFL == "Y")
dataTotalAE$TRTA <- with(dataTotalAE, reorder(TRT01A, TRT01AN))
getSummaryStatisticsTable(
data = dataTEAE,
rowVar = c("AESOC", "AEDECOD"),
colVar = "TRTA",
## total
# data
dataTotal = dataTotalAE,
# row total
rowVarTotalInclude = c("AESOC", "AEDECOD"), rowTotalLab = "Any TEAE",
stats = getStats("n (%)"),
labelVars = labelVars,
rowVarLab = c('AESOC' = "TEAE by SOC and Preferred Term,\nn (%)"),
# sort rows based on the total column:
rowOrder = "total",
rowOrderTotalFilterFct = function(x) subset(x, TRTA == "Total"),
title = paste("Table: Treatment-emergent Adverse Events by System Organ Class",
"and Preferred Term (Safety Analysis Set)"
),
file = file.path("tables_CSR", "Table_TEAE_SOCPT_atLeast1Subject.docx")
)
Table: Treatment-emergent Adverse Events by System Organ Class and Preferred Term (Safety Analysis Set) | |||
---|---|---|---|
TEAE by SOC and Preferred Term, | Placebo | Xanomeline Low Dose | Xanomeline High Dose |
Dictionary-Derived Term | |||
Any TEAE | 2 (100) | 2 (100) | 3 (100) |
GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS | 0 | 2 (100) | 3 (100) |
APPLICATION SITE PRURITUS | 0 | 2 (100) | 2 (66.7) |
APPLICATION SITE ERYTHEMA | 0 | 2 (100) | 1 (33.3) |
APPLICATION SITE IRRITATION | 0 | 1 (50.0) | 1 (33.3) |
APPLICATION SITE DERMATITIS | 0 | 0 | 1 (33.3) |
FATIGUE | 0 | 0 | 1 (33.3) |
SECRETION DISCHARGE | 0 | 1 (50.0) | 0 |
SUDDEN DEATH | 0 | 1 (50.0) | 0 |
MUSCULOSKELETAL AND CONNECTIVE TISSUE DISORDERS | 0 | 2 (100) | 2 (66.7) |
BACK PAIN | 0 | 0 | 1 (33.3) |
FLANK PAIN | 0 | 0 | 1 (33.3) |
MUSCULAR WEAKNESS | 0 | 1 (50.0) | 0 |
SHOULDER PAIN | 0 | 1 (50.0) | 0 |
PSYCHIATRIC DISORDERS | 1 (50.0) | 1 (50.0) | 1 (33.3) |
COMPLETED SUICIDE | 1 (50.0) | 0 | 0 |
CONFUSIONAL STATE | 0 | 1 (50.0) | 0 |
HALLUCINATION, VISUAL | 0 | 0 | 1 (33.3) |
GASTROINTESTINAL DISORDERS | 0 | 0 | 2 (66.7) |
NAUSEA | 0 | 0 | 2 (66.7) |
INFECTIONS AND INFESTATIONS | 0 | 1 (50.0) | 1 (33.3) |
LOWER RESPIRATORY TRACT INFECTION | 0 | 0 | 1 (33.3) |
PNEUMONIA | 0 | 1 (50.0) | 0 |
NERVOUS SYSTEM DISORDERS | 0 | 0 | 2 (66.7) |
AMNESIA | 0 | 0 | 1 (33.3) |
LETHARGY | 0 | 0 | 1 (33.3) |
PARTIAL SEIZURES WITH SECONDARY GENERALISATION | 0 | 0 | 1 (33.3) |
RENAL AND URINARY DISORDERS | 0 | 1 (50.0) | 1 (33.3) |
CALCULUS URETHRAL | 0 | 0 | 1 (33.3) |
INCONTINENCE | 0 | 1 (50.0) | 0 |
RESPIRATORY, THORACIC AND MEDIASTINAL DISORDERS | 0 | 1 (50.0) | 1 (33.3) |
DYSPNOEA | 0 | 1 (50.0) | 0 |
EPISTAXIS | 0 | 0 | 1 (33.3) |
SKIN AND SUBCUTANEOUS TISSUE DISORDERS | 0 | 1 (50.0) | 1 (33.3) |
ACTINIC KERATOSIS | 0 | 0 | 1 (33.3) |
ERYTHEMA | 0 | 1 (50.0) | 0 |
CARDIAC DISORDERS | 1 (50.0) | 0 | 0 |
MYOCARDIAL INFARCTION | 1 (50.0) | 0 | 0 |
INJURY, POISONING AND PROCEDURAL COMPLICATIONS | 0 | 1 (50.0) | 0 |
JOINT DISLOCATION | 0 | 1 (50.0) | 0 |
SKIN LACERATION | 0 | 1 (50.0) | 0 |
INVESTIGATIONS | 0 | 1 (50.0) | 0 |
NASAL MUCOSA BIOPSY | 0 | 1 (50.0) | 0 |
METABOLISM AND NUTRITION DISORDERS | 0 | 0 | 1 (33.3) |
DECREASED APPETITE | 0 | 0 | 1 (33.3) |
getSummaryStatisticsTable(
data = dataTEAE,
rowVar = c("AESOC", "AEDECOD"),
colVar = "TRTA",
## total
# data
dataTotal = dataTotalAE,
# row total
rowVarTotalInclude = c("AESOC", "AEDECOD"), rowTotalLab = "Any TEAE",
stats = getStats("n (%)"),
labelVars = labelVars,
rowVarLab = c('AESOC' = "SOC and Preferred Term,\nn (%)"),
# sort rows based on the total column:
rowOrder = "total",
rowOrderTotalFilterFct = function(x) subset(x, TRTA == "Total"),
title = paste("Table: Treatment-emergent Adverse Events by System Organ Class",
"and Preferred Term reported in at least 25% of the subjects",
"in any treatment group (Safety Analysis Set)"
),
file = file.path("tables_CSR", "Table_TEAE_SOCPT_atLeast25PercentsSubject.docx"),
# include only events occuring in at least 25% for at least one preferred term:
filterFct = function(x)
ddply(x, "AESOC", function(x){ # per AESOC to include the total
ddply(x, "AEDECOD", function(y){
yTotal <- subset(y, grepl("Total", TRTA))
if(any(yTotal$statPercN >= 25)) y
})
})
)
Table: Treatment-emergent Adverse Events by System Organ Class and Preferred Term reported in at least 25% of the subjects in any treatment group (Safety Analysis Set) | |||
---|---|---|---|
SOC and Preferred Term, |