diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6a171dd..1e3478a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,14 +11,14 @@ repos: - id: check-yaml - id: check-toml - id: check-added-large-files -- repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.5 - hooks: - - id: ruff-check - - id: ruff-format - repo: https://github.com/nestauk/pre-commit-hooks rev: v1.2.0 hooks: - id: nbstripout-preserve-timestamp - id: jupytext-enforce-pairing - id: jupytext-smart-sync +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.5 + hooks: + - id: ruff-check + - id: ruff-format diff --git a/daindex/main.py b/daindex/main.py index 12822cd..c252890 100644 --- a/daindex/main.py +++ b/daindex/main.py @@ -141,8 +141,8 @@ class DAIndex(object): `evaluate_all_groups_by_models` methods can be called to calculate the DAI for all groups relative to a single reference group. - The results can be accessed using the `get_group_ratios` and `get_group_figures` methods, or printed using - the `present_results` and `present_all_results` methods. + The results can be accessed using the `get_group_ratio` and `get_group_figure` methods (with a reference and + other group), or printed using the `present_results` and `present_all_results` methods. Args: cohort: The DataFrame containing the data for the cohort. @@ -160,7 +160,7 @@ class DAIndex(object): weight_sum_steps: The number of bins for the weighted sum of k-step cutoffs. n_jobs: The number of jobs to run in parallel. model_name: The name of the model to use in the plots. - decision_boundary: The decision boundary for the DA curve. + decision_boundary: The decision boundary for the DA curve. Defaults to 0.5. Methods: setup_groups: Set up the groups for the DAI calculation. @@ -174,10 +174,16 @@ class DAIndex(object): model objects. present_results: Print the DAI results for a pair of groups. present_all_results: Print the DAI results for all group pairs. - get_group_ratios: Get the DAI ratios for a pair of groups. - get_group_figures: Get the DAI figures for a pair of groups. + get_group_ratio: Get the DAI ratios for a pair of groups. + get_group_figure: Get the DAI figure for a pair of groups. get_all_ratios: Get the DAI ratios for all group pairs. get_all_figures: Get the DAI figures for all group pairs. + get_group_step_samples: Get the number of samples used for each step for a specific group. + get_group_sub_optimal_steps: Get information about sub-optimal steps for a specific group. + get_group_failed_steps: Get information about failed steps for a specific group. + get_all_groups_step_samples: Get step sample information for all evaluated groups. + get_all_groups_sub_optimal_steps: Get sub-optimal step information for all evaluated groups. + get_all_groups_failed_steps: Get failed step information for all evaluated groups. """ def __init__( @@ -221,6 +227,10 @@ def __init__( self.group_ratios = {} self.group_figures = {} self.issues = [] + # New attributes to track step information + self.group_sub_optimal_steps = {} # Stores sub-optimal steps for each group + self.group_failed_steps = {} # Stores failed steps for each group + self.group_step_samples = {} # Stores sample counts for each step for each group def setup_groups(self, groups: Group | list[Group], group_col: str = None) -> None: if not isinstance(groups, list): @@ -485,6 +495,16 @@ def process_step(s: int) -> tuple[float, int, float, bool, bool]: delayed(process_step)(s) for s in tqdm(self.step_scores, desc=f"Calculating k-steps for '{group}' group", position=1, leave=False) ) + + # Store step information internally + sub_opt_steps = {s[0]: s[1] for s in ret if s[3]} + failed_steps = {s[0]: s[1] for s in ret if s[4]} + step_samples = {s[0]: s[1] for s in ret} + + self.group_sub_optimal_steps[group] = sub_opt_steps + self.group_failed_steps[group] = failed_steps + self.group_step_samples[group] = step_samples + sub_opt_list = ", ".join([f"{s[0]}: {s[1]}" for s in ret if s[3]]) failed_list = ", ".join([f"{s[0]}: {s[1]}" for s in ret if s[4]]) if sub_opt_list or failed_list: @@ -745,10 +765,10 @@ def present_all_results(self) -> None: for group_pair in self.group_figures.keys(): self.present_results(*group_pair) - def get_group_ratios(self, reference_group: str, other_group: str) -> dict[str, float | str]: + def get_group_ratio(self, reference_group: str, other_group: str) -> dict[str, float | str]: return self.group_ratios[(reference_group, other_group)] - def get_group_figures(self, reference_group: str, other_group: str) -> plt.Figure: + def get_group_figure(self, reference_group: str, other_group: str) -> plt.Figure: return self.group_figures[(reference_group, other_group)] def get_all_ratios(self) -> pd.DataFrame: @@ -758,3 +778,81 @@ def get_all_ratios(self) -> pd.DataFrame: def get_all_figures(self) -> dict[tuple[str, str], plt.Figure]: return self.group_figures + + def get_group_step_samples(self, group: str) -> dict[float, int]: + """ + Returns the number of samples used for each step for the specified group. + + Args: + group: The name of the group to get step sample information for. + + Returns: + A dictionary mapping step scores to the number of samples used. + """ + if group not in self.group_step_samples: + raise ValueError(f"No step information available for group '{group}'. Run evaluation first.") + return self.group_step_samples[group] + + def get_group_sub_optimal_steps(self, group: str) -> dict[float, int]: + """ + Returns information about sub-optimal steps for the specified group. + These are steps where the number of samples was below the preferred threshold + but still acceptable. + + Args: + group: The name of the group to get sub-optimal step information for. + + Returns: + A dictionary mapping step scores to the number of samples used for + sub-optimal steps. + """ + if group not in self.group_sub_optimal_steps: + raise ValueError(f"No step information available for group '{group}'. Run evaluation first.") + return self.group_sub_optimal_steps[group] + + def get_group_failed_steps(self, group: str) -> dict[float, int]: + """ + Returns information about failed steps for the specified group. + These are steps where the number of samples was below the minimum threshold + and the step was excluded from the analysis. + + Args: + group: The name of the group to get failed step information for. + + Returns: + A dictionary mapping step scores to the number of samples used for + failed steps. + """ + if group not in self.group_failed_steps: + raise ValueError(f"No step information available for group '{group}'. Run evaluation first.") + return self.group_failed_steps[group] + + def get_all_groups_step_samples(self) -> dict[str, dict[float, int]]: + """ + Returns the number of samples used for each step for all evaluated groups. + + Returns: + A dictionary mapping group names to dictionaries of step scores and + their corresponding sample counts. + """ + return self.group_step_samples.copy() + + def get_all_groups_sub_optimal_steps(self) -> dict[str, dict[float, int]]: + """ + Returns information about sub-optimal steps for all evaluated groups. + + Returns: + A dictionary mapping group names to dictionaries of step scores and + their corresponding sample counts for sub-optimal steps. + """ + return self.group_sub_optimal_steps.copy() + + def get_all_groups_failed_steps(self) -> dict[str, dict[float, int]]: + """ + Returns information about failed steps for all evaluated groups. + + Returns: + A dictionary mapping group names to dictionaries of step scores and + their corresponding sample counts for failed steps. + """ + return self.group_failed_steps.copy() diff --git a/daindex/util.py b/daindex/util.py index 9e3ce79..4d7045b 100644 --- a/daindex/util.py +++ b/daindex/util.py @@ -86,8 +86,13 @@ def compare_two_groups( def area_under_curve(w_data: np.ndarray, decision_boundary: float = 0.5) -> tuple[float, float]: - """ - calculate the area under curve - do NOT do interpolation + """Calculate the area under curve - do NOT do interpolation + + Args: + w_data: The weighted data, should be a 2D array with shape (n, 3) where + the first column is the x values, the second column is the y values, + and the third column is the weights. + decision_boundary: The decision boundary for calculating the area under curve. """ prev = None area = 0.0 diff --git a/pyproject.toml b/pyproject.toml index 312f7b9..fd4e09c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ authors = [ { name = "Harrison Wilde", email = "h.wilde@ucl.ac.uk" }, { name = "Honghan Wu", email = "honghan.wu@gmail.com" }, ] -version = "0.7.3" +version = "0.8.0" description = "Deterioration Allocation Index Framework" license = "MIT" readme = "README.md" diff --git a/tutorials/meps_tutorial.ipynb b/tutorials/meps_tutorial.ipynb index eb37326..8e61197 100644 --- a/tutorials/meps_tutorial.ipynb +++ b/tutorials/meps_tutorial.ipynb @@ -494,9 +494,13 @@ " cohort=df,\n", " groups=groups,\n", " det_feature=det_feature,\n", + " decision_boundary=0.75, # Set decision boundary for DA curve\n", ")\n", "index.evaluate_all_groups_by_models(\n", - " models=lr_orig_panel19, feature_list=feature_list, reference_group=\"White\", n_jobs=6\n", + " models=lr_orig_panel19,\n", + " feature_list=feature_list,\n", + " reference_group=\"White\",\n", + " n_jobs=1, # Up n_jobs to parallelize if needed\n", ")" ] }, @@ -518,6 +522,24 @@ "index.get_all_ratios()" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "index.get_group_ratio(\"White\", \"Asian\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "index.get_all_groups_failed_steps()" + ] + }, { "cell_type": "code", "execution_count": null, @@ -527,8 +549,11 @@ } ], "metadata": { + "jupytext": { + "formats": "ipynb,py:percent" + }, "kernelspec": { - "display_name": "daindex", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -542,7 +567,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.5" + "version": "3.10.18" } }, "nbformat": 4, diff --git a/uv.lock b/uv.lock index 0d1f821..652b2fa 100644 --- a/uv.lock +++ b/uv.lock @@ -505,7 +505,7 @@ wheels = [ [[package]] name = "daindex" -version = "0.7.0" +version = "0.8.0" source = { editable = "." } dependencies = [ { name = "joblib" },