Adverse Event (AE) tables present the safety profile of investigational treatments by summarizing the adverse events experienced by participants during a clinical trial.
A well-constructed AE table typically presents multiple levels of aggregation simultaneously:
Overall adverse event rates (any AE)
System Organ Class (SOC) level summaries
Preferred Term (PT) level details within each SOC
This hierarchical presentation allows clinicians and regulators to quickly assess both the overall safety profile and drill down into specific types of adverse events.
3.1 Data Pre-processing
The first step in creating an AE table is to prepare our data sources. We’ll work with two standard CDISC datasets (available from package ‘pharmaverseadam’):
adsl: Subject-Level Analysis Dataset containing demographic and disposition information
adae: Adverse Events Analysis Dataset containing all reported adverse events
Code
# Load example datasetsadsl<-pharmaverseadam::adsladae<-pharmaverseadam::adaeadae<-adae|>dplyr::filter(# safety populationSAFFL=="Y")adae
# A tibble: 1,191 × 107
STUDYID DOMAIN USUBJID AESEQ AESPID AETERM AELLT AELLTCD AEDECOD AEPTCD AEHLT
<chr> <chr> <chr> <dbl> <chr> <chr> <chr> <dbl> <chr> <dbl> <chr>
1 CDISCP… AE 01-701… 1 E07 APPLI… APPL… NA APPLIC… NA HLT_…
2 CDISCP… AE 01-701… 2 E08 APPLI… APPL… NA APPLIC… NA HLT_…
3 CDISCP… AE 01-701… 3 E06 DIARR… DIAR… NA DIARRH… NA HLT_…
4 CDISCP… AE 01-701… 2 E09 ERYTH… LOCA… NA ERYTHE… NA HLT_…
5 CDISCP… AE 01-701… 1 E08 ERYTH… ERYT… NA ERYTHE… NA HLT_…
6 CDISCP… AE 01-701… 4 E08 ERYTH… ERYT… NA ERYTHE… NA HLT_…
7 CDISCP… AE 01-701… 3 E10 ATRIO… AV B… NA ATRIOV… NA HLT_…
8 CDISCP… AE 01-701… 1 E04 APPLI… APPL… NA APPLIC… NA HLT_…
9 CDISCP… AE 01-701… 2 E05 APPLI… APPL… NA APPLIC… NA HLT_…
10 CDISCP… AE 01-701… 1 E08 APPLI… APPL… NA APPLIC… NA HLT_…
# ℹ 1,181 more rows
# ℹ 96 more variables: AEHLTCD <dbl>, AEHLGT <chr>, AEHLGTCD <dbl>,
# AEBODSYS <chr>, AEBDSYCD <dbl>, AESOC <chr>, AESOCCD <dbl>, AESEV <chr>,
# AESER <chr>, AEACN <chr>, AEREL <chr>, AEOUT <chr>, AESCAN <chr>,
# AESCONG <chr>, AESDISAB <chr>, AESDTH <chr>, AESHOSP <chr>, AESLIFE <chr>,
# AESOD <chr>, AEDTC <chr>, AESTDTC <chr>, AEENDTC <chr>, AESTDY <dbl>,
# AEENDY <dbl>, TRTSDT <date>, TRTEDT <date>, DTHDT <date>, EOSDT <date>, …
Why filter by SAFFL == Y?
In clinical trials, the safety population typically includes all subjects who received at least one dose of study medication. The Safety Flag (SAFFL) identifies these subjects, ensuring we analyze only relevant safety data.
3.1.1 Calculating Treatment Group Denominators
For percentage calculations, we need to know the total number of subjects in each treatment arm. This denominator is crucial for interpreting adverse event rates.
Code
CT_ARM<-adsl|>semi_join(adae, by ="ARM")|>count(ARM, name ="denom")CT_ARM
# A tibble: 3 × 2
ARM denom
<chr> <int>
1 Placebo 86
2 Xanomeline High Dose 84
3 Xanomeline Low Dose 84
The semi_join() ensures we count only subjects who are in both datasets and meet our safety population criteria.
3.2 Building a Hierarchical Structure
The key to creating a professional AE table is to construct multiple levels of aggregation and then stack them together into a single dataset. This technique, often called stacking or layering, allows us to present summary statistics at different hierarchical levels within one cohesive table.
We’ll build three separate aggregation levels, each progressively more detailed:
3.2.1 Level 3: Preferred Term (Most Detailed)
The finest level of detail shows individual adverse event terms (Preferred Terms or PTs) within each System Organ Class. This is where we see specific conditions like “Headache” or “Nausea”.
Code
AE_TABLE_LEV2<-adae|>group_by(ARM, AESOC, AEDECOD)|>summarise( n =n_distinct(USUBJID), .groups ="drop")|>arrange(AESOC, AEDECOD, ARM)|>tibble::add_column(agg_level =2L)AE_TABLE_LEV2
We use n_distinct(USUBJID) to count unique subjects, not total events (a subject experiencing the same AE multiple times counts once)
The agg_level = 2L marker identifies this as the most detailed level
We group by ARM (treatment), AESOC (System Organ Class), and AEDECOD (Preferred Term)
3.2.2 Level 2: System Organ Class (Intermediate)
The middle level aggregates all events within each body system category (e.g., “Gastrointestinal disorders”, “Nervous system disorders”). It provides a summary view of which body systems are most affected.
Code
AE_TABLE_LEV1<-adae|>group_by(ARM, AESOC)|>summarise( n =n_distinct(USUBJID), .groups ="drop")|>arrange(AESOC, ARM)|>tibble::add_column(agg_level =1L)AE_TABLE_LEV1
# A tibble: 61 × 4
ARM AESOC n agg_level
<chr> <chr> <int> <int>
1 Placebo CARDIAC DISORDERS 13 1
2 Xanomeline High Dose CARDIAC DISORDERS 18 1
3 Xanomeline Low Dose CARDIAC DISORDERS 13 1
4 Xanomeline High Dose CONGENITAL, FAMILIAL AND GENETIC DISORD… 2 1
5 Xanomeline Low Dose CONGENITAL, FAMILIAL AND GENETIC DISORD… 1 1
6 Placebo EAR AND LABYRINTH DISORDERS 1 1
7 Xanomeline High Dose EAR AND LABYRINTH DISORDERS 1 1
8 Xanomeline Low Dose EAR AND LABYRINTH DISORDERS 2 1
9 Placebo EYE DISORDERS 4 1
10 Xanomeline High Dose EYE DISORDERS 1 1
# ℹ 51 more rows
3.2.3 Level 1: Overall Summary (Highest Level)
The top level shows the broadest view: how many subjects experienced any adverse event at all, regardless of type.
Code
AE_TABLE_LEV0<-adae|>group_by(ARM)|>summarise( n =n_distinct(USUBJID))|>arrange(ARM)|>tibble::add_column(agg_level =0L, AESOC ="ANY ADVERSE EVENTS")AE_TABLE_LEV0
# A tibble: 3 × 4
ARM n agg_level AESOC
<chr> <int> <int> <chr>
1 Placebo 69 0 ANY ADVERSE EVENTS
2 Xanomeline High Dose 79 0 ANY ADVERSE EVENTS
3 Xanomeline Low Dose 77 0 ANY ADVERSE EVENTS
3.2.4 Stacking the Layers: Combining All Levels
Now comes the crucial step: we stack (vertically combine) all three aggregation levels into a single dataset. This stacking technique creates a hierarchical structure that will translate directly into the visual hierarchy of our final table.
By stacking these levels together, we’ve created a single dataset that contains all the information needed for our table, with a structure that naturally reflects the hierarchical organization we want to display. Each row knows its level in the hierarchy (agg_level) and can be formatted appropriately.
Note
bind_rows(AE_TABLE_LEV0, AE_TABLE_LEV1, AE_TABLE_LEV2): Vertically stacks all three levels, preserving the structure of each. Rows from AE_TABLE_LEV0 appear first, followed by AE_TABLE_LEV1, then AE_TABLE_LEV2.
left_join(CT_ARM, by = "ARM"): Adds the denominator for each treatment arm, enabling percentage calculations.
mutate(pct = n / denom): Calculates the percentage of subjects experiencing each event. This is critical for clinical interpretation—raw counts alone can be misleading if treatment groups have different sizes.
first_level = is.na(AEDECOD): Creates a flag to identify SOC-level rows (which lack a Preferred Term). This will help with formatting later.
arrange(AESOC, agg_level, first_level, AEDECOD, ARM): Carefully orders the data so that:
Events are grouped by System Organ Class
Within each SOC, the summary appears first (lower agg_level)
Then individual Preferred Terms follow
Treatment arms stay together for each term
LABEL = coalesce(AEDECOD, AESOC): Creates a display label that shows the Preferred Term when available, otherwise shows the System Organ Class. This unified label column simplifies table creation.
stat_str = fmt_n_percent(n, pct, digit = 1): Formats the count and percentage into a standard clinical reporting format (e.g., “15 (23.4%)”).
3.3 Creating the Flextable
Now that we have our stacked hierarchical dataset, we need to transform it into a presentation-ready table. This involves two key steps: reshaping the data from long to wide format, and then applying sophisticated formatting with flextable.
3.3.1 Pivoting from Long to Wide Format
Our current dataset has one row per treatment arm per adverse event, which is ideal for data manipulation but not for presentation. We need to pivot it so that treatment arms become columns, creating the familiar clinical trial table layout.
For this example, we’ll work with a subset of the data to keep things manageable during the workshop:
names_from = "ARM": Treatment arms become column names
values_from = "stat_str": The formatted statistics (n, %) populate the cells
values_fill = "0 (0%)": When no subjects experienced a particular AE in a treatment arm, we display “0 (0%)” rather than leaving the cell empty
Notice that we keep AESOC, AEDECOD, and agg_level in the dataset even though they’re not displayed columns. These metadata columns will help us apply conditional formatting later.
3.3.2 Setting Global Formatting Standards
Before we create the table, let’s establish formatting defaults that align with our standards:
3.3.3 Building the Base Table with Visual Hierarchy
The first step in creating our flextable is to establish the basic structure and create visual hierarchy through indentation:
Code
ft<-x|>flextable(col_keys =c("LABEL", CT_ARM$ARM))|># Indent sub-level itemsprepend_chunks( j ="LABEL", i =~agg_level==2,as_chunk("\t"))|>add_header_lines("Table 15.3: 1 AE by SOC/PT")|>add_footer_lines(as_paragraph(as_sup("(1)"), " n (%)"))|>set_table_properties(layout ="fixed")|>width(width =1)|>width(width =2, j =1)ft
Table 15.3: 1 AE by SOC/PT
LABEL
Placebo
Xanomeline High Dose
Xanomeline Low Dose
ANY ADVERSE EVENTS
69 (80.2%)
79 (94.0%)
77 (91.7%)
CARDIAC DISORDERS
13 (15.1%)
18 (21.4%)
13 (15.5%)
ATRIAL FIBRILLATION
1 (1.2%)
3 (3.6%)
1 (1.2%)
ATRIAL FLUTTER
0 (0%)
1 (1.2%)
1 (1.2%)
ATRIAL HYPERTROPHY
1 (1.2%)
0 (0%)
0 (0%)
ATRIOVENTRICULAR BLOCK FIRST DEGREE
1 (1.2%)
0 (0%)
1 (1.2%)
ATRIOVENTRICULAR BLOCK SECOND DEGREE
2 (2.3%)
3 (3.6%)
0 (0%)
BRADYCARDIA
1 (1.2%)
0 (0%)
0 (0%)
BUNDLE BRANCH BLOCK LEFT
1 (1.2%)
0 (0%)
0 (0%)
BUNDLE BRANCH BLOCK RIGHT
1 (1.2%)
0 (0%)
1 (1.2%)
CARDIAC DISORDER
0 (0%)
1 (1.2%)
0 (0%)
CARDIAC FAILURE CONGESTIVE
1 (1.2%)
0 (0%)
0 (0%)
MYOCARDIAL INFARCTION
4 (4.7%)
4 (4.8%)
2 (2.4%)
PALPITATIONS
0 (0%)
0 (0%)
2 (2.4%)
SINUS ARRHYTHMIA
1 (1.2%)
0 (0%)
0 (0%)
SINUS BRADYCARDIA
2 (2.3%)
8 (9.5%)
7 (8.3%)
SUPRAVENTRICULAR EXTRASYSTOLES
1 (1.2%)
1 (1.2%)
1 (1.2%)
SUPRAVENTRICULAR TACHYCARDIA
0 (0%)
0 (0%)
1 (1.2%)
TACHYCARDIA
1 (1.2%)
0 (0%)
0 (0%)
VENTRICULAR EXTRASYSTOLES
0 (0%)
1 (1.2%)
2 (2.4%)
VENTRICULAR HYPERTROPHY
1 (1.2%)
0 (0%)
0 (0%)
WOLFF-PARKINSON-WHITE SYNDROME
0 (0%)
0 (0%)
1 (1.2%)
GASTROINTESTINAL DISORDERS
17 (19.8%)
21 (25.0%)
15 (17.9%)
ABDOMINAL DISCOMFORT
0 (0%)
1 (1.2%)
0 (0%)
ABDOMINAL PAIN
1 (1.2%)
1 (1.2%)
3 (3.6%)
CONSTIPATION
1 (1.2%)
0 (0%)
0 (0%)
DIARRHOEA
9 (10.5%)
4 (4.8%)
5 (6.0%)
DYSPEPSIA
1 (1.2%)
1 (1.2%)
1 (1.2%)
DYSPHAGIA
0 (0%)
0 (0%)
1 (1.2%)
FLATULENCE
1 (1.2%)
0 (0%)
0 (0%)
GASTROINTESTINAL HAEMORRHAGE
0 (0%)
1 (1.2%)
0 (0%)
GASTROOESOPHAGEAL REFLUX DISEASE
1 (1.2%)
0 (0%)
0 (0%)
GLOSSITIS
1 (1.2%)
0 (0%)
0 (0%)
HIATUS HERNIA
1 (1.2%)
0 (0%)
0 (0%)
NAUSEA
3 (3.5%)
6 (7.1%)
3 (3.6%)
RECTAL HAEMORRHAGE
0 (0%)
0 (0%)
1 (1.2%)
SALIVARY HYPERSECRETION
0 (0%)
4 (4.8%)
0 (0%)
STOMACH DISCOMFORT
0 (0%)
1 (1.2%)
0 (0%)
VOMITING
3 (3.5%)
7 (8.3%)
3 (3.6%)
(1) n (%)
Breaking down each formatting choice
col_keys = c("LABEL", CT_ARM$ARM): Specifies exactly which columns to display. We show the LABEL column first, followed by each treatment arm. This hides the metadata columns (AESOC, AEDECOD, agg_level) that we kept for conditional formatting.
padding(j = "LABEL", i = ~ agg_level == 2, padding.left = 12): Creates visual hierarchy by indenting Preferred Terms (level 2). This immediately shows readers which rows are detailed terms versus summary categories. The ~ syntax allows us to use a formula to conditionally apply formatting.
add_header_lines("Table 15.3: 1 AE by SOC/PT"): Adds a title row that spans all columns. This follows standard pharmaceutical numbering conventions for tables.
add_footer_lines(as_paragraph(as_sup("(1)"), " n (%)")): Adds a footnote explaining the data format, with superscript notation linking to the column headers we’ll add next.
set_table_properties(layout = "fixed"): Fixes column widths so the table doesn’t expand/contract in different contexts. This ensures consistency when the document is viewed in different Word versions or settings.
width(width = 1) and width(width = 2, j = 1): Sets column widths in inches. Treatment arm columns get 1 inch each, while the LABEL column gets 2 inches to accommodate longer adverse event terms.
3.3.4 Adding Sample Size Information to Headers
In clinical tables, it’s important to display the number of subjects in each treatment arm. This context allows readers to properly interpret the percentages and assess the reliability of the data:
Code
ft<-ft|>append_chunks( i =1, j =-1, part ="header",as_sup(" (1)"))|>append_chunks( i =1, j =-1, part ="header",as_chunk(fmt_header_n(CT_ARM$denom, newline =TRUE)))ft
Table 15.3: 1 AE by SOC/PT
LABEL
Placebo
Xanomeline High Dose
Xanomeline Low Dose
ANY ADVERSE EVENTS
69 (80.2%)
79 (94.0%)
77 (91.7%)
CARDIAC DISORDERS
13 (15.1%)
18 (21.4%)
13 (15.5%)
ATRIAL FIBRILLATION
1 (1.2%)
3 (3.6%)
1 (1.2%)
ATRIAL FLUTTER
0 (0%)
1 (1.2%)
1 (1.2%)
ATRIAL HYPERTROPHY
1 (1.2%)
0 (0%)
0 (0%)
ATRIOVENTRICULAR BLOCK FIRST DEGREE
1 (1.2%)
0 (0%)
1 (1.2%)
ATRIOVENTRICULAR BLOCK SECOND DEGREE
2 (2.3%)
3 (3.6%)
0 (0%)
BRADYCARDIA
1 (1.2%)
0 (0%)
0 (0%)
BUNDLE BRANCH BLOCK LEFT
1 (1.2%)
0 (0%)
0 (0%)
BUNDLE BRANCH BLOCK RIGHT
1 (1.2%)
0 (0%)
1 (1.2%)
CARDIAC DISORDER
0 (0%)
1 (1.2%)
0 (0%)
CARDIAC FAILURE CONGESTIVE
1 (1.2%)
0 (0%)
0 (0%)
MYOCARDIAL INFARCTION
4 (4.7%)
4 (4.8%)
2 (2.4%)
PALPITATIONS
0 (0%)
0 (0%)
2 (2.4%)
SINUS ARRHYTHMIA
1 (1.2%)
0 (0%)
0 (0%)
SINUS BRADYCARDIA
2 (2.3%)
8 (9.5%)
7 (8.3%)
SUPRAVENTRICULAR EXTRASYSTOLES
1 (1.2%)
1 (1.2%)
1 (1.2%)
SUPRAVENTRICULAR TACHYCARDIA
0 (0%)
0 (0%)
1 (1.2%)
TACHYCARDIA
1 (1.2%)
0 (0%)
0 (0%)
VENTRICULAR EXTRASYSTOLES
0 (0%)
1 (1.2%)
2 (2.4%)
VENTRICULAR HYPERTROPHY
1 (1.2%)
0 (0%)
0 (0%)
WOLFF-PARKINSON-WHITE SYNDROME
0 (0%)
0 (0%)
1 (1.2%)
GASTROINTESTINAL DISORDERS
17 (19.8%)
21 (25.0%)
15 (17.9%)
ABDOMINAL DISCOMFORT
0 (0%)
1 (1.2%)
0 (0%)
ABDOMINAL PAIN
1 (1.2%)
1 (1.2%)
3 (3.6%)
CONSTIPATION
1 (1.2%)
0 (0%)
0 (0%)
DIARRHOEA
9 (10.5%)
4 (4.8%)
5 (6.0%)
DYSPEPSIA
1 (1.2%)
1 (1.2%)
1 (1.2%)
DYSPHAGIA
0 (0%)
0 (0%)
1 (1.2%)
FLATULENCE
1 (1.2%)
0 (0%)
0 (0%)
GASTROINTESTINAL HAEMORRHAGE
0 (0%)
1 (1.2%)
0 (0%)
GASTROOESOPHAGEAL REFLUX DISEASE
1 (1.2%)
0 (0%)
0 (0%)
GLOSSITIS
1 (1.2%)
0 (0%)
0 (0%)
HIATUS HERNIA
1 (1.2%)
0 (0%)
0 (0%)
NAUSEA
3 (3.5%)
6 (7.1%)
3 (3.6%)
RECTAL HAEMORRHAGE
0 (0%)
0 (0%)
1 (1.2%)
SALIVARY HYPERSECRETION
0 (0%)
4 (4.8%)
0 (0%)
STOMACH DISCOMFORT
0 (0%)
1 (1.2%)
0 (0%)
VOMITING
3 (3.5%)
7 (8.3%)
3 (3.6%)
(1) n (%)
Understanding chunk composition
The append_chunks() function allows us to build complex cell content by appending text chunks with different formatting.
First append_chunks() call: Adds a superscript “(1)” to all treatment arm columns (j = -1 means “all columns except the first”). This superscript links to the footer note explaining the data format.
Second append_chunks() call: Adds the sample size for each treatment arm on a new line. The fmt_header_n() function formats the denominators in parentheses, creating headers like:
Placebo
(N=86)
3.3.5 Applying Labels and Formatting
The final step is to replace technical variable names with reader-friendly labels and apply alignment conventions:
Code
ft<-ft|>labelizor( labels =c(LABEL ="", unlist(labelled::var_label(adae))), j ="LABEL")|>labelizor( labels =stringr::str_to_sentence, j ="LABEL")|>mk_par( i =1, j =1, part ="header",as_paragraph(labelled::get_variable_labels(adae)$AESOC,"\n\t",labelled::get_variable_labels(adae)$AEDECOD))|>align(j =-1, align ="right", part ="all")ft
Primary System Organ Class Dictionary-Derived Term
Placebo
Xanomeline High Dose
Xanomeline Low Dose
Any adverse events
69 (80.2%)
79 (94.0%)
77 (91.7%)
Cardiac disorders
13 (15.1%)
18 (21.4%)
13 (15.5%)
Atrial fibrillation
1 (1.2%)
3 (3.6%)
1 (1.2%)
Atrial flutter
0 (0%)
1 (1.2%)
1 (1.2%)
Atrial hypertrophy
1 (1.2%)
0 (0%)
0 (0%)
Atrioventricular block first degree
1 (1.2%)
0 (0%)
1 (1.2%)
Atrioventricular block second degree
2 (2.3%)
3 (3.6%)
0 (0%)
Bradycardia
1 (1.2%)
0 (0%)
0 (0%)
Bundle branch block left
1 (1.2%)
0 (0%)
0 (0%)
Bundle branch block right
1 (1.2%)
0 (0%)
1 (1.2%)
Cardiac disorder
0 (0%)
1 (1.2%)
0 (0%)
Cardiac failure congestive
1 (1.2%)
0 (0%)
0 (0%)
Myocardial infarction
4 (4.7%)
4 (4.8%)
2 (2.4%)
Palpitations
0 (0%)
0 (0%)
2 (2.4%)
Sinus arrhythmia
1 (1.2%)
0 (0%)
0 (0%)
Sinus bradycardia
2 (2.3%)
8 (9.5%)
7 (8.3%)
Supraventricular extrasystoles
1 (1.2%)
1 (1.2%)
1 (1.2%)
Supraventricular tachycardia
0 (0%)
0 (0%)
1 (1.2%)
Tachycardia
1 (1.2%)
0 (0%)
0 (0%)
Ventricular extrasystoles
0 (0%)
1 (1.2%)
2 (2.4%)
Ventricular hypertrophy
1 (1.2%)
0 (0%)
0 (0%)
Wolff-parkinson-white syndrome
0 (0%)
0 (0%)
1 (1.2%)
Gastrointestinal disorders
17 (19.8%)
21 (25.0%)
15 (17.9%)
Abdominal discomfort
0 (0%)
1 (1.2%)
0 (0%)
Abdominal pain
1 (1.2%)
1 (1.2%)
3 (3.6%)
Constipation
1 (1.2%)
0 (0%)
0 (0%)
Diarrhoea
9 (10.5%)
4 (4.8%)
5 (6.0%)
Dyspepsia
1 (1.2%)
1 (1.2%)
1 (1.2%)
Dysphagia
0 (0%)
0 (0%)
1 (1.2%)
Flatulence
1 (1.2%)
0 (0%)
0 (0%)
Gastrointestinal haemorrhage
0 (0%)
1 (1.2%)
0 (0%)
Gastrooesophageal reflux disease
1 (1.2%)
0 (0%)
0 (0%)
Glossitis
1 (1.2%)
0 (0%)
0 (0%)
Hiatus hernia
1 (1.2%)
0 (0%)
0 (0%)
Nausea
3 (3.5%)
6 (7.1%)
3 (3.6%)
Rectal haemorrhage
0 (0%)
0 (0%)
1 (1.2%)
Salivary hypersecretion
0 (0%)
4 (4.8%)
0 (0%)
Stomach discomfort
0 (0%)
1 (1.2%)
0 (0%)
Vomiting
3 (3.5%)
7 (8.3%)
3 (3.6%)
(1) N (%)
Understanding the labeling process
First labelizor() call: Replaces column headers using the variable labels stored in the CDISC dataset metadata. The c(LABEL = "", ...) syntax sets the LABEL column header to empty, then uses the actual variable labels from adae for any matching column names.
Second labelizor() call: Applies sentence case formatting to labels using stringr::str_to_sentence. This converts labels like “CARDIAC DISORDERS” to “Cardiac disorders”.
mk_par() for the first header cell: Creates a more informative header for the LABEL column by combining two pieces of information:
The SOC label (“System Organ Class”)
The PT label (“Dictionary-Derived Term”)
The \n\t creates a new line with a tab, visually organizing the two-level hierarchy even in the header.
3.3.6 Save in a Word document
We now have a publication-ready Adverse Event table that:
Presents three levels of aggregation in a single view
Uses visual hierarchy (indentation) to show relationships
Includes sample sizes for proper interpretation
Follows defined formatting standards
This table is ready to be included in a Word document.