py-Pando
Pure-Python Pando: gene regulatory network inference from paired single-cell RNA + ATAC data, AnnData-native, parity-tested against the R package.
Pando (Fleck et al., Nature 2023, doi:10.1038/s41586-022-05459-w) fits, for every target gene, a regression of its expression on TF expression x accessibility of TF-binding peaks near the gene, and reads significant TF–peak terms as regulatory links. py-Pando ports it following the omicverse-rebuildr protocol: the R source is the specification, and pre-registered parity gates (data/manifest.yaml) were committed before any algorithmic code.
Install
pip install pypando # core
pip install "pypando[xgb]" # + xgboost models
pip install "pypando[umap]" # + UMAP layout of the module graph
Quick start
import pypando as pp
# rna: AnnData, cells x genes, log-normalised (Seurat LogNormalize)
# atac: AnnData, cells x peaks, TF-IDF (Signac RunTFIDF), var_names "chr-start-end"
annotation = pp.annotation_from_gtf("gencode.v41.basic.annotation.gff3.gz")
grn = pp.initiate_grn(rna, atac, annotation) # peaks minus exons
grn = pp.find_motifs(grn, genome="hg38.fa") # Pando's 1,590 motifs, MOODS
grn = pp.infer_grn(grn, genes=targets, method="glm") # one model per target gene
grn = pp.find_modules(grn) # TF modules
pp.coef(grn) # TF / target / region / estimate / std_err / pval / padj / corr
pp.gof(grn) # R^2 and number of variables per target gene
pp.NetworkModules(grn) # module table + genes_pos / genes_neg / regions / peaks
Within Epione: epione.grn.pando(rna, atac, genome="hg38", genes=targets).
Function map
| R (Pando 1.1.1) | Python | Parity on the fixture |
|---|---|---|
initiate_grn |
pp.initiate_grn |
identical regions (with and without regions= phastCons) |
find_motifs |
pp.find_motifs |
identical region x motif matrix (1,240,962 hits) |
find_peaks_near_genes (Signac, GREAT) |
pp.find_peaks_near_genes |
identical |
infer_grn(method = "glm") |
pp.infer_grn |
identical rows; estimates / SE / t / rsq to ~1e-14; -log10 p to 4e-13 |
infer_grn(method = "glmnet") |
lambda identical; coefficients Pearson 0.9999 (see notes) | |
infer_grn(method = "cv.glmnet") |
R's folds reproduced; coefficients within 5e-14 | |
infer_grn(method = "xgb") |
gate fails vs installed R (xgboost >= 3 drops Pando's parameters); identical (2e-16) with them honoured | |
infer_grn(method = "bagging_ridge" / "bayesian_ridge") |
~1e-14 (NumPy seeded as in R) | |
find_modules |
pp.find_modules |
identical modules |
fit_model, fit_glm, fit_glmnet, fit_cvglmnet, fit_xgb, fit_bagging_ridge, fit_bayesian_ridge |
same names | per-function, to ~1e-14 (notebook 3) |
format_coefs, sparse_cor, aggregate_matrix, aggregate_assay, GetAssaySummary |
same names | exact |
get_network_graph |
pp.get_network_graph |
graph + PageRank; UMAP layout via umap-learn (not R's uwot stream) |
coef, gof, GetNetwork, NetworkModules, NetworkTFs, NetworkRegions, NetworkFeatures, NetworkParams, NetworkGraph, DefaultNetwork, GetGRN, Params, create_GRNData |
same names | accessors |
plot_gof, plot_module_metrics, plot_network_graph |
same names (matplotlib; pip install 'pypando[plot]' for repelled labels) |
styled after Pando's ggplot2 figures: density-coloured gof scatter with marginal histograms and threshold lines, violins with boxplots, diagonal edges and magma PageRank nodes |
fit_brms |
— | not ported (Stan) |
get_tf_network |
pp.get_tf_network |
deliberate fix: the default prunes each leaf to its most significant path (mean −log10 padj) and honours order; R 1.1.1 silently prunes on the mean estimate and ignores order. keep_all_edges=True, order=np.inf = R, identical edges; default = R with the intended fix, identical edges |
plot_tf_network |
pp.plot_tf_network |
ggraph's layout = 'tree' reproduced (igraph Reingold-Tilford + ggraph's radial transform: node coordinates equal R's on the fixture) |
Full numbers: RECONSTRUCTION_REPORT.md and examples/compare_R_vs_Python.ipynb.
What makes the glm network identical
R's glm uses LINPACK's dqrdc2 QR with limited pivoting: a column whose remaining norm falls below 1e-11 of its original norm is moved to the end and its coefficient becomes NA. Pando's per-gene designs (hundreds to thousands of peak x TF products, often nearly collinear) sit right at that threshold, so the pivot decisions depend on the last bits of BLAS level-1 results. py-Pando's QR (pypando._blas, numba) calls the same BLAS routines (dnrm2, ddot, daxpy, dscal) in the same order as R's Fortran, multiplies with dgemv like R's %*%, sums in long double like R's sum(), and inverts with LAPACK dpotri like chol2inv. With the same BLAS build (here OpenBLAS 0.3.34) and one BLAS thread (infer_grn(blas_threads=1), the default), the glm network is bit-identical to R's, including genes where R's IRLS does not converge.
Notes
- cv.glmnet folds come from an emulation of R's
set.seed()+sample()(pypando.rrng), soinfer_grn(method="cv.glmnet", seed=42)uses R's folds afterset.seed(42). Fold fits follow glmnet 5.0: each fold on its own lambda path, predictions interpolated onto the full path (glmnet 4.x refitted folds on the full path;cv_glmnet_gaussian(fold_lambda="full")). - glmnet: py-Pando's coordinate descent follows glmnet's path rules (standardisation, lambda sequence,
fdev/devmaxstopping) but not its strong-rule screening, so for genes with hundreds of variables both stop within glmnet's 1e-7 tolerance at slightly different coefficients.fit_glmnetportsfamily = "gaussian"given as a string, which is howinfer_grncalls it; R'sfit_glmnetcalled directly with its defaultfamily = gaussian(a function) goes to glmnet's genericglmnet.pathsolver, which is not ported. - xgb: Pando calls
xgboost::xgboost(data, label, params = list(max_depth = 3, eta = 0.01), ...). xgboost >= 3 changed that function's interface and silently ignoresparams, training with depth 6 and learning rate 0.3. py-Pando uses Pando's parameters; with them honoured in R (xgb.train), importances match to 1e-15. - bagging_ridge draws from NumPy's global RNG, as Pando's scikit-learn call does from R;
infer_grn(seed=...)seeds it. - New in Python (not in R):
region_background,regions_near_genesandsubset_regionsscan only the regions a target gene can reach, against the background of all regions — the network is identical to a full scan.
Reproducing the R comparison
python tests/make_fixture.py pbmc_granulocyte_sorted_10k_filtered_feature_bc_matrix.h5 data/fixture
Rscript tests/r_reference_driver.R data/fixture ref # R 4.4.3, Pando 1.1.1
python tests/_run_candidate.py ref cand hg38.fa
python tests/parity_check.py ref cand results.json
PYPANDO_REF_DIR=ref PYPANDO_FASTA=hg38.fa PYPANDO_CAND_DIR=cand pytest tests/test_exact_match.py
Citation
Fleck, J.S., Jansen, S.M.J., Wollny, D. et al. Inferring and perturbing cell fate regulomes in human brain organoids. Nature 621, 365–372 (2023).
License
MIT, as the upstream R package (see LICENSE).
Metadata
Release files for pypando 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pypando-0.1.2.tar.gz | 4.3 MB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pypando-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 8.5 MB
Release files / pypando-0.1.2.tar.gz
| Download URL | pypando-0.1.2.tar.gz |
|---|---|
| Size | 4.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a54a5288b13b33287be74d47461956cfbea8f79b0c7749f4474a40219eb00d07
|
|
BLAKE2b-256 checksum How to use checksums |
2935c4b89417c7402dc8c26a5649fc38393368299296530e1399391399cb20c4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Release files / pypando-0.1.2-py3-none-any.whl
| Download URL | pypando-0.1.2-py3-none-any.whl |
|---|---|
| Size | 4.3 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cd7ed1d365eb0216ca52eb46e575c75373ec2465cc343387a094f550f0d5ac59
|
|
BLAKE2b-256 checksum How to use checksums |
09bf7b83b4a100f5f30cb3a27162af38f1cbc6f5ed4b0562ce0be3c7a7f4b9d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Security for pypando 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.