Skip to content

Commit

Permalink
UI review; 24.1a6 (#81)
Browse files Browse the repository at this point in the history
* start 24.1a6.dev

* set max of 8 degree on multitumor model w/ auto degree set

* remove fixed_lsc from excel report

* add back fixed_lsc

* begin refactor of input data

* aggregate summary

* nd outputs

* add mscombo results

* make results optional

* change version to 24.1a6

* update todo
  • Loading branch information
shapiromatron authored Nov 18, 2024
1 parent 2fdcb45 commit 3f2e7fc
Show file tree
Hide file tree
Showing 12 changed files with 131 additions and 60 deletions.
2 changes: 1 addition & 1 deletion src/bmdscore/bmds_helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#include "bmds_helper.h"

// calendar versioning; see https://peps.python.org/pep-0440/#pre-releases
std::string BMDS_VERSION = "24.1a4";
std::string BMDS_VERSION = "24.1a6";

double python_dichotomous_model_result::getSRAtDose(double targetDose, std::vector<double> doses) {
std::vector<double> diff;
Expand Down
2 changes: 1 addition & 1 deletion src/pybmds/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "24.1a5" # see docs/development for versioning
__version__ = "24.1a6" # see docs/development for versioning

from .batch import BatchResponse, BatchSession # noqa: F401
from .constants import DistType as ContinuousDistType # noqa: F401
Expand Down
6 changes: 1 addition & 5 deletions src/pybmds/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,7 @@ def to_docx(
report = Report.build_default()

for session in self.sessions:
session.to_docx(
report,
header_level=header_level,
citation=False,
)
session.to_docx(report, header_level=header_level, citation=False, **kw)

if citation and len(self.sessions) > 0:
write_citation(report, header_level=header_level)
Expand Down
18 changes: 15 additions & 3 deletions src/pybmds/models/multi_tumor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from ..types.multi_tumor import MultitumorAnalysis, MultitumorResult, MultitumorSettings
from ..types.priors import multistage_cancer_prior
from ..types.session import VersionSchema
from ..utils import unique_items
from .dichotomous import MultistageCancer


Expand Down Expand Up @@ -102,9 +103,15 @@ def write_docx_inputs_table(report: Report, session):
hdr = report.styles.tbl_header
body = report.styles.tbl_body

rows = session.models[0][0].settings.docx_table_data()
settings = [models[len(models) - 1].settings for models in session.models]
rows = {
"Setting": "Value",
"BMR": unique_items(settings, "bmr_text"),
"Confidence Level (one sided)": unique_items(settings, "confidence_level"),
"Maximum Degree": unique_items(settings, "degree"),
}
tbl = report.document.add_table(len(rows), 2, style=styles.table)
for idx, (key, value) in enumerate(rows):
for idx, (key, value) in enumerate(rows.items()):
write_cell(tbl.cell(idx, 0), key, style=hdr)
write_cell(tbl.cell(idx, 1), value, style=hdr if idx == 0 else body)

Expand Down Expand Up @@ -177,7 +184,9 @@ def _build_model_settings(self) -> list[list[DichotomousModelSettings]]:
ds_settings = []
degree_i = self.degrees[i]
degrees_i = (
range(degree_i, degree_i + 1) if degree_i > 0 else range(1, dataset.num_dose_groups)
range(degree_i, degree_i + 1)
if degree_i > 0
else range(1, min(dataset.num_dose_groups, 9)) # max of 8 if degree is 0 (auto)
)
for degree in degrees_i:
model_settings = self.settings.model_copy(
Expand Down Expand Up @@ -454,6 +463,7 @@ def to_docx(
dataset_format_long: bool = True,
all_models: bool = False,
bmd_cdf_table: bool = False,
**kw,
):
"""Return a Document object with the session executed
Expand Down Expand Up @@ -490,6 +500,8 @@ def to_docx(
report.document.add_paragraph("Maximum Likelihood Approach", h2)
write_docx_frequentist_table(report, self)
report.document.add_paragraph(add_mpl_figure(report.document, self.plot(), 6))
report.document.add_paragraph(self.results.ms_combo_text(), report.styles.fixed_width)

report.document.add_paragraph("Individual Model Results", h2)

for dataset, selected_idx, models in zip(
Expand Down
7 changes: 5 additions & 2 deletions src/pybmds/models/nested_dichotomous.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def get_priors_list(self) -> list[list]:
return self.settings.priors.priors_list(nphi=self.dataset.num_dose_groups)

def model_settings_text(self) -> str:
input_tbl = self.settings.tbl()
input_tbl = self.settings.tbl(self.results)
return multi_lstrip(
f"""
Input Summary:
Expand Down Expand Up @@ -142,13 +142,16 @@ class Nctr(BmdModelNestedDichotomous):
bmd_model_class = NestedDichotomousModelChoices.nctr.value
model_class = bmdscore.nested_model.nctr

def execute(self) -> NestedDichotomousResult:
raise NotImplementedError("TODO - future release")

def get_param_names(self) -> list[str]:
return ["a", "b", "theta1", "theta2", "rho"] + [
f"phi{i}" for i in range(1, self.dataset.num_dose_groups + 1)
]

def dr_curve(self, doses: np.ndarray, params: dict, fixed_lsc: float) -> np.ndarray:
raise NotImplementedError("TODO - update formula")
raise NotImplementedError("TODO - future release")

def get_default_prior_class(self) -> PriorClass:
return PriorClass.frequentist_restricted
Expand Down
12 changes: 5 additions & 7 deletions src/pybmds/reporting/styling.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,11 @@ def write_inputs_table(report: Report, session: Session):
hdr = report.styles.tbl_header
body = report.styles.tbl_body

model_index = 0
if hasattr(session.models[0].settings, "degree"):
degrees = [model.settings.degree for model in session.models]
model_index = degrees.index(max(degrees))
rows = session.models[model_index].settings.docx_table_data()
tbl = report.document.add_table(len(rows), 2, style=styles.table)
for idx, (key, value) in enumerate(rows):
results = session.models[0].results if len(session.models) > 0 else None
settings = [model.settings for model in session.models]
content = session.models[0].settings.docx_table_data(settings, results)
tbl = report.document.add_table(len(content), 2, style=styles.table)
for idx, (key, value) in enumerate(content.items()):
write_cell(tbl.cell(idx, 0), key, style=hdr)
write_cell(tbl.cell(idx, 1), value, style=hdr if idx == 0 else body)

Expand Down
25 changes: 13 additions & 12 deletions src/pybmds/types/continuous.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .. import bmdscore, constants
from ..constants import BOOL_YES_NO, ContinuousModelChoices, Dtype
from ..datasets.continuous import ContinuousDatasets
from ..utils import multi_lstrip, pretty_table
from ..utils import multi_lstrip, pretty_table, unique_items
from .common import (
BOUND_FOOTNOTE,
CONTINUOUS_TEST_FOOTNOTES,
Expand Down Expand Up @@ -100,17 +100,18 @@ def tbl(self, show_degree: bool = True) -> str:

return pretty_table(data, "")

def docx_table_data(self) -> list:
data = [
["Setting", "Value"],
["BMR", self.bmr_text],
["Distribution", self.distribution],
["Adverse Direction", self.direction],
["Maximum Polynomial Degree", self.degree],
["Confidence Level (one sided)", self.confidence_level],
]
if self.is_hybrid:
data.append(["Tail Probability", self.tail_prob])
@classmethod
def docx_table_data(cls, settings: list[Self], results) -> dict:
data = {
"Setting": "Value",
"BMR": unique_items(settings, "bmr_text"),
"Distribution": unique_items(settings, "distribution"),
"Adverse Direction": unique_items(settings, "direction"),
"Maximum Polynomial Degree": str(max(setting.degree for setting in settings)),
"Confidence Level (one sided)": unique_items(settings, "confidence_level"),
}
if settings[0].is_hybrid:
data["Tail Probability"] = unique_items(settings, "tail_prob")
return data

def update_record(self, d: dict) -> None:
Expand Down
23 changes: 15 additions & 8 deletions src/pybmds/types/dichotomous.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .. import bmdscore, constants
from ..constants import BOOL_YES_NO, DichotomousModelChoices
from ..datasets import DichotomousDataset
from ..utils import multi_lstrip, pretty_table
from ..utils import multi_lstrip, pretty_table, unique_items
from .common import (
BOUND_FOOTNOTE,
NumpyFloatArray,
Expand Down Expand Up @@ -69,13 +69,20 @@ def tbl(self, show_degree: bool = True) -> str:

return pretty_table(data, "")

def docx_table_data(self) -> list:
return [
["Setting", "Value"],
["BMR", self.bmr_text],
["Confidence Level (one sided)", self.confidence_level],
["Maximum Multistage Degree", self.degree],
]
@classmethod
def docx_table_data(cls, settings: list[Self], results) -> dict:
data = {
"Setting": "Value",
"BMR": unique_items(settings, "bmr_text"),
"Confidence Level (one sided)": unique_items(settings, "confidence_level"),
"Maximum Multistage Degree": str(max(setting.degree for setting in settings)),
}
if settings[0].priors.is_bayesian:
data.update(
Samples=unique_items(settings, "samples"),
**{"Burn-in": unique_items(settings, "burnin")},
)
return data

def update_record(self, d: dict) -> None:
"""Update data record for a tabular-friendly export"""
Expand Down
8 changes: 7 additions & 1 deletion src/pybmds/types/multi_tumor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from .. import bmdscore
from ..models.dichotomous import BmdModelDichotomousSchema
from ..utils import multi_lstrip, pretty_table
from ..utils import get_version, multi_lstrip, pretty_table
from .common import inspect_cpp_obj
from .dichotomous import (
DichotomousAnalysisCPPStructs,
Expand Down Expand Up @@ -95,6 +95,12 @@ def text(self, datasets, models) -> str:
"""
)

def ms_combo_text(self) -> str:
title = "Multitumor MS Combo Model".center(30) + "\n══════════════════════════════"
version = get_version()
version = f"Version: pybmds {version.python} (bmdscore {version.dll})"
return "\n\n".join([title, version, self.tbl()]) + "\n"

def tbl(self) -> str:
data = [
["BMD", self.bmd],
Expand Down
71 changes: 52 additions & 19 deletions src/pybmds/types/nested_dichotomous.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from .. import bmdscore, constants
from ..datasets import NestedDichotomousDataset
from ..utils import camel_to_title, multi_lstrip, pretty_table
from ..utils import camel_to_title, multi_lstrip, pretty_table, unique_items
from .common import NumpyFloatArray, clean_array, inspect_cpp_obj
from .priors import ModelPriors, PriorClass

Expand All @@ -22,6 +22,13 @@ class LitterSpecificCovariate(IntEnum):
OverallMean = 1
ControlGroupMean = 2

def label(self, fixed_lsc: float | None) -> str:
text = camel_to_title(self.name)
if self == self.Unused:
return text
lsc = f" ({fixed_lsc:.3f})" if fixed_lsc else ""
return f"{text}{lsc}"

@property
def text(self) -> str:
return "lsc-" if self == self.Unused else "lsc+"
Expand All @@ -31,6 +38,10 @@ class IntralitterCorrelation(IntEnum):
Zero = 0
Estimate = 1

@property
def label(self) -> str:
return camel_to_title(self.name)

@property
def text(self) -> str:
return "ilc+" if self == self.Estimate else "ilc-"
Expand Down Expand Up @@ -69,25 +80,47 @@ def confidence_level(self) -> float:
def modeling_approach(self) -> str:
return "MLE"

def _tbl_rows(self) -> list:
return [
["BMR", self.bmr_text],
["Confidence Level (one sided)", self.confidence_level],
["Litter Specific Covariate", camel_to_title(self.litter_specific_covariate.name)],
["Intralitter Correlation", self.intralitter_correlation.name],
["Estimate Background", self.estimate_background],
["Bootstrap Runs", self.bootstrap_n],
["Bootstrap Iterations", self.bootstrap_iterations],
["Bootstrap Seed", self.bootstrap_seed],
]

def tbl(self, degree_required: bool = False) -> str:
return pretty_table(self._tbl_rows(), "")
def tbl(self, results=None) -> str:
fixed_lsc = results.fixed_lsc if results else None
return pretty_table(
[
["BMR", self.bmr_text],
["Confidence Level (one sided)", self.confidence_level],
["Litter Specific Covariate", self.litter_specific_covariate.label(fixed_lsc)],
["Intralitter Correlation", self.intralitter_correlation.label],
["Estimate Background", self.estimate_background],
["Bootstrap Runs", self.bootstrap_n],
["Bootstrap Iterations", self.bootstrap_iterations],
["Bootstrap Seed", self.bootstrap_seed],
],
"",
)

def docx_table_data(self) -> list:
rows = self._tbl_rows()
rows.insert(0, ["Setting", "Value"])
return rows
@classmethod
def docx_table_data(cls, settings: list[Self], results) -> dict:
fixed_lsc = results.fixed_lsc if results else None
lsc = [
setting.litter_specific_covariate.label(fixed_lsc)
for setting in settings
if setting.litter_specific_covariate != LitterSpecificCovariate.Unused
]
ilc = [
setting.intralitter_correlation.label
for setting in settings
if setting.intralitter_correlation != IntralitterCorrelation.Zero
]
data = {
"Setting": "Value",
"BMR": unique_items(settings, "bmr_text"),
"Confidence Level (one sided)": unique_items(settings, "confidence_level"),
"Litter Specific Covariate": ", ".join(sorted(set(lsc))),
"Intralitter Correlation": ", ".join(sorted(set(ilc))),
"Estimate Background": unique_items(settings, "estimate_background"),
"Bootstrap Runs": unique_items(settings, "bootstrap_n"),
"Bootstrap Seed": unique_items(settings, "bootstrap_seed"),
"Bootstrap Iterations": unique_items(settings, "bootstrap_iterations"),
}
return data

def update_record(self, d: dict) -> None:
"""Update data record for a tabular-friendly export"""
Expand Down
4 changes: 4 additions & 0 deletions src/pybmds/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,7 @@ def get_version():

def camel_to_title(txt: str) -> str:
return re.sub(r"(?<=\w)([A-Z])", r" \1", txt)


def unique_items(settings: list, getter: str) -> str:
return ", ".join(sorted(list(set(str(getattr(setting, getter)) for setting in settings))))
13 changes: 12 additions & 1 deletion tests/test_pybmds/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from dataclasses import dataclass

import pybmds
from pybmds.utils import get_version
from pybmds.utils import get_version, unique_items


def test_citation():
Expand All @@ -9,3 +11,12 @@ def test_citation():
def test_get_version():
version = get_version()
assert int(version.dll.split(".")[0]) >= 24 # assume dll in format "YY.MM..."


@dataclass
class Foo:
bar: str


def test_unique_items():
assert unique_items([Foo(bar="b"), Foo(bar="a"), Foo(bar="b")], "bar") == "a, b"

0 comments on commit 3f2e7fc

Please sign in to comment.