csrlite 0.3.0__py3-none-any.whl → 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,68 +1,68 @@
1
- Metadata-Version: 2.4
2
- Name: csrlite
3
- Version: 0.3.0
4
- Summary: A hierarchical YAML-based framework for generating Tables, Listings, and Figures in clinical trials
5
- Author-email: Yilong Zhang <elong0527@gmail.com>, Ming-Chun Chen <hellomingchun@gmail.com>
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/elong0527/csrlite
8
- Project-URL: Documentation, https://elong0527.github.io/csrlite
9
- Project-URL: Repository, https://github.com/elong0527/csrlite.git
10
- Project-URL: Bug Tracker, https://github.com/elong0527/csrlite/issues
11
- Keywords: clinical-trials,biostatistics,yaml,tlf,tables,listings,figures
12
- Classifier: Development Status :: 3 - Alpha
13
- Classifier: Intended Audience :: Science/Research
14
- Classifier: License :: OSI Approved :: MIT License
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
19
- Requires-Python: >=3.10
20
- Description-Content-Type: text/markdown
21
- Requires-Dist: pydantic>=2.0.0
22
- Requires-Dist: pyyaml>=6.0
23
- Requires-Dist: polars>=0.20.0
24
- Requires-Dist: rtflite>=2.1.1
25
- Provides-Extra: rtf
26
- Requires-Dist: rtflite; extra == "rtf"
27
- Provides-Extra: plotting
28
- Requires-Dist: matplotlib>=3.5.0; extra == "plotting"
29
- Requires-Dist: plotly>=5.0.0; extra == "plotting"
30
- Provides-Extra: dev
31
- Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
32
- Requires-Dist: pytest>=9.0.1; extra == "dev"
33
- Requires-Dist: black>=22.0.0; extra == "dev"
34
- Requires-Dist: isort>=7.0.0; extra == "dev"
35
- Requires-Dist: ruff>=0.14.8; extra == "dev"
36
- Requires-Dist: mypy>=1.19.0; extra == "dev"
37
- Requires-Dist: quarto>=0.1.0; extra == "dev"
38
- Requires-Dist: pyre-check>=0.9.18; extra == "dev"
39
- Requires-Dist: jupyter>=1.1.1; extra == "dev"
40
- Requires-Dist: jupyter-cache>=1.0.1; extra == "dev"
41
- Requires-Dist: nbformat>=5.10.4; extra == "dev"
42
- Provides-Extra: all
43
- Requires-Dist: rtflite; extra == "all"
44
- Requires-Dist: matplotlib>=3.5.0; extra == "all"
45
- Requires-Dist: plotly>=5.0.0; extra == "all"
46
-
47
- # csrlite
48
-
49
- [![CI](https://github.com/elong0527/csrlite/actions/workflows/ci.yml/badge.svg)](https://github.com/elong0527/csrlite/actions/workflows/ci.yml)
50
- [![codecov](https://codecov.io/gh/elong0527/csrlite/branch/main/graph/badge.svg)](https://codecov.io/gh/elong0527/csrlite)
51
- [![PyPI version](https://badge.fury.io/py/csrlite.svg)](https://badge.fury.io/py/csrlite)
52
- [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
53
-
54
- A hierarchical YAML-based framework for generating Tables, Listings, and Figures in clinical trials.
55
-
56
- ## Installation
57
-
58
- ```bash
59
- pip install csrlite
60
- ```
61
-
62
- ## Documentation
63
-
64
- Visit [https://elong0527.github.io/csrlite](https://elong0527.github.io/csrlite) for full documentation.
65
-
66
- ## License
67
-
68
- MIT License - see [LICENSE](LICENSE) file for details.
1
+ Metadata-Version: 2.4
2
+ Name: csrlite
3
+ Version: 0.3.2
4
+ Summary: A hierarchical YAML-based framework for generating Tables, Listings, and Figures in clinical trials
5
+ Author-email: Yilong Zhang <elong0527@gmail.com>, Ming-Chun Chen <hellomingchun@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/elong0527/csrlite
8
+ Project-URL: Documentation, https://elong0527.github.io/csrlite
9
+ Project-URL: Repository, https://github.com/elong0527/csrlite.git
10
+ Project-URL: Bug Tracker, https://github.com/elong0527/csrlite/issues
11
+ Keywords: clinical-trials,biostatistics,yaml,tlf,tables,listings,figures
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: pydantic>=2.0.0
22
+ Requires-Dist: pyyaml>=6.0
23
+ Requires-Dist: polars>=0.20.0
24
+ Requires-Dist: rtflite>=2.1.1
25
+ Provides-Extra: rtf
26
+ Requires-Dist: rtflite; extra == "rtf"
27
+ Provides-Extra: plotting
28
+ Requires-Dist: matplotlib>=3.5.0; extra == "plotting"
29
+ Requires-Dist: plotly>=5.0.0; extra == "plotting"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
32
+ Requires-Dist: pytest>=9.0.1; extra == "dev"
33
+ Requires-Dist: black>=22.0.0; extra == "dev"
34
+ Requires-Dist: isort>=7.0.0; extra == "dev"
35
+ Requires-Dist: ruff>=0.14.8; extra == "dev"
36
+ Requires-Dist: mypy>=1.19.0; extra == "dev"
37
+ Requires-Dist: quarto>=0.1.0; extra == "dev"
38
+ Requires-Dist: pyre-check>=0.9.18; extra == "dev"
39
+ Requires-Dist: jupyter>=1.1.1; extra == "dev"
40
+ Requires-Dist: jupyter-cache>=1.0.1; extra == "dev"
41
+ Requires-Dist: nbformat>=5.10.4; extra == "dev"
42
+ Provides-Extra: all
43
+ Requires-Dist: rtflite; extra == "all"
44
+ Requires-Dist: matplotlib>=3.5.0; extra == "all"
45
+ Requires-Dist: plotly>=5.0.0; extra == "all"
46
+
47
+ # csrlite
48
+
49
+ [![CI](https://github.com/elong0527/csrlite/actions/workflows/ci.yml/badge.svg)](https://github.com/elong0527/csrlite/actions/workflows/ci.yml)
50
+ [![codecov](https://codecov.io/gh/elong0527/csrlite/branch/main/graph/badge.svg)](https://codecov.io/gh/elong0527/csrlite)
51
+ [![PyPI version](https://badge.fury.io/py/csrlite.svg)](https://badge.fury.io/py/csrlite)
52
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
53
+
54
+ A hierarchical YAML-based framework for generating Tables, Listings, and Figures in clinical trials.
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ pip install csrlite
60
+ ```
61
+
62
+ ## Documentation
63
+
64
+ Visit [https://elong0527.github.io/csrlite](https://elong0527.github.io/csrlite) for full documentation.
65
+
66
+ ## License
67
+
68
+ MIT License - see [LICENSE](LICENSE) file for details.
@@ -0,0 +1,23 @@
1
+ csrlite/__init__.py,sha256=TotlI9TWnCkOxP8OOG5AfTMKjWLhM2-AJOEA6YF5ZLQ,2190
2
+ csrlite/ae/__init__.py,sha256=Pp661SGbsoYoXLmd24dUQB_lx1jYKWEsCEPjgVg5UUU,15
3
+ csrlite/ae/ae_listing.py,sha256=fZWhYmi_Vzp974z-3o7EQ9joizEjF0mio2eB5_I8ghU,18883
4
+ csrlite/ae/ae_specific.py,sha256=P1glCd5K_ur_KexxbVaC79BmowgQSMI9K8xdzL6OmIM,17505
5
+ csrlite/ae/ae_summary.py,sha256=Vk4bUonnjAji2zeJkiPHW7nwgpwUG1UfbuzH4M0w1MM,14093
6
+ csrlite/ae/ae_utils.py,sha256=tOVOdsj-TRTw1w5T4JqFTYUwTfOppZWGlRHi6nkNWRA,2073
7
+ csrlite/cm/cm_listing.py,sha256=VRgEdwWNlgjqU5W_-ObAxUwqapDf2G44xffwDiybIa4,18904
8
+ csrlite/cm/cm_summary.py,sha256=DDWvubtENtf_Hr537xyGjYP2MOxS-meLs-yInFRynXM,10479
9
+ csrlite/common/config.py,sha256=qWFTNSaeWnqAagXM6FwACdQafXLG_nFWxFHEk5dAo-I,912
10
+ csrlite/common/count.py,sha256=T9nB9hYdc2jIIuV9HISK2ZS0wXNV_FUBDCKW_VVM1fQ,9078
11
+ csrlite/common/parse.py,sha256=J4UkmNlG10mrJ-O68-po1LlfLchK_kSvfdU1GOaAYgQ,9627
12
+ csrlite/common/plan.py,sha256=tYnK-5TVCoGWobxkqmasYYYkc9DJlHmOiTIcrP3MFRo,13293
13
+ csrlite/common/rtf.py,sha256=fbube-5QoWYE8u6GCjE5qCMS6BzmXqJhoZI8GpOYXJM,5610
14
+ csrlite/common/utils.py,sha256=XLUQqYtEm4SLNImvNDxAuj9OOu6Eogm3_uIp0vyfpfQ,1143
15
+ csrlite/common/yaml_loader.py,sha256=ZPgXw5X9eitMHCPEJBHzeYtcjesEQ8sP0hOFSYnzrgk,3142
16
+ csrlite/disposition/__init__.py,sha256=AiLW7Os7SBFrvvkaUMrxYEntK15_XXT0HYpZQxBx7ro,87
17
+ csrlite/disposition/disposition.py,sha256=W1Oik5hqDfalQ69NXullH7XYWP3oRw2x2nFuFWlE3Qo,10679
18
+ csrlite/ie/ie.py,sha256=9kEAe4aY3xfHe7O-o2ZD3LNfL5eTha2ZPkAflopjPYU,13544
19
+ csrlite/pd/pd_listing.py,sha256=xPqpJFuxx_7vzjADGDSBo7RK2GpGWpR6SIMnCjKNirI,17632
20
+ csrlite-0.3.2.dist-info/METADATA,sha256=RDlBzyKYUN2AGe_k-ZOOB0USa6g-qdl4OZPa-FwJFGk,2896
21
+ csrlite-0.3.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
22
+ csrlite-0.3.2.dist-info/top_level.txt,sha256=59zJTvGH5zx2FY4vCl4kgnH8awT0cZrg21Mace7IFlU,8
23
+ csrlite-0.3.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
csrlite/ie/ie_listing.py DELETED
@@ -1,109 +0,0 @@
1
- # pyre-strict
2
- """
3
- Inclusion/Exclusion (IE) Listing Analysis Functions
4
- """
5
-
6
- from pathlib import Path
7
-
8
- import polars as pl
9
-
10
- from ..common.parse import StudyPlanParser
11
- from ..common.plan import StudyPlan
12
- from ..common.rtf import create_rtf_listing
13
- from ..common.utils import apply_common_filters
14
-
15
-
16
- def study_plan_to_ie_listing(
17
- study_plan: StudyPlan,
18
- ) -> list[str]:
19
- """
20
- Generate IE Listing outputs.
21
- """
22
- # Meta data
23
- analysis_type = "ie_listing"
24
- output_dir = study_plan.output_dir
25
- title = "Listing of Protocol Deviations"
26
-
27
- # Ensure output directory exists
28
- Path(output_dir).mkdir(parents=True, exist_ok=True)
29
-
30
- # Initialize parser
31
- parser = StudyPlanParser(study_plan)
32
-
33
- # Get expanded plan (Manually expansion to avoid AttributeError)
34
- plans = study_plan.study_data.get("plans", [])
35
- all_specs = []
36
- for plan_data in plans:
37
- expanded = study_plan.expander.expand_plan(plan_data)
38
- for p in expanded:
39
- all_specs.append(study_plan.expander.create_analysis_spec(p))
40
-
41
- plan_df = pl.DataFrame(all_specs)
42
-
43
- if "analysis" in plan_df.columns:
44
- listing_plans = plan_df.filter(pl.col("analysis") == analysis_type)
45
- else:
46
- listing_plans = pl.DataFrame()
47
-
48
- generated_files = []
49
-
50
- # If listing_plans is empty, create a dummy row to force generation
51
- if listing_plans.height == 0:
52
- listing_plans = pl.DataFrame([{"population": "enrolled", "analysis": analysis_type}])
53
-
54
- for analysis in listing_plans.iter_rows(named=True):
55
- # Load ADSL
56
- pop_name = analysis.get("population", "enrolled")
57
-
58
- try:
59
- (adsl_raw,) = parser.get_datasets("adsl")
60
- pop_filter = parser.get_population_filter(pop_name)
61
-
62
- adsl, _ = apply_common_filters(
63
- population=adsl_raw,
64
- observation=None,
65
- population_filter=pop_filter,
66
- observation_filter=None,
67
- )
68
-
69
- except ValueError as e:
70
- print(f"Error loading population: {e}")
71
- continue
72
-
73
- # Output filename
74
- filename = f"{analysis_type}_{pop_name}.rtf".lower()
75
- output_path = f"{output_dir}/{filename}"
76
-
77
- # Generate DF
78
- df = ie_listing_df(adsl)
79
-
80
- # Generate RTF
81
- ie_listing_rtf(df, output_path, title=title)
82
-
83
- generated_files.append(output_path)
84
-
85
- return generated_files
86
-
87
-
88
- def ie_listing_df(adsl: pl.DataFrame) -> pl.DataFrame:
89
- """Select columns for Listing."""
90
- # Check if DCSREAS exists
91
- cols = ["USUBJID", "DCSREAS"]
92
- available = [c for c in cols if c in adsl.columns]
93
- return adsl.select(available)
94
-
95
-
96
- def ie_listing_rtf(df: pl.DataFrame, output_path: str, title: str | list[str] = "") -> None:
97
- """Generate RTF Listing."""
98
- col_widths = [1.5, 3.5] # Approximate ratio
99
-
100
- rtf_doc = create_rtf_listing(
101
- df=df,
102
- col_header=list(df.columns),
103
- col_widths=col_widths,
104
- title=title,
105
- footnote=[],
106
- source=[],
107
- )
108
-
109
- rtf_doc.write_rtf(output_path)
csrlite/mh/mh_listing.py DELETED
@@ -1,209 +0,0 @@
1
- # pyre-strict
2
- """
3
- Medical History (MH) Listing Analysis Functions
4
- """
5
-
6
- from pathlib import Path
7
-
8
- import polars as pl
9
-
10
- from ..common.parse import StudyPlanParser
11
- from ..common.plan import StudyPlan
12
- from ..common.rtf import create_rtf_listing
13
- from ..common.utils import apply_common_filters
14
-
15
-
16
- def mh_listing(
17
- population: pl.DataFrame,
18
- observation: pl.DataFrame,
19
- population_filter: str | None = "SAFFL = 'Y'",
20
- observation_filter: str | None = "MHOCCUR = 'Y'",
21
- id: tuple[str, str] = ("USUBJID", "Subject ID"),
22
- title: list[str] | None = None,
23
- footnote: list[str] | None = None,
24
- source: list[str] | None = None,
25
- output_file: str = "mh_listing.rtf",
26
- population_columns: list[tuple[str, str]] | None = None,
27
- observation_columns: list[tuple[str, str]] | None = None,
28
- sort_columns: list[str] | None = None,
29
- ) -> str:
30
- """
31
- Generate Medical History Listing.
32
- """
33
- if title is None:
34
- title = ["Listing of Medical History"]
35
-
36
- # Generate DF
37
- df = mh_listing_df(
38
- population=population,
39
- observation=observation,
40
- population_filter=population_filter,
41
- observation_filter=observation_filter,
42
- id_col=id[0],
43
- pop_cols=population_columns,
44
- obs_cols=observation_columns,
45
- sort_cols=sort_columns,
46
- )
47
-
48
- # Generate RTF
49
- mh_listing_rtf(df=df, output_path=output_file, title=title, footnote=footnote, source=source)
50
-
51
- return output_file
52
-
53
-
54
- def mh_listing_df(
55
- population: pl.DataFrame,
56
- observation: pl.DataFrame,
57
- population_filter: str | None,
58
- observation_filter: str | None,
59
- id_col: str,
60
- pop_cols: list[tuple[str, str]] | None,
61
- obs_cols: list[tuple[str, str]] | None,
62
- sort_cols: list[str] | None,
63
- ) -> pl.DataFrame:
64
- # Defaults
65
- if pop_cols is None:
66
- # Default interesting cols from ADSL
67
- pop_cols = [("TRT01A", "Treatment"), ("AGE", "Age"), ("SEX", "Sex")]
68
-
69
- if obs_cols is None:
70
- # Default from ADMH
71
- obs_cols = [
72
- ("MHSEQ", "Seq"),
73
- ("MHBODSYS", "System Organ Class"),
74
- ("MHDECOD", "Preferred Term"),
75
- ("MHSTDTC", "Start Date"),
76
- ("MHENRTPT", "Status"),
77
- ]
78
-
79
- # Apply filters
80
- adsl, adq = apply_common_filters(
81
- population=population,
82
- observation=observation,
83
- population_filter=population_filter,
84
- observation_filter=observation_filter,
85
- )
86
-
87
- if adq is None:
88
- raise ValueError("Observation data is missing")
89
-
90
- # Join
91
- # Select specific columns from ADSL
92
- pop_col_names = [c[0] for c in pop_cols]
93
- # Ensure ID is there
94
- if id_col not in pop_col_names:
95
- pop_col_names = [id_col] + pop_col_names
96
-
97
- adsl_sub = adsl.select(pop_col_names)
98
-
99
- joined = adq.join(adsl_sub, on=id_col, how="inner")
100
-
101
- # Sort
102
- if sort_cols:
103
- # Check if cols exist
104
- valid_sorts = [c for c in sort_cols if c in joined.columns]
105
- if valid_sorts:
106
- joined = joined.sort(valid_sorts)
107
-
108
- # Select display columns (id + pop + obs)
109
- display_cols = [id_col] + [c[0] for c in pop_cols if c[0] != id_col] + [c[0] for c in obs_cols]
110
- final_df = joined.select([c for c in display_cols if c in joined.columns])
111
-
112
- # Rename for display?
113
- # Usually listing keeps raw names or we Map them.
114
- # The create_rtf_listing function takes col_header list.
115
-
116
- return final_df
117
-
118
-
119
- def mh_listing_rtf(
120
- df: pl.DataFrame,
121
- output_path: str,
122
- title: list[str] | str,
123
- footnote: list[str] | None,
124
- source: list[str] | None,
125
- ) -> None:
126
- if df.is_empty():
127
- return
128
-
129
- # Generate headers from predefined mapping or current logic?
130
- # Here we just use column names for simplicity or we could pass headers.
131
- # We didn't output headers from mh_listing_df.
132
- # Let's assume the order is maintained.
133
-
134
- headers = list(df.columns)
135
-
136
- # Approximate widths
137
- # ID: 1, TRT: 1.5, AGE: 0.5, SEX: 0.5, SEQ: 0.5, SOC: 2, PT: 2, DATE: 1, STATUS: 1
138
- # Total ~ 10 units?
139
- # Simple uniform distribution or weighted?
140
- n_cols = len(headers)
141
- col_widths = [1.0] * n_cols
142
-
143
- rtf_doc = create_rtf_listing(
144
- df=df,
145
- col_header=headers,
146
- col_widths=col_widths,
147
- title=title,
148
- footnote=footnote,
149
- source=source,
150
- )
151
-
152
- rtf_doc.write_rtf(output_path)
153
-
154
-
155
- def study_plan_to_mh_listing(study_plan: StudyPlan) -> list[str]:
156
- """
157
- Batch generate MH listings.
158
- """
159
- analysis_type = "mh_listing"
160
- output_dir = study_plan.output_dir
161
-
162
- parser = StudyPlanParser(study_plan)
163
-
164
- plans = study_plan.study_data.get("plans", [])
165
- all_specs = []
166
- for plan_data in plans:
167
- expanded = study_plan.expander.expand_plan(plan_data)
168
- for p in expanded:
169
- all_specs.append(study_plan.expander.create_analysis_spec(p))
170
-
171
- plan_df = pl.DataFrame(all_specs)
172
-
173
- if "analysis" in plan_df.columns:
174
- mh_plans = plan_df.filter(pl.col("analysis") == analysis_type)
175
- else:
176
- mh_plans = pl.DataFrame()
177
-
178
- generated_files = []
179
-
180
- for analysis in mh_plans.iter_rows(named=True):
181
- pop_name = analysis.get("population", "enrolled")
182
-
183
- try:
184
- # Load Population
185
- adsl, _ = parser.get_population_data(pop_name, "trt01a") # dummy group
186
-
187
- (admh,) = parser.get_datasets("admh")
188
-
189
- filename = f"{analysis_type}_{pop_name}.rtf".lower()
190
- output_path = f"{output_dir}/{filename}"
191
- Path(output_path).parent.mkdir(parents=True, exist_ok=True)
192
-
193
- mh_listing(
194
- population=adsl,
195
- observation=admh,
196
- population_filter=None,
197
- observation_filter=None, # Show all?
198
- output_file=output_path,
199
- title=["Listing of Medical History", f"({pop_name} Population)"],
200
- source=["Source: ADSL, ADMH"],
201
- )
202
-
203
- generated_files.append(output_path)
204
-
205
- except Exception as e:
206
- print(f"Error generating MH listing: {e}")
207
- continue
208
-
209
- return generated_files