trackcell#

Full specification: docs/INTEGRATION.md in the repository.

Scope#

pyglmGamPoi implements the default SCT v2 offset model:

  • formula y ~ log_umi → intercept-only GLM with offset = log(10^log10_umi)

  • fixed slope log_umi = log(10)

  • output columns theta, Intercept, log_umi (genes × 3 DataFrame)

Recommended patch in trackcell fit_offset_model:

from pyglmGamPoi import fit_offset_model, is_available

if method == "glmGamPoi_offset" and is_available():
    return fit_offset_model(
        umi, regressor_data, gene_index,
        allow_inf_theta=allow_inf_theta,
        apply_shrinkage=True,
    )

Matrix layout#

  • Input to pyglmGamPoi: genes × cells (trackcell VST internal layout after transpose)

  • AnnData default: cells × genes — transpose before calling, or use pyglmGamPoi.adata

allow_inf_theta#

When True (trackcell SCT v2 default), genes at the Poisson boundary (overdispersion MLE = 0) receive theta = Inf, matching R fit_glmGamPoi_offset(..., allow_inf_theta = TRUE).

When False, theta is capped at mean(mu) / 1e-4 per gene.

Parity status#

Feature

Status

Notes

Per-gene intercept + offset NB

done

Newton + Brent; rough_beta_init fix for scRNA counts

Cox-Reid overdispersion MLE

done

Local golden section + Newton; matches R nlminb

Inf theta / allow_inf_theta

done

217/217 on GSE288946 step1 vs R live

Cross-gene overdispersion_shrinkage

done

loc_median_fit on first-pass rowMeans(Mu) + intercept refit

fit_glmGamPoi_offset vs R golden

done

tests/test_parity_r.py, tests/test_theta_od_parity.py

Algorithm notes#

Overdispersion MLE uses the same Cox-Reid adjusted log-likelihood as glmGamPoi::overdispersion_mle_impl. Optimization starts from the method-of-moments dispersion and searches locally (log_start ± 8 on log_theta) to avoid numerical artefacts at log_theta < -30. If the first OD pass at the initial intercept collapses to ~Poisson, OD is retried after refitting the intercept.