spacr.guide_attribution

Attribute a guide and a probability to every cell of a pooled screen.

Distinct from spacr.attribution, the Grad-CAM and saliency library (what a trained classifier attends to). This module estimates which guide is in each cell.

A pooled screen never observes which cell carried which guide: sequencing gives a fraction per well. A guide with a measurable effect on the classification score shifts the scores, which allows cells to be attributed probabilistically.

prior pi_g the guide’s read fraction in the well, normalised likelihood f_g(s) how likely this score is if the cell carries g posterior r_ig ∝ pi_g * f_g(s_i)

subject to the constraint:

sum_i r_ig = N * pi_g for every guide

Without it, two guides with effects in the same direction would both claim the same top-scoring cells. With it, the total mass attributed to each guide is pinned to its read fraction, so they split the top cells in proportion instead. It is enforced by iterative proportional fitting, which is the same fixed point as fitting a mixture whose mixing proportions are known and fixed.

The result is an attribution, not a genotype; every name in this module says “attributed” for that reason.

Two guides with the same effect cannot be distinguished: their posteriors come back at exactly pi_1 : pi_2 for every cell.

Classes

Assignment

Guide assignment for each cell with its observed sequencing counts.

Attribution

What one cell was attributed, and how sure that is.

Preflight

Whether a guide can be assigned at all, checked before assigning.

Functions

assign_well(→ Assignment)

Give every cell exactly one guide, with the counts sequencing implies.

attributable(→ Tuple[bool, float])

Return (can_it, best_possible) for one guide's posterior ceiling.

attribute_well(→ Tuple[Attribution, ...])

One Attribution per cell, in the order the scores came.

effective_dimension(→ float)

Return the effective number of independent columns in matrix.

normalise_fractions(→ Dict[str, float])

The guides' fractions, scaled to sum to 1.

posterior(→ Tuple[numpy.ndarray, Tuple[str, ...]])

(r, guides) -- each cell's probability of carrying each guide.

posterior_multivariate(→ Tuple[numpy.ndarray, ...)

posterior(), but reading every measurement instead of one score.

preflight(→ Preflight)

Report whether guide can be called, and in how many of its wells.

Module Contents

class spacr.guide_attribution.Assignment[source]

Guide assignment for each cell with its observed sequencing counts.

Variables:
  • guides – assigned guide name for each input cell, in input order.

  • cost – total negative log likelihood of the assignment; lower is better.

  • degeneracy – mean cost of changing a cell to its best alternative guide, in nats; values near zero indicate interchangeable solutions.

  • counts – number of cells assigned to each guide.

property decisive: bool[source]

Whether the constraints actually pinned the answer down.

class spacr.guide_attribution.Attribution[source]

What one cell was attributed, and how sure that is.

Variables:
  • guide – assigned guide name, or AMBIGUOUS when the posterior did not reach the call threshold.

  • probability – largest posterior probability reached for the cell.

  • ambiguous – whether probability was below the call threshold.

  • entropy – Shannon entropy of the cell’s guide posterior, in bits.

  • runner_up – second-highest guide name, or an empty string when no second guide exists.

property called: bool[source]

Whether this cell was assigned a guide at all.

AMBIGUOUS is not a call. A cell whose reads support two guides has no single perturbation, and counting it as called would attribute a phenotype to whichever guide happened to be listed first.

Returns:

True when unambiguously assigned.

class spacr.guide_attribution.Preflight[source]

Whether a guide can be assigned at all, checked before assigning.

Variables:
  • guide – guide whose achievable posterior was evaluated.

  • wells – number of wells containing a positive fraction of the guide.

  • callable_wells – wells in which the guide can reach threshold.

  • best – highest posterior ceiling reached across the guide’s wells.

  • threshold – posterior probability required for a guide call.

note() → str[source]

One line for a caption or a console.

property hopeless: bool[source]

True when no well in the screen could ever call this guide.

spacr.guide_attribution.assign_well(scores: Sequence[float], fractions: Mapping[str, float], effects: Mapping[str, float], *, centre: float = 0.0, scale: float = 1.0, likelihood: str = 'lognormal', seed: int = 0) → Assignment[source]

Give every cell exactly one guide, with the counts sequencing implies.

Parameters:
  • scores – one classification or measurement score per cell, in the desired output order.

  • fractions – measured guide fractions for the well; usable positive values are normalized before integer cell counts are apportioned.

  • effects – fitted score effect keyed by guide; absent guides use a zero effect.

Returns:

an Assignment. guides is one name per cell, in the order the scores came.

The counts are round(N * pi_g) adjusted to sum to N exactly, so every cell is assigned. The largest remainders take the slack, which is the standard apportionment and is deterministic.

spacr.guide_attribution.attributable(effect: float, scale: float, prior: float, *, threshold: float = DEFAULT_THRESHOLD, others: Sequence[Tuple[float, float]] | None = None, span: float = 4.0, centre: float = 0.0, likelihood: str = 'lognormal', grid: int = 513) → Tuple[bool, float][source]

Return (can_it, best_possible) for one guide’s posterior ceiling.

Determine whether a guide can reach the assignment threshold within the supplied score range.

Parameters:
  • effect – the guide’s fitted shift on the selected likelihood scale.

  • scale – the positive spread of scores used to express the plausible range and likelihood width; non-positive values resolve to one.

  • prior – the guide’s sequencing fraction in the well, clipped to the closed interval from zero to one.

  • others – the competition, as (effect, prior) pairs. Omit it and the rest of the well is treated as one competitor with no effect, which provides the most permissive comparison.

  • span – how far out, in units of scale, a score may plausibly lie. The posterior ceiling is maximized across this finite range.

The calculation evaluates competing guides at the same candidate score. Opposite-signed effects can therefore separate more strongly than a flat competitor. Without a finite span the plain-Bayes posterior may tend to one, so no useful finite ceiling would exist.

spacr.guide_attribution.attribute_well(scores: Sequence[float], fractions: Mapping[str, float], effects: Mapping[str, float], *, threshold: float = DEFAULT_THRESHOLD, centre: float = 0.0, scale: float = 1.0, likelihood: str = 'lognormal', seed: int = 0) → Tuple[Attribution, ...][source]

One Attribution per cell, in the order the scores came.

Parameters:
  • scores – one classification or measurement score per cell, in the output order.

  • fractions – sequencing fractions keyed by guide for this well. The usable positive fractions are normalised before attribution.

  • effects – fitted score effect keyed by guide; a missing guide is treated as having zero effect.

  • threshold – posterior probability the best guide must reach (0.55 by default) for the cell to be annotated with it; below it the cell is tagged AMBIGUOUS and still carries the highest probability any guide reached.

  • seed – an exact tie is broken at random by a generator seeded with this value, so a re-run produces the same annotation.

spacr.guide_attribution.effective_dimension(matrix: numpy.ndarray) → float[source]

Return the effective number of independent columns in matrix.

The participation ratio of the correlation matrix’s eigenvalues, (sum lambda)^2 / sum lambda^2 – 785 for 785 orthogonal columns, and 1 for 785 copies of one column. The same statistic the sweep uses to count a guide’s effective wells, applied to the other axis.

Parameters:

matrix – cells x measurements, already finite.

Returns:

the effective count, between 1 and the number of columns.

spacr.guide_attribution.normalise_fractions(fractions: Mapping[str, float]) → Dict[str, float][source]

The guides’ fractions, scaled to sum to 1.

Parameters:

fractions – guide names mapped to their measured fraction in one well. Missing, non-finite, and non-positive values are discarded.

A well whose fractions sum to nothing usable comes back empty rather than uniform, so no prior is invented without sequencing data.

spacr.guide_attribution.posterior(scores: Sequence[float], priors: Mapping[str, float], effects: Mapping[str, float], *, centre: float = 0.0, scale: float = 1.0, likelihood: str = 'lognormal', iterations: int = 200, tolerance: float = 1e-09) → Tuple[numpy.ndarray, Tuple[str, ...]][source]

(r, guides) – each cell’s probability of carrying each guide.

Parameters:
  • scores – one finite classification or measurement score per cell.

  • priors – normalized guide fractions; insertion order defines the returned matrix columns.

  • effects – fitted score shifts keyed by guide. A missing guide uses a zero shift.

Returns:

r with one row per cell and one column per guide, rows summing to 1, and columns summing to n_cells * prior – the constraint that stops two same-direction guides both claiming the same cells.

Iterative proportional fitting: scale the columns to the target masses, renormalise the rows to 1, repeat. Both operations are monotone in the right sense and the fixed point satisfies both constraints at once. It is the same answer as a mixture fitted with its mixing proportions held at the sequencing fractions.

spacr.guide_attribution.posterior_multivariate(measurements: numpy.ndarray, priors: Mapping[str, float], effects: Mapping[str, Sequence[float]], *, centres: Sequence[float] | None = None, scales: Sequence[float] | None = None, likelihood: str = 'lognormal', correct_for_correlation: bool = True, iterations: int = 200, tolerance: float = 1e-09) → Tuple[numpy.ndarray, Tuple[str, ...], Dict[str, float]][source]

posterior(), but reading every measurement instead of one score.

Option C. Each guide carries a vector of effects – one per measurement – and a cell’s evidence is the summed log-density across them, scaled by the design effect (see the note above). The same iterative proportional fitting then applies, so the guide masses still match the sequencing.

Parameters:
  • measurements – cells x measurements.

  • priors – normalised sequencing fraction keyed by guide. Its key order defines the columns in the returned posterior matrix.

  • effects – {guide: [effect per measurement]}, in the columns’ order. A guide with no entry has zero effect in every measurement.

  • correct_for_correlation – scale the evidence by effective_dimension / n_measurements. Leave it on unless the columns really are independent; see the note above for why.

Returns:

(r, guides, diagnostics). The diagnostics carry n_measurements, effective_dimension and the scale_factor applied, because a reader has to be able to see how much the correction did.

spacr.guide_attribution.preflight(guide, fractions_by_well, effects, *, scale, centre=0.0, threshold=DEFAULT_THRESHOLD, likelihood='lognormal', span=4.0) → Preflight[source]

Report whether guide can be called, and in how many of its wells.

Reported before anything is assigned. A guide whose effect is small against the spread of scores can never reach the threshold, regardless of cell count or fit quality.

Parameters:
  • guide – the guide being asked about.

  • fractions_by_well – {well: {guide: fraction}} – every guide in each well, because a ceiling is a comparison and comparing a guide against nothing returns its prior.

  • effects – each guide’s fitted effect.

  • scale – the spread of the scores, per plate.

Returns:

a Preflight.

A guide is counted callable in a well when attributable() says so in that well – the answer differs between wells because the prior does, and a guide at 0.6 of one well and 0.02 of another is not the same question.