lineage.states.StateDistributionCR
Competing-risks state distributions.
The Gamma/GaPhs emissions in this package treat a cell's fate (Bernoulli) and its phase duration (Gamma) as independent observations, and discard the duration of any cell that dies. That throws away every death time and, more subtly, mis-states the likelihood of a time-censored cell: "no event yet at time t" is written as P(division > t) when it should be P(division > t AND death > t).
Here each phase instead carries two latent clocks,
T_D ~ Gamma(a, s) division / transition T_X ~ Gamma(a_x, s_x) death
and we observe min(T_D, T_X) together with an indicator of which fired. The three likelihood cases are the standard competing-risks ones:
transition seen at t f_D(t) * S_X(t) death seen at t f_X(t) * S_D(t) censored at t S_D(t) * S_X(t)
The division probability is then derived, P(divide) = int f_D(t) S_X(t) dt, rather than fit as a free Bernoulli parameter, so the death fraction and the death timing are forced to agree. With the G1 death clock pinned to a constant hazard (shape 1) this costs no degrees of freedom relative to the Bernoulli/Gamma model.
Attributes
Classes
One cell-cycle phase with competing division and death clocks. |
|
G1 and G2 phases, each with its own pair of competing clocks. |
Functions
|
log Gamma(a, scale) pdf, vectorized directly against scipy.special. |
|
log Gamma(a, scale) survival function, vectorized directly against scipy.special. |
|
Split cells into the three competing-risks cases. |
|
Weighted right-censored MLE for exponential scales, one per group. |
|
Fit both clocks of one state, sharing each shape across conditions. |
|
M step across several conditions at once, matching the Gamma model's interface. |
Module Contents
- lineage.states.StateDistributionCR.TIME_FLOOR = 1e-10
- lineage.states.StateDistributionCR._gamma_logpdf(t, a, scale)
log Gamma(a, scale) pdf, vectorized directly against scipy.special.
- Parameters:
t (numpy.ndarray)
a (float)
scale (float)
- Return type:
numpy.ndarray
- lineage.states.StateDistributionCR._gamma_logsf(t, a, scale)
log Gamma(a, scale) survival function, vectorized directly against scipy.special.
- Parameters:
t (numpy.ndarray)
a (float)
scale (float)
- Return type:
numpy.ndarray
- lineage.states.StateDistributionCR.event_masks(x)
Split cells into the three competing-risks cases.
xhas the usual three per-phase columns[fate, duration, censoring], where fate is 1 for surviving the phase, 0 for dying in it, and NaN when unknown, and the censoring flag is 1 when the phase was seen through to its end.Cells whose duration is negative have been masked for cross validation, and cells with a NaN duration never entered the phase; both are excluded everywhere.
- Returns:
boolean masks for (division observed, death observed, censored)
- Parameters:
x (numpy.ndarray)
- Return type:
tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]
- lineage.states.StateDistributionCR.exponential_estimator(obs, events, weights, param_idx, K)
Weighted right-censored MLE for exponential scales, one per group.
With a constant hazard the MLE is total time at risk over events observed, which needs no iteration. A pseudocount keeps a group with no observed deaths finite.
- Parameters:
obs (numpy.ndarray)
events (numpy.ndarray)
weights (numpy.ndarray)
param_idx (numpy.ndarray)
K (int)
- lineage.states.StateDistributionCR.fit_clocks(distributions, x_list, gammas_list, state_j)
Fit both clocks of one state, sharing each shape across conditions.
distributionsis either a singleStateDistributionor a list of them, one per condition. The shape parameters are shared across conditions and the scales are free, mirroring howatonce_estimatortreats the Gamma model.- Parameters:
x_list (list[numpy.ndarray])
gammas_list (list[numpy.ndarray])
state_j (int)
- lineage.states.StateDistributionCR.atonce_estimator(all_tHMMobj, x_list, gammas_list, phase)
M step across several conditions at once, matching the Gamma model's interface.
- Parameters:
all_tHMMobj (list)
x_list (list)
gammas_list (list[numpy.ndarray])
phase (Literal['all', 'G1', 'G2'])
- class lineage.states.StateDistributionCR.StateDistribution(gamma_a=7.0, gamma_scale=4.5, death_a=1.0, death_scale=40.0, fixed_death_shape=True)
One cell-cycle phase with competing division and death clocks.
paramsis[bern_p, gamma_a, gamma_scale, death_a, death_scale]. The first three entries keep the meaning they have inStateDistribution, so downstream figure code that indexes them positionally continues to work;bern_pis now derived from the two clocks rather than fit.- Parameters:
gamma_a (float)
gamma_scale (float)
death_a (float)
death_scale (float)
fixed_death_shape (bool)
- atonce_estimator
- fixed_death_shape = True
- params
- property div_clock
- property death_clock
- division_probability()
P(the division clock fires first) = int f_D(t) S_X(t) dt.
quadevaluates the integrand at ~100-200 points per call, and this runs once per state per M step. Using the frozenrv_continuousmachinery for each point (asdiv.pdf/death.sfdo) costs orders of magnitude more than the arithmetic itself, so the integrand is written directly againstscipy.specialinstead.- Return type:
float
- rvs(size, rng=None)
Draw min(T_D, T_X) and record which clock fired.
- Parameters:
size (int)
- dist(other)
Wasserstein distance between the division clocks of two states.
Kept on the division clock alone so that the number is comparable with the Bernoulli/Gamma model's.
- Return type:
float
- dof()
Two division-clock parameters plus the death clock's scale, and its shape when that is not pinned. The Bernoulli is derived, so it is not counted.
- Return type:
int
- logpdf(x)
Competing-risks log likelihood of each cell's phase observation.
Called once per state on every E step, over the whole lineage array, so it is vectorized directly against
scipy.specialrather than going through the frozenrv_continuousmachinery (self.div_clock.logpdfetc.), which pays generic validation/broadcasting overhead on every call regardless of array size.- Parameters:
x (numpy.ndarray)
- Return type:
numpy.ndarray
- estimator(x, gammas)
Weighted M step for a single condition.
The two clocks separate in the complete-data likelihood, so each is just a weighted right-censored Gamma fit over every timed cell.
- Parameters:
x (numpy.ndarray)
gammas (numpy.ndarray)
- censor_lineage_array(censor_condition, tree, obs, states, desired_experiment_time=2000000000000.0)
Applies censoring to array representation directly.
- Parameters:
censor_condition (int)
tree (scipy.sparse.csr_array)
obs (numpy.ndarray)
states (numpy.ndarray)
- Return type:
tuple[scipy.sparse.csr_array, numpy.ndarray, numpy.ndarray]
- class lineage.states.StateDistributionCR.StateDistributionPhase(gamma_a1=7.0, gamma_scale1=3.0, gamma_a2=14.0, gamma_scale2=6.0, death_scale1=40.0, death_a2=3.0, death_scale2=20.0)
G1 and G2 phases, each with its own pair of competing clocks.
paramsis[bern_p1, bern_p2, a1, s1, a2, s2, death_a1, death_s1, death_a2, death_s2]. The leading six entries matchStateDistributionexactly.- Parameters:
gamma_a1 (float)
gamma_scale1 (float)
gamma_a2 (float)
gamma_scale2 (float)
death_scale1 (float)
death_a2 (float)
death_scale2 (float)
- atonce_estimator
- G1
- G2
- params
- _sync()
Mirror the sub-distributions' parameters into the flat
paramsarray.
- rvs(size, rng=None)
- Parameters:
size (int)
- dist(other)
- Return type:
float
- dof()
- Return type:
int
- logpdf(x)
- Parameters:
x (numpy.ndarray)
- Return type:
numpy.ndarray
- estimator(x, gammas)
- Parameters:
x (numpy.ndarray)
gammas (numpy.ndarray)
- censor_lineage_array(censor_condition, tree, obs, states, desired_experiment_time=2000000000000.0)
- Parameters:
censor_condition (int)
tree (scipy.sparse.csr_array)
obs (numpy.ndarray)
states (numpy.ndarray)
- Return type:
tuple[scipy.sparse.csr_array, numpy.ndarray, numpy.ndarray]