Metadata-Version: 2.4
Name: crispr-centipede
Version: 1.0.0
Summary: Sensor-based per-variant effect scoring for base-editing tiling screens — ridge regression of per-guide sensor editing frequencies against a phenotype (the gRNA-only, frequentist companion to CRISPR-millipede)
License: AGPL-3.0-or-later
License-File: LICENSE.txt
Author: Basheer Becerra
Author-email: bbecerra@fas.harvard.edu
Requires-Python: >=3.8,<3.12
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Dist: matplotlib (>=3.7,<4.0)
Requires-Dist: numpy (>=1.24,<2.0)
Requires-Dist: pandas (>=1.5,<3)
Requires-Dist: scikit-learn (>=1.3,<2.0)
Requires-Dist: scipy (>=1.10,<2.0)
Description-Content-Type: text/markdown

# CRISPR-centipede

**Sensor-based per-variant effect scoring for base-editing tiling screens** — the gRNA-only, frequentist
companion to [CRISPR-millipede](../CRISPR-millipede-target). Where CRISPR-millipede models installed alleles
from *direct target amplicon-sequencing* with a Bayesian linear model, **CRISPR-centipede** regresses a
per-guide phenotype on per-guide **editing** values using **ridge regression**, and reports a per-variant
effect-score table shaped like a millipede sigma table.

CRISPR-centipede is **blind to how the editing was measured** — it works the same whether the editing comes
from a guide's self-edited protospacer, an attached sensor/surrogate, or a predictive model. Per guide it
needs only:

1. **editing information** — a per-variant editing value (a guides × variants matrix);
2. **counts in both populations** — an enriched and a baseline read count;
3. **coordinates** — an optional coordinate per variant (only for position-aware estimators and plots).

Converting any particular assay (e.g. [CRISPR-Correct](../CRISPR-Correct-Folder) observed mutations) into an
editing matrix is done **upstream, outside this package**.

## Install

```bash
pip install crispr-centipede           # once published
# or, from source:
cd CRISPR-centipede && pip install -e .
```

Python `>=3.8,<3.12`; depends only on numpy / scipy / pandas / scikit-learn / matplotlib.

## Pipeline

```python
import pandas as pd
import crispr_centipede as cc

# 1) Build a dataset: per-replicate editing matrix (guides x variants) + per-guide counts.
rep0 = cc.CentipedeReplicate(
    editing=editing_df0,        # DataFrame, index=guide id, columns=variant ids, values in [0,1]
    enriched=high_counts0,      # Series, index=guide id
    baseline=low_counts0,       # Series, index=guide id
)
ds = cc.CentipedeDataset([rep0, rep1, ...], coordinates=None)   # coordinates optional (parsed from ids if absent)

# 2) Model: ridge (default) with Wald / hat-matrix-df / permutation inference.
spec  = cc.CentipedeModelSpecification(estimator="ridge", inference=("wald", "hatdf", "perm"))
group = cc.CentipedeModelExperimentalGroup(ds, spec)

group.summary          # per-variant: PIP, Coefficient, Coefficient StdDev, wald_p, p_hatdf, p_perm, ...
group.metadata         # {alpha, cv_r2, n_guides, edf}
group.save("my_screen")  # -> my_screen_summary.csv + my_screen_result.pkl

# 3) Visualize (generic per-variant plots).
cc.visualization.plot_coefficient_board(group.summary)
```

Variant ids are opaque labels. If they follow the `"{position}{ref}>{alt}"` convention (e.g. `"24A>G"`),
centipede can parse a coordinate from them; otherwise pass `coordinates={variant: position, ...}`. Coordinates
are only needed by the position-aware estimators (`sliding`/`segmented`/`fused`) and plots — plain ridge/OLS
don't use them.

Have your data as one combined frame (variant-frequency columns + count columns)? Use
`cc.CentipedeDataset.from_design_matrix(df, enriched_col=..., baseline_col=...)`.

### Editing as counts, per-population edited counts, and a coverage filter

Instead of a `[0,1]` frequency you can pass **raw counts** and let the package compute the frequency
(`editing = presort_edited / presort_total`). You may also supply the per-population **edited** counts to get
per-variant editing rates, and apply a **global** guide-coverage filter (a guide failing a threshold on the
pooled, summed-across-replicate count is dropped from every replicate):

```python
ds = cc.CentipedeDataset.from_arrays(
    presort_edited=[edited_df0, ...],     # list[DataFrame]: guides x variants edited-read counts
    presort_total=[total_s0, ...],        # list[Series]:    per-guide total reads (the denominator)
    enriched=[high0, ...], baseline=[low0, ...],          # phenotype counts (Series per replicate)
    enriched_edited=[high_edited0, ...],  # optional: edited counts in the enriched population
    baseline_edited=[low_edited0, ...],   # optional: edited counts in the baseline population
    coordinates=variant_coords,
    min_enriched=10, min_baseline=10,     # optional global coverage filter (also min_presort_total)
)

ds.variant_enrichment()        # per-variant editing-weighted mean response (model-free "plain enrichment")
ds.population_editing_rate("enriched")   # per-variant pooled edited/total in the enriched population
ds.editing_rate_lfc()          # per-variant log2(enriched rate / baseline rate)
ds.subset_guides(guide_ids)    # restrict every replicate to a guide subset
```

### Output (`group.summary`)

Indexed by variant id; columns mirror a CRISPR-millipede sigma table plus ridge-native fields:

| column | meaning |
|---|---|
| `Coefficient` | standardized ridge β (effect per SD of editing) |
| `Coefficient StdDev` | bootstrap standard error |
| `PIP` | bootstrap sign-stability ∈ [0,1] |
| `wald_p` | analytic ridge Wald p (anti-conservative under shrinkage) |
| `p_hatdf` | hat-matrix df-corrected t-test p (calibrated) |
| `p_perm`, `p_perm_fwer` | permutation p (per-feature; max-β FWER) |
| `max_edit_freq`, `n_guides_edited` | coverage diagnostics |

### Alternative estimators (non-default)

`estimator="ols"` (unbiased, valid t-test), or the position-aware variants `"fused"` / `"sliding"` /
`"segmented"` via `CentipedeModelSpecification(estimator=..., extra={...})`.

### Count normalization (optional)

`count_normalization` rescales the enriched (High) counts by a single pooled factor before scoring, to remove the
enriched-vs-baseline library-size imbalance — the guide-count analog of CRISPR-millipede's WT-allele normalization:

- `None` (default) — no rescaling.
- `"total"` — factor `Σ baseline / Σ enriched` over the fitted guides (pins whole-library `log2(H'/L')` ≈ 0).
- `"null_anchor"` — factor computed over `null_anchor_guide_ids` (your negative-control guides), pinning their
  pooled `log2(H'/L')` ≈ 0. The controls define the scale only; they are not added as design rows.

```python
# score relative to a negative-control null
cc.ridge_scores(ds, count_normalization="null_anchor", null_anchor_guide_ids=neg_control_ids,
                editing_threshold=None, coedit_threshold=None, degree=None)
```

Under the mean-centered Normal-Ridge/OLS fit this is ~a constant shift of the response, so β and hit calls change
only at second order — its role is to anchor the reported response/β scale to a defined null, not to reshape the fit.

## What this package is *not*

It does not include the Bayesian allele model (that's CRISPR-millipede), the parsing of any particular assay's
raw output (do that upstream), nor the genome-coordinate β-logo **track** plotting (that lives with the
analysis project and calls this package for the per-variant scores).

