dspin 1.3.2__tar.gz → 1.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dspin-1.3.2/dspin.egg-info → dspin-1.3.4}/PKG-INFO +1 -1
- dspin-1.3.4/README.md +175 -0
- {dspin-1.3.2 → dspin-1.3.4}/dspin/annotate.py +0 -1
- {dspin-1.3.2 → dspin-1.3.4}/dspin/compute.py +151 -9
- {dspin-1.3.2 → dspin-1.3.4}/dspin/dspin.py +74 -25
- {dspin-1.3.2 → dspin-1.3.4/dspin.egg-info}/PKG-INFO +1 -1
- {dspin-1.3.2 → dspin-1.3.4}/setup.py +1 -1
- dspin-1.3.2/README.md +0 -134
- {dspin-1.3.2 → dspin-1.3.4}/LICENSE +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/__pycache__/__init__.cpython-310.pyc +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/__pycache__/__init__.cpython-37.pyc +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/__pycache__/compute.cpython-310.pyc +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/__pycache__/compute.cpython-37.pyc +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/__pycache__/dspin_abstract.cpython-310.pyc +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/__pycache__/plot.cpython-310.pyc +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/__pycache__/plotting.cpython-37.pyc +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/dspin/__init__.py +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/dspin/plot.py +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/dspin.egg-info/SOURCES.txt +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/dspin.egg-info/dependency_links.txt +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/dspin.egg-info/requires.txt +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/dspin.egg-info/top_level.txt +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/requirements.txt +0 -0
- {dspin-1.3.2 → dspin-1.3.4}/setup.cfg +0 -0
dspin-1.3.4/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# DSPIN
|
|
2
|
+
|
|
3
|
+
Tutorial, code and examples of the D-SPIN framework for the preprint "D-SPIN constructs gene regulatory network models from multiplexed scRNA-seq data revealing organizing principles of cellular perturbation response" ([bioRxiv](https://www.biorxiv.org/content/10.1101/2023.04.19.537364))
|
|
4
|
+
|
|
5
|
+
<div align="center">
|
|
6
|
+
<img src="figure/readme/Overview_20250523.png" width="800">
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
D-SPIN is implemented in Python and MATLAB. The Python code is sufficient for the analysis of most datasets with less than 100k cells and a few hundreds of conditions. The MATLAB code is more efficient on large datasets as MATLAB can be easily deployed on clusters for parallelization with the build-in "parfor" function.
|
|
12
|
+
|
|
13
|
+
The python code can be installed with
|
|
14
|
+
|
|
15
|
+
`pip install dspin`
|
|
16
|
+
|
|
17
|
+
The MATLAB code can be downloaded from the folder "DSPIN_matlab", and directly executable in MATLAB after specifying the path to the data.
|
|
18
|
+
|
|
19
|
+
## Demos
|
|
20
|
+
|
|
21
|
+
Two demos of D-SPIN are available on Google Colab.
|
|
22
|
+
|
|
23
|
+
The first demo reconstructs the regulatory network of simulated hematopoietic stem cell (HSC) differentiation network with perturbations using the BEELINE framework (Pratapa, Aditya, et al. Nature methods, 2020).
|
|
24
|
+
|
|
25
|
+
[Demo1](https://colab.research.google.com/drive/1Nja8AAcP0L7Ag6DGbyn21SxGafsYX4fA?usp=sharing)
|
|
26
|
+
|
|
27
|
+
The second demo reconstructs regulatory network and response vector in a public single-cell dataset of immune dictionary (Cui, Ang, et al. Nature, 2024), where mouse were treated with different cytokines and lymph nodes were than collected and profiled.
|
|
28
|
+
|
|
29
|
+
[Demo2](https://colab.research.google.com/drive/1E8v4awQ-m4D02emG0jp6kmRDmkZ8hjjN?usp=sharing)
|
|
30
|
+
|
|
31
|
+
## Dependencies
|
|
32
|
+
|
|
33
|
+
DSPIN package was tested with the following python packages versions:
|
|
34
|
+
|
|
35
|
+
- python (3.9.18)
|
|
36
|
+
- anndata (0.10.3)
|
|
37
|
+
- matplotlib (3.8.2)
|
|
38
|
+
- scanpy (1.9.6)
|
|
39
|
+
- tqdm (4.65.0)
|
|
40
|
+
- leidenalg (0.10.1)
|
|
41
|
+
- igraph (0.10.8)
|
|
42
|
+
|
|
43
|
+
Note: other versions may work as well.
|
|
44
|
+
|
|
45
|
+
## Input data
|
|
46
|
+
|
|
47
|
+
D‑SPIN can work with many perturbation types **as long as they share the same core regulatory network**:
|
|
48
|
+
|
|
49
|
+
* **Genetic screens** – Perturb‑seq, CRISPR, RNAi
|
|
50
|
+
* **Chemical or signaling cues** – drug treatments, growth‑factor changes
|
|
51
|
+
* **Physiological differences** – healthy vs disease, different patients, time‑course samples
|
|
52
|
+
* **Spatial niches** – local micro‑environments from spatial transcriptomics
|
|
53
|
+
|
|
54
|
+
Because D‑SPIN models the *entire* distribution of transcriptional states, it expects **single‑cell RNA‑seq** data. Bulk data is possible but less informative.
|
|
55
|
+
|
|
56
|
+
#### AnnData requirements
|
|
57
|
+
|
|
58
|
+
* `adata.X` — log‑normalized (`log1p`) count matrix **after** QC: remove low‑quality cells, filter high mitochondrial content, keep highly variable genes.
|
|
59
|
+
* `adata.obs['sample_id']` — condition label; cells with the same value form one perturbation group (≥ 25 cells per group is ideal).
|
|
60
|
+
* `adata.obs['batch']` — batch identifier for cells processed together; D‑SPIN corrects batch effects by comparing conditions within each batch.
|
|
61
|
+
* `adata.obs['if_control']` — `True` for controls, `False` otherwise; perturbation effects are measured relative to these controls (or to the overall average if no controls exist).
|
|
62
|
+
|
|
63
|
+
## Building network models with D‑SPIN
|
|
64
|
+
|
|
65
|
+
#### Gene‑level network models
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from dspin.dspin import DSPIN
|
|
69
|
+
|
|
70
|
+
model = DSPIN(adata, save_path, num_spin=adata.shape[1])
|
|
71
|
+
model.network_inference(
|
|
72
|
+
sample_id_key='sample_id',
|
|
73
|
+
method='pseudo_likelihood',
|
|
74
|
+
directed=True,
|
|
75
|
+
run_with_matlab=False,
|
|
76
|
+
params={'stepsz': 0.05, 'lam_l1_j': 0.01}
|
|
77
|
+
)
|
|
78
|
+
model.response_relative_to_control(
|
|
79
|
+
sample_id_key='sample_id',
|
|
80
|
+
if_control_key='if_control',
|
|
81
|
+
batch_key='batch'
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
* **Results saved to `model`:**
|
|
86
|
+
* `model.network` – regulatory weights **J**
|
|
87
|
+
* `model.responses` – gene‑level responses **h**
|
|
88
|
+
* `model.relative_responses` – responses relative to control (per batch)
|
|
89
|
+
|
|
90
|
+
* **Key options**
|
|
91
|
+
* `num_spin=adata.shape[1]` → build a *gene‑level* network
|
|
92
|
+
* `directed=True` → only supported with `pseudo_likelihood`
|
|
93
|
+
* `run_with_matlab=True` → skip Python inference and write variables to `save_path` for the MATLAB version (speeds up very large data sets)
|
|
94
|
+
* `params` → hyper‑parameters such as step size, training epochs, and L1/L2 regularisation
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
#### Program‑level network models
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from dspin.dspin import DSPIN
|
|
102
|
+
|
|
103
|
+
model = DSPIN(adata, save_path, num_spin=15) # 15 gene programs
|
|
104
|
+
model.gene_program_discovery(
|
|
105
|
+
num_repeat=10,
|
|
106
|
+
seed=0,
|
|
107
|
+
cluster_key='cell_type' # balance cell types
|
|
108
|
+
)
|
|
109
|
+
model.network_inference(
|
|
110
|
+
sample_id_key='sample_id',
|
|
111
|
+
method='mcmc_maximum_likelihood'
|
|
112
|
+
)
|
|
113
|
+
model.response_relative_to_control()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
* **Extra outputs**
|
|
117
|
+
* Gene‑program compositions in `save_path/onmf`
|
|
118
|
+
* Consensus program gene lists in `save_path`
|
|
119
|
+
|
|
120
|
+
* **Tips**
|
|
121
|
+
* Choose `num_spin` ≈ 5 × (number of cell types); keep ≤ 40
|
|
122
|
+
* `cluster_key` lets D‑SPIN down‑sample over‑represented cell types before oNMF
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
#### Finding gene regulators of programs
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# model_program : program‑level DSPIN object
|
|
130
|
+
# model_gene : gene‑level DSPIN object
|
|
131
|
+
model_gene.program_regulator_discovery(
|
|
132
|
+
model_program.program_representation,
|
|
133
|
+
sample_id_key='sample_id',
|
|
134
|
+
params={'stepsz': 0.02, 'lam_l1_interaction': 0.01}
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
* **Prerequisite** – `adata.obs` metadata must match between the two models so each cell’s gene and program states align.
|
|
139
|
+
|
|
140
|
+
* **Outputs in `model_gene`:**
|
|
141
|
+
* `model_gene.program_interactions` – regression coefficients **J** (gene ↔ program)
|
|
142
|
+
* `model_gene.program_activities` – global activity **h** for each program
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## Application to the T cell population of the immune dictionary dataset
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
<div align="center">
|
|
149
|
+
<img src="figure/readme/Figure2_immune_dictionary.png" width="600">
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div align="left">
|
|
153
|
+
<em>Figure Overview and program discovery for the immune dictionary dataset. (A) t-distributed stochastic neighbour (t-SNE) embedding for a subsetted cell population from the immune dictionary dataset \cite{cui2024dictionary}. The subset dataset includes CD4, CD8, and regulatory T cells treated by 12 different cytokines, as well as corresponding control samples treated by phosphate-buffered saline (PBS).
|
|
154
|
+
(B) Heatmaps of gene expression and discretized gene program levels for control and IFN-$\alpha$1-treated samples. The gene programs are weighted averages of single-gene expressions that characterize and denoise the major expression pattern of the gene matrix.</em>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div align="center">
|
|
158
|
+
<img src="figure/readme/Figure3_program_network.png" width="600">
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<div align="left">
|
|
162
|
+
<em>Figure Program/gene-level regulatory network inferred by D-SPIN.
|
|
163
|
+
(A) Diagram of D-SPIN-inferred network model on gene programs. The network is partitioned into 4 modules, each associated with a T cell type in the population.
|
|
164
|
+
(B) Heatmap of the program response of each cytokine. Clustering of the response partitioned the cytokines into 3 major categories.
|
|
165
|
+
(C) Diagram of the core subnetwork of the D-SPIN-inferred gene network model. The node sizes scale with the number of identified interactions. The network is partitioned into 4 modules, each primarily composed of genes that have elevated expression in one specific T cell type.
|
|
166
|
+
(D) Interaction diagram for the subnetwork of IFN-$\alpha$1/IFN-$\beta$ and IFN-$\gamma$ acting on the program P18-IFN-$\gamma$ response. D-SPIN model shows that Type I and Type II interferons have different effectors to activate the program.</em>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
## References
|
|
170
|
+
|
|
171
|
+
1. Jiang, Jialong, et al. "D-SPIN constructs gene regulatory network models from multiplexed scRNA-seq data revealing organizing principles of cellular perturbation response." bioRxiv (2023).
|
|
172
|
+
|
|
173
|
+
2. Pratapa, Aditya, et al. "Benchmarking algorithms for gene regulatory network inference from single-cell transcriptomic data." Nature methods 17.2 (2020): 147-154.
|
|
174
|
+
|
|
175
|
+
3. Cui, Ang, et al. "Dictionary of immune responses to cytokines at single-cell resolution." Nature 625.7994 (2024): 377-384.
|
|
@@ -20,6 +20,7 @@ from tqdm import tqdm
|
|
|
20
20
|
from typing import Tuple, List, Callable, Any, Dict
|
|
21
21
|
import warnings
|
|
22
22
|
from scipy.sparse import issparse
|
|
23
|
+
from joblib import Parallel, delayed, parallel_backend
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def category_balance_number(
|
|
@@ -107,7 +108,7 @@ def summary_components(all_components: np.array,
|
|
|
107
108
|
gene_weight = np.sum(all_components ** 2, axis=0) ** 0.5
|
|
108
109
|
gene_sele_filt = gene_weight > np.mean(gene_weight)
|
|
109
110
|
all_components_sub = all_components[:, gene_sele_filt]
|
|
110
|
-
kmeans_gene = KMeans(n_clusters=2 * num_spin, random_state=0, n_init=10).fit(all_components_sub.T)
|
|
111
|
+
kmeans_gene = KMeans(n_clusters=min(2 * num_spin, all_components_sub.shape[1]), random_state=0, n_init=10).fit(all_components_sub.T)
|
|
111
112
|
|
|
112
113
|
# Initialize an array to store the average component for each cluster.
|
|
113
114
|
components_kmeans = np.zeros((num_spin, num_gene))
|
|
@@ -526,7 +527,7 @@ def sample_states(samp_full: np.array, onmf_rep_tri: np.array) -> Tuple[np.array
|
|
|
526
527
|
return state_list, samp_list
|
|
527
528
|
|
|
528
529
|
|
|
529
|
-
def
|
|
530
|
+
def para_moments_numpy(j_mat: np.array, h_vec: np.array) -> Tuple[np.array, np.array]:
|
|
530
531
|
"""
|
|
531
532
|
Calculate the correlation and mean parameters given a j matrix and an h vector.
|
|
532
533
|
|
|
@@ -565,6 +566,74 @@ def para_moments(j_mat: np.array, h_vec: np.array) -> Tuple[np.array, np.array]:
|
|
|
565
566
|
|
|
566
567
|
return corr_para, mean_para
|
|
567
568
|
|
|
569
|
+
@numba.njit(fastmath=True, cache=True)
|
|
570
|
+
def para_moments(j_mat: np.ndarray,
|
|
571
|
+
h_vec: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
572
|
+
"""
|
|
573
|
+
Calculate the correlation and mean parameters given an interaction
|
|
574
|
+
matrix and an external field vector, using a Numba‑accelerated kernel.
|
|
575
|
+
|
|
576
|
+
Parameters
|
|
577
|
+
----------
|
|
578
|
+
j_mat : np.ndarray
|
|
579
|
+
Interaction matrix.
|
|
580
|
+
h_vec : np.ndarray
|
|
581
|
+
External field vector.
|
|
582
|
+
|
|
583
|
+
Returns
|
|
584
|
+
-------
|
|
585
|
+
Tuple[np.ndarray, np.ndarray]
|
|
586
|
+
The correlation parameter array of shape (n, n).
|
|
587
|
+
The mean parameter array of shape (n,).
|
|
588
|
+
"""
|
|
589
|
+
num_spin: int = j_mat.shape[0]
|
|
590
|
+
|
|
591
|
+
# Copy so we can safely modify the diagonal in‑place
|
|
592
|
+
j_dbl = j_mat.copy()
|
|
593
|
+
for ii in range(num_spin):
|
|
594
|
+
j_dbl[ii, ii] += j_mat[ii, ii]
|
|
595
|
+
|
|
596
|
+
num_sample: int = 3 ** num_spin
|
|
597
|
+
|
|
598
|
+
partition: float = 0.0
|
|
599
|
+
mean_para = np.zeros(num_spin, dtype=np.float64)
|
|
600
|
+
corr_para = np.zeros((num_spin, num_spin), dtype=np.float64)
|
|
601
|
+
|
|
602
|
+
spins = np.empty(num_spin, dtype=np.int8)
|
|
603
|
+
|
|
604
|
+
for idx in range(num_sample):
|
|
605
|
+
# Decode `idx` → base‑3 spin configuration in {‑1, 0, 1}
|
|
606
|
+
tmp = idx
|
|
607
|
+
for s in range(num_spin - 1, -1, -1):
|
|
608
|
+
spins[s] = (tmp % 3) - 1
|
|
609
|
+
tmp //= 3
|
|
610
|
+
|
|
611
|
+
# Energy: E = −h·s − ½ sᵀJs
|
|
612
|
+
energy: float = 0.0
|
|
613
|
+
for ii in range(num_spin):
|
|
614
|
+
energy -= h_vec[ii] * spins[ii]
|
|
615
|
+
|
|
616
|
+
js_term: float = 0.0
|
|
617
|
+
for ii in range(num_spin):
|
|
618
|
+
dot: float = 0.0
|
|
619
|
+
for jj in range(num_spin):
|
|
620
|
+
dot += j_dbl[ii, jj] * spins[jj]
|
|
621
|
+
js_term += spins[ii] * dot
|
|
622
|
+
energy -= 0.5 * js_term
|
|
623
|
+
|
|
624
|
+
weight: float = np.exp(-energy)
|
|
625
|
+
|
|
626
|
+
partition += weight
|
|
627
|
+
for ii in range(num_spin):
|
|
628
|
+
mean_para[ii] += spins[ii] * weight
|
|
629
|
+
for jj in range(num_spin):
|
|
630
|
+
corr_para[ii, jj] += spins[ii] * spins[jj] * weight
|
|
631
|
+
|
|
632
|
+
mean_para /= partition
|
|
633
|
+
corr_para /= partition
|
|
634
|
+
|
|
635
|
+
return corr_para, mean_para
|
|
636
|
+
|
|
568
637
|
|
|
569
638
|
@numba.njit(fastmath=True)
|
|
570
639
|
def pseudol_gradient(cur_j: np.ndarray,
|
|
@@ -602,7 +671,7 @@ def pseudol_gradient(cur_j: np.ndarray,
|
|
|
602
671
|
for ii in range(num_spin):
|
|
603
672
|
j_no_diag[ii, ii] = 0.0
|
|
604
673
|
|
|
605
|
-
eff_h = j_no_diag @ cur_state + cur_h
|
|
674
|
+
eff_h = j_no_diag @ cur_state + cur_h
|
|
606
675
|
|
|
607
676
|
exp_plus = np.empty_like(eff_h)
|
|
608
677
|
exp_minus = np.empty_like(eff_h)
|
|
@@ -727,10 +796,10 @@ def samp_moments(j_mat: np.ndarray,
|
|
|
727
796
|
return corr_para, mean_para
|
|
728
797
|
|
|
729
798
|
|
|
730
|
-
def compute_gradient(cur_j: np.ndarray,
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
799
|
+
def compute_gradient(cur_j: np.ndarray,
|
|
800
|
+
cur_h: np.ndarray,
|
|
801
|
+
raw_data: List[Tuple[np.ndarray, np.ndarray]],
|
|
802
|
+
method: str, train_dat: Dict[str, Any]) -> Tuple[np.ndarray, np.ndarray]:
|
|
734
803
|
"""
|
|
735
804
|
Compute the gradient based on the specified method.
|
|
736
805
|
|
|
@@ -763,7 +832,7 @@ def compute_gradient(cur_j: np.ndarray,
|
|
|
763
832
|
# Computing gradients using the specified method.
|
|
764
833
|
if method == 'pseudo_likelihood':
|
|
765
834
|
j_grad, h_grad = pseudol_gradient(
|
|
766
|
-
cur_j, cur_h[:, kk
|
|
835
|
+
cur_j, cur_h[:, kk: kk + 1], raw_data[kk], directed=train_dat['directed'])
|
|
767
836
|
h_grad = h_grad.flatten()
|
|
768
837
|
else:
|
|
769
838
|
# Distinguishing between other methods and computing gradients
|
|
@@ -772,7 +841,7 @@ def compute_gradient(cur_j: np.ndarray,
|
|
|
772
841
|
corr_para, mean_para = para_moments(cur_j, cur_h[:, kk])
|
|
773
842
|
elif method == 'mcmc_maximum_likelihood':
|
|
774
843
|
corr_para, mean_para = samp_moments(
|
|
775
|
-
cur_j, cur_h, train_dat['mcmc_samplingsz'], train_dat['mcmc_samplingmix'], train_dat['mcmc_samplegap'])
|
|
844
|
+
cur_j, cur_h[:, kk], train_dat['mcmc_samplingsz'], train_dat['mcmc_samplingmix'], train_dat['mcmc_samplegap'])
|
|
776
845
|
j_grad = corr_para - raw_data[kk][0]
|
|
777
846
|
h_grad = mean_para - raw_data[kk][1].flatten()
|
|
778
847
|
|
|
@@ -782,6 +851,77 @@ def compute_gradient(cur_j: np.ndarray,
|
|
|
782
851
|
return rec_jgrad, rec_hgrad
|
|
783
852
|
|
|
784
853
|
|
|
854
|
+
def _gradient_one_round(kk: int,
|
|
855
|
+
cur_j: np.ndarray,
|
|
856
|
+
cur_h: np.ndarray,
|
|
857
|
+
raw_data_k,
|
|
858
|
+
method: str,
|
|
859
|
+
train_dat: Dict[str, Any]):
|
|
860
|
+
"""Helper that compute gradient for a single round."""
|
|
861
|
+
if method == 'pseudo_likelihood':
|
|
862
|
+
j_grad, h_grad = pseudol_gradient(cur_j, cur_h[:, kk:kk + 1], raw_data_k, directed=train_dat['directed'])
|
|
863
|
+
h_grad = h_grad.flatten()
|
|
864
|
+
else:
|
|
865
|
+
if method == 'maximum_likelihood':
|
|
866
|
+
corr_para, mean_para = para_moments(cur_j, cur_h[:, kk])
|
|
867
|
+
elif method == 'mcmc_maximum_likelihood':
|
|
868
|
+
corr_para, mean_para = samp_moments(
|
|
869
|
+
cur_j, cur_h[:, kk], train_dat['mcmc_samplingsz'], train_dat['mcmc_samplingmix'], train_dat['mcmc_samplegap'])
|
|
870
|
+
j_grad = corr_para - raw_data_k[0]
|
|
871
|
+
h_grad = mean_para - raw_data_k[1].flatten()
|
|
872
|
+
|
|
873
|
+
return kk, j_grad, h_grad
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
def compute_gradient_para(cur_j: np.ndarray,
|
|
877
|
+
cur_h: np.ndarray,
|
|
878
|
+
raw_data: List[Tuple[np.ndarray, np.ndarray]],
|
|
879
|
+
method: str, train_dat: Dict[str, Any]) -> Tuple[np.ndarray, np.ndarray]:
|
|
880
|
+
"""
|
|
881
|
+
Compute the gradient based on the specified method with parallel processing.
|
|
882
|
+
|
|
883
|
+
Parameters
|
|
884
|
+
----------
|
|
885
|
+
cur_j : np.ndarray
|
|
886
|
+
Current j matrix.
|
|
887
|
+
cur_h : np.ndarray
|
|
888
|
+
Current h matrix.
|
|
889
|
+
raw_data : List[Tuple[np.ndarray, np.ndarray]]
|
|
890
|
+
Raw data used to calculate the gradient (each element is a tuple of correlation and mean).
|
|
891
|
+
method : str
|
|
892
|
+
The method to calculate the gradient; possible values are 'pseudo_likelihood',
|
|
893
|
+
'maximum_likelihood', and 'mcmc_maximum_likelihood'.
|
|
894
|
+
train_dat : Dict[str, Any]
|
|
895
|
+
Training data and hyperparameters.
|
|
896
|
+
|
|
897
|
+
Returns
|
|
898
|
+
-------
|
|
899
|
+
Tuple[np.array, np.array]
|
|
900
|
+
The gradient of j.
|
|
901
|
+
The gradient of h.
|
|
902
|
+
"""
|
|
903
|
+
num_spin, num_round = cur_h.shape
|
|
904
|
+
rec_jgrad = np.zeros((num_spin, num_spin, num_round))
|
|
905
|
+
rec_hgrad = np.zeros((num_spin, num_round))
|
|
906
|
+
|
|
907
|
+
n_jobs = train_dat.get('max_workers', min(num_round, os.cpu_count()))
|
|
908
|
+
chunk = train_dat.get('chunk_size', 1) # rounds per task
|
|
909
|
+
backend = 'loky' # process‑based
|
|
910
|
+
|
|
911
|
+
# print(f'Processing with {n_jobs} workers...')
|
|
912
|
+
with parallel_backend(backend, n_jobs=n_jobs):
|
|
913
|
+
results = Parallel(batch_size=chunk)(
|
|
914
|
+
delayed(_gradient_one_round)(kk, cur_j, cur_h, raw_data[kk], method, train_dat)
|
|
915
|
+
for kk in range(num_round)
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
for kk, j_grad, h_grad in results:
|
|
919
|
+
rec_jgrad[:, :, kk] = j_grad
|
|
920
|
+
rec_hgrad[:, kk] = h_grad
|
|
921
|
+
|
|
922
|
+
return rec_jgrad, rec_hgrad
|
|
923
|
+
|
|
924
|
+
|
|
785
925
|
def apply_regularization(rec_jgrad: np.ndarray,
|
|
786
926
|
rec_hgrad: np.ndarray,
|
|
787
927
|
cur_j: np.ndarray,
|
|
@@ -964,6 +1104,8 @@ def learn_network_adam(raw_data: Any,
|
|
|
964
1104
|
|
|
965
1105
|
backtrack_counter = 0
|
|
966
1106
|
counter = 1
|
|
1107
|
+
|
|
1108
|
+
tqdm._instances.clear()
|
|
967
1109
|
pbar = tqdm(total=num_epoch)
|
|
968
1110
|
|
|
969
1111
|
while counter <= num_epoch:
|
|
@@ -34,6 +34,7 @@ import matplotlib.patheffects as patheffects
|
|
|
34
34
|
import warnings
|
|
35
35
|
import itertools
|
|
36
36
|
from typing import List, Dict, Optional, Tuple, Union
|
|
37
|
+
from scipy.stats import skew
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
class AbstractDSPIN(ABC):
|
|
@@ -206,6 +207,7 @@ class AbstractDSPIN(ABC):
|
|
|
206
207
|
self._samp_list = self._samp_list[example_list_ind]
|
|
207
208
|
raw_data = raw_data[example_list_ind]
|
|
208
209
|
self._raw_data = raw_data
|
|
210
|
+
print("Using a subset of {} samples for network inference.".format(len(self._samp_list)))
|
|
209
211
|
|
|
210
212
|
num_sample = len(raw_data)
|
|
211
213
|
params = {'num_epoch': 400,
|
|
@@ -337,8 +339,8 @@ class AbstractDSPIN(ABC):
|
|
|
337
339
|
raise ValueError(f"Column '{batch_key}' not found in adata.obs.")
|
|
338
340
|
|
|
339
341
|
sample_list = self._samp_list
|
|
340
|
-
sample_to_control = self.adata.obs.groupby(sample_id_key)[if_control_key].first().to_dict()
|
|
341
|
-
sample_to_batch = self.adata.obs.groupby(sample_id_key)[batch_key].first().to_dict()
|
|
342
|
+
sample_to_control = self.adata.obs.groupby(sample_id_key, observed=False)[if_control_key].first().to_dict()
|
|
343
|
+
sample_to_batch = self.adata.obs.groupby(sample_id_key, observed=False)[batch_key].first().to_dict()
|
|
342
344
|
|
|
343
345
|
if_control = np.array([sample_to_control[sample] for sample in sample_list])
|
|
344
346
|
batch_index = np.array([sample_to_batch[sample] for sample in sample_list])
|
|
@@ -382,8 +384,20 @@ class GeneDSPIN(AbstractDSPIN):
|
|
|
382
384
|
else:
|
|
383
385
|
self._onmf_rep_ori = adata.X
|
|
384
386
|
|
|
387
|
+
gene_skew = np.zeros(num_spin)
|
|
388
|
+
|
|
389
|
+
for ii in range(num_spin):
|
|
390
|
+
cur_gene = self._onmf_rep_ori[:, ii]
|
|
391
|
+
gene_skew[ii] = skew(cur_gene, bias=False)
|
|
392
|
+
|
|
393
|
+
if gene_skew.mean() > 2:
|
|
394
|
+
default_clip = 95
|
|
395
|
+
print("Input gene expression seem to be heavy-tailed. Using 95 percentile for clipping before discretization.")
|
|
396
|
+
else:
|
|
397
|
+
default_clip = 100
|
|
398
|
+
|
|
385
399
|
dparams = {'use_default_discretize': True,
|
|
386
|
-
'clip_percentile':
|
|
400
|
+
'clip_percentile': default_clip,
|
|
387
401
|
'num_init': 10}
|
|
388
402
|
dparams.update(discretize_params)
|
|
389
403
|
|
|
@@ -484,6 +498,42 @@ class ProgramDSPIN(AbstractDSPIN):
|
|
|
484
498
|
def onmf_decomposition(self, value):
|
|
485
499
|
self._onmf_summary = value
|
|
486
500
|
|
|
501
|
+
def compute_onmf_expression(self,
|
|
502
|
+
sample_matrix: np.ndarray,
|
|
503
|
+
gene_group_ind: List[List[int]]):
|
|
504
|
+
"""
|
|
505
|
+
Compute the oNMF expression for a given sample matrix and gene components of each program.
|
|
506
|
+
|
|
507
|
+
Parameters
|
|
508
|
+
----------
|
|
509
|
+
sample_matrix : np.ndarray
|
|
510
|
+
The sample matrix containing gene expressions.
|
|
511
|
+
gene_group_ind : List[List[int]]
|
|
512
|
+
List of lists containing indices of genes for each program component.
|
|
513
|
+
|
|
514
|
+
Returns
|
|
515
|
+
-------
|
|
516
|
+
NMF
|
|
517
|
+
An NMF object containing the computed components.
|
|
518
|
+
"""
|
|
519
|
+
num_spin = self.num_spin
|
|
520
|
+
adata = self.adata
|
|
521
|
+
|
|
522
|
+
# Initialize and compute the summary matrix
|
|
523
|
+
components_summary = np.zeros((num_spin, adata.shape[1]))
|
|
524
|
+
for ii in range(num_spin):
|
|
525
|
+
sub_matrix = sample_matrix[:, gene_group_ind[ii]]
|
|
526
|
+
sub_onmf = NMF(n_components=1, init='random', random_state=0).fit(sub_matrix)
|
|
527
|
+
components_summary[ii, gene_group_ind[ii]] = sub_onmf.components_
|
|
528
|
+
|
|
529
|
+
# Normalize the summary components and finalize the onmf_summary object
|
|
530
|
+
components_summary = normalize(components_summary, axis=1, norm='l2')
|
|
531
|
+
onmf_summary = NMF(n_components=num_spin, init='random', random_state=0)
|
|
532
|
+
onmf_summary.components_ = components_summary
|
|
533
|
+
|
|
534
|
+
return onmf_summary
|
|
535
|
+
|
|
536
|
+
|
|
487
537
|
def summarize_onmf_result(self,
|
|
488
538
|
large_subsample_matrix: np.ndarray,
|
|
489
539
|
num_onmf_components: int,
|
|
@@ -504,10 +554,6 @@ class ProgramDSPIN(AbstractDSPIN):
|
|
|
504
554
|
NMF
|
|
505
555
|
An NMF object containing the summarized components.
|
|
506
556
|
"""
|
|
507
|
-
|
|
508
|
-
# Retrieve the number of spins and annotated data
|
|
509
|
-
num_spin = self.num_spin
|
|
510
|
-
adata = self.adata
|
|
511
557
|
prior_programs_mask = self.prior_programs_mask
|
|
512
558
|
seed_list = self.onmf_parameters['seed_list']
|
|
513
559
|
summary_method = self.onmf_parameters['summary_method']
|
|
@@ -520,7 +566,7 @@ class ProgramDSPIN(AbstractDSPIN):
|
|
|
520
566
|
cur_onmf = np.load(self.save_path + f'onmf/onmf_components_{num_onmf_components}_repeat_{cur_seed}.npy', allow_pickle=True).item()
|
|
521
567
|
rec_components[ii] = cur_onmf.components_
|
|
522
568
|
|
|
523
|
-
all_components = rec_components.reshape(-1, np.sum(prior_programs_mask))
|
|
569
|
+
all_components = rec_components.reshape(- 1, np.sum(prior_programs_mask))
|
|
524
570
|
|
|
525
571
|
gene_group_ind = summary_components(all_components, num_onmf_components, summary_method=summary_method, figure_folder=self.fig_folder)
|
|
526
572
|
|
|
@@ -529,17 +575,8 @@ class ProgramDSPIN(AbstractDSPIN):
|
|
|
529
575
|
gene_group_ind = [sub_mask_ind[gene_list] for gene_list in gene_group_ind]
|
|
530
576
|
gene_group_ind += self.prior_programs_ind
|
|
531
577
|
|
|
532
|
-
#
|
|
533
|
-
|
|
534
|
-
for ii in range(num_spin):
|
|
535
|
-
sub_matrix = large_subsample_matrix[:, gene_group_ind[ii]]
|
|
536
|
-
sub_onmf = NMF(n_components=1, init='random', random_state=0).fit(sub_matrix)
|
|
537
|
-
components_summary[ii, gene_group_ind[ii]] = sub_onmf.components_
|
|
538
|
-
|
|
539
|
-
# Normalize the summary components and finalize the onmf_summary object
|
|
540
|
-
components_summary = normalize(components_summary, axis=1, norm='l2')
|
|
541
|
-
onmf_summary = NMF(n_components=num_spin, init='random', random_state=0)
|
|
542
|
-
onmf_summary.components_ = components_summary
|
|
578
|
+
# Evaluate the expression level of each program and the weights for each gene in the program
|
|
579
|
+
onmf_summary = self.compute_onmf_expression(large_subsample_matrix, gene_group_ind)
|
|
543
580
|
|
|
544
581
|
return onmf_summary
|
|
545
582
|
|
|
@@ -549,7 +586,6 @@ class ProgramDSPIN(AbstractDSPIN):
|
|
|
549
586
|
cluster_key: str = 'leiden',
|
|
550
587
|
mode: str = 'compute_summary',
|
|
551
588
|
prior_programs: List[List[str]] = None,
|
|
552
|
-
clip_percentile: float = 100,
|
|
553
589
|
params: dict = {},
|
|
554
590
|
discretize_params: dict = {}) -> None:
|
|
555
591
|
"""
|
|
@@ -567,10 +603,10 @@ class ProgramDSPIN(AbstractDSPIN):
|
|
|
567
603
|
Mode of operation. Options: 'compute_summary', 'compute_only', 'summary_only'. Default is 'compute_summary'.
|
|
568
604
|
prior_programs : List[List[str]], optional
|
|
569
605
|
List of predefined gene programs. Default is None.
|
|
570
|
-
clip_percentile : float, optional
|
|
571
|
-
Percentile for clipping values before discretization. Default is 100.
|
|
572
606
|
params : dict, optional
|
|
573
607
|
Additional parameters for oNMF. Default is an empty dictionary.
|
|
608
|
+
discretize_params : dict, optional
|
|
609
|
+
Parameters for discretization of the oNMF representation. Default is an empty dictionary.
|
|
574
610
|
"""
|
|
575
611
|
|
|
576
612
|
if mode not in ['compute_summary', 'compute_only', 'summary_only']:
|
|
@@ -595,6 +631,7 @@ class ProgramDSPIN(AbstractDSPIN):
|
|
|
595
631
|
|
|
596
632
|
if onmf_parameters['num_subsample_large'] is None:
|
|
597
633
|
onmf_parameters['num_subsample_large'] = min(onmf_parameters['num_subsample'] * 5, adata.shape[0])
|
|
634
|
+
print(onmf_parameters['num_subsample_large'])
|
|
598
635
|
|
|
599
636
|
if onmf_parameters['balance_method'] not in ['equal', 'proportional', 'squareroot', None]:
|
|
600
637
|
raise ValueError('balance_method must be one of equal, proportional, squareroot, or None')
|
|
@@ -607,7 +644,13 @@ class ProgramDSPIN(AbstractDSPIN):
|
|
|
607
644
|
prior_program_ind = [[np.where(adata.var_names == gene)[0][0] for gene in gene_list] for gene_list in prior_programs]
|
|
608
645
|
preprogram_flat = [gene for program in prior_programs for gene in program]
|
|
609
646
|
if len(prior_programs) > num_spin:
|
|
610
|
-
raise ValueError('Number of
|
|
647
|
+
raise ValueError('Number of prior programs must be less than the number of programs')
|
|
648
|
+
elif len(prior_programs) == num_spin:
|
|
649
|
+
warnings.warn('Number of prior programs is equal to the number of programs. No oNMF is performed.')
|
|
650
|
+
mode = 'summary_only'
|
|
651
|
+
elif len(prior_programs) == 0:
|
|
652
|
+
warnings.warn('Empty prior programs')
|
|
653
|
+
|
|
611
654
|
# Generate mask to exclude genes in prior programs from the annotated data
|
|
612
655
|
prior_program_mask = ~ np.isin(adata.var_names, preprogram_flat)
|
|
613
656
|
onmf_parameters['num_onmf_components'] -= len(prior_programs)
|
|
@@ -639,13 +682,19 @@ class ProgramDSPIN(AbstractDSPIN):
|
|
|
639
682
|
compute_onmf_decomposition(sub_gene_matrix_normed, num_onmf_components, seed=cur_seed, params=onmf_parameters, save_path=self.save_path + f'onmf/onmf_components_{num_onmf_components}_repeat_{cur_seed}.npy')
|
|
640
683
|
|
|
641
684
|
if mode == 'compute_summary' or mode == 'summary_only':
|
|
685
|
+
|
|
686
|
+
cluster_label_raw = adata.obs[cluster_key].values
|
|
687
|
+
cluster_label = pd.factorize(cluster_label_raw)[0]
|
|
642
688
|
|
|
643
689
|
# Perform subsampling and standard deviation clipping on the matrix
|
|
644
|
-
std_clipped, sub_large_gene_matrix_normed = subsample_normalize_gene_matrix(
|
|
690
|
+
std_clipped, sub_large_gene_matrix_normed = subsample_normalize_gene_matrix(adata.X, cluster_label, num_subsample=onmf_parameters['num_subsample_large'], seed=seed, params=onmf_parameters)
|
|
645
691
|
self.matrix_std = std_clipped
|
|
646
692
|
|
|
647
693
|
# Summarize the oNMF decompositions
|
|
648
|
-
|
|
694
|
+
if num_onmf_components > 0:
|
|
695
|
+
onmf_summary = self.summarize_onmf_result(sub_large_gene_matrix_normed, num_onmf_components, num_repeat)
|
|
696
|
+
else:
|
|
697
|
+
onmf_summary = self.compute_onmf_expression(sub_large_gene_matrix_normed, prior_program_ind)
|
|
649
698
|
|
|
650
699
|
self._onmf_summary = onmf_summary
|
|
651
700
|
|
|
@@ -21,7 +21,7 @@ install_requires = [
|
|
|
21
21
|
# ]
|
|
22
22
|
|
|
23
23
|
# Managing your version (adjust accordingly)
|
|
24
|
-
version = "1.3.
|
|
24
|
+
version = "1.3.4" # You can manage versioning in a more sophisticated way if needed
|
|
25
25
|
|
|
26
26
|
# Long description (usually a README file)
|
|
27
27
|
readme = open("README_PyPI.md").read() # Adjust the file name if necessary
|
dspin-1.3.2/README.md
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# DSPIN
|
|
2
|
-
|
|
3
|
-
Tutorial, code and examples of the D-SPIN framework for the preprint "D-SPIN constructs gene regulatory network models from multiplexed scRNA-seq data revealing organizing principles of cellular perturbation response" ([bioRxiv](https://www.biorxiv.org/content/10.1101/2023.04.19.537364))
|
|
4
|
-
|
|
5
|
-

|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
D-SPIN is implemented in Python and Matlab. The Python code is sufficient for analysis of small datasets with around 20k cells and tens of conditions. The Matlab code is only used for network inference on large datasets as Matlab can be easily deployed on clusters for parallelization with the build-in "parfor" function.
|
|
10
|
-
|
|
11
|
-
The python code can be installed with
|
|
12
|
-
|
|
13
|
-
`pip install dspin`
|
|
14
|
-
|
|
15
|
-
The Matlab code can be downloaded from the folder "DSPIN_matlab", and directly executable in Matlab after specifying the path to the data.
|
|
16
|
-
|
|
17
|
-
## Demos
|
|
18
|
-
|
|
19
|
-
Two demos of D-SPIN are available on Google Colab.
|
|
20
|
-
|
|
21
|
-
The first demo reconstructs the regulatory network of simulated hematopoietic stem cell (HSC) differentiation network with perturbations using the BEELINE framework (Pratapa, Aditya, et al. Nature methods, 2020).
|
|
22
|
-
|
|
23
|
-
[Demo1](https://colab.research.google.com/drive/1mjJ09_E9kjEYsGCzf10-aZfuOqHUEV5u?usp=sharing)
|
|
24
|
-
|
|
25
|
-
The second demo reconstructs regulatory network and response vector in a single-cell dataset collected in the ThomsonLab.In the dataset, human peripheral blood mononuclear cells (PBMCs) were treated with various signaling molecules with different dosages.
|
|
26
|
-
|
|
27
|
-
[Demo2](https://colab.research.google.com/drive/1U2WbmKvai3Y_-vlLV0SY1dv1LHdFjg2w?usp=sharing)
|
|
28
|
-
|
|
29
|
-
## Dependencies
|
|
30
|
-
|
|
31
|
-
DSPIN package was tested with the following python packages versions:
|
|
32
|
-
|
|
33
|
-
- python (3.9.18)
|
|
34
|
-
- anndata (0.10.3)
|
|
35
|
-
- matplotlib (3.8.2)
|
|
36
|
-
- scanpy (1.9.6)
|
|
37
|
-
- tqdm (4.65.0)
|
|
38
|
-
- leidenalg (0.10.1)
|
|
39
|
-
- igraph (0.10.8)
|
|
40
|
-
|
|
41
|
-
Note: other versions may work as well.
|
|
42
|
-
|
|
43
|
-
## D-SPIN Overview
|
|
44
|
-
|
|
45
|
-
D-SPIN contains three main steps:
|
|
46
|
-
* Gene program discovery
|
|
47
|
-
* Network inference
|
|
48
|
-
* Network analysis
|
|
49
|
-
|
|
50
|
-
The input data should be AnnData object after typical single-cell preprocessing steps including cell counts normalization, log transformation, highly-varible gene selection, and clustering (optional). The input data should contain the following information. The attribute name is subject to the user while the following are defaults of the code:
|
|
51
|
-
* Normalized, log-transformed and filtered gene expression matrix in adata.X
|
|
52
|
-
* Sample information for each cell in adata.obs['sample_id']
|
|
53
|
-
* Batch information for each cell in adata.obs['batch']
|
|
54
|
-
* Clustering information for each cell in adata.obs['leiden']
|
|
55
|
-
* If some samples are control conditions, the relative response vectors will be computed for each batch with respect to the average of control conditions. The control conditions can be indicated in adata.obs['if_control'] with 1 for control and 0 for non-control. Note that all cells in the same sample and batch should have the same value in adata.obs['if_control'].
|
|
56
|
-
|
|
57
|
-
The D-SPIN is initiallized by model = dspin.DSPIN(adata) with the following major arguments:
|
|
58
|
-
|
|
59
|
-
* adata: AnnData object with the above information
|
|
60
|
-
* save_path: path to save the results
|
|
61
|
-
* num_spin (default 15): number of gene programs in the model
|
|
62
|
-
* filter_threshold (default 0.02): threshold for minimal proportion of cells that each gene program is expressed in
|
|
63
|
-
|
|
64
|
-
### Gene program discovery
|
|
65
|
-
|
|
66
|
-
By default, D-SPIN use orgthogonal non-negative matrix factorization (oNMF) to discover gene programs that coexpress in the data. The user can also specify pre-defined gene programs in full or partial. There are three senarios depending on the num_spin and the input parameter prior_programs.
|
|
67
|
-
* No pre-defined gene programs: empty prior_programs (default)
|
|
68
|
-
* Full pre-defined gene programs: the number of gene programs in prior_programs is equal to num_spin
|
|
69
|
-
* Partial pre-defined gene programs: the number of gene programs in prior_programs is less than num_spin
|
|
70
|
-
|
|
71
|
-
The gene program discovery contains two stages, the first stage subset and normalize the gene matrix, and the second stage run oNMF multiple times on the subset matrix to obtain consensus gene programs. Subsetting the gene matrix is primarily for computational efficiency, and preferrentially sampling cells with smaller population also helps to avoid overfitting on over-represented cell states in the dataset. As the algorithm for oNMF is stochastic, the algorithm is run multiples times and results from multiple run are combined to obtain a set of consensus gene program. Also, the number of oNMF components can be specified by the user to discover gene programs with finer resolution. For example, run the algorithm with 20 components in each individual run but combine the 20 programs into 15 consensus programs.
|
|
72
|
-
|
|
73
|
-
Overall, in the gene program discovery function model.gene_program_discovery() , D-SPIN takes the following major arguments:
|
|
74
|
-
|
|
75
|
-
* num_onmf_components (int, optional): The number of oNMF components. Default is infer based on num_spin and pre-assigned programs.
|
|
76
|
-
* num_subsample (int, optional): Number of cells to use in each individual oNMF components computation. Default is 10000.
|
|
77
|
-
* num_subsample_large (int, optional): Number of cells to use in computing consensus oNMF components. Default is 5 times of num_subsample.
|
|
78
|
-
* num_repeat (int, optional): Number of times to repeat the oNMF computation. Default is 10.
|
|
79
|
-
* balance_obs (str, optional): Cell type or clustering label in adata.obs for balanced sampling. Default is None.
|
|
80
|
-
* balance_method (str, optional): Method used for balancing. Options are 'squareroot', 'equal', 'proportional'. Default is 'squareroot'.
|
|
81
|
-
* max_sample_rate (float, optional): Maximum oversampling rate for underrepresented cell type or clustering categories. Default is 2.
|
|
82
|
-
* prior_programs (List[List[str]], optional): List of pre-assigned gene programs. Default is None.
|
|
83
|
-
* summary_method (str, optional): Method used for computing sensus oNMF components. Options are 'kmeans' and 'leiden'. Default is 'kmeans'.
|
|
84
|
-
|
|
85
|
-
### Network inference
|
|
86
|
-
|
|
87
|
-
In the network inference step, D-SPIN automatically choose between three inference methods depending on the number of gene programs:
|
|
88
|
-
|
|
89
|
-
* Exact maximum-likelihood inference (MLE)
|
|
90
|
-
* Markov-Chain Monte-Carlo (MCMC) maximum-likelihood inference
|
|
91
|
-
* Pseudo-likelihood inference (PL).
|
|
92
|
-
|
|
93
|
-
MLE is exactly solution of the problem but only applies to small network. MCMC is slightly more scalable to networks with 20-40 nodes. PL takes more simplified approximation to the problem which scales to hundreds of nodes, but requires larger cell number for the approximation to be effective. in the gene program discovery function model.network_inference(), D-SPIN takes the following major arguments
|
|
94
|
-
|
|
95
|
-
* sample_col_name (str, optional): Column name in adata.obs for sample information. Default is 'sample_id'
|
|
96
|
-
* control_col_name (str, optional): Column name in adata.obs for whether the cell is in a control sample. If not provided, all cells are treated as non-control and relative response vector are the same as response vector.
|
|
97
|
-
* batch_col_name (str, optional): Column name in adata.obs for batch information. Relative response are computed by the control response vector in the same batch. If not provided, all cells are treated as one batch.
|
|
98
|
-
* method (str, optional): Method used for network inference. Options are 'auto', 'maximum_likelihood', 'mcmc_maximum_likelihood', 'pseudo_likelihood'.
|
|
99
|
-
* example_list (List[str], optional): List of samples to use for network inference. Default is using all samples available in adata.obs[sample_col_name].
|
|
100
|
-
* record_step (int, optional): Number of steps to record training progress. Default is 10.
|
|
101
|
-
* params (Dict[str, Any], optional): Hypterparameters for the gradient descent of network inference. Default is None.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
### Network analysis
|
|
105
|
-
|
|
106
|
-
After network inference, D-SPIN provides three outputs:
|
|
107
|
-
|
|
108
|
-
* Inferred network adjacenty matrix in model.network
|
|
109
|
-
* Inferred pertrubation response in model.response
|
|
110
|
-
* Relative perturbation response to control in model.relative_response
|
|
111
|
-
|
|
112
|
-
Typical downstream analysis includes network visualization, module identification, and graph clustering of perturbation response with the network.
|
|
113
|
-
|
|
114
|
-
## Application to signaling response data of human PBMCs
|
|
115
|
-
|
|
116
|
-
D-SPIN takes single-cell sequencing data of multiple perturbation conditions. In the second demo, PBMCs are treated with different signaling molecules such as CD3 antibody, LPS, IL1B, and TGFB1
|
|
117
|
-
|
|
118
|
-

|
|
119
|
-
|
|
120
|
-
D-SPIN identifies a set of gene programs that coexpress in the data, and represent each cell as a combination of gene program expression states.
|
|
121
|
-
|
|
122
|
-

|
|
123
|
-
|
|
124
|
-
D-SPIN uses cross-correlation and mean of each perturbation condition to inferred a unified regulatory network and the response vector of each perturbation condition. The inference can be parallelized across perturbation conditions. The inference code is in Matlab using "parfor", while for demo purpose Python code (without parallelization) is provided.
|
|
125
|
-
|
|
126
|
-
The inferred regulatory network and perturbations can be jointly analyzed to reveal how perturbations act in the context of the regulatory network.
|
|
127
|
-
|
|
128
|
-

|
|
129
|
-
|
|
130
|
-
## References
|
|
131
|
-
|
|
132
|
-
1. Jiang, Jialong, et al. "D-SPIN constructs gene regulatory network models from multiplexed scRNA-seq data revealing organizing principles of cellular perturbation response." bioRxiv (2023).
|
|
133
|
-
|
|
134
|
-
2. Pratapa, Aditya, et al. "Benchmarking algorithms for gene regulatory network inference from single-cell transcriptomic data." Nature methods 17.2 (2020): 147-154.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|