dspin 1.3.3__tar.gz → 1.3.5__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: dspin
3
- Version: 1.3.3
3
+ Version: 1.3.5
4
4
  Summary: Regulatory network models from single-cell perturbation profiling
5
5
  Home-page: https://github.com/JialongJiang/DSPIN
6
6
  Author: Jialong Jiang
@@ -25,6 +25,18 @@ Requires-Dist: scanpy
25
25
  Requires-Dist: tqdm
26
26
  Requires-Dist: igraph
27
27
  Requires-Dist: leidenalg
28
+ Dynamic: author
29
+ Dynamic: author-email
30
+ Dynamic: classifier
31
+ Dynamic: description
32
+ Dynamic: description-content-type
33
+ Dynamic: home-page
34
+ Dynamic: keywords
35
+ Dynamic: license
36
+ Dynamic: license-file
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
28
40
 
29
41
  # D-SPIN: Constructing regulatory network models from scRNA-seq perturbation profiling
30
42
 
@@ -4,7 +4,6 @@
4
4
  @Author : Jialong Jiang
5
5
  '''
6
6
 
7
-
8
7
  import csv
9
8
  import pandas as pd
10
9
  from typing import List
@@ -108,7 +108,7 @@ def summary_components(all_components: np.array,
108
108
  gene_weight = np.sum(all_components ** 2, axis=0) ** 0.5
109
109
  gene_sele_filt = gene_weight > np.mean(gene_weight)
110
110
  all_components_sub = all_components[:, gene_sele_filt]
111
- 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)
112
112
 
113
113
  # Initialize an array to store the average component for each cluster.
114
114
  components_kmeans = np.zeros((num_spin, num_gene))
@@ -207,6 +207,7 @@ class AbstractDSPIN(ABC):
207
207
  self._samp_list = self._samp_list[example_list_ind]
208
208
  raw_data = raw_data[example_list_ind]
209
209
  self._raw_data = raw_data
210
+ print("Using a subset of {} samples for network inference.".format(len(self._samp_list)))
210
211
 
211
212
  num_sample = len(raw_data)
212
213
  params = {'num_epoch': 400,
@@ -497,6 +498,42 @@ class ProgramDSPIN(AbstractDSPIN):
497
498
  def onmf_decomposition(self, value):
498
499
  self._onmf_summary = value
499
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
+
500
537
  def summarize_onmf_result(self,
501
538
  large_subsample_matrix: np.ndarray,
502
539
  num_onmf_components: int,
@@ -517,10 +554,6 @@ class ProgramDSPIN(AbstractDSPIN):
517
554
  NMF
518
555
  An NMF object containing the summarized components.
519
556
  """
520
-
521
- # Retrieve the number of spins and annotated data
522
- num_spin = self.num_spin
523
- adata = self.adata
524
557
  prior_programs_mask = self.prior_programs_mask
525
558
  seed_list = self.onmf_parameters['seed_list']
526
559
  summary_method = self.onmf_parameters['summary_method']
@@ -533,7 +566,7 @@ class ProgramDSPIN(AbstractDSPIN):
533
566
  cur_onmf = np.load(self.save_path + f'onmf/onmf_components_{num_onmf_components}_repeat_{cur_seed}.npy', allow_pickle=True).item()
534
567
  rec_components[ii] = cur_onmf.components_
535
568
 
536
- all_components = rec_components.reshape(-1, np.sum(prior_programs_mask))
569
+ all_components = rec_components.reshape(- 1, np.sum(prior_programs_mask))
537
570
 
538
571
  gene_group_ind = summary_components(all_components, num_onmf_components, summary_method=summary_method, figure_folder=self.fig_folder)
539
572
 
@@ -542,17 +575,8 @@ class ProgramDSPIN(AbstractDSPIN):
542
575
  gene_group_ind = [sub_mask_ind[gene_list] for gene_list in gene_group_ind]
543
576
  gene_group_ind += self.prior_programs_ind
544
577
 
545
- # Initialize and compute the summary matrix
546
- components_summary = np.zeros((num_spin, adata.shape[1]))
547
- for ii in range(num_spin):
548
- sub_matrix = large_subsample_matrix[:, gene_group_ind[ii]]
549
- sub_onmf = NMF(n_components=1, init='random', random_state=0).fit(sub_matrix)
550
- components_summary[ii, gene_group_ind[ii]] = sub_onmf.components_
551
-
552
- # Normalize the summary components and finalize the onmf_summary object
553
- components_summary = normalize(components_summary, axis=1, norm='l2')
554
- onmf_summary = NMF(n_components=num_spin, init='random', random_state=0)
555
- 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)
556
580
 
557
581
  return onmf_summary
558
582
 
@@ -585,9 +609,9 @@ class ProgramDSPIN(AbstractDSPIN):
585
609
  Parameters for discretization of the oNMF representation. Default is an empty dictionary.
586
610
  """
587
611
 
588
- if mode not in ['compute_summary', 'compute_only', 'summary_only']:
612
+ if mode not in ['compute_summary', 'compute_only', 'summary_only', 'precomputed']:
589
613
  raise ValueError(
590
- 'Mode must be one of compute_summary, compute_only, or summary_only')
614
+ 'Mode must be one of compute_summary, compute_only, summary_only, or precomputed.')
591
615
 
592
616
  adata = self.adata
593
617
  num_spin = self.num_spin
@@ -607,6 +631,7 @@ class ProgramDSPIN(AbstractDSPIN):
607
631
 
608
632
  if onmf_parameters['num_subsample_large'] is None:
609
633
  onmf_parameters['num_subsample_large'] = min(onmf_parameters['num_subsample'] * 5, adata.shape[0])
634
+ # print(onmf_parameters['num_subsample_large'])
610
635
 
611
636
  if onmf_parameters['balance_method'] not in ['equal', 'proportional', 'squareroot', None]:
612
637
  raise ValueError('balance_method must be one of equal, proportional, squareroot, or None')
@@ -619,7 +644,13 @@ class ProgramDSPIN(AbstractDSPIN):
619
644
  prior_program_ind = [[np.where(adata.var_names == gene)[0][0] for gene in gene_list] for gene_list in prior_programs]
620
645
  preprogram_flat = [gene for program in prior_programs for gene in program]
621
646
  if len(prior_programs) > num_spin:
622
- raise ValueError('Number of prior_programs must be less than the number of spins')
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
+
623
654
  # Generate mask to exclude genes in prior programs from the annotated data
624
655
  prior_program_mask = ~ np.isin(adata.var_names, preprogram_flat)
625
656
  onmf_parameters['num_onmf_components'] -= len(prior_programs)
@@ -650,20 +681,36 @@ class ProgramDSPIN(AbstractDSPIN):
650
681
 
651
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')
652
683
 
653
- if mode == 'compute_summary' or mode == 'summary_only':
684
+ if mode == 'compute_summary' or mode == 'summary_only' or mode == 'precomputed':
685
+
686
+ cluster_label_raw = adata.obs[cluster_key].values
687
+ cluster_label = pd.factorize(cluster_label_raw)[0]
654
688
 
655
689
  # Perform subsampling and standard deviation clipping on the matrix
656
- std_clipped, sub_large_gene_matrix_normed = subsample_normalize_gene_matrix(cur_gene_matrix, cluster_label, num_subsample=onmf_parameters['num_subsample_large'], seed=seed + rep, params=onmf_parameters)
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)
657
691
  self.matrix_std = std_clipped
658
-
659
- # Summarize the oNMF decompositions
660
- onmf_summary = self.summarize_onmf_result(sub_large_gene_matrix_normed, num_onmf_components, num_repeat)
692
+
693
+ if mode != 'precomputed':
694
+ # Summarize the oNMF decompositions
695
+ if num_onmf_components > 0:
696
+ onmf_summary = self.summarize_onmf_result(sub_large_gene_matrix_normed, num_onmf_components, num_repeat)
697
+ else:
698
+ onmf_summary = self.compute_onmf_expression(sub_large_gene_matrix_normed, prior_program_ind)
699
+
700
+ onmf_save_path = self.save_path + f'onmf/onmf_summary_total_{num_spin}_onmf_{num_onmf_components}_prior_{len(prior_program_ind)}.npy'
701
+ np.save(onmf_save_path, onmf_summary)
702
+ print(f"Consensus oNMF results saved to {onmf_save_path}")
703
+ else:
704
+ assert 'precomputed_onmf_path' in params.keys(), "For precomputed mode, 'precomputed_onmf_path' must be provided in params."
705
+ onmf_summary = np.load(params['precomputed_onmf_path'], allow_pickle=True).item()
661
706
 
662
707
  self._onmf_summary = onmf_summary
663
708
 
664
709
  # Save the gene programs to a CSV file
665
710
  file_path = onmf_to_csv(onmf_summary.components_, adata.var_names, self.save_path + f'gene_programs_total_{num_spin}_onmf_{num_onmf_components}_prior_{len(prior_program_ind)}.csv', thres=0.01)
666
- print('Gene programs saved to {}'.format(file_path))
711
+ print('Gene program information saved to {}'.format(file_path))
712
+ file_wpath = onmf_to_csv(onmf_summary.components_, adata.var_names, self.save_path + f'gene_programs_total_{num_spin}_onmf_{num_onmf_components}_prior_{len(prior_program_ind)}_weights.csv', thres=0.01, if_write_weights=True)
713
+ # print('Gene programs saved to {}'.format(file_path))
667
714
  self.gene_program_csv = file_path
668
715
 
669
716
  gene_matrix = self.adata.X
@@ -21,7 +21,7 @@ from scipy import optimize
21
21
  from scipy.spatial.distance import pdist
22
22
 
23
23
 
24
- def onmf_to_csv(features, gene_name, file_path, thres=0.01):
24
+ def onmf_to_csv(features, gene_name, file_path, thres=0.01, if_write_weights=False):
25
25
  """
26
26
  Extract gene names in ONMF to CSV.
27
27
 
@@ -30,6 +30,7 @@ def onmf_to_csv(features, gene_name, file_path, thres=0.01):
30
30
  gene_name (List[str]): Names of the genes corresponding to feature indices.
31
31
  file_path (str): Path of the file where the CSV will be written.
32
32
  thres (float): Threshold value for filtering features.
33
+ if_write_weights (bool): If True, writes weights to the CSV file.
33
34
 
34
35
  Returns:
35
36
  str: Path of the created CSV file.
@@ -51,6 +52,11 @@ def onmf_to_csv(features, gene_name, file_path, thres=0.01):
51
52
  cur_line.insert(0, spin)
52
53
  write.writerow(cur_line) # Write the current line to the CSV file
53
54
 
55
+ if if_write_weights:
56
+ cur_line = [features[spin, ind].round(6) for ind in gene_ind]
57
+ cur_line.insert(0, f'Weights_{spin}')
58
+ write.writerow(cur_line)
59
+
54
60
  # Read the created CSV, transpose it and save it again
55
61
  pd_csv = pd.read_csv(file_path, names=range(rec_max_gene + 1))
56
62
  pd_csv = pd_csv.transpose()
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: dspin
3
- Version: 1.3.3
3
+ Version: 1.3.5
4
4
  Summary: Regulatory network models from single-cell perturbation profiling
5
5
  Home-page: https://github.com/JialongJiang/DSPIN
6
6
  Author: Jialong Jiang
@@ -25,6 +25,18 @@ Requires-Dist: scanpy
25
25
  Requires-Dist: tqdm
26
26
  Requires-Dist: igraph
27
27
  Requires-Dist: leidenalg
28
+ Dynamic: author
29
+ Dynamic: author-email
30
+ Dynamic: classifier
31
+ Dynamic: description
32
+ Dynamic: description-content-type
33
+ Dynamic: home-page
34
+ Dynamic: keywords
35
+ Dynamic: license
36
+ Dynamic: license-file
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
28
40
 
29
41
  # D-SPIN: Constructing regulatory network models from scRNA-seq perturbation profiling
30
42
 
@@ -21,7 +21,7 @@ install_requires = [
21
21
  # ]
22
22
 
23
23
  # Managing your version (adjust accordingly)
24
- version = "1.3.3" # You can manage versioning in a more sophisticated way if needed
24
+ version = "1.3.5" # 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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes