Metadata-Version: 2.5
Name: illumine
Version: 0.1.0
Summary: Customizable scientific figures built on Matplotlib.
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: matplotlib>=3.8
Requires-Dist: numpy>=1.26
Requires-Dist: pandas>=2.1
Requires-Dist: scipy>=1.12
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.9; extra == 'dev'
Provides-Extra: labels
Requires-Dist: adjusttext>=1.2; extra == 'labels'
Provides-Extra: smooth
Requires-Dist: statsmodels>=0.14; extra == 'smooth'
Description-Content-Type: text/markdown

# illumine

Customizable scientific figures built on Matplotlib. Version 0.1.0 is an initial
review release with volcano plots, GSEA panels, ORA dot/bar plots, statistical
box plots, and correlation plots. All figures expose their Matplotlib objects.

## Install locally

```bash
python -m pip install -e .
python -m pip install -e '.[labels,smooth,dev]'
```

This project has not been published to PyPI. Run commands from this directory.
Requires Python 3.10 or newer.

## Quick start

```python
import illumine as il

result = il.volcano(de_results, logfc="log2FoldChange", p="padj", label="gene")
result.ax.set_title("Treatment vs control")
result.save("volcano.pdf")
```

Run `python examples/gallery.py` to generate example data, figures, an HTML
gallery, and statistical result tables in `examples/output/`.
The full gallery uses the optional `labels` and `smooth` extras.

The prepared gallery is at `examples/output/index.html`; every chart is available
as PNG, PDF, and SVG. Input tables and computed statistics are saved alongside it.
All example data is synthetic. The GSEA examples compute running ES traces only
and omit NES and significance rather than inventing analysis results.

## Public API

| Function | Purpose | Primary inputs |
| --- | --- | --- |
| `il.volcano()` | Differential-expression volcano | DataFrame, log2 fold change, p-value |
| `il.gsea()` | One or multiple aligned GSEA traces | `GSEAData` or a list of traces |
| `il.ora_dotplot()` | ORA bubbles, optionally across groups | Term, adjusted p, count, gene ratio |
| `il.ora_barplot()` | Horizontal ORA bars | Term, adjusted p, count |
| `il.boxplot()` | Two/multiple groups, hue, paired samples | Long-form DataFrame, x, y |
| `il.corrplot()` | Correlation, fitted line, confidence band | DataFrame, x, y, optional hue |
| `il.compare_groups()` | Compute tests without drawing | Same group identifiers as boxplot |

Use `help(il.boxplot)` or the other function names for full signatures.

### Volcano

```python
result = il.volcano(
    de_results,
    logfc="log2FoldChange",
    p="padj",
    label="gene",
    fc_cutoff=1,
    p_cutoff=0.05,
    label_genes=["TP53", "STAT1"],
    point_kws={"s": 18},
)
```

The same p column controls the vertical coordinate and significance cutoff.
Significant fold changes must exceed `fc_cutoff` in magnitude. Without explicit
`label_genes`, `top_n` selects the smallest p-values among significant genes.
Zero p-values are clipped at `p_floor=1e-300` for display; source values remain
unchanged in `result.data`. Point clouds are rasterized inside PDF/SVG by default;
set `point_kws={"rasterized": False}` for vector points. Automatic label repulsion
uses the optional `labels` extra; without it, labels are placed at their points
and a warning explains how to enable repulsion. Set `repel=False` to opt out.

### GSEA

```python
trace = il.GSEAData.from_ranked_series(
    ranked_scores,
    pathway_genes,
    term="Interferon response",
    weight=1,
)
result = il.gsea(trace, mark_genes=["STAT1", "IRF1"])

# Adapt a GSEApy analysis without depending on its drawing functions.
trace = il.GSEAData.from_gseapy(gseapy_result, "Interferon response")
result = il.gsea(trace)
```

`ranked_scores` is a Series with unique gene IDs as its index. The helper sorts
scores in descending order, preserves input order for ties, and calculates the
weighted running enrichment score. Members absent from the ranking are ignored;
gene sets hitting zero or all ranked genes are rejected. The helper does not
perform GSEA hypothesis testing. Supply NES, nominal p-value, and adjusted p/FDR
from the original analysis using `nes=`, `pvalue=`, and `padj=`. They are omitted
when unavailable. In the GSEA figure, `padj` is displayed as FDR; supply the
analysis's FDR quantity deliberately, since different procedures differ.

Precomputed results can instead instantiate `GSEAData(ranking, hits, running_es,
term, ...)`. Ranking must already be sorted; hits must be unique zero-based
integer positions into that exact ranking. Curve and ranking lengths must match.
Displayed ranks are one-based. GSEApy adapters expect a result with a Series
ranking and detailed `results[term]` fields `hits`, `RES`, `nes`, `pval`, and `fdr`.
Summary enrichment tables alone cannot reconstruct the curves.

Pass a list of traces for multiple pathways. All traces must share identical gene
order and ranking values. `result.axes` provides `es`, `hits`, and `rank`. Embed
the whole composite chart using a Matplotlib `subplot_spec`, for example:

```python
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(12, 6), layout="constrained")
grid = fig.add_gridspec(1, 2)
il.gsea(trace, subplot_spec=grid[0])
il.volcano(de_results, ax=fig.add_subplot(grid[1]))
```

### ORA

```python
result = il.ora_dotplot(
    enrichment,
    term="Description",
    padj="p.adjust",
    count="Count",
    gene_ratio="GeneRatio",
    top_n=12,
)
result = il.ora_barplot(enrichment, x="neglog10padj")
result = il.ora_dotplot(combined_results, group="contrast", top_n=10)
```

Default columns are `term`, `padj`, `count`, and `gene_ratio`. GeneRatio accepts
numeric fractions or strings such as `8/120`; the denominator must be positive.
The calling analysis defines its denominator. RichFactor and FoldEnrichment are
not treated as interchangeable with GeneRatio: pass their actual numeric column
name as `x` to display one of those metrics explicitly.

Terms are filtered by adjusted p (`cutoff=0.05`, or `None` to disable) and ordered
by increasing adjusted p. `top_n` is selected per group. Bubbles map Count to
marker area and `-log10(padj)` to color; `vmin`/`vmax` fix a shared color scale.
With `group`, bubbles use categorical groups on x and do not require GeneRatio.
Multi-group bars use categorical colors and a shared numeric x scale. Each term
must occur once per group. Invalid inputs are rejected before filtering.

### Statistical box plots

```python
result = il.boxplot(
    measurements,
    x="group",
    y="expression",
    order=["Control", "Low dose", "High dose"],
    test="welch",
    comparisons=[("Control", "Low dose"), ("Control", "High dose")],
    p_adjust="holm",
    stat_label="padj",
)
result.stats.to_csv("group_comparisons.csv", index=False)
```

No test is selected implicitly (`test=None`). Available two-sided tests:

| Test | Interpretation |
| --- | --- |
| `welch` | Independent two-sample t-test without equal-variance assumption |
| `student` | Independent two-sample t-test with equal variances |
| `mannwhitney` | Independent Mann-Whitney U test |
| `paired_t` | Paired t-test with matching subject IDs |
| `wilcoxon` | Paired Wilcoxon signed-rank test |

Omitting comparisons when a test is given compares all observed groups. Correction
methods: `holm` (default), `bonferroni`, `bh`, `by`, or `None`. The family contains
exactly the comparisons in a single call; it is not automatically extended across
genes, other figures, or earlier calls. `hide_ns` hides nonsignificant annotations
after correction and does not change that family. `stat_label` accepts `pvalue`,
`padj`, or `stars`; stars use adjusted p-values. All raw and adjusted values,
sample sizes, test statistics, and mean differences are preserved in `result.stats`.

Paired tests require `pair_id="subject"`; IDs must be unique within each group
and identical across the compared groups. Rows are aligned by ID before testing.
`connect_pairs=True` adds subject lines for plots without hue. With hue, compare
explicit keys such as `(("Control", "Female"), ("Treatment", "Female"))`; groups
are never silently pooled. `yscale="log"` supports positive observations and
positions the brackets in logarithmic coordinates.

External results are accepted using `stat_results=table` with `group1`, `group2`,
`pvalue`, and optionally `padj`. External values are not recalculated or adjusted.
If `padj` is missing it equals the supplied p-value and `p_adjust` is recorded as
`none`. Use `test=None` with external results. Multi-factor models, omnibus tests,
and repeated-measures models should currently be computed outside illumine.

### Correlation

```python
result = il.corrplot(
    measurements,
    x="signature_score",
    y="immune_score",
    corr_method="spearman",
    fit_method="linear",
    ci=0.95,
)
```

`corr_method` (`pearson` or `spearman`) determines the annotated coefficient and
p-value independently of `fit_method` (`linear`, `lowess`, or `None`). LOWESS
requires the `smooth` extra and `ci=None`. Linear confidence bands are pointwise
t intervals for the estimated mean under ordinary least squares assumptions,
not prediction intervals or a correlation confidence interval. Complete finite
pairs are used for both fitting and correlation. Every group needs at least three
observations and nonconstant x and y. With hue, each group is fit and tested
separately; correlation p-values are raw and not automatically adjusted.

## Customization and result objects

Every function returns `il.PlotResult`:

```python
result.fig                 # Matplotlib Figure
result.ax                  # Main Axes, or the ES panel for GSEA
result.axes                # Named axes, including colorbar axes where present
result.artists             # Named artists; nested dictionaries for groups
result.data                # Plotted data, including display transformations
result.stats               # Computed or supplied statistical results
result.metadata            # Dropped counts, settings, and interpretation notes
result.save("figure.svg")  # PNG/PDF/SVG supported; returns the saved Path
result.close()             # Release the figure when finished
```

Input tables are copied. Volcano, boxplot, and corrplot omit missing/nonfinite
numeric observations with a warning by default (`na_policy="raise"` rejects
them); ORA and GSEA reject invalid analysis inputs. Arbitrary text in numeric
columns is rejected rather than silently coerced to missing values. No function
calls `show()` or saves a file implicitly. Use `plt.show()` in a script, display
`result.fig` in a notebook, or call `result.save(...)`.

Single-panel functions accept `ax=`. `theme="paper"` (default), `"minimal"`, or a
dictionary of Matplotlib rc settings applies locally; global style is restored
even if plotting fails. Existing supplied axes are styled explicitly. Use
`theme=None` to preserve the existing axes style and current rc settings. Color
palettes accept lists or mappings keyed by groups. Layer-specific dictionaries
(`point_kws`, `line_kws`, `label_kws`, `bracket_kws`, `box_kws`, `bar_kws`,
`band_kws`) accept the corresponding Matplotlib options; inspect each function's
signature for supported layers. Data coordinates and group positions remain
owned by illumine; override artist appearance through these dictionaries.

## Development

```bash
python -m pip install -e '.[dev,labels,smooth]'
python -m pytest -q -W error
ruff check src tests examples/gallery.py
python -m build
```

Tests cover reference statistics, multiplicity adjustment, paired subject
alignment, GSEA score calculation, input validation, style isolation, axes
composition, label containment, and real PNG/PDF/SVG exports. The gallery is also
rendered with warnings treated as errors and inspected visually.

This initial release focuses on editable figures. Advanced GSEA expression
heatmaps, automatic gene-label collision handling within GSEA panels, rich
faceting, and a broad analysis engine are not included yet.
