csrlite 0.1.0__py3-none-any.whl → 0.2.1__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.
- csrlite/__init__.py +71 -50
- csrlite/ae/__init__.py +1 -1
- csrlite/ae/ae_listing.py +494 -492
- csrlite/ae/ae_specific.py +483 -478
- csrlite/ae/ae_summary.py +401 -399
- csrlite/ae/ae_utils.py +62 -132
- csrlite/common/config.py +34 -0
- csrlite/common/count.py +293 -199
- csrlite/common/parse.py +308 -308
- csrlite/common/plan.py +365 -353
- csrlite/common/rtf.py +137 -0
- csrlite/common/utils.py +33 -33
- csrlite/common/yaml_loader.py +71 -71
- csrlite/disposition/__init__.py +2 -2
- csrlite/disposition/disposition.py +332 -301
- csrlite/ie/ie.py +405 -0
- {csrlite-0.1.0.dist-info → csrlite-0.2.1.dist-info}/METADATA +68 -68
- csrlite-0.2.1.dist-info/RECORD +20 -0
- csrlite-0.1.0.dist-info/RECORD +0 -17
- {csrlite-0.1.0.dist-info → csrlite-0.2.1.dist-info}/WHEEL +0 -0
- {csrlite-0.1.0.dist-info → csrlite-0.2.1.dist-info}/top_level.txt +0 -0
csrlite/ie/ie.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# pyre-strict
|
|
2
|
+
"""
|
|
3
|
+
Inclusion/Exclusion (IE) Table Analysis Functions
|
|
4
|
+
|
|
5
|
+
This module provides a pipeline for IE summary analysis:
|
|
6
|
+
- ie_ard: Generate Analysis Results Data (ARD)
|
|
7
|
+
- ie_df: Transform ARD to display format
|
|
8
|
+
- ie_rtf: Generate formatted RTF output
|
|
9
|
+
- study_plan_to_ie_summary: Batch generation from StudyPlan
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import polars as pl
|
|
16
|
+
|
|
17
|
+
from ..common.parse import StudyPlanParser
|
|
18
|
+
from ..common.plan import StudyPlan
|
|
19
|
+
from ..common.rtf import create_rtf_listing, create_rtf_table_n_pct
|
|
20
|
+
from ..common.utils import apply_common_filters
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def study_plan_to_ie_summary(
|
|
24
|
+
study_plan: StudyPlan,
|
|
25
|
+
) -> list[str]:
|
|
26
|
+
"""
|
|
27
|
+
Generate IE Summary Table outputs for all analyses defined in StudyPlan.
|
|
28
|
+
"""
|
|
29
|
+
# Meta data
|
|
30
|
+
analysis_type = "ie_summary"
|
|
31
|
+
output_dir = study_plan.output_dir
|
|
32
|
+
title = "Summary of Protocol Deviations (Inclusion/Exclusion)"
|
|
33
|
+
# footnote = ["Percentages are based on the number of enrolled participants."]
|
|
34
|
+
|
|
35
|
+
# Defaults
|
|
36
|
+
criteria_df_name = "adie"
|
|
37
|
+
|
|
38
|
+
# Ensure output directory exists
|
|
39
|
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
40
|
+
|
|
41
|
+
# Initialize parser
|
|
42
|
+
parser = StudyPlanParser(study_plan)
|
|
43
|
+
|
|
44
|
+
# Get expanded plan (Manually expansion to avoid AttributeError)
|
|
45
|
+
plans = study_plan.study_data.get("plans", [])
|
|
46
|
+
all_specs = []
|
|
47
|
+
for plan_data in plans:
|
|
48
|
+
expanded = study_plan.expander.expand_plan(plan_data)
|
|
49
|
+
for p in expanded:
|
|
50
|
+
all_specs.append(study_plan.expander.create_analysis_spec(p))
|
|
51
|
+
|
|
52
|
+
plan_df = pl.DataFrame(all_specs)
|
|
53
|
+
|
|
54
|
+
if "analysis" in plan_df.columns:
|
|
55
|
+
ie_plans = plan_df.filter(pl.col("analysis") == analysis_type)
|
|
56
|
+
else:
|
|
57
|
+
ie_plans = pl.DataFrame()
|
|
58
|
+
|
|
59
|
+
generated_files = []
|
|
60
|
+
|
|
61
|
+
# Iterate over analyses
|
|
62
|
+
for analysis in ie_plans.iter_rows(named=True):
|
|
63
|
+
# Load data
|
|
64
|
+
# Note: IE analysis needs both ADSL (for population/group) and ADIE (for criteria)
|
|
65
|
+
pop_name = analysis.get("population", "enrolled")
|
|
66
|
+
group_kw = analysis.get("group") # Can be None
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
if group_kw:
|
|
70
|
+
# Load Filtered Population (ADSL) with Group
|
|
71
|
+
adsl, group_col = parser.get_population_data(pop_name, group_kw)
|
|
72
|
+
group_col = group_col.upper()
|
|
73
|
+
grp_suffix = group_col
|
|
74
|
+
else:
|
|
75
|
+
# Load Filtered Population (ADSL) without Group
|
|
76
|
+
# Manual load + filter since get_population_data requires group
|
|
77
|
+
(adsl_raw,) = parser.get_datasets("adsl")
|
|
78
|
+
pop_filter = parser.get_population_filter(pop_name)
|
|
79
|
+
|
|
80
|
+
adsl, _ = apply_common_filters(
|
|
81
|
+
population=adsl_raw,
|
|
82
|
+
observation=None,
|
|
83
|
+
population_filter=pop_filter,
|
|
84
|
+
observation_filter=None,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
group_col = None
|
|
88
|
+
grp_suffix = "total"
|
|
89
|
+
|
|
90
|
+
except ValueError as e:
|
|
91
|
+
print(f"Error loading population: {e}")
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
# Load ADIE
|
|
95
|
+
try:
|
|
96
|
+
(adie,) = parser.get_datasets(criteria_df_name)
|
|
97
|
+
except ValueError as e:
|
|
98
|
+
print(f"Error loading datasets: {e}")
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
# Output filename
|
|
102
|
+
filename = f"{analysis_type}_{pop_name}_{grp_suffix}.rtf".lower()
|
|
103
|
+
output_path = f"{output_dir}/{filename}"
|
|
104
|
+
|
|
105
|
+
# Generate ARD
|
|
106
|
+
ard = ie_ard(adsl=adsl, adie=adie, group_col=group_col)
|
|
107
|
+
|
|
108
|
+
# Generate DF
|
|
109
|
+
df = ie_df(ard)
|
|
110
|
+
|
|
111
|
+
# Generate RTF
|
|
112
|
+
ie_rtf(df, output_path, title=title)
|
|
113
|
+
|
|
114
|
+
generated_files.append(output_path)
|
|
115
|
+
|
|
116
|
+
return generated_files
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def ie_ard(adsl: pl.DataFrame, adie: pl.DataFrame, group_col: str | None = None) -> pl.DataFrame:
|
|
120
|
+
"""
|
|
121
|
+
Generate Analysis Results Data (ARD) for IE Table.
|
|
122
|
+
|
|
123
|
+
Structure:
|
|
124
|
+
- Total Screening Failures
|
|
125
|
+
- Exclusion Criteria Met
|
|
126
|
+
- [Detail]
|
|
127
|
+
- Inclusion Criteria Not Met
|
|
128
|
+
- [Detail]
|
|
129
|
+
"""
|
|
130
|
+
# If group_col is None, create a dummy group column
|
|
131
|
+
actual_group_col: str = group_col if group_col else "Total"
|
|
132
|
+
|
|
133
|
+
# 1. Prepare Data
|
|
134
|
+
# Join ADIE to ADSL to get treatment group info
|
|
135
|
+
df_joined: pl.DataFrame = adie.join(
|
|
136
|
+
adsl.select(["USUBJID"] + ([group_col] if group_col else [])), on="USUBJID", how="inner"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if not group_col:
|
|
140
|
+
# Add dummy Total column
|
|
141
|
+
df_joined = df_joined.with_columns(pl.lit("Total").alias("Total"))
|
|
142
|
+
|
|
143
|
+
# Define hierarchy
|
|
144
|
+
results: list[dict[str, Any]] = []
|
|
145
|
+
|
|
146
|
+
# Get distinct groups
|
|
147
|
+
groups: list[str]
|
|
148
|
+
if group_col:
|
|
149
|
+
groups_raw: list[str | None] = sorted(adsl.select(group_col).unique().to_series().to_list())
|
|
150
|
+
groups = [g for g in groups_raw if g is not None]
|
|
151
|
+
else:
|
|
152
|
+
groups = ["Total"]
|
|
153
|
+
|
|
154
|
+
# Helper to calculate n and pct (pct of what? usually pct of failures? or pct of screened?)
|
|
155
|
+
# Usually IE table % is based on Total Screening Failures.
|
|
156
|
+
# Let's count Total Screening Failures per Group first.
|
|
157
|
+
|
|
158
|
+
# Total Screening Failures (Subjects present in ADIE)
|
|
159
|
+
# Note: A subject can match multiple criteria.
|
|
160
|
+
total_failures_by_group = df_joined.group_by(actual_group_col).agg(
|
|
161
|
+
pl.col("USUBJID").n_unique().alias("count")
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
total_failures_map: dict[str, int] = {
|
|
165
|
+
row[actual_group_col]: row["count"] for row in total_failures_by_group.iter_rows(named=True)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Helper for row generation
|
|
169
|
+
def add_row(
|
|
170
|
+
label: str, filter_expr: pl.Expr | None = None, is_header: bool = False, indent: int = 0
|
|
171
|
+
) -> None:
|
|
172
|
+
row_data: dict[str, Any] = {"label": label, "indent": indent, "is_header": is_header}
|
|
173
|
+
|
|
174
|
+
for g in groups:
|
|
175
|
+
# Filter data for this group
|
|
176
|
+
g_df = df_joined.filter(pl.col(actual_group_col) == g)
|
|
177
|
+
|
|
178
|
+
if filter_expr is not None:
|
|
179
|
+
# Filter specific criteria
|
|
180
|
+
g_df = g_df.filter(filter_expr)
|
|
181
|
+
|
|
182
|
+
n = g_df.select("USUBJID").n_unique()
|
|
183
|
+
|
|
184
|
+
# Pct based on total failures in that group?
|
|
185
|
+
denom = total_failures_map.get(g, 0)
|
|
186
|
+
pct = (n / denom * 100) if denom > 0 else 0.0
|
|
187
|
+
|
|
188
|
+
row_data[f"count_{g}"] = n
|
|
189
|
+
row_data[f"pct_{g}"] = pct
|
|
190
|
+
|
|
191
|
+
results.append(row_data)
|
|
192
|
+
|
|
193
|
+
# 1. Total Screening Failures
|
|
194
|
+
add_row("Total Screening Failures")
|
|
195
|
+
|
|
196
|
+
# 2. Exclusion Criteria Met
|
|
197
|
+
excl_expr = pl.col("PARAMCAT") == "EXCLUSION CRITERIA MET"
|
|
198
|
+
add_row("Exclusion Criteria Met", excl_expr, is_header=True, indent=1)
|
|
199
|
+
|
|
200
|
+
# Details for Exclusion
|
|
201
|
+
excl_params = (
|
|
202
|
+
df_joined.filter(excl_expr).select("PARAM").unique().sort("PARAM").to_series().to_list()
|
|
203
|
+
)
|
|
204
|
+
for param in excl_params:
|
|
205
|
+
add_row(param, excl_expr & (pl.col("PARAM") == param), indent=2)
|
|
206
|
+
|
|
207
|
+
# 3. Inclusion Criteria Not Met
|
|
208
|
+
incl_expr = pl.col("PARAMCAT") == "INCLUSION CRITERIA NOT MET"
|
|
209
|
+
add_row("Inclusion Criteria Not Met", incl_expr, is_header=True, indent=1)
|
|
210
|
+
|
|
211
|
+
# Details for Inclusion
|
|
212
|
+
incl_params = (
|
|
213
|
+
df_joined.filter(incl_expr).select("PARAM").unique().sort("PARAM").to_series().to_list()
|
|
214
|
+
)
|
|
215
|
+
for param in incl_params:
|
|
216
|
+
add_row(param, incl_expr & (pl.col("PARAM") == param), indent=2)
|
|
217
|
+
|
|
218
|
+
return pl.DataFrame(results)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def ie_df(ard: pl.DataFrame) -> pl.DataFrame:
|
|
222
|
+
"""Transform ARD to display DataFrame."""
|
|
223
|
+
# Find group columns
|
|
224
|
+
cols = ard.columns
|
|
225
|
+
group_cols = [c for c in cols if c.startswith("count_")]
|
|
226
|
+
groups = [c.replace("count_", "") for c in group_cols]
|
|
227
|
+
|
|
228
|
+
# Create valid Polars expressions for selecting columns
|
|
229
|
+
# Apply indentation: 3 spaces per indent level
|
|
230
|
+
# Note: Using \u00A0 (NBSP) might be safer for RTF if spaces get collapsed,
|
|
231
|
+
# but regular spaces usually work in table cells. Let's start with regular spaces.
|
|
232
|
+
|
|
233
|
+
select_exprs = [
|
|
234
|
+
(pl.lit(" ").repeat_by(pl.col("indent")).list.join("") + pl.col("label")).alias(
|
|
235
|
+
"Criteria"
|
|
236
|
+
)
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
for g in groups:
|
|
240
|
+
# Format n (%)
|
|
241
|
+
# We need to construct the string.
|
|
242
|
+
# Polars string formatting
|
|
243
|
+
# format: "{n} ({pct:.1f})"
|
|
244
|
+
|
|
245
|
+
# Note: Polars doesn't have f-string strictly in expressions like python
|
|
246
|
+
# We use strict casting and concatenation
|
|
247
|
+
|
|
248
|
+
col_n = pl.col(f"count_{g}")
|
|
249
|
+
col_pct = pl.col(f"pct_{g}")
|
|
250
|
+
|
|
251
|
+
fmt = (
|
|
252
|
+
col_n.cast(pl.Utf8)
|
|
253
|
+
+ " ("
|
|
254
|
+
+ col_pct.map_elements(lambda x: f"{x:.1f}", return_dtype=pl.Utf8)
|
|
255
|
+
+ ")"
|
|
256
|
+
).alias(g)
|
|
257
|
+
|
|
258
|
+
select_exprs.append(fmt)
|
|
259
|
+
|
|
260
|
+
return ard.select(select_exprs)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def ie_rtf(df: pl.DataFrame, output_path: str, title: str = "") -> None:
|
|
264
|
+
"""Generate RTF."""
|
|
265
|
+
|
|
266
|
+
# Rename Criteria column to empty string for display if needed or keep as is?
|
|
267
|
+
# Usually "Criteria".
|
|
268
|
+
|
|
269
|
+
# Calculate number of columns
|
|
270
|
+
n_cols = len(df.columns)
|
|
271
|
+
|
|
272
|
+
# Build first-level column headers (use actual column names)
|
|
273
|
+
col_header_1 = list(df.columns)
|
|
274
|
+
|
|
275
|
+
# Build second-level column headers (empty for first, "n (%)" for groups)
|
|
276
|
+
col_header_2 = [""] + ["n (%)"] * (n_cols - 1)
|
|
277
|
+
|
|
278
|
+
# Calculate column widths - auto-calculate
|
|
279
|
+
# [n_cols-1, 1, 1, 1, ...]
|
|
280
|
+
col_widths = [float(n_cols - 1)] + [1.0] * (n_cols - 1)
|
|
281
|
+
|
|
282
|
+
rtf_doc = create_rtf_table_n_pct(
|
|
283
|
+
df=df,
|
|
284
|
+
col_header_1=col_header_1,
|
|
285
|
+
col_header_2=col_header_2,
|
|
286
|
+
col_widths=col_widths,
|
|
287
|
+
title=[title],
|
|
288
|
+
footnote=None,
|
|
289
|
+
source=None,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
rtf_doc.write_rtf(output_path)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def study_plan_to_ie_listing(
|
|
296
|
+
study_plan: StudyPlan,
|
|
297
|
+
) -> list[str]:
|
|
298
|
+
"""
|
|
299
|
+
Generate IE Listing outputs.
|
|
300
|
+
"""
|
|
301
|
+
# Meta data
|
|
302
|
+
analysis_type = "ie_listing"
|
|
303
|
+
output_dir = study_plan.output_dir
|
|
304
|
+
title = "Listing of Protocol Deviations"
|
|
305
|
+
|
|
306
|
+
# Ensure output directory exists
|
|
307
|
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
308
|
+
|
|
309
|
+
# Initialize parser
|
|
310
|
+
parser = StudyPlanParser(study_plan)
|
|
311
|
+
|
|
312
|
+
# Get expanded plan (Manually expansion to avoid AttributeError)
|
|
313
|
+
plans = study_plan.study_data.get("plans", [])
|
|
314
|
+
all_specs = []
|
|
315
|
+
for plan_data in plans:
|
|
316
|
+
expanded = study_plan.expander.expand_plan(plan_data)
|
|
317
|
+
for p in expanded:
|
|
318
|
+
all_specs.append(study_plan.expander.create_analysis_spec(p))
|
|
319
|
+
|
|
320
|
+
plan_df = pl.DataFrame(all_specs)
|
|
321
|
+
|
|
322
|
+
# Filter for ie_listing if user specified it, otherwise we might trigger it manually
|
|
323
|
+
# But for now let's assume if this function is called, we want to run for "ie_listing"
|
|
324
|
+
# If the user only asked for "ie_listing", we might need to add it to the plan
|
|
325
|
+
# or just run it blindly for all?
|
|
326
|
+
# Usually we filter by analysis_type.
|
|
327
|
+
if "analysis" in plan_df.columns:
|
|
328
|
+
listing_plans = plan_df.filter(pl.col("analysis") == analysis_type)
|
|
329
|
+
else:
|
|
330
|
+
listing_plans = pl.DataFrame()
|
|
331
|
+
|
|
332
|
+
# If no specific listing plans are found, maybe we should just generate one
|
|
333
|
+
# default one for the whole study?
|
|
334
|
+
# But sticking to the pattern:
|
|
335
|
+
generated_files = []
|
|
336
|
+
|
|
337
|
+
# If listing_plans is empty, let's create a default one to ensure we generate
|
|
338
|
+
# something for the user
|
|
339
|
+
# since they explicitly asked for it.
|
|
340
|
+
if listing_plans.height == 0:
|
|
341
|
+
# Create a dummy row to force generation
|
|
342
|
+
listing_plans = pl.DataFrame([{"population": "enrolled", "analysis": analysis_type}])
|
|
343
|
+
|
|
344
|
+
for analysis in listing_plans.iter_rows(named=True):
|
|
345
|
+
# Load ADSL
|
|
346
|
+
pop_name = analysis.get("population", "enrolled")
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
# Load Filtered Population (ADSL) without Group
|
|
350
|
+
# (Listings usually don't group by columns like tables)
|
|
351
|
+
# But if they did, we'd handle it. For now, just load raw ADSL.
|
|
352
|
+
(adsl_raw,) = parser.get_datasets("adsl")
|
|
353
|
+
# We could filter, but user just said "show USUBJID and DCSREAS from adsl".
|
|
354
|
+
# Applying population filter if possible.
|
|
355
|
+
pop_filter = parser.get_population_filter(pop_name)
|
|
356
|
+
|
|
357
|
+
adsl, _ = apply_common_filters(
|
|
358
|
+
population=adsl_raw,
|
|
359
|
+
observation=None,
|
|
360
|
+
population_filter=pop_filter,
|
|
361
|
+
observation_filter=None,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
except ValueError as e:
|
|
365
|
+
print(f"Error loading population: {e}")
|
|
366
|
+
continue
|
|
367
|
+
|
|
368
|
+
# Output filename
|
|
369
|
+
filename = f"{analysis_type}_{pop_name}.rtf".lower()
|
|
370
|
+
output_path = f"{output_dir}/{filename}"
|
|
371
|
+
|
|
372
|
+
# Generate DF
|
|
373
|
+
df = ie_listing_df(adsl)
|
|
374
|
+
|
|
375
|
+
# Generate RTF
|
|
376
|
+
ie_listing_rtf(df, output_path, title=title)
|
|
377
|
+
|
|
378
|
+
generated_files.append(output_path)
|
|
379
|
+
|
|
380
|
+
return generated_files
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def ie_listing_df(adsl: pl.DataFrame) -> pl.DataFrame:
|
|
384
|
+
"""Select columns for Listing."""
|
|
385
|
+
# Check if DCSREAS exists
|
|
386
|
+
cols = ["USUBJID", "DCSREAS"]
|
|
387
|
+
available = [c for c in cols if c in adsl.columns]
|
|
388
|
+
return adsl.select(available)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def ie_listing_rtf(df: pl.DataFrame, output_path: str, title: str = "") -> None:
|
|
392
|
+
"""Generate RTF Listing."""
|
|
393
|
+
|
|
394
|
+
col_widths = [1.5, 3.5] # Approximate ratio
|
|
395
|
+
|
|
396
|
+
rtf_doc = create_rtf_listing(
|
|
397
|
+
df=df,
|
|
398
|
+
col_header=list(df.columns),
|
|
399
|
+
col_widths=col_widths,
|
|
400
|
+
title=[title],
|
|
401
|
+
footnote=None,
|
|
402
|
+
source=None,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
rtf_doc.write_rtf(output_path)
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: csrlite
|
|
3
|
-
Version: 0.1
|
|
4
|
-
Summary: A hierarchical YAML-based framework for generating Tables, Listings, and Figures in clinical trials
|
|
5
|
-
Author-email: Clinical Biostatistics Team <biostat@example.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>=
|
|
32
|
-
Requires-Dist: pytest
|
|
33
|
-
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
34
|
-
Requires-Dist: isort>=
|
|
35
|
-
Requires-Dist:
|
|
36
|
-
Requires-Dist:
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
Requires-Dist:
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
Requires-Dist:
|
|
41
|
-
Requires-Dist:
|
|
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
|
-
[](https://github.com/elong0527/csrlite/actions/workflows/ci.yml)
|
|
50
|
-
[](https://codecov.io/gh/elong0527/csrlite)
|
|
51
|
-
[](https://badge.fury.io/py/csrlite)
|
|
52
|
-
[](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.2.1
|
|
4
|
+
Summary: A hierarchical YAML-based framework for generating Tables, Listings, and Figures in clinical trials
|
|
5
|
+
Author-email: Clinical Biostatistics Team <biostat@example.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
|
+
[](https://github.com/elong0527/csrlite/actions/workflows/ci.yml)
|
|
50
|
+
[](https://codecov.io/gh/elong0527/csrlite)
|
|
51
|
+
[](https://badge.fury.io/py/csrlite)
|
|
52
|
+
[](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,20 @@
|
|
|
1
|
+
csrlite/__init__.py,sha256=zKceiP6Z47_o4GUjVLhIqPO6iMefyj6BGKffVHLUTjs,1729
|
|
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/common/config.py,sha256=qWFTNSaeWnqAagXM6FwACdQafXLG_nFWxFHEk5dAo-I,912
|
|
8
|
+
csrlite/common/count.py,sha256=T9nB9hYdc2jIIuV9HISK2ZS0wXNV_FUBDCKW_VVM1fQ,9078
|
|
9
|
+
csrlite/common/parse.py,sha256=J4UkmNlG10mrJ-O68-po1LlfLchK_kSvfdU1GOaAYgQ,9627
|
|
10
|
+
csrlite/common/plan.py,sha256=tYnK-5TVCoGWobxkqmasYYYkc9DJlHmOiTIcrP3MFRo,13293
|
|
11
|
+
csrlite/common/rtf.py,sha256=g1n765i2HPpwkh6aQOzlw4UmCC21-JMZEezFtUaO6U8,4440
|
|
12
|
+
csrlite/common/utils.py,sha256=XLUQqYtEm4SLNImvNDxAuj9OOu6Eogm3_uIp0vyfpfQ,1143
|
|
13
|
+
csrlite/common/yaml_loader.py,sha256=ZPgXw5X9eitMHCPEJBHzeYtcjesEQ8sP0hOFSYnzrgk,3142
|
|
14
|
+
csrlite/disposition/__init__.py,sha256=AiLW7Os7SBFrvvkaUMrxYEntK15_XXT0HYpZQxBx7ro,87
|
|
15
|
+
csrlite/disposition/disposition.py,sha256=W1Oik5hqDfalQ69NXullH7XYWP3oRw2x2nFuFWlE3Qo,10679
|
|
16
|
+
csrlite/ie/ie.py,sha256=9kEAe4aY3xfHe7O-o2ZD3LNfL5eTha2ZPkAflopjPYU,13544
|
|
17
|
+
csrlite-0.2.1.dist-info/METADATA,sha256=zstZU0eiNMFz4ICMeOf9VqCvUGywxMkXTg0hCqGe9HY,2911
|
|
18
|
+
csrlite-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
csrlite-0.2.1.dist-info/top_level.txt,sha256=59zJTvGH5zx2FY4vCl4kgnH8awT0cZrg21Mace7IFlU,8
|
|
20
|
+
csrlite-0.2.1.dist-info/RECORD,,
|
csrlite-0.1.0.dist-info/RECORD
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
csrlite/__init__.py,sha256=o7HOFA9KKbyfq8l_26dqNHBDz2jqDJm8lQBvXYfBYdQ,1164
|
|
2
|
-
csrlite/ae/__init__.py,sha256=gZHPLATRF9f8QBwwQtEjQRtXMsqOJsUK2sbUMLjiE5U,14
|
|
3
|
-
csrlite/ae/ae_listing.py,sha256=EwmU5CTmqmkuiOsA7FedEF83S9MJ1YPlmf5AMsksUCU,18343
|
|
4
|
-
csrlite/ae/ae_specific.py,sha256=s-Zj6WQhKKHARMt5LkoSw74iThPVd-_92l8eYhUGiPc,16898
|
|
5
|
-
csrlite/ae/ae_summary.py,sha256=NlqbuW0N0aiJ6i3fLCDJJPxPuqk1mv6i5svPsIT1xD0,13637
|
|
6
|
-
csrlite/ae/ae_utils.py,sha256=6UhUrTkyOgpxpl5YFoNjteLBgkf0Gtw5lgQApCkwf3c,4121
|
|
7
|
-
csrlite/common/count.py,sha256=gdTSlA-nr5B6e3fuP9pelASf_FdaeRKYzujpE0bbzvA,6925
|
|
8
|
-
csrlite/common/parse.py,sha256=Vz9C7ljkDygT2qkP6TlY3T3p71D6BD5GtIwRKv6p8ps,9319
|
|
9
|
-
csrlite/common/plan.py,sha256=QhsBD7b-AU_mc-JScLHM1Oiw7FJ4AKN1iHWX80-ukuw,11988
|
|
10
|
-
csrlite/common/utils.py,sha256=SAqEnwDtE32LuQqnMVQr_1Xfdp-z54wIrwbwwPBE9lU,1022
|
|
11
|
-
csrlite/common/yaml_loader.py,sha256=_v9pkbAUVshTqVoMLqMiEn17awL2K0kFR4pdDArMSOM,3071
|
|
12
|
-
csrlite/disposition/__init__.py,sha256=KMtGoBjN4aKNYvXHmZ0GX-f4RnmQ3coYbUrkFeU8Es0,85
|
|
13
|
-
csrlite/disposition/disposition.py,sha256=UMm4Z1fFQ6VJ-KSqSaMP7qEzLoSa399kRSZx-oPKEqM,9274
|
|
14
|
-
csrlite-0.1.0.dist-info/METADATA,sha256=HNbQnMH5LqJ2Vq_KMky8u9_ofP0YOe40AynZM9384FI,2799
|
|
15
|
-
csrlite-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
16
|
-
csrlite-0.1.0.dist-info/top_level.txt,sha256=59zJTvGH5zx2FY4vCl4kgnH8awT0cZrg21Mace7IFlU,8
|
|
17
|
-
csrlite-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|