Metadata-Version: 2.4
Name: syzygy-cfa
Version: 0.0.1.dev0
Summary: Combinatorial Fusion Analysis for Python
Author-email: "Olivier J. M. Béquignon" <olivier.bequignon.maintainer@gmail.com>
Maintainer-email: "Olivier J. M. Béquignon" <olivier.bequignon.maintainer@gmail.com>
Project-URL: Homepage, https://github.com/OlivierBeq/syzygy-cfa
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas
Requires-Dist: numpy>=2.0.0
Requires-Dist: scipy
Requires-Dist: scikit-learn
Dynamic: license-file

<p align="center">
  <img src="assets/logo.svg" alt="Syzygy CFA logo" width="140">
</p>

<h1 align="center">Syzygy CFA</h1>

<p align="center">
  Combinatorial Fusion Analysis for Python — squeeze more performance out of the models you already have, by
  finding the best way to combine them. 🧩
</p>

<p align="center">
  <img alt="PyPI" src="https://img.shields.io/badge/pypi-syzygy--cfa-blue.svg">
  <img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-yellow.svg">
  <img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-blue.svg">
  <img alt="Status" src="https://img.shields.io/badge/status-early%20development-orange.svg">
</p>

---

**Syzygy CFA** implements **Combinatorial Fusion Analysis (CFA)** — a principled way to combine the outputs of several
models (classifiers, regressors, rankers) rather than pick a single "best" one. Instead of guessing which
ensembling recipe will work, Syzygy CFA systematically tries every model subset and every fusion strategy, and tells
you which one actually performs best on held-out data.

It ships in two flavours:

* 🔧 **`CombinatorialFusion`** — a low-level analyzer that works directly on arrays of predictions, for full control.
* 🤖 **`ClassifierFusion` / `RegressorFusion`** — scikit-learn-style ensembles that wrap a list of **already-fitted**
  models and pick the best fusion strategy for you.

## Table of Contents

- [Features](#-features)
- [Installation](#-installation)
- [Quick Start](#-quick-start)
  - [Fusing already-fitted models](#fusing-already-fitted-models)
  - [Low-level array-based analysis](#low-level-array-based-analysis)
- [How It Works](#-how-it-works)
- [Supported Metrics](#-supported-metrics)
- [Running the Tests](#-running-the-tests)
- [Acknowledgements](#-acknowledgements)
- [License](#-license)

## ✨ Features

- **No retraining required** — `ClassifierFusion`/`RegressorFusion` accept models you've already trained; Syzygy CFA
  only learns *how to combine* them.
- **Exhaustive strategy search** — every non-empty subset of models is tried, in both score-space and rank-space,
  with three weighting schemes each (simple average, performance-weighted, diversity-weighted).
- **Robust weight estimation** — strategies are scored across multiple folds/seeds and averaged, not picked from a
  single lucky split.
- **scikit-learn compatible** — `ClassifierFusion`/`RegressorFusion` subclass `BaseEstimator`, so `get_params()`,
  `set_params()`, and pipelines work as expected.
- **Metric-agnostic** — plug in any of the built-in classification, regression, or ranking metrics (see
  [Supported Metrics](#-supported-metrics)).

## 📦 Installation

```bash
pip install syzygy-cfa
```

or, for local development:

```bash
git clone https://github.com/OlivierBeq/syzygy-cfa.git
cd syzygy-cfa
pip install -e .
```

Requires **Python 3.10+**, `numpy`, `pandas`, `scipy`, and `scikit-learn`.

## 🚀 Quick Start

### Fusing already-fitted models

This is the recommended entry point: train your own models however you like, then let Syzygy CFA figure out how best
to combine them on a held-out set.

```python
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC

from syzygy import ClassifierFusion

# Models trained beforehand, on your own training set — Syzygy CFA never touches this step.
lr = LogisticRegression().fit(X_train, y_train)
rf = RandomForestClassifier().fit(X_train, y_train)
svm = SVC(probability=True).fit(X_train, y_train)

# `X_holdout, y_holdout` is data these models have NOT been trained on.
fusion = ClassifierFusion(
    estimators=[("lr", lr), ("rf", rf), ("svm", svm)],
    metric="roc-auc",   # the metric used to pick the best fusion strategy
    cv=5,               # folds carved out of X_holdout to estimate weights robustly
)
fusion.fit(X_holdout, y_holdout)

print(fusion.best_combination_)   # e.g. "rf&svm_wps"
print(fusion.fusion_results_)     # every strategy that was tried, ranked best-first

# Use it on new data exactly like any scikit-learn classifier.
fusion.predict_proba(X_new)
fusion.predict(X_new)
```

`RegressorFusion` works the same way for regression models, using `.predict()` only:

```python
from syzygy import RegressorFusion

fusion = RegressorFusion(estimators=[("ridge", ridge), ("rf", rf), ("svr", svr)], metric="mae")
fusion.fit(X_holdout, y_holdout)
fusion.predict(X_new)
```

### Low-level array-based analysis

If you already have raw prediction arrays (e.g. from a cross-validation loop across several seeds) and just want
the analysis, use `CombinatorialFusion` directly. Its arguments share terminology with
`ClassifierFusion`/`RegressorFusion`: `metric` is the same, and `holdout_predictions` is the array equivalent of
what those estimators compute internally, on the `X` passed to `.fit()`:

```python
from syzygy import CombinatorialFusion

analyzer = CombinatorialFusion(
    model_names=["xgb", "rf", "svm"],
    holdout_predictions=[xgb_holdout_preds, rf_holdout_preds, svm_holdout_preds],   # one array (or list of arrays) per model
    test_predictions=[xgb_test_preds, rf_test_preds, svm_test_preds],
    y_holdout=y_holdout,
    y_test=y_test,
)

results = analyzer.analyze(metric="roc-auc")   # ranked DataFrame of every strategy, best first
results.head()
```

## 🧠 How It Works

For `n` models, CFA looks at every non-empty subset and, for each one, builds several candidate predictions:

| Suffix | Space | Combination                                                        |
|--------|-------|---------------------------------------------------------------------|
| *(none)* | score | single model                                                       |
| `_r`     | rank  | single model                                                        |
| `_ups`   | score | simple average across the subset                                   |
| `_ups_r` | rank  | simple average across the subset                                   |
| `_wps`   | score | weighted by each model's **Performance Strength** (PS)              |
| `_wps_r` | rank  | weighted by each model's Performance Strength                       |
| `_ds`    | score | weighted by each model's **Diversity Strength** (DS)                |
| `_ds_r`  | rank  | weighted by each model's Diversity Strength                         |

- **Performance Strength (PS)** is how well a model scores on its own, on held-out data.
- **Diversity Strength (DS)** is how different a model's predictions are from the rest of the ensemble — models that
  disagree more with the pack can contribute more complementary information.

Every one of these candidates is scored against your chosen metric, across every seed/fold, and averaged. The
result is a single ranked table (`analyze()` / `fusion_results_`) — no guessing which ensembling trick to reach for.

## 📏 Supported Metrics

Pass any of these names as `metric`, whether to `ClassifierFusion`/`RegressorFusion` or to
`CombinatorialFusion.analyze()` / `.combine()`:

| Classification | Regression | Ranking / Correlation |
|-----------------|------------|------------------------|
| `roc-auc`, `pr-auc` | `mse`, `rmse`, `mae`, `r2` | `spearman`, `kendall`, `pearson` |
| `f1`, `micro-f1`, `macro-f1` | | |
| `precision`, `recall`, `accuracy`, `kappa` | | |
| `rp@k`, `pr@k` | | |

## 🧪 Running the Tests

```bash
pip install -e . pytest
pytest tests/
```

## 🙏 Acknowledgements

Syzygy CFA draws inspiration from [mquazi/cfanalysis](https://github.com/mquazi/cfanalysis), an earlier open-source
implementation of Combinatorial Fusion Analysis for ADMET modelling, and the accompanying paper:

> Jiang N., Quazi M., Schweikert C., Hsu F., Oprea T. & Sirimulla S.<br/>
> Enhancing ADMET Property Models Performance through Combinatorial Fusion Analysis.<br/>ChemRxiv. 29 November 2023.<br/>
DOI: https://doi.org/10.26434/chemrxiv-2023-dh70x


Syzygy CFA is an independent implementation (not a fork, and not affiliated with the original authors) with a
different API, a different internal architecture, and a scikit-learn-compatible estimator layer that operates on
already-fitted models rather than raw prediction arrays. `tests/test_reference_example.py` uses example data
copied from that repository (see `tests/data/reference_cfanalysis/README.md` for provenance) to cross-check
Syzygy CFA's output against an independent computation.

## 📄 License

Released under the [MIT License](LICENSE).
