rectanglepy 0.1.6__tar.gz → 0.1.8__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.
Files changed (53) hide show
  1. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.bumpversion.cfg +1 -1
  2. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/CHANGELOG.md +16 -0
  3. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/PKG-INFO +3 -1
  4. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/pyproject.toml +3 -1
  5. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/src/rectanglepy/pp/create_signature.py +17 -6
  6. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/src/rectanglepy/pp/rectangle_signature.py +4 -0
  7. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/src/rectanglepy/rectangle.py +18 -5
  8. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/tests/test_rectangle.py +18 -3
  9. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.cruft.json +0 -0
  10. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.editorconfig +0 -0
  11. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  12. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  13. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  14. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  15. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.github/workflows/build.yaml +0 -0
  16. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.github/workflows/release.yaml +0 -0
  17. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.github/workflows/release_testpypi.yaml +0 -0
  18. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.github/workflows/test.yaml +0 -0
  19. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.gitignore +0 -0
  20. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.pre-commit-config.yaml +0 -0
  21. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/.readthedocs.yaml +0 -0
  22. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/LICENSE +0 -0
  23. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/README.md +0 -0
  24. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/Makefile +0 -0
  25. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/_static/.gitkeep +0 -0
  26. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/_templates/.gitkeep +0 -0
  27. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/_templates/autosummary/class.rst +0 -0
  28. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/api.md +0 -0
  29. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/changelog.md +0 -0
  30. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/conf.py +0 -0
  31. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/contributing.md +0 -0
  32. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/extensions/typed_returns.py +0 -0
  33. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/index.md +0 -0
  34. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/make.bat +0 -0
  35. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/notebooks/example.ipynb +0 -0
  36. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/references.bib +0 -0
  37. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/docs/references.md +0 -0
  38. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/src/rectanglepy/__init__.py +0 -0
  39. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/src/rectanglepy/data/hao1_annotations_small.zip +0 -0
  40. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/src/rectanglepy/data/hao1_counts_small.zip +0 -0
  41. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/src/rectanglepy/data/small_fino_bulks.zip +0 -0
  42. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/src/rectanglepy/pp/__init__.py +0 -0
  43. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/src/rectanglepy/tl/__init__.py +0 -0
  44. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/src/rectanglepy/tl/deconvolution.py +0 -0
  45. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/tests/data/TIL10_signature.txt +0 -0
  46. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/tests/data/bulk_small.csv +0 -0
  47. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/tests/data/cell_annotations_small.txt +0 -0
  48. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/tests/data/quanTIseq_SimRNAseq_mixture_smaller.csv +0 -0
  49. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/tests/data/quanTIseq_SimRNAseq_read_fractions_small.txt +0 -0
  50. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/tests/data/sc_object_small.csv +0 -0
  51. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/tests/data/signature_hao1.csv +0 -0
  52. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/tests/test_pp.py +0 -0
  53. {rectanglepy-0.1.6 → rectanglepy-0.1.8}/tests/test_tl.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.1.6
2
+ current_version = 0.1.8
3
3
  tag = True
4
4
  commit = True
5
5
 
@@ -10,6 +10,22 @@ and this project adheres to [Semantic Versioning][].
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.1.8] - 2024-09-06
14
+
15
+ ### Added
16
+
17
+ - RectangleSignatureResult now returns correlation of bulk genes with the calculated Unknown cell type fraction
18
+
19
+ ### Fixed
20
+
21
+ - The consensus method now returns the estimations normalized to 1
22
+
23
+ ## [0.1.7] - 2024-07-25
24
+
25
+ ### Fixed
26
+
27
+ - Fixed bug in the consensus method.
28
+
13
29
  ## [0.1.6] - 2024-05-07
14
30
 
15
31
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rectanglepy
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: Hierarchical deconvolution of bulk transcriptomics
5
5
  Project-URL: Documentation, https://rectanglepy.readthedocs.io/
6
6
  Project-URL: Source, https://github.com/ComputationalBiomedicineGroup/Rectangle
@@ -30,7 +30,9 @@ License: MIT License
30
30
  SOFTWARE.
31
31
  License-File: LICENSE
32
32
  Requires-Python: >=3.10
33
+ Requires-Dist: anndata<0.10.9,>=0.8.0
33
34
  Requires-Dist: loguru
35
+ Requires-Dist: numpy<2.0.0,>=1.0.0
34
36
  Requires-Dist: pydeseq2==0.4.1
35
37
  Requires-Dist: quadprog==0.1.11
36
38
  Provides-Extra: dev
@@ -4,7 +4,7 @@ requires = ["hatchling"]
4
4
 
5
5
  [project]
6
6
  name = "rectanglepy"
7
- version = "0.1.6"
7
+ version = "0.1.8"
8
8
  description = "Hierarchical deconvolution of bulk transcriptomics"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -22,6 +22,8 @@ dependencies = [
22
22
  "pydeseq2==0.4.1",
23
23
  "quadprog==0.1.11",
24
24
  "loguru",
25
+ "numpy>=1.0.0,<2.0.0",
26
+ "anndata>=0.8.0,<0.10.9",
25
27
  ]
26
28
 
27
29
  [project.optional-dependencies]
@@ -34,7 +34,9 @@ def _create_condition_number_matrices(de_adjusted, pseudo_signature):
34
34
  loop_range = min(longest_de_analysis, 200)
35
35
  range_minimum = 30
36
36
 
37
- if loop_range < range_minimum:
37
+ if loop_range < 8:
38
+ range_minimum = 2
39
+ elif loop_range < range_minimum:
38
40
  range_minimum = 8
39
41
 
40
42
  for i in range(range_minimum, loop_range):
@@ -245,6 +247,7 @@ def build_rectangle_signatures(
245
247
  subsample: bool = False,
246
248
  sample_size: int = 1500,
247
249
  n_cpus: int = None,
250
+ run: int = 0,
248
251
  ) -> RectangleSignatureResult:
249
252
  r"""Builds rectangle signatures based on single-cell count data and annotations.
250
253
 
@@ -272,6 +275,8 @@ def build_rectangle_signatures(
272
275
  The log fold change threshold for the DE analysis (only used if optimize_cutoffs is False).
273
276
  n_cpus
274
277
  The number of cpus to use for the DE analysis. Defaults to the number of cpus available.
278
+ run
279
+ The consensus run number for the analysis. Defaults to 0.
275
280
 
276
281
  Returns
277
282
  -------
@@ -286,7 +291,7 @@ def build_rectangle_signatures(
286
291
 
287
292
  annotations = adata.obs[cell_type_col]
288
293
  if subsample:
289
- annotations = _even(annotations, sample_size)
294
+ annotations = _even(annotations, sample_size, run)
290
295
  adata = adata[annotations.index]
291
296
 
292
297
  if layer is not None:
@@ -365,9 +370,14 @@ def _optimize_parameters(
365
370
  bulks, real_fractions = _generate_pseudo_bulks(sc_data, annotations, genes)
366
371
  for p in ps:
367
372
  for lfc in lfcs:
368
- rmse, pearson_r = _assess_parameter_fit(lfc, p, bulks, real_fractions, pseudo_signature_counts, de_results)
369
- logger.info(f"RMSE:{rmse}, Pearson R:{pearson_r} for p={p}, lfc={lfc}")
370
- results.append({"p": p, "lfc": lfc, "rmse": rmse, "pearson_r": pearson_r})
373
+ try:
374
+ rmse, pearson_r = _assess_parameter_fit(
375
+ lfc, p, bulks, real_fractions, pseudo_signature_counts, de_results
376
+ )
377
+ logger.info(f"RMSE:{rmse}, Pearson R:{pearson_r} for p={p}, lfc={lfc}")
378
+ results.append({"p": p, "lfc": lfc, "rmse": rmse, "pearson_r": pearson_r})
379
+ except Exception as e:
380
+ logger.error(f"Error in assessing parameter fit for p={p}, lfc={lfc}: {e}")
371
381
 
372
382
  results_df = pd.DataFrame(results)
373
383
  results_df = results_df.sort_values(by=["pearson_r", "rmse"], ascending=[False, True])
@@ -448,7 +458,8 @@ def _reduce_to_common_genes(bulks: pd.DataFrame, sc_data: pd.DataFrame):
448
458
  return bulks, sc_data
449
459
 
450
460
 
451
- def _even(annotations: pd.Series, number: int) -> pd.Series:
461
+ def _even(annotations: pd.Series, number: int, run=0) -> pd.Series:
462
+ np.random.seed(run)
452
463
  assert number > 0, "Number of cells must be greater than 0"
453
464
  annotation_counts = annotations.value_counts()
454
465
  selected_cells = []
@@ -22,6 +22,8 @@ class RectangleSignatureResult:
22
22
  The number of marker genes per cell type, as a dictionary. Keys are cell type names, values are the number of marker genes.
23
23
  optimization_result
24
24
  The result of the p lfc cut off optimization, as a pd.DataFrame. Contains the following columns: p, lfc, pearson_r, rsme
25
+ unkn_gene_corr
26
+ The correlation between the unknown cell type and the genes linked to the unknown cell type.
25
27
  """
26
28
 
27
29
  def __init__(
@@ -36,6 +38,7 @@ class RectangleSignatureResult:
36
38
  marker_genes_per_cluster: dict[str, [str]] = None,
37
39
  clustered_signature_genes: pd.Series = None,
38
40
  cluster_assignments: list[int or str] = None,
41
+ unkn_gene_corr: pd.Series = None,
39
42
  ):
40
43
  self.signature_genes = signature_genes
41
44
  self.bias_factors = bias_factors
@@ -47,6 +50,7 @@ class RectangleSignatureResult:
47
50
  self.clustered_bias_factors = clustered_bias_factors
48
51
  self.clustered_signature_genes = clustered_signature_genes
49
52
  self.assignments = cluster_assignments
53
+ self.unkn_gene_corr = unkn_gene_corr
50
54
 
51
55
  def get_signature_matrix(self, include_mrna_bias=True) -> pd.DataFrame:
52
56
  """Calculates the signature matrix by multiplying the pseudobulk_sig_cpm DataFrame subset by signature_genes and the bias_factors Series.
@@ -94,8 +94,8 @@ def rectangle_consens(
94
94
  rectangle_signature_results = []
95
95
  most_recent_signatures = None
96
96
 
97
- for _i in range(consensus_runs):
98
- logger.info(f"Running consensus run {_i + 1} of {consensus_runs}")
97
+ for i in range(consensus_runs):
98
+ logger.info(f"Running consensus run {i + 1} of {consensus_runs}")
99
99
  signatures = build_rectangle_signatures(
100
100
  adata,
101
101
  cell_type_col,
@@ -108,14 +108,19 @@ def rectangle_consens(
108
108
  n_cpus=n_cpus,
109
109
  subsample=subsample,
110
110
  sample_size=sample_size,
111
+ run=i,
111
112
  )
112
- rectangle_signature_results.append(signatures)
113
113
  most_recent_signatures = signatures
114
114
  cell_fractions = deconvolution(signatures, bulks, correct_mrna_bias, n_cpus)
115
115
  estimations.append(cell_fractions)
116
-
116
+ unkn_gene_corr = _genes_linked_to_unkn(bulks, cell_fractions["Unknown"])
117
+ signatures.unkn_gene_corr = unkn_gene_corr
118
+ rectangle_signature_results.append(signatures)
119
+ consensus_estimations = pd.concat(estimations).groupby(level=0).median()
120
+ # normalize the estimations to 1
121
+ consensus_estimations = consensus_estimations.div(consensus_estimations.sum(axis=1), axis=0)
117
122
  consensus_results = ConsensusResult(estimations, rectangle_signature_results)
118
- return pd.concat(estimations).groupby(level=0).median(), most_recent_signatures, consensus_results
123
+ return consensus_estimations, most_recent_signatures, consensus_results
119
124
 
120
125
 
121
126
  def rectangle(
@@ -196,3 +201,11 @@ def load_tutorial_data() -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
196
201
  bulks = pd.read_csv(bulks_file, index_col=0, compression="zip")
197
202
 
198
203
  return sc_counts.T, annotations, bulks.T
204
+
205
+
206
+ def _genes_linked_to_unkn(bulks: DataFrame, unkn_fractions: pd.Series):
207
+ genes = bulks.columns
208
+ corr = []
209
+ for gene in genes:
210
+ corr.append(unkn_fractions.corr(bulks.loc[:, gene]))
211
+ return pd.Series(corr, index=genes).sort_values(ascending=False)
@@ -15,22 +15,37 @@ def test_load_tutorial_data():
15
15
 
16
16
  def test_rectangle():
17
17
  sc_data, annotations, bulks = rectanglepy.load_tutorial_data()
18
+ sc_data = sc_data.iloc[:, :500]
18
19
  sc_data_adata = AnnData(sc_data, obs=annotations.to_frame(name="cell_type"))
19
20
 
20
21
  result = rectanglepy.rectangle(sc_data_adata, bulks)
22
+ estimations, signatures = result
21
23
 
22
- assert isinstance(result[0], pd.DataFrame)
23
- assert isinstance(result[1], RectangleSignatureResult)
24
+ assert isinstance(estimations, pd.DataFrame)
25
+ assert isinstance(signatures, RectangleSignatureResult)
26
+ assert isinstance(signatures.unkn_gene_corr, pd.Series)
24
27
 
25
28
 
26
29
  def test_rectangle_consensus():
27
30
  sc_data, annotations, bulks = rectanglepy.load_tutorial_data()
31
+ sc_data = sc_data.iloc[:, :500]
28
32
  sc_data_adata = AnnData(sc_data, obs=annotations.to_frame(name="cell_type"))
29
33
 
30
34
  result = rectanglepy.rectangle_consens(
31
- sc_data_adata, bulks, optimize_cutoffs=False, p=0.5, lfc=0.0, consensus_runs=2, sample_size=50
35
+ sc_data_adata, bulks, optimize_cutoffs=False, p=0.5, lfc=0.0, consensus_runs=3, sample_size=50
32
36
  )
33
37
 
34
38
  assert isinstance(result[0], pd.DataFrame)
35
39
  assert isinstance(result[1], RectangleSignatureResult)
36
40
  assert isinstance(result[2], ConsensusResult)
41
+
42
+ estimations = result[0]
43
+ consensus_results = result[2]
44
+ signature_results = consensus_results.rectangle_signature_results
45
+ bias_factors = [result.bias_factors for result in signature_results]
46
+ # there was a problem with the random sampling, the bias factors should differ
47
+ assert bias_factors[0]["Monocytes"] != bias_factors[1]["Monocytes"]
48
+ assert bias_factors[0]["Monocytes"] != bias_factors[2]["Monocytes"]
49
+ assert bias_factors[1]["Monocytes"] != bias_factors[2]["Monocytes"]
50
+ # in the consensus we have to make sure that the estimations are again normalized to 1
51
+ assert estimations.sum(axis=1).all() == 1.0
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