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.")13 Disposition Analysis
Objectives
This guide demonstrates disposition analysis showing subject enrollment and study completion status. For design philosophy and architecture details, see ae_summary.qmd.
13.1 Setup
from csrlite import load_plan, study_plan_to_disposition_summary
from csrlite.disposition.disposition import disposition_ard, disposition_df, disposition_rtf, disposition13.2 What’s Different?
Disposition Analysis shows subject enrollment status, study completion, and discontinuation reasons in a hierarchical table format with indented subcategories.
13.3 StudyPlan-Driven Workflow
study_plan = load_plan("studies/xyz123/yaml/plan_xyz123.yaml")
study_plan.get_plan_df().filter(pl.col("analysis") == "disposition_summary")2026-02-03 15:27:39,408 - csrlite.common.plan - INFO - Successfully loaded dataset 'adsl' from 'studies/xyz123/yaml/../../../data/adsl.parquet'
2026-02-03 15:27:39,411 - csrlite.common.plan - INFO - Successfully loaded dataset 'adae' from 'studies/xyz123/yaml/../../../data/adae.parquet'
2026-02-03 15:27:39,412 - csrlite.common.plan - INFO - Successfully loaded dataset 'adie' from 'studies/xyz123/yaml/../../../data/adie.parquet'
2026-02-03 15:27:39,413 - csrlite.common.plan - INFO - Successfully loaded dataset 'adpd' from 'studies/xyz123/yaml/../../../data/adpd.parquet'
shape: (1, 5)
| analysis | population | observation | parameter | group |
|---|---|---|---|---|
| str | str | str | str | str |
| "disposition_summary" | "enrolled" | null | null | "trt01a" |
output_files = study_plan_to_disposition_summary(study_plan)studies/xyz123/rtf/disposition_summary_enrolled_trt01a.rtf
13.3.1 Disposition Summary
13.4 Complete Pipeline
adsl = pl.read_parquet("data/adsl.parquet")
disposition(
population=adsl,
population_filter=None,
id=("USUBJID", "Subject ID"),
group=("TRT01A", "Treatment Group"),
ds_term=("EOSSTT", "Disposition Status"),
dist_reason_term=("DCSREAS", "Discontinued Reason"),
title=[
"Disposition Summary Table",
"(Intent-to-Treat Population)"
],
footnote=["Percentages are based on the number of enrolled participants."],
source=None,
output_file="studies/xyz123/rtf/disposition.rtf",
total=True,
missing_group="error"
)studies/xyz123/rtf/disposition.rtf
'studies/xyz123/rtf/disposition.rtf'
13.5 Step-by-Step Pipeline
13.5.1 Step 1: Generate ARD
Key Parameters: - ds_term: Tuple (column, label) for status (Completed, Ongoing, Discontinued) - dist_reason_term: Tuple (column, label) for discontinuation reason
_ard = disposition_ard(
population=adsl,
population_filter=None,
id=("USUBJID", "Subject ID"),
group=("TRT01A", "Treatment Group"),
ds_term=("EOSSTT", "Disposition Status"),
dist_reason_term=("DCSREAS", "Discontinued Reason"),
total=True,
missing_group="error"
)
_ard
shape: (48, 3)
| __index__ | __group__ | __value__ |
|---|---|---|
| str | str | str |
| "Enrolled" | "Placebo" | "86" |
| "Enrolled" | "Xanomeline High Dose" | "84" |
| "Enrolled" | "Xanomeline Low Dose" | "84" |
| "Enrolled" | "Total" | "254" |
| "Completed" | "Placebo" | " 58 ( 67.4)" |
| … | … | … |
| " Lost to Follow-up" | "Total" | " 2 ( 0.8)" |
| " Physician Decision" | "Total" | " 3 ( 1.2)" |
| " Protocol Violation" | "Total" | " 3 ( 1.2)" |
| " Sponsor Decision" | "Total" | " 7 ( 2.8)" |
| " Withdrew Consent" | "Total" | " 27 ( 10.6)" |
Output Structure: Long format with __index__, __group__, __value__ columns.
13.5.2 Step 2: Transform to Display Format
_df = disposition_df(_ard)
_df
shape: (12, 5)
| Term | Placebo | Xanomeline High Dose | Xanomeline Low Dose | Total |
|---|---|---|---|---|
| str | str | str | str | str |
| "Enrolled" | "86" | "84" | "84" | "254" |
| "Completed" | " 58 ( 67.4)" | " 27 ( 32.1)" | " 25 ( 29.8)" | "110 ( 43.3)" |
| "Discontinued" | " 28 ( 32.6)" | " 57 ( 67.9)" | " 59 ( 70.2)" | "144 ( 56.7)" |
| " Adverse Event" | " 8 ( 9.3)" | " 40 ( 47.6)" | " 44 ( 52.4)" | " 92 ( 36.2)" |
| " Death" | " 2 ( 2.3)" | " 0 ( 0.0)" | " 1 ( 1.2)" | " 3 ( 1.2)" |
| … | … | … | … | … |
| " Lost to Follow-up" | " 1 ( 1.2)" | " 0 ( 0.0)" | " 1 ( 1.2)" | " 2 ( 0.8)" |
| " Physician Decision" | " 1 ( 1.2)" | " 2 ( 2.4)" | " 0 ( 0.0)" | " 3 ( 1.2)" |
| " Protocol Violation" | " 1 ( 1.2)" | " 1 ( 1.2)" | " 1 ( 1.2)" | " 3 ( 1.2)" |
| " Sponsor Decision" | " 2 ( 2.3)" | " 3 ( 3.6)" | " 2 ( 2.4)" | " 7 ( 2.8)" |
| " Withdrew Consent" | " 9 ( 10.5)" | " 8 ( 9.5)" | " 10 ( 11.9)" | " 27 ( 10.6)" |
Output Structure: Wide format with “Disposition Status” column showing statuses and treatment groups as columns.
13.5.3 Step 3: Generate RTF Output
disposition_rtf(
_df,
title=[
"Disposition Summary Table",
"(Intent-to-Treat Population)"
],
footnote=["Percentages are based on the number of enrolled participants."],
source=None,
col_rel_width=[2.5, 1, 1, 1, 1]
).write_rtf("studies/xyz123/rtf/disposition_step.rtf")studies/xyz123/rtf/disposition_step.rtf