diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..fa85c213 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,80 @@ +name: Documentation + +on: + push: + branches: [main] + pull_request: + branches: [main] + types: [opened, reopened, synchronize, closed] + +permissions: + contents: write + pull-requests: write + +concurrency: + group: docs-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-publish: + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install uv and Python + uses: astral-sh/setup-uv@v5 + with: + python-version: '3.11' + + - name: Set up Quarto + uses: quarto-dev/quarto-actions/setup@v2 + + - name: Install project and docs dependencies + run: uv sync --group docs + + - name: Build Great Docs site + run: uv run great-docs build + + - name: Publish documentation + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: gh-pages + folder: great-docs/_site + clean: true + clean-exclude: | + pr-preview/ + + preview: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install uv and Python + if: github.event.action != 'closed' + uses: astral-sh/setup-uv@v5 + with: + python-version: '3.11' + + - name: Set up Quarto + if: github.event.action != 'closed' + uses: quarto-dev/quarto-actions/setup@v2 + + - name: Install project and docs dependencies + if: github.event.action != 'closed' + run: uv sync --group docs + + - name: Build Great Docs preview + if: github.event.action != 'closed' + run: uv run great-docs build + + - name: Deploy PR preview + uses: rossjrw/pr-preview-action@v1 + with: + source-dir: great-docs/_site + preview-branch: gh-pages + umbrella-dir: pr-preview + action: auto diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 56be1d26..7d70e089 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,17 +1,14 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - +# Build and test the Python package. Documentation is handled separately in docs.yml. name: Python package on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] jobs: build: - runs-on: ubuntu-latest strategy: fail-fast: false @@ -19,47 +16,28 @@ jobs: python-version: ["3.10"] permissions: id-token: write - contents: write + contents: read steps: - - uses: actions/checkout@v4 - - - name: Install uv and set the python version - uses: astral-sh/setup-uv@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install the project - run: uv sync --all-extras --dev + - uses: actions/checkout@v4 - - name: Build Package - run: uv build + - name: Install uv and set the Python version + uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python-version }} - - name: Run tests - run: uv run pytest tests + - name: Install the project + run: uv sync --all-extras --dev - - name: Show package version - run: grep -r "version" pyproject.toml || grep -r "__version__" rtichoke/ || python -c "import rtichoke; print(rtichoke.__version__)" - - - name: Set up Quarto - uses: quarto-dev/quarto-actions/setup@v2 - - - name: Set Git identity for Quarto publishing - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" + - name: Build package + run: uv build - - name: Quartodoc build - working-directory: docs - run: uv run quartodoc build + - name: Run tests + run: uv run pytest tests - - name: Clean any leftover Quarto publish worktree - run: rm -rf .quarto - - - name: Render and Publish - working-directory: docs - run: uv run quarto publish gh-pages --no-browser --no-prompt --token "${{ secrets.GITHUB_TOKEN }}" + - name: Show package version + run: grep -r "version" pyproject.toml || grep -r "__version__" rtichoke/ || python -c "import rtichoke; print(rtichoke.__version__)" - - name: Publish package - if: github.ref == 'refs/heads/main' && matrix.python-version == '3.10' - run: uv publish \ No newline at end of file + - name: Publish package + if: github.ref == 'refs/heads/main' && matrix.python-version == '3.10' + run: uv publish diff --git a/.gitignore b/.gitignore index 79c2125f..f9984f23 100644 --- a/.gitignore +++ b/.gitignore @@ -1,164 +1,146 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ -**__pycache__ - -# PyCharm -.idea/ - -# RStudio project files -**.Rproj.user/ -**.Rproj.user* -**.Rproj -**.Rhistory - -# MacOS -.DS_Store +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff +instance/ +.webassets-cache + +# Scrapy stuff +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# PEP 582 +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ +**__pycache__ + +# PyCharm +.idea/ + +# RStudio project files +**.Rproj.user/ +**.Rproj.user* +**.Rproj +**.Rhistory + +# MacOS +.DS_Store *.html -docs/walkthrough_aj_estimate.html -docs/_site/walkthrough_aj_estimate.html -docs/_site/walkthrough_aj_estimate.html src/rtichoke/summary_report/summary_report_template.html -docs/_site/walkthrough_aj_estimate.html -docs/_site/walkthrough_aj_estimate.html -docs/combined_adjusted_data.pkl -docs/_site/walkthrough_aj_estimate.html probs_dict.pkl -docs/_site/walkthrough_aj_estimate.html reals_dict.pkl times_dict.pkl -docs/_site/example.html -docs/_site/walkthrough_aj_estimate.html -# quarto site related files -docs/_site/ -*.js -*.html +# Documentation build artifacts +great-docs/ +*.js +*.html *.css +!site.css objects.json diff --git a/AGENTS.md b/AGENTS.md index 04f30799..319c1053 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,17 +6,17 @@ This document provides guidance for AI agents working on the `rtichoke` reposito To set up the development environment, follow these steps: -1. **Install `uv`**: If you don't have `uv` installed, please follow the official installation instructions. -2. **Create a virtual environment**: Use `uv venv` to create a virtual environment. -3. **Install dependencies**: Install the project dependencies, including the `dev` dependencies, with the following command: +1. **Install `uv`**. +2. **Create a virtual environment** with `uv venv`. +3. **Install development dependencies** with: ```bash - uv pip install -e .[dev] + uv sync --dev ``` ## Running Tests -The test suite is run using `pytest`. To run the tests, use the following command: +Run the test suite with: ```bash uv run pytest @@ -30,9 +30,9 @@ Strive to use a functional programming style as much as possible. Avoid side eff ### Docstrings -All exported functions must have NumPy-style docstrings. This is to ensure that the documentation is clear, consistent, and can be easily parsed by tools like `quartodoc`. +All exported functions must have NumPy-style docstrings. Great Docs parses these docstrings to generate the API reference, so parameter, return-value, and usage documentation should remain accurate and user-facing. -Example of a NumPy-style docstring: +Example: ```python def my_function(param1, param2): @@ -50,27 +50,35 @@ def my_function(param1, param2): bool Description of the return value. """ - # function body return True ``` ## Pre-commit Hooks -This repository uses pre-commit hooks to ensure code quality and consistency. The following hooks are configured: +This repository uses pre-commit hooks for code quality and consistency, including `ruff-check`, `ruff-format`, and `uv-lock`. -* **`ruff-check`**: A linter to check for common errors and style issues. -* **`ruff-format`**: A code formatter to ensure a consistent code style. -* **`uv-lock`**: A hook to keep the `uv.lock` file up to date. +Run them manually with: -Before committing, please ensure that the pre-commit hooks pass. You can run them manually on all files with `pre-commit run --all-files`. +```bash +pre-commit run --all-files +``` ## Documentation -The documentation for this project is built using `quartodoc`. The documentation is automatically built and deployed via GitHub Actions. There is no need to build the documentation manually. +Documentation is built with Great Docs and Quarto. + +- Great Docs configuration: `great-docs.yml` +- Narrative guides: `user_guide/` +- Documentation dependencies: the `docs` dependency group in `pyproject.toml` +- Local build: `uv sync --group docs` followed by `uv run great-docs build` +- Pull requests receive a rendered preview under the repository's GitHub Pages site. +- Merges to `main` publish the production documentation automatically. + +Great Docs requires Python 3.11 or newer for documentation builds. This does not change the package's Python >=3.9 runtime support. ## Type Checking -This project uses `ty` for type checking. To check for type errors, run the following command: +This project uses `ty` for type checking. Run: ```bash uv run ty check src tests diff --git a/README.md b/README.md index 8582ab8c..53e240ce 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,17 @@ `rtichoke` is a Python library for visualizing the performance of predictive models. It provides a flexible and intuitive way to create a variety of common evaluation plots, including: -* **ROC Curves** -* **Precision-Recall Curves** -* **Gains and Lift Charts** -* **Decision Curves** +* **ROC Curves** +* **Precision-Recall Curves** +* **Gains and Lift Charts** +* **Calibration Curves** +* **Decision Curves** -The library is designed to be easy to use, while still offering a high degree of control over the final plots. For some reproducible examples please visit the [rtichoke blog](https://uriahf.github.io/rtichoke-py/blog.html)! +The library is designed to be easy to use while still offering a high degree of control over the final plots. ## Installation -You can install `rtichoke` from PyPI: +Install `rtichoke` from PyPI: ```bash pip install rtichoke @@ -19,27 +20,27 @@ pip install rtichoke ## Getting Started -To use `rtichoke`, you'll need two main inputs: +To use `rtichoke`, you'll usually need two main inputs: -* `probs`: A dictionary containing your model's predicted probabilities. -* `reals`: A dictionary of the true binary outcomes. +* `probs`: A dictionary containing model-predicted probabilities. +* `reals`: A dictionary containing the observed outcomes. -Here's a quick example of how to create a ROC curve for a single model: +Here's a quick example of creating a ROC curve for a single model: ```python import numpy as np import rtichoke as rk -# Sample data for a model. Note that the probabilities for the -# positive class (1) are generally higher than for the negative class (0). -probs = {'Model A': np.array([0.1, 0.9, 0.4, 0.8, 0.3, 0.7, 0.2, 0.6])} -reals = {'Population': np.array([0, 1, 0, 1, 0, 1, 0, 1])} +probs = { + "Model A": np.array([0.1, 0.9, 0.4, 0.8, 0.3, 0.7, 0.2, 0.6]) +} +reals = { + "Population": np.array([0, 1, 0, 1, 0, 1, 0, 1]) +} - -# Create the ROC curve fig = rk.create_roc_curve( - probs=probs, - reals=reals + probs=probs, + reals=reals, ) fig.show() @@ -47,11 +48,13 @@ fig.show() ## Key Features -* **Simple API**: Create complex visualizations with just a few lines of code. -* **Time-to-Event Analysis**: Native support for models with time-dependent outcomes, including censoring and competing risks. -* **Interactive Plots**: Built on Plotly for interactive, publication-quality figures. -* **Flexible Data Handling**: Works seamlessly with NumPy and Polars. +* **Simple API**: Create complex visualizations with a small amount of code. +* **Time-to-Event Analysis**: Support for time-dependent outcomes, including censoring and competing risks. +* **Interactive Plots**: Plotly-based interactive visualizations. +* **Flexible Data Handling**: Works with common Python array/data-frame workflows, including NumPy and Polars. ## Documentation -For a complete guide to the library, including a "Getting Started" tutorial and a full API reference, please see the **[official documentation](https://uriahf.github.io/rtichoke-py/)**. +The official documentation, including the Getting Started guide and API reference, is published at: + +https://uriahf.github.io/rtichoke_python/ diff --git a/docs/fonts/Fraunces9pt-Light.woff2 b/assets/fonts/Fraunces9pt-Light.woff2 similarity index 100% rename from docs/fonts/Fraunces9pt-Light.woff2 rename to assets/fonts/Fraunces9pt-Light.woff2 diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 3147a4d5..00000000 --- a/docs/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/.quarto/ - -**/*.quarto_ipynb -_sidebar.yml \ No newline at end of file diff --git a/docs/_brand.yml b/docs/_brand.yml deleted file mode 100644 index f9f51aa5..00000000 --- a/docs/_brand.yml +++ /dev/null @@ -1,45 +0,0 @@ -color: - palette: - white: "#fff7f5" - black: "#000000" - navbar-bg: white # alias - navbar-fg: "#c54b29" - navbar-hl: "#c54b29" - primary: "#FEF0EC" - link: "#ce3d15" - background: white - foreground: black - primary: primary - light: white - dark: black - danger: navbar-fg - link: link - -typography: - fonts: - - source: file - family: Fraunces9pt-Light - files: - - fonts/Fraunces9pt-Light.woff2 - - family: Fraunces - source: google - weight: [400, 700] - style: [normal, italic] - - family: Commissioner - source: google - weight: [400, 600] - style: [normal, italic] - base: - family: Commissioner - size: 16px - weight: 400 - line-height: 1.5 - headings: - family: Fraunces9pt-Light - weight: 700 - style: normal - line-height: 1.2 - link: - color: link - weight: 600 - decoration: underline diff --git a/docs/_quarto.yml b/docs/_quarto.yml deleted file mode 100644 index 82c745e0..00000000 --- a/docs/_quarto.yml +++ /dev/null @@ -1,43 +0,0 @@ -project: - type: website - -website: - title: "rtichoke" - sidebar: - - id: user-guide - title: "User Guide" - style: "docked" - contents: - - text: "Getting Started" - href: tutorials/getting_started.qmd - - id: api-reference - title: "API Reference" - style: "docked" - contents: - - href: reference/index.qmd - text: "Reference" - -quartodoc: - package: rtichoke - sections: - - title: Performance Data - desc: Functions for creating performance data. - contents: - - prepare_performance_data - - prepare_performance_data_times - - title: Discrimination - desc: Functions for Discrimination. - contents: - - create_roc_curve - - create_precision_recall_curve - - create_gains_curve - - create_lift_curve - - plot_roc_curve - - plot_precision_recall_curve - - plot_gains_curve - - plot_lift_curve - - title: Utility - desc: Functions for Utility. - contents: - - create_decision_curve - - plot_decision_curve diff --git a/docs/dcurves_example.py b/docs/dcurves_example.py deleted file mode 100644 index e5c3a43b..00000000 --- a/docs/dcurves_example.py +++ /dev/null @@ -1,724 +0,0 @@ -import marimo - -__generated_with = "0.14.7" -app = marimo.App(width="columns") - - -@app.cell(column=0) -def _(): - from dcurves import dca - import pandas as pd - import numpy as np - import lifelines - import plotly.express as px - import polars as pl - from rtichoke.helpers.sandbox_observable_helpers import ( - create_list_data_to_adjust, - create_adjusted_data, - create_aj_data_combinations, - cast_and_join_adjusted_data, - create_breaks_values, - ) - - df_time_to_cancer_dx = pd.read_csv( - "https://raw.githubusercontent.com/ddsjoberg/dca-tutorial/main/data/df_time_to_cancer_dx.csv" - ) - return ( - cast_and_join_adjusted_data, - create_adjusted_data, - create_aj_data_combinations, - create_breaks_values, - create_list_data_to_adjust, - dca, - df_time_to_cancer_dx, - lifelines, - np, - pl, - px, - ) - - -@app.cell -def _(df_time_to_cancer_dx, lifelines): - cph = lifelines.CoxPHFitter() - cph.fit( - df=df_time_to_cancer_dx, - duration_col="ttcancer", - event_col="cancer", - formula="age + famhistory + marker", - ) - - cph_pred_vals = cph.predict_survival_function( - df_time_to_cancer_dx[["age", "famhistory", "marker"]], times=[1.5] - ) - - df_time_to_cancer_dx["pr_failure18"] = [1 - val for val in cph_pred_vals.iloc[0, :]] - return - - -@app.cell -def _(df_time_to_cancer_dx): - (df_time_to_cancer_dx["pr_failure18"] >= 0.5).sum() - return - - -@app.cell -def _(df_time_to_cancer_dx): - df_time_to_cancer_dx - return - - -@app.cell -def _(): - outcome = "cancer" - time_to_outcome_col = "ttcancer" - prevalence = None - time = 1.5 - return outcome, prevalence, time, time_to_outcome_col - - -@app.cell -def _(df_time_to_cancer_dx): - (df_time_to_cancer_dx["pr_failure18"] >= 0.5).sum() - return - - -@app.cell -def _(df_time_to_cancer_dx, outcome, time_to_outcome_col): - from dcurves.risks import _create_risks_df - - risks_df = _create_risks_df( - data=df_time_to_cancer_dx, - outcome=outcome, - time=1.5, - time_to_outcome_col=time_to_outcome_col, - ) - - risks_df - return (risks_df,) - - -@app.cell -def _(risks_df): - risks_df["pr_failure18"].hist() - return - - -@app.cell -def _(risks_df): - (risks_df["pr_failure18"] >= 0.5).sum() - return - - -@app.cell -def _(df_time_to_cancer_dx, risks_df): - import plotly.graph_objects as go - - x = risks_df["pr_failure18"] - y = df_time_to_cancer_dx["pr_failure18"] - cancer = risks_df["cancer"] - - fig_test = go.Figure() - - # Cancer = 0 (circle) - fig_test.add_trace( - go.Scatter( - x=x[cancer == 0], - y=y[cancer == 0], - mode="markers", - marker=dict(symbol="circle", size=8, opacity=0.6), - name="Cancer = 0", - ) - ) - - # Cancer = 1 (square) - fig_test.add_trace( - go.Scatter( - x=x[cancer == 1], - y=y[cancer == 1], - mode="markers", - marker=dict(symbol="square", size=8, opacity=0.6), - name="Cancer = 1", - ) - ) - - fig_test.update_layout( - title="Comparison of pr_failure18 across DataFrames", - xaxis_title="risks_df['pr_failure18']", - yaxis_title="df_time_to_cancer_dx['pr_failure18']", - template="plotly_white", - ) - - fig_test.show() - return - - -@app.cell -def _(risks_df): - from dcurves.risks import _rectify_model_risk_boundaries - - modelnames = ["pr_failure18"] - - rectified_risks_df = _rectify_model_risk_boundaries( - risks_df=risks_df, modelnames=modelnames - ) - - rectified_risks_df - return modelnames, rectified_risks_df - - -@app.cell -def _(outcome, prevalence, rectified_risks_df, time, time_to_outcome_col): - from dcurves.prevalence import _calc_prevalence - - prevalence_value = _calc_prevalence( - risks_df=rectified_risks_df, - outcome=outcome, - prevalence=prevalence, - time=time, - time_to_outcome_col=time_to_outcome_col, - ) - - prevalence_value - return (prevalence_value,) - - -@app.cell -def _(modelnames, np, prevalence_value, rectified_risks_df): - from dcurves.dca import _create_initial_df - - thresholds = np.arange(0, 1, 0.5) - - initial_df = _create_initial_df( - thresholds=thresholds, - modelnames=modelnames, - input_df_rownum=len(rectified_risks_df.index), - prevalence_value=prevalence_value, - ) - - initial_df - return initial_df, thresholds - - -@app.cell -def _(outcome, risks_df, thresholds, time, time_to_outcome_col): - from dcurves.dca import _calc_risk_rate_among_test_pos - - risk_rate_among_test_pos = _calc_risk_rate_among_test_pos( - risks_df=risks_df, - outcome=outcome, - model="pr_failure18", - thresholds=thresholds, - time_to_outcome_col=time_to_outcome_col, - time=time, - ) - - risk_rate_among_test_pos - return - - -@app.cell -def _( - outcome, - prevalence_value, - risks_df, - thresholds, - time, - time_to_outcome_col, -): - from dcurves.dca import _calc_test_pos_rate, _calc_tp_rate - - test_pos_rate = _calc_test_pos_rate( - risks_df=risks_df, thresholds=thresholds, model="pr_failure18" - ) - - print("test positive rate:", test_pos_rate) - - tp_rate = _calc_tp_rate( - risks_df=risks_df, - thresholds=thresholds, - model="pr_failure18", - outcome=outcome, - time=time, - time_to_outcome_col=time_to_outcome_col, - test_pos_rate=test_pos_rate, - prevalence_value=prevalence_value, - ) - - print("true positive rate:", tp_rate) - return - - -@app.cell -def _( - initial_df, - outcome, - prevalence_value, - rectified_risks_df, - thresholds, - time, - time_to_outcome_col, -): - from dcurves.dca import _calc_initial_stats - - initial_stats_df = _calc_initial_stats( - initial_df=initial_df, - risks_df=rectified_risks_df, - thresholds=thresholds, - outcome=outcome, - prevalence_value=prevalence_value, - time=time, - time_to_outcome_col=time_to_outcome_col, - ) - - initial_stats_df - return - - -@app.cell -def _(rectified_risks_df): - rectified_risks_df - return - - -@app.cell -def _(df_time_to_cancer_dx): - probs_dict = {"full": df_time_to_cancer_dx["pr_failure18"]} - - reals_mapping = { - "censor": 0, - "diagnosed with cancer": 1, - "dead other causes": 2, - } - - reals_dict = df_time_to_cancer_dx["cancer_cr"].map(reals_mapping) - - times_dict = df_time_to_cancer_dx["ttcancer"] - - df_time_to_cancer_dx["cancer_enum"] = reals_dict - - df_time_to_cancer_dx - return probs_dict, reals_dict, times_dict - - -@app.cell -def _(dca, df_time_to_cancer_dx, np): - stdca_coxph_results_composite = dca( - data=df_time_to_cancer_dx, - outcome="cancer_enum", - modelnames=["pr_failure18"], - # thresholds=np.arange(0, 0.51, 0.1), - # thresholds=np.arange(0.5, 1, 0.1), - thresholds=np.arange(0, 1, 0.5), - time=1.5, - time_to_outcome_col="ttcancer", - ) - - stdca_coxph_results_composite - return - - -@app.cell -def _(create_aj_data_combinations, create_breaks_values, probs_dict): - stratified_by = ["probability_threshold"] - # stratified_by = ["probability_threshold"] - # stratified_by = ["ppcr"] - # stratified_by = ["probability_threshold"] - - by = 0.01 - breaks = create_breaks_values(None, "probability_threshold", by) - - heuristics_sets = [ - { - "censoring_heuristic": "adjusted", - "competing_heuristic": "adjusted_as_negative", - }, - { - "censoring_heuristic": "adjusted", - "competing_heuristic": "adjusted_as_censored", - }, - { - "censoring_heuristic": "adjusted", - "competing_heuristic": "adjusted_as_composite", - }, - ] - - aj_data_combinations = create_aj_data_combinations( - list(probs_dict.keys()), - heuristics_sets, - fixed_time_horizons=[1.5], - stratified_by=stratified_by, - by=by, - breaks=breaks, - ) - - aj_data_combinations - return aj_data_combinations, breaks, by, heuristics_sets, stratified_by - - -@app.cell -def _( - aj_data_combinations, - by, - create_list_data_to_adjust, - probs_dict, - reals_dict, - stratified_by, - times_dict, -): - list_data_to_adjust_polars = create_list_data_to_adjust( - aj_data_combinations, - probs_dict, - reals_dict, - times_dict, - stratified_by=stratified_by, - by=by, - ) - list_data_to_adjust_polars - return (list_data_to_adjust_polars,) - - -@app.cell -def _( - breaks, - create_adjusted_data, - heuristics_sets, - list_data_to_adjust_polars, - stratified_by, -): - adjusted_data = create_adjusted_data( - list_data_to_adjust_polars, - heuristics_sets=heuristics_sets, - fixed_time_horizons=[1.5], - breaks=breaks, - stratified_by=stratified_by, - # risk_set_scope=["within_stratum"]#, # , , - # risk_set_scope=["pooled_by_cutoff"], # , # , , - risk_set_scope=["pooled_by_cutoff", "within_stratum"], # , , - ) - - adjusted_data - return (adjusted_data,) - - -@app.cell -def _(adjusted_data, aj_data_combinations, cast_and_join_adjusted_data): - final_adjusted_data_polars = cast_and_join_adjusted_data( - aj_data_combinations, adjusted_data - ) - - final_adjusted_data_polars - return (final_adjusted_data_polars,) - - -@app.cell -def _(final_adjusted_data_polars): - final_adjusted_data_polars - return - - -@app.cell -def _(final_adjusted_data_polars): - from rtichoke.helpers.sandbox_observable_helpers import ( - _calculate_cumulative_aj_data, - ) - - cumulative_aj_data = _calculate_cumulative_aj_data(final_adjusted_data_polars) - - cumulative_aj_data - return (cumulative_aj_data,) - - -@app.cell -def _(): - return - - -@app.cell -def _(cumulative_aj_data): - from rtichoke.helpers.sandbox_observable_helpers import ( - _turn_cumulative_aj_to_performance_data, - ) - - performance_data = _turn_cumulative_aj_to_performance_data(cumulative_aj_data) - - performance_data - return (performance_data,) - - -@app.cell -def _(performance_data): - from rtichoke.discrimination.gains import plot_gains_curve - - plot_gains_curve(performance_data) - return - - -@app.cell -def _(performance_data, pl): - performance_data_with_nb_calculated = ( - performance_data.with_columns( - ( - (pl.col("true_positives") / pl.col("n")) - - (pl.col("false_positives") / pl.col("n")) - * pl.col("chosen_cutoff") - / (1 - pl.col("chosen_cutoff")) - ).alias("net_benefit") - ) - .filter( - pl.col("censoring_heuristic") == "adjusted", - pl.col("competing_heuristic") == "adjusted_as_censored", - ) - .sort(pl.col("chosen_cutoff")) - ) - - performance_data_with_nb_calculated - return - - -@app.cell -def _(dca, df_time_to_cancer_dx, np): - stdca_coxph_results = dca( - data=df_time_to_cancer_dx, - outcome="cancer", - modelnames=["pr_failure18"], - thresholds=np.arange(0, 0.51, 0.01), - time=1.5, - time_to_outcome_col="ttcancer", - ) - - stdca_coxph_results - return (stdca_coxph_results,) - - -@app.cell -def _(px, stdca_coxph_results): - # Create plotly express figure - fig = px.line( - stdca_coxph_results, - x="threshold", - y="net_benefit", - color="model", - markers=True, - title="Decision Curve Analysis", - labels={ - "threshold": "Threshold Probability", - "net_benefit": "Net Benefit", - }, - ) - - # Update layout to match rtichoke look - fig.update_layout( - template="simple_white", - title_font_size=20, - title_x=0.5, - legend_title_text="", - legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), - margin=dict(l=40, r=40, t=60, b=40), - xaxis=dict(range=[-0.01, 0.23], showgrid=False, tickmode="linear", dtick=0.05), - yaxis=dict( - range=[-0.01, 0.23], - showgrid=False, - zeroline=True, - zerolinewidth=1, - zerolinecolor="gray", - ), - ) - - fig.show() - return - - -@app.cell(column=1, hide_code=True) -def _(): - import marimo as mo - - fill_color_radio = mo.ui.radio( - options=["classification_outcome", "reals_labels"], - value="classification_outcome", - label="Fill Colors", - ) - - fill_color_radio - return fill_color_radio, mo - - -@app.cell(hide_code=True) -def _(mo): - risk_set_scope_radio = mo.ui.radio( - options=["pooled_by_cutoff", "within_stratum"], - value="pooled_by_cutoff", - label="Risk Set Scope", - ) - - risk_set_scope_radio - return (risk_set_scope_radio,) - - -@app.cell(hide_code=True) -def _(mo): - stratified_by_radio = mo.ui.radio( - options=["probability_threshold"], - value="probability_threshold", - label="Stratified By", - ) - - stratified_by_radio - return (stratified_by_radio,) - - -@app.cell(hide_code=True) -def _(mo): - censoring_heuristic_radio = mo.ui.radio( - options=["adjusted"], - value="adjusted", - label="Censoring Heuristic", - ) - - censoring_heuristic_radio - return (censoring_heuristic_radio,) - - -@app.cell(hide_code=True) -def _(mo): - competing_heuristic_radio = mo.ui.radio( - options=[ - "adjusted_as_negative", - "adjusted_as_censored", - "adjusted_as_composite", - ], - value="adjusted_as_negative", - label="Competing Heuristic", - ) - - competing_heuristic_radio - return (competing_heuristic_radio,) - - -@app.cell(hide_code=True) -def _(by, mo): - slider_cutoff = mo.ui.slider(start=0, stop=1, step=by, label="Cutoff") - slider_cutoff - return (slider_cutoff,) - - -@app.cell(column=2, hide_code=True) -def _( - by, - censoring_heuristic_radio, - competing_heuristic_radio, - fill_color_radio, - final_adjusted_data_polars, - pl, - px, - risk_set_scope_radio, - slider_cutoff, - stratified_by_radio, -): - chosen_cutoff_data = final_adjusted_data_polars.filter( - pl.col("censoring_heuristic") == censoring_heuristic_radio.value, - pl.col("competing_heuristic") == competing_heuristic_radio.value, - pl.col("chosen_cutoff") == slider_cutoff.value, - pl.col("risk_set_scope") == risk_set_scope_radio.value, - pl.col("stratified_by") == stratified_by_radio.value, - ).sort(pl.col("strata")) - - color_discrete_map = { - "real_positives": "#4C5454", - "real_competing": "#C880B7", - "real_negatives": "#E0E0E0", - "real_censored": "#E3F09B", - "true_negatives": "#009e73", - "true_positives": "#009e73", - "false_negatives": "#FAC8CD", - "false_positives": "#FAC8CD", - } - - fig_new = px.bar( - chosen_cutoff_data, - x="mid_point", - y="reals_estimate", - color=fill_color_radio.value, - color_discrete_map=color_discrete_map, - # color="reals_labels", - # color_discrete_map=color_discrete_map, - category_orders={ - "reals_labels": list(color_discrete_map.keys()) - }, # fixes domain order - hover_data=chosen_cutoff_data.columns, # like tip: true - ) - - fig_new.update_layout( - barmode="stack", # stacked bars (use "group" for side-by-side) - plot_bgcolor="rgba(0,0,0,0)", # transparent background - paper_bgcolor="rgba(0,0,0,0)", - legend=dict(title=""), - ) - - if stratified_by_radio.value == "probability_threshold": - vertical_line = slider_cutoff.value - else: - vertical_line = 1 - slider_cutoff.value + by / 2 - - fig_new.add_vline( - x=vertical_line, - line=dict(color="red", width=2, dash="dash"), - annotation_text=f"Cutoff: {slider_cutoff.value}", - annotation_position="top right", - ) - - fig_new - return - - -@app.cell(hide_code=True) -def _( - censoring_heuristic_radio, - competing_heuristic_radio, - performance_data, - pl, - px, - stratified_by_radio, -): - chosen_performance_data = performance_data.filter( - pl.col("censoring_heuristic") == censoring_heuristic_radio.value, - pl.col("competing_heuristic") == competing_heuristic_radio.value, - pl.col("stratified_by") == stratified_by_radio.value, - ).sort(pl.col("chosen_cutoff")) - - # Create plotly express figure - fig_rtichoke = px.line( - chosen_performance_data, - x="chosen_cutoff", - y="net_benefit", - markers=True, - title="Decision Curve Analysis", - labels={ - "threshold": "Threshold Probability", - "net_benefit": "Net Benefit", - }, - ) - - # Update layout to match rtichoke look - fig_rtichoke.update_layout( - template="simple_white", - title_font_size=20, - title_x=0.5, - legend_title_text="", - legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), - margin=dict(l=40, r=40, t=60, b=40), - xaxis=dict(showgrid=False, tickmode="linear", dtick=0.05), - yaxis=dict( - showgrid=False, - zeroline=True, - zerolinewidth=1, - zerolinecolor="gray", - ), - ) - - fig_rtichoke.show() - return - - -if __name__ == "__main__": - app.run() diff --git a/docs/index.qmd b/docs/index.qmd deleted file mode 100644 index ad09712f..00000000 --- a/docs/index.qmd +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: "rtichoke Documentation" ---- - -Welcome to the official documentation for `rtichoke`, a Python library for visualizing the performance of predictive models. - -## Getting Started - -If you're new to `rtichoke`, the best place to start is the **[Getting Started Tutorial](./tutorials/getting_started.qmd)**. It will walk you through the basics of installing the library, preparing your data, and creating your first plot. - -## API Reference - -For detailed information on the functions and classes provided by `rtichoke`, please refer to the **[API Reference](./reference/index.qmd)**. diff --git a/docs/line_ppcr_04.svg b/docs/line_ppcr_04.svg deleted file mode 100644 index e8c97843..00000000 --- a/docs/line_ppcr_04.svg +++ /dev/null @@ -1,766 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ๐Ÿ˜ท - - - - - - - - - - ๐Ÿ˜ท - - - - - - - - - - ๐Ÿ˜ท - - - - - - - - - - ๐Ÿ˜ท - - - - - - - - - - ๐Ÿ˜ท - - - - - - - - - - ๐Ÿ˜ท - - - - - - - - - - ๐Ÿ˜ฌ - - - - - - - - - - - - - - - - - - - ๐Ÿคข - - ๐Ÿ’Š - - - - - - - - - ๐Ÿคข - - - - - - - - - - ๐Ÿคจ - - ๐Ÿ’Š - - - - - - - - - ๐Ÿคข - - ๐Ÿ’Š - ๐Ÿ’Š - ๐Ÿ’Š - ๐Ÿ’Š - ๐Ÿ’Š - ๐Ÿ’Š - ๐Ÿ’Š - ๐Ÿ’Š - - diff --git a/docs/output/site_libs/bootstrap/bootstrap-icons.woff b/docs/output/site_libs/bootstrap/bootstrap-icons.woff deleted file mode 100644 index dbeeb055..00000000 Binary files a/docs/output/site_libs/bootstrap/bootstrap-icons.woff and /dev/null differ diff --git a/docs/reference/.gitignore b/docs/reference/.gitignore deleted file mode 100644 index 8a053231..00000000 --- a/docs/reference/.gitignore +++ /dev/null @@ -1 +0,0 @@ -**/*.qmd diff --git a/docs/reference/create_calibration_curve.qmd b/docs/reference/create_calibration_curve.qmd deleted file mode 100644 index 9a553109..00000000 --- a/docs/reference/create_calibration_curve.qmd +++ /dev/null @@ -1,25 +0,0 @@ -# create_calibration_curve { #rtichoke.create_calibration_curve } - -```python -create_calibration_curve( - probs, - reals, - calibration_type='discrete', - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Creates Calibration Curve - -Args: - probs (Dict[str, List[float]]): _description_ - reals (Dict[str, List[int]]): _description_ - calibration_type (str, optional): _description_. Defaults to "discrete". - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/create_decision_curve.qmd b/docs/reference/create_decision_curve.qmd deleted file mode 100644 index d476cca2..00000000 --- a/docs/reference/create_decision_curve.qmd +++ /dev/null @@ -1,33 +0,0 @@ -# create_decision_curve { #rtichoke.create_decision_curve } - -```python -create_decision_curve( - probs, - reals, - decision_type='conventional', - min_p_threshold=0, - max_p_threshold=1, - by=0.01, - stratified_by='probability_threshold', - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Create Decision Curve - -Args: - probs (Dict[str, List[float]]): _description_ - reals (Dict[str, List[int]]): _description_ - decision_type (str, optional): _description_. Defaults to "conventional". - min_p_threshold (float, optional): _description_. Defaults to 0. - max_p_threshold (float, optional): _description_. Defaults to 1. - by (float, optional): _description_. Defaults to 0.01. - stratified_by (str, optional): _description_. Defaults to "probability_threshold". - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/create_gains_curve.qmd b/docs/reference/create_gains_curve.qmd deleted file mode 100644 index 7515db39..00000000 --- a/docs/reference/create_gains_curve.qmd +++ /dev/null @@ -1,27 +0,0 @@ -# create_gains_curve { #rtichoke.create_gains_curve } - -```python -create_gains_curve( - probs, - reals, - by=0.01, - stratified_by='probability_threshold', - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Create Gains Curve - -Args: - probs (Dict[str, List[float]]): _description_ - reals (Dict[str, List[int]]): _description_ - by (float, optional): _description_. Defaults to 0.01. - stratified_by (str, optional): _description_. Defaults to "probability_threshold". - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/create_lift_curve.qmd b/docs/reference/create_lift_curve.qmd deleted file mode 100644 index 47b02a54..00000000 --- a/docs/reference/create_lift_curve.qmd +++ /dev/null @@ -1,27 +0,0 @@ -# create_lift_curve { #rtichoke.create_lift_curve } - -```python -create_lift_curve( - probs, - reals, - by=0.01, - stratified_by='probability_threshold', - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Create Lift Curve - -Args: - probs (Dict[str, List[float]]): _description_ - reals (Dict[str, List[int]]): _description_ - by (float, optional): _description_. Defaults to 0.01. - stratified_by (str, optional): _description_. Defaults to "probability_threshold". - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/create_precision_recall_curve.qmd b/docs/reference/create_precision_recall_curve.qmd deleted file mode 100644 index e8c89bf2..00000000 --- a/docs/reference/create_precision_recall_curve.qmd +++ /dev/null @@ -1,27 +0,0 @@ -# create_precision_recall_curve { #rtichoke.create_precision_recall_curve } - -```python -create_precision_recall_curve( - probs, - reals, - by=0.01, - stratified_by='probability_threshold', - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Create Precision Recall Curve - -Args: - probs (Dict[str, List[float]]): _description_ - reals (Dict[str, List[int]]): _description_ - by (float, optional): _description_. Defaults to 0.01. - stratified_by (str, optional): _description_. Defaults to "probability_threshold". - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/create_roc_curve.qmd b/docs/reference/create_roc_curve.qmd deleted file mode 100644 index 8e20904d..00000000 --- a/docs/reference/create_roc_curve.qmd +++ /dev/null @@ -1,27 +0,0 @@ -# create_roc_curve { #rtichoke.create_roc_curve } - -```python -create_roc_curve( - probs, - reals, - by=0.01, - stratified_by='probability_threshold', - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Create ROC Curve - -Args: - probs (Dict[str, List[float]]): _description_ - reals (Dict[str, List[int]]): _description_ - by (float, optional): _description_. Defaults to 0.01. - stratified_by (str, optional): _description_. Defaults to "probability_threshold". - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/index.qmd b/docs/reference/index.qmd deleted file mode 100644 index 0304c16e..00000000 --- a/docs/reference/index.qmd +++ /dev/null @@ -1,33 +0,0 @@ -# Function reference {.doc .doc-index} - -## Calibration - -Functions for Calibration. - -| | | -| --- | --- | -| [create_calibration_curve](create_calibration_curve.qmd#rtichoke.create_calibration_curve) | Creates Calibration Curve | - -## Discrimination - -Functions for Discrimination. - -| | | -| --- | --- | -| [create_roc_curve](create_roc_curve.qmd#rtichoke.create_roc_curve) | Create ROC Curve | -| [create_precision_recall_curve](create_precision_recall_curve.qmd#rtichoke.create_precision_recall_curve) | Create Precision Recall Curve | -| [create_gains_curve](create_gains_curve.qmd#rtichoke.create_gains_curve) | Create Gains Curve | -| [create_lift_curve](create_lift_curve.qmd#rtichoke.create_lift_curve) | Create Lift Curve | -| [plot_roc_curve](plot_roc_curve.qmd#rtichoke.plot_roc_curve) | Plot ROC Curve | -| [plot_precision_recall_curve](plot_precision_recall_curve.qmd#rtichoke.plot_precision_recall_curve) | Plot Precision Recall Curve | -| [plot_gains_curve](plot_gains_curve.qmd#rtichoke.plot_gains_curve) | Plot Gains Curve | -| [plot_lift_curve](plot_lift_curve.qmd#rtichoke.plot_lift_curve) | Plot Lift Curve | - -## Utility - -Functions for Utility. - -| | | -| --- | --- | -| [create_decision_curve](create_decision_curve.qmd#rtichoke.create_decision_curve) | Create Decision Curve | -| [plot_decision_curve](plot_decision_curve.qmd#rtichoke.plot_decision_curve) | Plot Decision Curve | \ No newline at end of file diff --git a/docs/reference/plot_decision_curve.qmd b/docs/reference/plot_decision_curve.qmd deleted file mode 100644 index 3a48f9a5..00000000 --- a/docs/reference/plot_decision_curve.qmd +++ /dev/null @@ -1,27 +0,0 @@ -# plot_decision_curve { #rtichoke.plot_decision_curve } - -```python -plot_decision_curve( - performance_data, - decision_type, - min_p_threshold=0, - max_p_threshold=1, - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Plot Decision Curve - -Args: - performance_data (DataFrame): _description_ - decision_type (str): _description_ - min_p_threshold (int, optional): _description_. Defaults to 0. - max_p_threshold (int, optional): _description_. Defaults to 1. - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/plot_gains_curve.qmd b/docs/reference/plot_gains_curve.qmd deleted file mode 100644 index c51ba693..00000000 --- a/docs/reference/plot_gains_curve.qmd +++ /dev/null @@ -1,21 +0,0 @@ -# plot_gains_curve { #rtichoke.plot_gains_curve } - -```python -plot_gains_curve( - performance_data, - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Plot Gains Curve - -Args: - performance_data (DataFrame): _description_ - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/plot_lift_curve.qmd b/docs/reference/plot_lift_curve.qmd deleted file mode 100644 index d93792f5..00000000 --- a/docs/reference/plot_lift_curve.qmd +++ /dev/null @@ -1,21 +0,0 @@ -# plot_lift_curve { #rtichoke.plot_lift_curve } - -```python -plot_lift_curve( - performance_data, - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Plot Lift Curve - -Args: - performance_data (DataFrame): _description_ - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/plot_precision_recall_curve.qmd b/docs/reference/plot_precision_recall_curve.qmd deleted file mode 100644 index ca511a20..00000000 --- a/docs/reference/plot_precision_recall_curve.qmd +++ /dev/null @@ -1,21 +0,0 @@ -# plot_precision_recall_curve { #rtichoke.plot_precision_recall_curve } - -```python -plot_precision_recall_curve( - performance_data, - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Plot Precision Recall Curve - -Args: - performance_data (DataFrame): _description_ - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/plot_roc_curve.qmd b/docs/reference/plot_roc_curve.qmd deleted file mode 100644 index 788f49e7..00000000 --- a/docs/reference/plot_roc_curve.qmd +++ /dev/null @@ -1,21 +0,0 @@ -# plot_roc_curve { #rtichoke.plot_roc_curve } - -```python -plot_roc_curve( - performance_data, - size=None, - color_values=None, - url_api='http://localhost:4242/', -) -``` - -Plot ROC Curve - -Args: - performance_data (DataFrame): _description_ - size (Optional[int], optional): _description_. Defaults to None. - color_values (List[str], optional): _description_. Defaults to None. - url_api (_type_, optional): _description_. Defaults to "http://localhost:4242/". - -Returns: - Figure: _description_ \ No newline at end of file diff --git a/docs/reference/rtichoke.qmd b/docs/reference/rtichoke.qmd deleted file mode 100644 index 016ad817..00000000 --- a/docs/reference/rtichoke.qmd +++ /dev/null @@ -1,4 +0,0 @@ -# rtichoke { #rtichoke.rtichoke } - -`rtichoke` - diff --git a/docs/small_data_example.py b/docs/small_data_example.py deleted file mode 100644 index ec5143f6..00000000 --- a/docs/small_data_example.py +++ /dev/null @@ -1,403 +0,0 @@ -import marimo - -__generated_with = "0.14.7" -app = marimo.App(width="columns") - - -@app.cell(column=0) -def _(): - import numpy as np - import polars as pl - import plotly.express as px - - from rtichoke.helpers.sandbox_observable_helpers import ( - create_breaks_values, - create_list_data_to_adjust, - create_adjusted_data, - create_aj_data_combinations, - cast_and_join_adjusted_data, - ) - - return ( - cast_and_join_adjusted_data, - create_adjusted_data, - create_aj_data_combinations, - create_breaks_values, - create_list_data_to_adjust, - np, - pl, - px, - ) - - -@app.cell -def _(np, pl): - probs_dict_test = { - "small_data_set": np.array( - [0.9, 0.85, 0.95, 0.88, 0.6, 0.7, 0.51, 0.2, 0.1, 0.33] - ) - } - reals_dict_test = [1, 1, 1, 1, 0, 2, 1, 2, 0, 1] - times_dict_test = [24.1, 9.7, 49.9, 18.6, 34.8, 14.2, 39.2, 46.0, 31.5, 4.3] - - data_to_adjust = pl.DataFrame( - { - "strata": np.repeat("small_data_test", 10), - # "probs": probs_test["test_data"], - "reals": reals_dict_test, - "times": times_dict_test, - } - ) - - data_to_adjust - return probs_dict_test, reals_dict_test, times_dict_test - - -@app.cell -def _(create_aj_data_combinations, create_breaks_values): - by = 0.2 - breaks = create_breaks_values(None, "probability_threshold", by) - stratified_by = ["probability_threshold", "ppcr"] - # stratified_by = ["probability_threshold"] - - fixed_time_horizons = [10.0, 20.0, 30.0, 40.0, 50.0] - - # stratified_by = ["ppcr"] - - heuristics_sets = [ - { - "censoring_heuristic": "excluded", - "competing_heuristic": "adjusted_as_negative", - }, - { - "censoring_heuristic": "excluded", - "competing_heuristic": "adjusted_as_composite", - }, - { - "censoring_heuristic": "excluded", - "competing_heuristic": "adjusted_as_censored", - }, - { - "censoring_heuristic": "adjusted", - "competing_heuristic": "adjusted_as_negative", - }, - { - "censoring_heuristic": "adjusted", - "competing_heuristic": "adjusted_as_censored", - }, - { - "censoring_heuristic": "adjusted", - "competing_heuristic": "adjusted_as_composite", - }, - { - "censoring_heuristic": "excluded", - "competing_heuristic": "excluded", - }, - { - "censoring_heuristic": "adjusted", - "competing_heuristic": "excluded", - }, - ] - - aj_data_combinations = create_aj_data_combinations( - ["small_data_set"], - heuristics_sets=heuristics_sets, - fixed_time_horizons=fixed_time_horizons, - stratified_by=stratified_by, - by=by, - breaks=breaks, - risk_set_scope=["pooled_by_cutoff", "within_stratum"], - ) - - # aj_data_combinations - - aj_data_combinations - return ( - aj_data_combinations, - breaks, - by, - fixed_time_horizons, - heuristics_sets, - stratified_by, - ) - - -@app.cell -def _( - aj_data_combinations, - by, - create_list_data_to_adjust, - probs_dict_test, - reals_dict_test, - stratified_by, - times_dict_test, -): - list_data_to_adjust_polars_probability_threshold = create_list_data_to_adjust( - aj_data_combinations, - probs_dict_test, - reals_dict_test, - times_dict_test, - stratified_by=stratified_by, - by=by, - ) - - list_data_to_adjust_polars_probability_threshold - return (list_data_to_adjust_polars_probability_threshold,) - - -@app.cell -def _( - breaks, - create_adjusted_data, - fixed_time_horizons, - heuristics_sets, - list_data_to_adjust_polars_probability_threshold, - stratified_by, -): - adjusted_data = create_adjusted_data( - list_data_to_adjust_polars_probability_threshold, - heuristics_sets=heuristics_sets, - fixed_time_horizons=fixed_time_horizons, - breaks=breaks, - stratified_by=stratified_by, - # risk_set_scope = ["pooled_by_cutoff"] - risk_set_scope=["pooled_by_cutoff", "within_stratum"], - ) - - adjusted_data - return (adjusted_data,) - - -@app.cell -def _(adjusted_data, aj_data_combinations, cast_and_join_adjusted_data): - final_adjusted_data_polars = cast_and_join_adjusted_data( - aj_data_combinations, adjusted_data - ) - - final_adjusted_data_polars - return (final_adjusted_data_polars,) - - -@app.cell -def _(final_adjusted_data_polars): - from rtichoke.helpers.sandbox_observable_helpers import ( - _calculate_cumulative_aj_data, - ) - - cumulative_aj_data = _calculate_cumulative_aj_data(final_adjusted_data_polars) - - cumulative_aj_data - return (cumulative_aj_data,) - - -@app.cell -def _(cumulative_aj_data): - from rtichoke.helpers.sandbox_observable_helpers import ( - _turn_cumulative_aj_to_performance_data, - ) - - performance_data = _turn_cumulative_aj_to_performance_data(cumulative_aj_data) - - performance_data - return - - -@app.cell(column=1) -def _(): - from rtichoke.performance_data.performance_data_times import ( - prepare_performance_data_times, - ) - - return (prepare_performance_data_times,) - - -@app.cell -def _( - fixed_time_horizons, - prepare_performance_data_times, - probs_dict_test, - reals_dict_test, - times_dict_test, -): - prepare_performance_data_times( - probs_dict_test, reals_dict_test, times_dict_test, fixed_time_horizons, by=0.1 - ) - return - - -@app.cell -def _(np): - probs_dict_test = { - "small_data_set": np.array( - [0.9, 0.85, 0.95, 0.88, 0.6, 0.7, 0.51, 0.2, 0.1, 0.33] - ) - } - reals_dict_test = [1, 1, 1, 1, 0, 2, 1, 2, 0, 1] - times_dict_test = [24.1, 9.7, 49.9, 18.6, 34.8, 14.2, 39.2, 46.0, 31.5, 4.3] - - fixed_time_horizons = [10.0, 20.0, 30.0, 40.0, 50.0] - - return ( - fixed_time_horizons, - probs_dict_test, - reals_dict_test, - times_dict_test, - ) - - -@app.cell(column=2, hide_code=True) -def _(mo): - fill_color_radio = mo.ui.radio( - options=["classification_outcome", "reals_labels"], - value="classification_outcome", - label="Fill Colors", - ) - - fill_color_radio - return (fill_color_radio,) - - -@app.cell(hide_code=True) -def _(mo): - risk_set_scope_radio = mo.ui.radio( - options=["pooled_by_cutoff", "within_stratum"], - value="pooled_by_cutoff", - label="Risk Set Scope", - ) - - risk_set_scope_radio - return (risk_set_scope_radio,) - - -@app.cell(hide_code=True) -def _(mo): - stratified_by_radio = mo.ui.radio( - options=["probability_threshold", "ppcr"], - value="probability_threshold", - label="Stratified By", - ) - - stratified_by_radio - return (stratified_by_radio,) - - -@app.cell(hide_code=True) -def _(by): - import marimo as mo - - slider_cutoff = mo.ui.slider(start=0, stop=1, step=by, label="Cutoff") - slider_cutoff - return mo, slider_cutoff - - -@app.cell(hide_code=True) -def _(mo): - fixed_time_horizons_slider = mo.ui.slider( - start=10, stop=50, step=10, label="Fixed Time Horizon" - ) - fixed_time_horizons_slider - return (fixed_time_horizons_slider,) - - -@app.cell(hide_code=True) -def _(mo): - censoring_heuristic_radio = mo.ui.radio( - options=["adjusted", "excluded"], - value="adjusted", - label="Censoring Heuristic", - ) - - censoring_heuristic_radio - return (censoring_heuristic_radio,) - - -@app.cell(hide_code=True) -def _(mo): - competing_heuristic_radio = mo.ui.radio( - options=[ - "adjusted_as_negative", - "adjusted_as_censored", - "adjusted_as_composite", - "excluded", - ], - value="adjusted_as_negative", - label="Censoring Heuristic", - ) - - competing_heuristic_radio - return (competing_heuristic_radio,) - - -@app.cell(column=3, hide_code=True) -def _( - by, - censoring_heuristic_radio, - competing_heuristic_radio, - fill_color_radio, - final_adjusted_data_polars, - fixed_time_horizons_slider, - pl, - px, - risk_set_scope_radio, - slider_cutoff, - stratified_by_radio, -): - chosen_cutoff_data = final_adjusted_data_polars.filter( - pl.col("censoring_heuristic") == censoring_heuristic_radio.value, - pl.col("competing_heuristic") == competing_heuristic_radio.value, - pl.col("chosen_cutoff") == slider_cutoff.value, - pl.col("fixed_time_horizon") == fixed_time_horizons_slider.value, - pl.col("risk_set_scope") == risk_set_scope_radio.value, - pl.col("stratified_by") == stratified_by_radio.value, - ).sort(pl.col("strata")) - - color_discrete_map = { - "real_positives": "#4C5454", - "real_competing": "#C880B7", - "real_negatives": "#E0E0E0", - "real_censored": "#E3F09B", - "true_negatives": "#009e73", - "true_positives": "#009e73", - "false_negatives": "#FAC8CD", - "false_positives": "#FAC8CD", - } - - fig_new = px.bar( - chosen_cutoff_data, - x="mid_point", - y="reals_estimate", - color=fill_color_radio.value, - color_discrete_map=color_discrete_map, - # color="reals_labels", - # color_discrete_map=color_discrete_map, - category_orders={ - "reals_labels": list(color_discrete_map.keys()) - }, # fixes domain order - hover_data=chosen_cutoff_data.columns, # like tip: true - ) - - fig_new.update_layout( - barmode="stack", # stacked bars (use "group" for side-by-side) - plot_bgcolor="rgba(0,0,0,0)", # transparent background - paper_bgcolor="rgba(0,0,0,0)", - legend=dict(title=""), - ) - - if stratified_by_radio.value == "probability_threshold": - vertical_line = slider_cutoff.value - else: - vertical_line = 1 - slider_cutoff.value + by / 2 - - fig_new.add_vline( - x=vertical_line, - line=dict(color="red", width=2, dash="dash"), - annotation_text=f"Cutoff: {slider_cutoff.value}", - annotation_position="top right", - ) - - fig_new - return - - -if __name__ == "__main__": - app.run() diff --git a/docs/tutorials/getting_started.qmd b/docs/tutorials/getting_started.qmd deleted file mode 100644 index fe26b0d7..00000000 --- a/docs/tutorials/getting_started.qmd +++ /dev/null @@ -1,101 +0,0 @@ ---- -title: "Getting Started with rtichoke" ---- - -This tutorial provides an introduction to the `rtichoke` library, showing how to visualize model performance for different scenarios. - -## 1. Import Libraries - -First, let's import the necessary libraries. We'll need `numpy` for data manipulation and `rtichoke` for the core functionality. - -```python -import numpy as np -import rtichoke as rk -``` - -## 2. Understanding the Inputs - -`rtichoke` expects two main inputs for creating performance curves: - -* **`probs` (Probabilities)**: A dictionary where keys are model or population names and values are lists or NumPy arrays of predicted probabilities. -* **`reals` (Outcomes)**: A dictionary where keys are population names and values are lists or NumPy arrays of the true binary outcomes (0 or 1). - -Let's look at the three main use cases. - -### Use Case 1: Single Model - -This is the simplest case, where you want to evaluate the performance of a single predictive model. - -For this, you provide `probs` with a single entry for your model and `reals` with a single entry for the corresponding outcomes. - -```python -# Sample data for a model. Note that the probabilities for the -# positive class (1) are generally higher than for the negative class (0). -probs_single = {"Model A": np.array([0.1, 0.9, 0.4, 0.8, 0.3, 0.7, 0.2, 0.6])} -reals_single = {"Population": np.array([0, 1, 0, 1, 0, 1, 0, 1])} - -# Create a ROC curve -fig = rk.create_roc_curve( - probs=probs_single, - reals=reals_single, -) - -# In an interactive environment (like a Jupyter notebook), -# this will display the plot. -fig.show() -``` - -### Use Case 2: Models Comparison - -Often, you want to compare the performance of several different models on the *same* population. - -For this, you provide `probs` with an entry for each model you want to compare. `reals` will still have a single entry, since the outcome data is the same for all models. - -```python -# Sample data for two models. Model A is better at separating the classes. -probs_comparison = { - "Model A": np.array([0.1, 0.9, 0.2, 0.8, 0.3, 0.7]), - "Model B": np.array([0.2, 0.8, 0.3, 0.7, 0.4, 0.6]), - "Random Guess": np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5]) -} -reals_comparison = {"Population": np.array([0, 1, 0, 1, 0, 1])} - - -# Create a precision-recall curve to compare the models -fig = rk.create_precision_recall_curve( - probs=probs_comparison, - reals=reals_comparison, -) - -fig.show() -``` - -### Use Case 3: Several Populations - -This is useful when you want to evaluate a single model's performance across different populations. A common example is comparing performance on a training set versus a testing set to check for overfitting. - -For this, you provide `probs` with an entry for each population and `reals` with a corresponding entry for each population's outcomes. - -```python -# Sample data for a train and test set. -# The model performs slightly better on the train set. -probs_populations = { - "Train": np.array([0.1, 0.9, 0.2, 0.8, 0.3, 0.7]), - "Test": np.array([0.2, 0.8, 0.3, 0.7, 0.4, 0.6]) -} -reals_populations = { - "Train": np.array([0, 1, 0, 1, 0, 1]), - "Test": np.array([0, 1, 0, 1, 0, 0]) # Note one outcome is different -} - -# Create a calibration curve to compare the model's performance -# on the two populations. -fig = rk.create_calibration_curve( - probs=probs_populations, - reals=reals_populations, -) - -fig.show() -``` - -And that's it! You've now seen how to create three of the most common evaluation plots with `rtichoke`. From here, you can explore the other curve types and options that the library has to offer in the [API Reference](../reference/index.qmd). diff --git a/docs/walkthrough_aj_estimate.py b/docs/walkthrough_aj_estimate.py deleted file mode 100644 index dc70c321..00000000 --- a/docs/walkthrough_aj_estimate.py +++ /dev/null @@ -1,442 +0,0 @@ -import marimo - -__generated_with = "0.14.7" -app = marimo.App(width="columns") - - -@app.cell(column=0) -def _(): - import polars as pl - import pandas as pd - import plotly.express as px - from lifelines import CoxPHFitter, WeibullAFTFitter - - df_time_to_cancer_dx = pd.read_csv( - "https://raw.githubusercontent.com/ddsjoberg/dca-tutorial/main/data/df_time_to_cancer_dx.csv" - ) - return CoxPHFitter, WeibullAFTFitter, df_time_to_cancer_dx, pl, px - - -@app.cell -def _(CoxPHFitter, WeibullAFTFitter, df_time_to_cancer_dx): - cph = CoxPHFitter() - thin_model = CoxPHFitter() - aft_model = WeibullAFTFitter() - - cox_formula = "age + famhistory + marker" - thin_formula = "age + marker" - aft_formula = "age + marker" - - cph.fit( - df_time_to_cancer_dx, - duration_col="ttcancer", - event_col="cancer", - formula=cox_formula, - ) - - thin_model.fit( - df_time_to_cancer_dx, - duration_col="ttcancer", - event_col="cancer", - formula=thin_formula, - ) - - aft_model.fit( - df_time_to_cancer_dx, - duration_col="ttcancer", - event_col="cancer", - formula=aft_formula, - ) - - cph_pred_vals = ( - ( - 1 - - cph.predict_survival_function( - df_time_to_cancer_dx[["age", "famhistory", "marker"]], times=[1.5] - ) - ) - .iloc[0, :] - .values - ) - - thin_pred_vals = ( - ( - 1 - - thin_model.predict_survival_function( - df_time_to_cancer_dx[["age", "famhistory", "marker"]], times=[1.5] - ) - ) - .iloc[0, :] - .values - ) - - aft_pred_vals = ( - ( - 1 - - aft_model.predict_survival_function( - df_time_to_cancer_dx[["age", "famhistory", "marker"]], times=[1.5] - ) - ) - .iloc[0, :] - .values - ) - - print(type(cph_pred_vals)) - - probs_dict = { - "full": cph_pred_vals, - "thin": thin_pred_vals, - "aft": aft_pred_vals, - } - - reals_mapping = { - "censor": 0, - "diagnosed with cancer": 1, - "dead other causes": 2, - } - - reals_dict = df_time_to_cancer_dx["cancer_cr"].map(reals_mapping) - - times_dict = df_time_to_cancer_dx["ttcancer"] - return probs_dict, reals_dict, times_dict - - -@app.cell -def _(): - import marimo as mo - - return (mo,) - - -@app.cell -def _(probs_dict): - from rtichoke.helpers.sandbox_observable_helpers import ( - create_aj_data_combinations, - create_breaks_values, - ) - - stratified_by = ["probability_threshold", "ppcr"] - - # stratified_by = ["probability_threshold"] - # stratified_by = ["ppcr"] - - by = 0.1 - breaks = create_breaks_values(None, "probability_threshold", by) - # fixed_time_horizons = [1.0, 1.5, 3.0, 5.0] - fixed_time_horizons = [1.0, 3.0, 5.0] - stratified_by = stratified_by - - heuristics_sets = [ - { - "censoring_heuristic": "adjusted", - "competing_heuristic": "adjusted_as_negative", - }, - { - "censoring_heuristic": "excluded", - "competing_heuristic": "adjusted_as_negative", - }, - # { - # "censoring_assumption": "adjusted", - # "competing_assumption": "adjusted_as_censored", - # }, - # { - # "censoring_assumption": "excluded", - # "competing_assumption": "adjusted_as_censored", - # }, - # {"censoring_assumption": "adjusted", "competing_assumption": "excluded"}, - # {"censoring_assumption": "excluded", "competing_assumption": "excluded"}, - ] - - aj_data_combinations = create_aj_data_combinations( - list(probs_dict.keys()), - heuristics_sets, - fixed_time_horizons, - stratified_by, - by, - breaks, - ) - - aj_data_combinations - return ( - aj_data_combinations, - breaks, - by, - fixed_time_horizons, - heuristics_sets, - stratified_by, - ) - - -@app.cell -def _(aj_data_combinations): - aj_data_combinations - return - - -@app.cell -def _(mo): - mo.md(r"""## create list data to adjust polars""") - return - - -@app.cell -def _( - aj_data_combinations, - by, - probs_dict, - reals_dict, - stratified_by, - times_dict, -): - from rtichoke.helpers.sandbox_observable_helpers import ( - create_list_data_to_adjust, - create_adjusted_data, - cast_and_join_adjusted_data, - ) - - list_data_to_adjust_polars = create_list_data_to_adjust( - aj_data_combinations, - probs_dict, - reals_dict, - times_dict, - stratified_by=stratified_by, - by=by, - ) - - list_data_to_adjust_polars - return ( - cast_and_join_adjusted_data, - create_adjusted_data, - list_data_to_adjust_polars, - ) - - -@app.cell -def _( - breaks, - create_adjusted_data, - fixed_time_horizons, - heuristics_sets, - list_data_to_adjust_polars, - stratified_by, -): - adjusted_data = create_adjusted_data( - list_data_to_adjust_polars, - heuristics_sets=heuristics_sets, - fixed_time_horizons=fixed_time_horizons, - breaks=breaks, - stratified_by=stratified_by, - # stratified_by=["probability_threshold", "ppcr"] - risk_set_scope=["pooled_by_cutoff", "within_stratum"], - ) - - adjusted_data - return (adjusted_data,) - - -@app.cell -def _(adjusted_data, aj_data_combinations, cast_and_join_adjusted_data): - final_adjusted_data_polars = cast_and_join_adjusted_data( - aj_data_combinations, adjusted_data - ) - - final_adjusted_data_polars - return (final_adjusted_data_polars,) - - -@app.cell -def _(final_adjusted_data_polars): - final_adjusted_data_polars - return - - -@app.cell -def _(final_adjusted_data_polars): - from rtichoke.helpers.sandbox_observable_helpers import ( - _calculate_cumulative_aj_data, - ) - - cumulative_aj_data = _calculate_cumulative_aj_data(final_adjusted_data_polars) - - cumulative_aj_data - return (cumulative_aj_data,) - - -@app.cell -def _(cumulative_aj_data): - from rtichoke.helpers.sandbox_observable_helpers import ( - _turn_cumulative_aj_to_performance_data, - ) - - performance_data = _turn_cumulative_aj_to_performance_data(cumulative_aj_data) - - performance_data - return - - -@app.cell(column=1, hide_code=True) -def _(mo): - reference_group_radio = mo.ui.radio( - options=["full", "thin", "aft"], value="full", label="Model" - ) - - reference_group_radio - return (reference_group_radio,) - - -@app.cell(hide_code=True) -def _(mo): - fill_color_radio = mo.ui.radio( - options=["classification_outcome", "reals_labels"], - value="classification_outcome", - label="Fill Colors", - ) - - fill_color_radio - return (fill_color_radio,) - - -@app.cell(hide_code=True) -def _(mo): - risk_set_scope_radio = mo.ui.radio( - options=["pooled_by_cutoff", "within_stratum"], - value="pooled_by_cutoff", - label="Risk Set Scope", - ) - - risk_set_scope_radio - return (risk_set_scope_radio,) - - -@app.cell(hide_code=True) -def _(mo): - stratified_by_radio = mo.ui.radio( - options=["probability_threshold", "ppcr"], - value="probability_threshold", - label="Stratified By", - ) - - stratified_by_radio - return (stratified_by_radio,) - - -@app.cell(hide_code=True) -def _(by, mo): - slider_cutoff = mo.ui.slider(start=0, stop=1, step=by, label="Cutoff") - slider_cutoff - return (slider_cutoff,) - - -@app.cell(hide_code=True) -def _(mo): - fixed_time_horizons_slider = mo.ui.slider( - start=1, stop=5, step=2, label="Fixed Time Horizon" - ) - fixed_time_horizons_slider - return (fixed_time_horizons_slider,) - - -@app.cell(hide_code=True) -def _(mo): - competing_heuristic_radio = mo.ui.radio( - options=[ - "adjusted_as_negative", - "adjusted_as_censored", - "adjusted_as_composite", - "excluded", - ], - value="adjusted_as_negative", - label="Censoring Heuristic", - ) - - competing_heuristic_radio - return (competing_heuristic_radio,) - - -@app.cell(hide_code=True) -def _(mo): - censoring_heuristic_radio = mo.ui.radio( - options=["adjusted", "excluded"], - value="adjusted", - label="Censoring Heuristic", - ) - - censoring_heuristic_radio - return (censoring_heuristic_radio,) - - -@app.cell(column=2, hide_code=True) -def _( - by, - censoring_heuristic_radio, - competing_heuristic_radio, - fill_color_radio, - final_adjusted_data_polars, - fixed_time_horizons_slider, - pl, - px, - reference_group_radio, - risk_set_scope_radio, - slider_cutoff, - stratified_by_radio, -): - chosen_cutoff_data = final_adjusted_data_polars.filter( - pl.col("chosen_cutoff") == slider_cutoff.value, - pl.col("fixed_time_horizon") == fixed_time_horizons_slider.value, - pl.col("reference_group") == reference_group_radio.value, - pl.col("risk_set_scope") == risk_set_scope_radio.value, - pl.col("stratified_by") == stratified_by_radio.value, - pl.col("censoring_heuristic") == censoring_heuristic_radio.value, - pl.col("competing_heuristic") == competing_heuristic_radio.value, - ).sort(pl.col("strata")) - - color_discrete_map = { - "real_positives": "#4C5454", - "real_competing": "#C880B7", - "real_negatives": "#E0E0E0", - "real_censored": "#E3F09B", - "true_negatives": "#009e73", - "true_positives": "#009e73", - "false_negatives": "#FAC8CD", - "false_positives": "#FAC8CD", - } - - fig_new = px.bar( - chosen_cutoff_data, - x="mid_point", - y="reals_estimate", - color=fill_color_radio.value, - color_discrete_map=color_discrete_map, - # color="reals_labels", - # color_discrete_map=color_discrete_map, - category_orders={ - "reals_labels": list(color_discrete_map.keys()) - }, # fixes domain order - hover_data=chosen_cutoff_data.columns, # like tip: true - ) - - fig_new.update_layout( - barmode="stack", # stacked bars (use "group" for side-by-side) - plot_bgcolor="rgba(0,0,0,0)", # transparent background - paper_bgcolor="rgba(0,0,0,0)", - legend=dict(title=""), - ) - - if stratified_by_radio.value == "probability_threshold": - vertical_line = slider_cutoff.value - else: - vertical_line = 1 - slider_cutoff.value + by / 2 - - fig_new.add_vline( - x=vertical_line, - line=dict(color="red", width=2, dash="dash"), - annotation_text=f"Cutoff: {slider_cutoff.value}", - annotation_position="top right", - ) - - # fig_new - return - - -if __name__ == "__main__": - app.run() diff --git a/docs/walkthrough_aj_estimate_files/libs/bootstrap/bootstrap-icons.woff b/docs/walkthrough_aj_estimate_files/libs/bootstrap/bootstrap-icons.woff deleted file mode 100644 index dbeeb055..00000000 Binary files a/docs/walkthrough_aj_estimate_files/libs/bootstrap/bootstrap-icons.woff and /dev/null differ diff --git a/great-docs.yml b/great-docs.yml new file mode 100644 index 00000000..8dcb1d78 --- /dev/null +++ b/great-docs.yml @@ -0,0 +1,52 @@ +display_name: rtichoke +parser: numpy +repo: https://github.com/uriahf/rtichoke_python +user_guide: user_guide +homepage: user_guide +site_url: https://uriahf.github.io/rtichoke_python/ + +site: + css: site.css + +source: + enabled: true + branch: main + placement: usage + +reference: + - title: Performance Data + desc: Prepare classification and time-to-event data for visualization. + contents: + - prepare_performance_data + - prepare_binned_classification_data + - prepare_performance_data_times + - prepare_binned_classification_data_times + + - title: Discrimination + desc: ROC, precision-recall, gains, and lift visualizations. + contents: + - create_roc_curve + - create_roc_curve_times + - plot_roc_curve + - create_precision_recall_curve + - create_precision_recall_curve_times + - plot_precision_recall_curve + - create_gains_curve + - create_gains_curve_times + - plot_gains_curve + - create_lift_curve + - create_lift_curve_times + - plot_lift_curve + + - title: Calibration + desc: Calibration visualizations for classification and time-to-event models. + contents: + - create_calibration_curve + - create_calibration_curve_times + + - title: Utility + desc: Decision-curve analysis for classification and time-to-event models. + contents: + - create_decision_curve + - create_decision_curve_times + - plot_decision_curve diff --git a/pyproject.toml b/pyproject.toml index 5645f67d..43fd6ff8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,12 +37,17 @@ dev = [ "ty>=0.0.1a12", "scikit-learn>=1.6.1", "polarstate>=0.1.6", - "quartodoc>=0.11.1", +] +docs = [ + "great-docs", ] [tool.uv.workspace] members = ["rtichoke"] +[tool.uv.dependency-groups] +docs = {requires-python = ">=3.11"} + [build-system] requires = ["uv_build>=0.7.20,<0.8.0"] build-backend = "uv_build" diff --git a/site.css b/site.css new file mode 100644 index 00000000..14644969 --- /dev/null +++ b/site.css @@ -0,0 +1,184 @@ +@import url("https://fonts.googleapis.com/css2?family=Commissioner:wght@400;500;600&family=Fraunces:opsz,wght@9..144,600;9..144,700&display=swap"); + +@font-face { + font-family: "Fraunces9pt-Light"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("assets/fonts/Fraunces9pt-Light.woff2") format("woff2"); +} + +:root { + --rtichoke-bg: #fff7f5; + --rtichoke-surface: #ffffff; + --rtichoke-soft: #fef0ec; + --rtichoke-primary: #c54b29; + --rtichoke-accent: #ce3d15; + --rtichoke-border: #f0cfc3; + --rtichoke-text: #281d19; + --rtichoke-muted: #725f57; + --rtichoke-shadow: 0 10px 28px rgba(96, 46, 27, 0.07); +} + +body { + background: var(--rtichoke-bg); + color: var(--rtichoke-text); + font-family: "Commissioner", system-ui, sans-serif; + font-weight: 400; + letter-spacing: 0.005em; +} + +h1, +h2, +h3, +h4, +h5, +h6, +.navbar-brand, +.quarto-title-block .title, +.sidebar-title { + color: var(--rtichoke-primary); + font-family: "Fraunces9pt-Light", "Fraunces", Georgia, serif; + font-weight: 700; + letter-spacing: -0.015em; +} + +a { + color: var(--rtichoke-accent); + text-decoration-color: rgba(206, 61, 21, 0.35); + text-decoration-thickness: 0.1em; + text-underline-offset: 0.14em; +} + +a:hover, +a:focus-visible { + color: var(--rtichoke-primary); + text-decoration-color: currentColor; +} + +.navbar, +.navbar-dark, +.navbar-light { + background: rgba(255, 247, 245, 0.96) !important; + border-bottom: 1px solid var(--rtichoke-border); + box-shadow: 0 5px 18px rgba(96, 46, 27, 0.05); + backdrop-filter: blur(12px); +} + +.navbar-brand, +.navbar .nav-link, +.navbar .navbar-title { + color: var(--rtichoke-primary) !important; +} + +.navbar .nav-link:hover, +.navbar .nav-link:focus, +.navbar .nav-link.active { + color: var(--rtichoke-accent) !important; +} + +#quarto-sidebar, +.sidebar, +.quarto-secondary-nav { + background: var(--rtichoke-bg); + border-color: var(--rtichoke-border); +} + +.sidebar-item a, +.sidebar-navigation a { + border-radius: 0.55rem; +} + +.sidebar-item a:hover, +.sidebar-item a.active, +.sidebar-navigation a:hover, +.sidebar-navigation a.active { + background: var(--rtichoke-soft); + color: var(--rtichoke-accent) !important; +} + +.quarto-title-block, +.gd-group-card, +.gd-api-card, +.card, +.callout { + background: var(--rtichoke-surface); + border-color: var(--rtichoke-border) !important; + border-radius: 0.9rem; + box-shadow: var(--rtichoke-shadow); +} + +.quarto-title-block { + padding: 1.25rem 1.4rem; +} + +blockquote { + background: var(--rtichoke-soft); + border-left: 4px solid var(--rtichoke-primary); + border-radius: 0 0.7rem 0.7rem 0; + color: #523d35; + padding: 0.85rem 1.1rem; +} + +div.sourceCode, +pre, +code:not(.sourceCode) { + border-color: var(--rtichoke-border) !important; +} + +div.sourceCode, +pre { + background: #fffdfc; + border-radius: 0.75rem; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8); +} + +code:not(.sourceCode) { + background: var(--rtichoke-soft); + color: #8f321b; + border-radius: 0.35rem; +} + +.table, +table { + --bs-table-bg: transparent; + --bs-table-striped-bg: rgba(254, 240, 236, 0.72); + border-color: var(--rtichoke-border); +} + +.badge, +.btn-primary { + background-color: var(--rtichoke-primary) !important; + border-color: var(--rtichoke-primary) !important; +} + +.btn-outline-primary { + color: var(--rtichoke-primary); + border-color: var(--rtichoke-primary); +} + +.btn-outline-primary:hover { + background-color: var(--rtichoke-primary); + color: #fff; +} + +.page-footer, +footer.footer { + background: var(--rtichoke-surface); + border-top: 1px solid var(--rtichoke-border); + color: var(--rtichoke-muted); +} + +@media (prefers-color-scheme: dark) { + body.quarto-dark { + --rtichoke-bg: #211815; + --rtichoke-surface: #2d211d; + --rtichoke-soft: #3a2721; + --rtichoke-primary: #f29a7f; + --rtichoke-accent: #ffad91; + --rtichoke-border: #684235; + --rtichoke-text: #fff4f0; + --rtichoke-muted: #d5b9af; + --rtichoke-shadow: none; + } +} diff --git a/user_guide/00-getting-started.qmd b/user_guide/00-getting-started.qmd new file mode 100644 index 00000000..4e4f5567 --- /dev/null +++ b/user_guide/00-getting-started.qmd @@ -0,0 +1,92 @@ +--- +title: "Getting Started" +guide-section: "Getting Started" +--- + +`rtichoke` is a Python library for interactive visualization of predictive-model performance. It supports discrimination, calibration, utility, and time-to-event evaluation workflows. + +## Installation + +Install `rtichoke` from PyPI: + +```bash +pip install rtichoke +``` + +## Import + +```python +import numpy as np +import rtichoke as rk +``` + +## Inputs + +Most `rtichoke` plotting functions use two dictionaries: + +- `probs`: model predictions, keyed by model or population name. +- `reals`: observed outcomes, keyed by population name. + +## Single model + +```python +probs_single = { + "Model A": np.array([0.1, 0.9, 0.4, 0.8, 0.3, 0.7, 0.2, 0.6]) +} +reals_single = { + "Population": np.array([0, 1, 0, 1, 0, 1, 0, 1]) +} + +fig = rk.create_roc_curve( + probs=probs_single, + reals=reals_single, +) + +fig.show() +``` + +## Compare models + +When several models are evaluated on the same population, provide one probability vector per model and one outcome vector for the shared population. + +```python +probs_comparison = { + "Model A": np.array([0.1, 0.9, 0.2, 0.8, 0.3, 0.7]), + "Model B": np.array([0.2, 0.8, 0.3, 0.7, 0.4, 0.6]), + "Random Guess": np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5]), +} +reals_comparison = { + "Population": np.array([0, 1, 0, 1, 0, 1]) +} + +fig = rk.create_precision_recall_curve( + probs=probs_comparison, + reals=reals_comparison, +) + +fig.show() +``` + +## Compare populations + +To compare a model across populations, provide matching keys in `probs` and `reals`. + +```python +probs_populations = { + "Train": np.array([0.1, 0.9, 0.2, 0.8, 0.3, 0.7]), + "Test": np.array([0.2, 0.8, 0.3, 0.7, 0.4, 0.6]), +} +reals_populations = { + "Train": np.array([0, 1, 0, 1, 0, 1]), + "Test": np.array([0, 1, 0, 1, 0, 0]), +} + +fig = rk.create_calibration_curve( + probs=probs_populations, + reals=reals_populations, +) + +fig.show() +``` + +From here, use the API Reference for the full set of curve types, parameters, and time-to-event variants.