import os
import sys
from pathlib import Path
import polars as pl
sys.path.insert(0, 'src')
from rtflite import LibreOfficeConverter
try:
converter = LibreOfficeConverter()
except Exception:
converter = None
print("WARNING: LibreOffice not found. PDF conversion will be skipped.")6 AE Listing
This guide demonstrates AE listing showing detailed individual adverse event records with key information like severity, relationship, and outcomes. For design philosophy and architecture details, see ae_summary.qmd.
6.1 Setup
from csrlite import load_plan, study_plan_to_ae_listing
from csrlite.ae.ae_listing import ae_listing_ard, ae_listing_rtf, ae_listing6.2 What’s Different?
AE Summary shows high-level aggregated counts (e.g., “12 (14.3%)” subjects with any AE). Uses a three-step pipeline with data pivoting.
AE Specific shows aggregated counts by individual event terms (e.g., “5 (6.0%)” subjects with “Diarrhea”). Uses a three-step pipeline with data pivoting.
AE Listing shows detailed individual event records with information like subject ID, study day, severity, relationship, action taken, and outcome. Uses a simpler two-step pipeline (no pivoting needed). Can access both population (demographics like SEX, RACE, AGE) and observation (event details) columns, with optional grouping by subject or treatment for section headers.
6.3 StudyPlan-Driven Workflow
study_plan = load_plan("studies/xyz123/yaml/plan_xyz123.yaml")
study_plan.get_plan_df().filter(pl.col("analysis") == "ae_listing")2026-02-03 15:26:20,068 - csrlite.common.plan - INFO - Successfully loaded dataset 'adsl' from 'studies/xyz123/yaml/../../../data/adsl.parquet'
2026-02-03 15:26:20,072 - csrlite.common.plan - INFO - Successfully loaded dataset 'adae' from 'studies/xyz123/yaml/../../../data/adae.parquet'
2026-02-03 15:26:20,073 - csrlite.common.plan - INFO - Successfully loaded dataset 'adie' from 'studies/xyz123/yaml/../../../data/adie.parquet'
2026-02-03 15:26:20,075 - csrlite.common.plan - INFO - Successfully loaded dataset 'adpd' from 'studies/xyz123/yaml/../../../data/adpd.parquet'
| analysis | population | observation | parameter | group |
|---|---|---|---|---|
| str | str | str | str | str |
| "ae_listing" | "apat" | "week12" | "ser" | "trt01a" |
| "ae_listing" | "apat" | "week24" | "ser" | "trt01a" |
| "ae_listing" | "apat" | "week12" | "ser" | "trt01a" |
| "ae_listing" | "apat" | "week24" | "ser" | "trt01a" |
output_files = study_plan_to_ae_listing(study_plan)studies/xyz123/rtf/ae_listing_apat_week12_ser.rtf
studies/xyz123/rtf/ae_listing_apat_week24_ser.rtf
studies/xyz123/rtf/ae_listing_apat_week12_ser.rtf
studies/xyz123/rtf/ae_listing_apat_week24_ser.rtf
6.3.1 week12_ser
6.3.2 week24_ser
6.4 Complete Pipeline
adsl = pl.read_parquet("data/adsl.parquet")
adae = pl.read_parquet("data/adae.parquet")
ae_listing(
population=adsl,
observation=adae,
population_filter="SAFFL = 'Y'",
observation_filter="TRTEMFL = 'Y'",
parameter_filter=None,
id=("USUBJID", "Subject ID"),
title=[
"Listing of Participants With Adverse Events",
"(Treatment-Emergent, Safety Population)"
],
footnote=["Adverse events are sorted by subject ID and study day."],
source=["Source: ADSL and ADAE datasets"],
output_file="studies/xyz123/rtf/ae_listing.rtf",
population_columns=[
("TRT01A", "Treatment")
],
observation_columns=[
("AEDECOD", "Adverse Event"),
("ASTDY", "Study Day"),
],
sort_columns=["TRT01A", "USUBJID", "ASTDY"],
group_by=["USUBJID"],
page_by=["TRT01A"],
)studies/xyz123/rtf/ae_listing.rtf
'studies/xyz123/rtf/ae_listing.rtf'
6.5 Step-by-Step Pipeline
6.5.1 Step 1: Generate ARD
Key Parameters: - population_columns: List of tuples (column_name, label) from population (ADSL) to include (e.g., [("SEX", "Sex"), ("RACE", "Race")]). - observation_columns: List of tuples (column_name, label) from observation (ADAE) to include (e.g., [("AEDECOD", "Adverse Event")]). - sort_columns: List of column names to sort by. If None (default), sorts by id column. - parameter_filter: Optional SQL WHERE clause for filtering AEs (e.g., “AESER = ‘Y’” for serious AEs) - page_by: Optional list of columns to create an __index__ column for pagination.
Example with default (ID only):
_ard_minimal = ae_listing_ard(
population=adsl,
observation=adae,
population_filter="SAFFL = 'Y'",
observation_filter="TRTEMFL = 'Y'",
parameter_filter=None,
id=("USUBJID", "Subject ID"),
)
_ard_minimal.head(5)| __index__ | USUBJID |
|---|---|
| str | str |
| "Subject ID = 01-701-1015" | "01-701-1015" |
| "Subject ID = 01-701-1015" | "01-701-1015" |
| "Subject ID = 01-701-1015" | "01-701-1015" |
| "Subject ID = 01-701-1023" | "01-701-1023" |
| "Subject ID = 01-701-1023" | "01-701-1023" |
Example with explicit columns and pagination:
population_columns = [
("SEX", "Sex"),
("RACE", "Race"),
("AGE", "Age"),
("TRT01A", "Treatment")
]
observation_columns = [
("AEDECOD", "Adverse Event"),
("ASTDY", "Study Day"),
]
_ard = ae_listing_ard(
population=adsl,
observation=adae,
population_filter="SAFFL = 'Y'",
observation_filter="TRTEMFL = 'Y'",
parameter_filter=None,
id=("USUBJID", "Subject ID"),
population_columns=population_columns,
observation_columns=observation_columns,
sort_columns=["TRT01A", "USUBJID", "ASTDY"],
page_by=["USUBJID", "AGE", "SEX", "RACE", "TRT01A"],
)
_ard.head(10)| __index__ | USUBJID | AEDECOD | ASTDY |
|---|---|---|---|
| str | str | str | f64 |
| "Subject ID = 01-701-1015, Age … | "01-701-1015" | "APPLICATION SITE ERYTHEMA" | 2.0 |
| "Subject ID = 01-701-1015, Age … | "01-701-1015" | "APPLICATION SITE PRURITUS" | 2.0 |
| "Subject ID = 01-701-1015, Age … | "01-701-1015" | "DIARRHOEA" | 8.0 |
| "Subject ID = 01-701-1023, Age … | "01-701-1023" | "ERYTHEMA" | 3.0 |
| "Subject ID = 01-701-1023, Age … | "01-701-1023" | "ERYTHEMA" | 3.0 |
| "Subject ID = 01-701-1023, Age … | "01-701-1023" | "ERYTHEMA" | 3.0 |
| "Subject ID = 01-701-1023, Age … | "01-701-1023" | "ATRIOVENTRICULAR BLOCK SECOND … | 22.0 |
| "Subject ID = 01-701-1028, Age … | "01-701-1028" | "APPLICATION SITE ERYTHEMA" | 3.0 |
| "Subject ID = 01-701-1028, Age … | "01-701-1028" | "APPLICATION SITE PRURITUS" | 21.0 |
| "Subject ID = 01-701-1034, Age … | "01-701-1034" | "APPLICATION SITE PRURITUS" | 58.0 |
Output Structure: Filtered observation records joined with population data, containing selected columns from both datasets, sorted as specified.
6.5.2 Step 2: Generate RTF Output
The ARD from step 1 is already display-ready (filtered and sorted), so we can generate RTF directly.
Key Parameters: - column_labels: A dictionary mapping column names to their display labels. - group_by: Optional list of column names to group by for section headers.
First, let’s create the column_labels dictionary:
id_col = ("USUBJID", "Subject ID")
column_labels = dict([id_col] + population_columns + observation_columns)
ae_listing_rtf(
_ard.head(10),
column_labels=column_labels,
title=[
"Adverse Event Listing",
"(Treatment-Emergent, Safety Population)"
],
footnote=["Adverse events are sorted by treatment, subject ID, and study day."],
source=["Source: ADSL and ADAE datasets"],
group_by=["USUBJID"],
page_by=["__index__"],
).write_rtf("studies/xyz123/rtf/ae_listing_step.rtf")studies/xyz123/rtf/ae_listing_step.rtf
6.6 Customizing Columns, Grouping, and Pagination
You can customize which columns to display, how to group sections, and how to paginate the output.
ae_listing(
population=adsl,
observation=adae,
population_filter="SAFFL = 'Y'",
observation_filter="TRTEMFL = 'Y'",
parameter_filter="AESER = 'Y'", # Only serious events
id=("USUBJID", "Subject ID"),
title=[
"Serious Adverse Event Listing",
"(Treatment-Emergent, Safety Population)"
],
footnote=["Listing includes only serious adverse events."],
source=["Source: ADSL and ADAE datasets"],
output_file="studies/xyz123/rtf/ae_listing_custom.rtf",
population_columns=[("SEX", "Sex"), ("AGE", "Age"), ("TRT01A", "Treatment")], # Minimal demographics
observation_columns=[
("AEDECOD", "Adverse Event"),
("AESEV", "Severity"),
("AEREL", "Related"),
("AEOUT", "Outcome")
], # Key event details
sort_columns=["TRT01A", "USUBJID"], # Sort by treatment and subject
group_by=["USUBJID"], # Group by subject for section headers
page_by=["TRT01A", "USUBJID", "AGE", "SEX"], # Create a new page for each treatment group
)studies/xyz123/rtf/ae_listing_custom.rtf
'studies/xyz123/rtf/ae_listing_custom.rtf'