Metadata-Version: 2.4
Name: csh-model
Version: 0.1.2
Summary: Induced CSH classifier with pluggable relation-induction backends.
Author: Laplace Placiano
License-Expression: MIT
Keywords: machine-learning,classification,csh,sheaf,tabular
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.23
Requires-Dist: scikit-learn>=1.2
Requires-Dist: joblib>=1.2
Provides-Extra: torch
Requires-Dist: torch>=2.0; extra == "torch"
Provides-Extra: xgboost
Requires-Dist: xgboost>=1.7; extra == "xgboost"
Provides-Extra: pandas
Requires-Dist: pandas>=1.5; extra == "pandas"
Provides-Extra: viz
Requires-Dist: matplotlib>=3.7; extra == "viz"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Provides-Extra: all
Requires-Dist: torch>=2.0; extra == "all"
Requires-Dist: xgboost>=1.7; extra == "all"
Requires-Dist: pandas>=1.5; extra == "all"
Requires-Dist: matplotlib>=3.7; extra == "all"
Dynamic: license-file

# csh-model

Reusable Python package for the **induced CSH classifier** extracted from the original sepsis benchmark script and repackaged as a clean GitHub repo.

This is not a castrated toy version. The repo keeps the CSH model layer complete: rigorous PyTorch CSH classifier, singleton/diad/triad relation induction, Platt calibration, sensitivity-threshold helpers, named presets, relation explainability including heatmap data/plots, and optional automatic selection across multiple induced variants. What it removes is the benchmark clutter: GEO downloaders, plotting pipelines, global output folders, auto-`pip install`, hardcoded Windows cache paths, and paper-specific exports.

## What is inside

`CSHClassifier` trains a PyTorch CSH tabular model after inducing relations from feature scores. You can select the induction backend directly:

| backend | extra dependency | meaning |
|---|---:|---|
| `logreg` | none beyond base install | absolute logistic-regression coefficients |
| `rf` | none beyond base install | random-forest feature importances |
| `et` | none beyond base install | extra-trees feature importances |
| `xgb` | `xgboost` | XGBoost feature importances |
| `rf_logreg` | none beyond base install | weighted RF + logistic scores |
| `ensemble` | `xgboost` | weighted RF + ET + XGB + logistic scores |

Named presets match the original benchmark naming style:

```text
rigorous_csh_classifier_rf_induced
rigorous_csh_classifier_et_induced
rigorous_csh_classifier_xgb_induced
rigorous_csh_classifier_logreg_induced
rigorous_csh_classifier_rf_logreg_induced
rigorous_csh_classifier_ensemble_induced
```

## Install

For normal CSH use:

```bash
python -m pip install -e .[torch]
```

For the XGBoost-induced variant and full preset coverage:

```bash
python -m pip install -e .[all]
```

For explainability heatmaps only, add the visualization extra:

```bash
python -m pip install -e .[torch,viz]
```

For development:

```bash
python -m pip install -e .[all,dev]
pytest
```

## Quick start

```python
from csh_model import CSHClassifier, CSHConfig, RelationConfig

clf = CSHClassifier(
    induction="xgb",
    relation_config=RelationConfig(top_features=12, max_diads=20, max_triads=6),
    csh_config=CSHConfig(max_epochs=56, batch_size=512),
    random_state=42,
)

clf.fit(X_train, y_train, feature_names=gene_names)
prob = clf.predict_proba(X_test)[:, 1]
labels = clf.predict(X_test)

clf.save("artifacts/csh.joblib")
```

Load the model later:

```python
from csh_model import load_csh_classifier

clf = load_csh_classifier("artifacts/csh.joblib")
prob = clf.predict_proba(X_new)[:, 1]
```

## Use an original-style preset

```python
from csh_model import make_csh_classifier

clf = make_csh_classifier("rigorous_csh_classifier_xgb_induced", random_state=42)
clf.fit(X_fit, y_fit, X_cal=X_cal, y_cal=y_cal, feature_names=feature_names)
```

## Try multiple CSH variants and select the best

```python
from csh_model import CSHModelSelector, CSHConfig, RelationConfig

selector = CSHModelSelector(
    candidates=(
        "rigorous_csh_classifier_logreg_induced",
        "rigorous_csh_classifier_rf_induced",
        "rigorous_csh_classifier_xgb_induced",
        "rigorous_csh_classifier_ensemble_induced",
    ),
    relation_config=RelationConfig(top_features=12, max_diads=20, max_triads=6),
    csh_config=CSHConfig(max_epochs=56),
    metric="auc",
    random_state=42,
)
selector.fit(X_train, y_train, X_val=X_val, y_val=y_val, feature_names=feature_names)

print(selector.best_name_, selector.best_score_)
prob = selector.predict_proba(X_test)[:, 1]
```

If `xgboost` is not installed and `skip_unavailable=True`, XGB-based candidates are skipped rather than crashing the whole selector.

## Explicit source calibration split

If you already have the source-fit and source-calibration split, pass it directly. This mirrors the benchmark workflow more closely than an internal split.

```python
clf.fit(
    X_fit,
    y_fit,
    X_cal=X_cal,
    y_cal=y_cal,
    feature_names=feature_names,
)
```

## Inspect induced structure and heatmaps

```python
relations = clf.explain_relations()
scores = clf.induction_scores()

# Pure NumPy, no plotting dependency.
heatmap, relation_labels, feature_labels = clf.relation_heatmap_data()

# Optional matplotlib plot.
clf.plot_relation_heatmap(path="artifacts/csh_relation_heatmap.png")
```

Each relation includes original feature indices, feature names, arity, and induction score. The heatmap rows are induced relations and the columns are features. By default the matrix shows only the compact feature support used by the CSH relations; pass `include_all_features=True` to project back to the original input feature space.

## Minimal dependency strategy

The base package depends only on:

```text
numpy
scikit-learn
joblib
```

Optional dependencies are lazy-loaded only when needed:

```text
torch      required to fit or predict with CSHClassifier
xgboost    required only for induction="xgb" or induction="ensemble"
pandas     not required, but DataFrame inputs work if pandas is installed
matplotlib optional, required only for plot_relation_heatmap
```

No module in this repo auto-installs packages at import time.

## Repo layout

```text
src/csh_model/
  __init__.py        public API
  config.py          CSH and relation configs
  induction.py       RF, ET, XGB, LogReg and ensemble feature scoring
  relations.py       singleton, diad and triad relation construction
  torch_model.py     PyTorch CSH implementation
  calibration.py     Platt calibration helpers
  estimator.py       sklearn-style CSHClassifier
  presets.py         original-style named presets
  selection.py       CSHModelSelector for candidate search
  explainability.py  relation matrices and optional heatmap plotting
  thresholds.py      sensitivity and rule-out threshold helpers
examples/
  quickstart.py
tests/
  test_smoke.py
```

## Generalizing beyond gene-expression data

The package is intentionally a **numeric tabular CSH** implementation rather than a sepsis-specific pipeline. It does not know about GEO, genes, platforms, or clinical cohorts. Any modality can be used after you convert it into a two-dimensional feature matrix:

```text
rows    = samples, patients, images, documents, windows, molecules, devices, ...
columns = numeric features, embeddings, biomarkers, handcrafted descriptors, ...
y       = binary labels encoded as 0/1 or any two label values
```

Useful patterns:

```python
# pandas tables preserve column names automatically.
clf.fit(df_train, y_train)

# image/text/time-series workflows should pass extracted features or embeddings.
clf.fit(embedding_matrix, y, feature_names=[f"embed_{i}" for i in range(embedding_matrix.shape[1])])

# domain IDs let the CSH domain context represent cohorts, hospitals, batches, studies, sites, devices, or acquisition protocols.
clf.fit(X, y, domain_ids=site_ids)
prob = clf.predict_proba(X_external, domain_ids=external_site_ids)[:, 1]
```

The current classifier is binary because the original benchmark was binary. Multi-class can be added later in the PyTorch head and label handling without changing relation induction.

## Updating without pain

Most future changes should land in one small place:

| change | edit |
|---|---|
| default architecture | `CSHConfig` in `src/csh_model/config.py` |
| relation counts or arity penalty | `RelationConfig` or `src/csh_model/presets.py` |
| new induction backend | one function plus one branch in `src/csh_model/induction.py` |
| new named model | mapping in `src/csh_model/presets.py` |
| threshold policy | `src/csh_model/thresholds.py` |

Then run:

```bash
make test
```
