rectanglepy 0.5.1__tar.gz → 0.5.3__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 (54) hide show
  1. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.bumpversion.cfg +1 -1
  2. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.github/workflows/build.yaml +6 -2
  3. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.github/workflows/release.yaml +8 -4
  4. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.github/workflows/test.yaml +4 -0
  5. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/PKG-INFO +2 -1
  6. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/pyproject.toml +3 -2
  7. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/src/rectanglepy/pp/rectangle_signature.py +4 -0
  8. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/src/rectanglepy/rectangle.py +2 -1
  9. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/src/rectanglepy/tl/deconvolution.py +41 -20
  10. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/tests/test_rectangle.py +1 -0
  11. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/tests/test_tl.py +7 -4
  12. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.cruft.json +0 -0
  13. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.editorconfig +0 -0
  14. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  15. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  16. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  17. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  18. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.github/workflows/release_testpypi.yaml +0 -0
  19. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.gitignore +0 -0
  20. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.pre-commit-config.yaml +0 -0
  21. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/.readthedocs.yaml +0 -0
  22. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/CHANGELOG.md +0 -0
  23. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/LICENSE +0 -0
  24. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/README.md +0 -0
  25. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/Makefile +0 -0
  26. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/_static/.gitkeep +0 -0
  27. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/_static/rec_logo.001.png +0 -0
  28. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/_templates/.gitkeep +0 -0
  29. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/_templates/autosummary/class.rst +0 -0
  30. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/api.md +0 -0
  31. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/changelog.md +0 -0
  32. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/conf.py +0 -0
  33. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/contributing.md +0 -0
  34. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/extensions/typed_returns.py +0 -0
  35. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/index.md +0 -0
  36. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/make.bat +0 -0
  37. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/notebooks/example.ipynb +0 -0
  38. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/references.bib +0 -0
  39. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/docs/references.md +0 -0
  40. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/src/rectanglepy/__init__.py +0 -0
  41. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/src/rectanglepy/data/hao1_annotations_small.zip +0 -0
  42. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/src/rectanglepy/data/hao1_counts_small.zip +0 -0
  43. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/src/rectanglepy/data/small_fino_bulks.zip +0 -0
  44. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/src/rectanglepy/pp/__init__.py +0 -0
  45. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/src/rectanglepy/pp/create_signature.py +0 -0
  46. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/src/rectanglepy/tl/__init__.py +0 -0
  47. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/tests/data/TIL10_signature.txt +0 -0
  48. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/tests/data/bulk_small.csv +0 -0
  49. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/tests/data/cell_annotations_small.txt +0 -0
  50. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/tests/data/quanTIseq_SimRNAseq_mixture_smaller.csv +0 -0
  51. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/tests/data/quanTIseq_SimRNAseq_read_fractions_small.txt +0 -0
  52. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/tests/data/sc_object_small.csv +0 -0
  53. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/tests/data/signature_hao1.csv +0 -0
  54. {rectanglepy-0.5.1 → rectanglepy-0.5.3}/tests/test_pp.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.5.1
2
+ current_version = 0.5.3
3
3
  tag = True
4
4
  commit = True
5
5
 
@@ -23,6 +23,10 @@ jobs:
23
23
  include:
24
24
  - os: ubuntu-latest
25
25
  python: "3.10"
26
+ - os: ubuntu-latest
27
+ python: "3.11"
28
+ - os: ubuntu-latest
29
+ python: "3.12"
26
30
  env:
27
31
  OS: ${{ matrix.os }}
28
32
  PYTHON: ${{ matrix.python }}
@@ -55,10 +59,10 @@ jobs:
55
59
  needs: test
56
60
  steps:
57
61
  - uses: actions/checkout@v2
58
- - name: Set up Python 3.10
62
+ - name: Set up Python 3.12
59
63
  uses: actions/setup-python@v2
60
64
  with:
61
- python-version: "3.10"
65
+ python-version: "3.12"
62
66
  - name: Install twine
63
67
  run: pip install twine
64
68
  - name: Install build dependencies
@@ -18,6 +18,10 @@ jobs:
18
18
  include:
19
19
  - os: ubuntu-latest
20
20
  python: "3.10"
21
+ - os: ubuntu-latest
22
+ python: "3.11"
23
+ - os: ubuntu-latest
24
+ python: "3.12"
21
25
  env:
22
26
  OS: ${{ matrix.os }}
23
27
  PYTHON: ${{ matrix.python }}
@@ -49,10 +53,10 @@ jobs:
49
53
  needs: test
50
54
  steps:
51
55
  - uses: actions/checkout@v2
52
- - name: Set up Python 3.10
56
+ - name: Set up Python 3.12
53
57
  uses: actions/setup-python@v2
54
58
  with:
55
- python-version: "3.10"
59
+ python-version: "3.12"
56
60
  - name: Install build dependencies
57
61
  run: python -m pip install --upgrade pip wheel twine build
58
62
  - name: Build package
@@ -68,10 +72,10 @@ jobs:
68
72
  - name: Checkout code
69
73
  uses: actions/checkout@v3
70
74
 
71
- - name: Set up Python 3.10
75
+ - name: Set up Python 3.12
72
76
  uses: actions/setup-python@v4
73
77
  with:
74
- python-version: "3.10"
78
+ python-version: "3.12"
75
79
 
76
80
  - name: Install hatch
77
81
  run: pip install hatch
@@ -22,6 +22,10 @@ jobs:
22
22
  include:
23
23
  - os: ubuntu-latest
24
24
  python: "3.10"
25
+ - os: ubuntu-latest
26
+ python: "3.11"
27
+ - os: ubuntu-latest
28
+ python: "3.12"
25
29
  env:
26
30
  OS: ${{ matrix.os }}
27
31
  PYTHON: ${{ matrix.python }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rectanglepy
3
- Version: 0.5.1
3
+ Version: 0.5.3
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
@@ -36,6 +36,7 @@ Requires-Dist: numpy<2.0.0,>=1.0.0
36
36
  Requires-Dist: pydeseq2==0.4.11
37
37
  Requires-Dist: quadprog==0.1.12
38
38
  Requires-Dist: scipy==1.13.0
39
+ Requires-Dist: setuptools<70.0.0,>=69.0.0
39
40
  Requires-Dist: statsmodels>=0.14.1
40
41
  Provides-Extra: dev
41
42
  Requires-Dist: bump2version; extra == 'dev'
@@ -4,7 +4,7 @@ requires = ["hatchling"]
4
4
 
5
5
  [project]
6
6
  name = "rectanglepy"
7
- version = "0.5.1"
7
+ version = "0.5.3"
8
8
  description = "Hierarchical deconvolution of bulk transcriptomics"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -25,7 +25,8 @@ dependencies = [
25
25
  "loguru",
26
26
  "numpy>=1.0.0,<2.0.0",
27
27
  "anndata>=0.8.0,<0.10.9",
28
- "statsmodels>=0.14.1"
28
+ "statsmodels>=0.14.1",
29
+ "setuptools>=69.0.0,<70.0.0",
29
30
 
30
31
  ]
31
32
 
@@ -24,6 +24,8 @@ class RectangleSignatureResult:
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
25
  unkn_gene_corr
26
26
  The correlation between the unknown cell type and the genes linked to the unknown cell type.
27
+ unkn_bulk_err
28
+ The result of 'bulk - bulk_est' for the reconstructed bulk used to calculate the unknown cell type content.
27
29
  """
28
30
 
29
31
  def __init__(
@@ -39,6 +41,7 @@ class RectangleSignatureResult:
39
41
  clustered_signature_genes: pd.Series = None,
40
42
  cluster_assignments: list[int or str] = None,
41
43
  unkn_gene_corr: pd.Series = None,
44
+ unkn_bulk_err: pd.DataFrame = None,
42
45
  ):
43
46
  self.signature_genes = signature_genes
44
47
  self.bias_factors = bias_factors
@@ -51,6 +54,7 @@ class RectangleSignatureResult:
51
54
  self.clustered_signature_genes = clustered_signature_genes
52
55
  self.assignments = cluster_assignments
53
56
  self.unkn_gene_corr = unkn_gene_corr
57
+ self.unkn_bulk_err = unkn_bulk_err
54
58
 
55
59
  def get_signature_matrix(self, include_mrna_bias=True) -> pd.DataFrame:
56
60
  """Calculates the signature matrix by multiplying the pseudobulk_sig_cpm DataFrame subset by signature_genes and the bias_factors Series.
@@ -70,7 +70,7 @@ def rectangle(
70
70
  gene_expression_threshold=gene_expression_threshold,
71
71
  )
72
72
 
73
- estimations = deconvolution(signatures, bulks, correct_mrna_bias, n_cpus)
73
+ estimations, bulk_err = deconvolution(signatures, bulks, correct_mrna_bias, n_cpus)
74
74
 
75
75
  if "Unknown" in estimations.columns:
76
76
  try:
@@ -81,6 +81,7 @@ def rectangle(
81
81
  else:
82
82
  unkn_gene_corr = None
83
83
  signatures.unkn_gene_corr = unkn_gene_corr
84
+ signatures.unkn_bulk_err = bulk_err
84
85
 
85
86
  return estimations, signatures
86
87
 
@@ -177,7 +177,13 @@ def deconvolution(
177
177
 
178
178
  Returns
179
179
  -------
180
- A DataFrame containing the estimated cell fractions resulting from deconvolution. Each row represents a sample and each column represents a cell type.
180
+ estimation_df : pd.DataFrame
181
+ A DataFrame containing the estimated cell fractions. Each row represents
182
+ a sample, each column represents a cell type (including 'Unknown' if applicable).
183
+ bulk_err_df : pd.DataFrame
184
+ A DataFrame containing the gene-level difference between the true bulk
185
+ expression and the reconstructed bulk expression (i.e., `bulk - bulk_est`)
186
+ for each sample.
181
187
 
182
188
  """
183
189
  bulks = bulks.div(bulks.sum(axis=1), axis=0) * 1e6
@@ -194,28 +200,31 @@ def deconvolution(
194
200
  delayed(_process_bulk)(signatures, i, bulk, bulks.columns, correct_mrna_bias)
195
201
  for i, bulk in enumerate(bulks.values)
196
202
  )
203
+ estimations = [result[0] for result in results]
204
+ bulk_err = [result[1] for result in results]
205
+ estimation_df = pd.DataFrame(estimations, index=bulks.index)
206
+ bulk_err_df = pd.DataFrame(bulk_err, index=bulks.index)
197
207
 
198
- result_df = pd.DataFrame(results, index=bulks.index)
199
-
200
- # Return the result DataFrame
201
- return result_df
208
+ return estimation_df, bulk_err_df
202
209
 
203
210
 
204
211
  def _process_bulk(
205
212
  signatures: RectangleSignatureResult, i: int, bulk: pd.Series, var_names: pd.Index, correct_mrna_bias: bool
206
- ) -> pd.Series:
213
+ ) -> tuple[pd.Series, pd.Series]:
207
214
  try:
208
215
  logger.info(f"Deconvolute fractions for bulk: {i}")
209
216
  bulk = pd.Series(bulk, index=var_names)
210
- result = _deconvolute(signatures, bulk, correct_mrna_bias=correct_mrna_bias)
217
+ estimations, bulk_err = _deconvolute(signatures, bulk, correct_mrna_bias=correct_mrna_bias)
211
218
  logger.info(f"Finished deconvolution for bulk: {i}")
212
- return result
219
+ return estimations, bulk_err
213
220
  except Exception as e:
214
221
  logger.warning(f"Deconvolution failed for bulk: {i} with error: {e}")
215
- return pd.Series(index=signatures.pseudobulk_sig_cpm.columns)
222
+ return pd.Series(index=signatures.pseudobulk_sig_cpm.columns), pd.Series(index=bulk.index)
216
223
 
217
224
 
218
- def _deconvolute(signatures: RectangleSignatureResult, bulk: pd.Series, correct_mrna_bias: bool = True) -> pd.Series:
225
+ def _deconvolute(
226
+ signatures: RectangleSignatureResult, bulk: pd.Series, correct_mrna_bias: bool = True
227
+ ) -> tuple[pd.Series, pd.Series]:
219
228
  bulk_direct_reduced = bulk[bulk.index.isin(signatures.signature_genes)]
220
229
  signature_genes_direct_reduced = signatures.signature_genes[
221
230
  signatures.signature_genes.isin(bulk_direct_reduced.index)
@@ -231,8 +240,10 @@ def _deconvolute(signatures: RectangleSignatureResult, bulk: pd.Series, correct_
231
240
  start_fractions = _calculate_dwls(signature, bulk)
232
241
 
233
242
  if clustered_pseudobulk_sig_cpm is None:
234
- start_fractions = correct_for_unknown_cell_content(bulk, pseudobulk_sig_cpm, start_fractions, bias_factors)
235
- return start_fractions
243
+ start_fractions, bulk_err = correct_for_unknown_cell_content(
244
+ bulk, pseudobulk_sig_cpm, start_fractions, bias_factors
245
+ )
246
+ return start_fractions, bulk_err
236
247
 
237
248
  cluster_bias_factors = signatures.clustered_bias_factors
238
249
  if not correct_mrna_bias:
@@ -249,8 +260,10 @@ def _deconvolute(signatures: RectangleSignatureResult, bulk: pd.Series, correct_
249
260
  recursive_fractions = _calculate_dwls(signature, bulk, signatures.assignments, clustered_fractions)
250
261
  except Exception as e:
251
262
  logger.warning(f"Recursive deconvolution failed with error: {e}")
252
- start_fractions = correct_for_unknown_cell_content(bulk, pseudobulk_sig_cpm, start_fractions, bias_factors)
253
- return start_fractions
263
+ start_fractions, bulk_err = correct_for_unknown_cell_content(
264
+ bulk, pseudobulk_sig_cpm, start_fractions, bias_factors
265
+ )
266
+ return start_fractions, bulk_err
254
267
 
255
268
  final_fractions = []
256
269
 
@@ -269,13 +282,15 @@ def _deconvolute(signatures: RectangleSignatureResult, bulk: pd.Series, correct_
269
282
 
270
283
  final_fractions = pd.Series(final_fractions, index=start_fractions.index)
271
284
 
272
- final_fractions = correct_for_unknown_cell_content(bulk, pseudobulk_sig_cpm, final_fractions, bias_factors)
273
- return final_fractions
285
+ final_fractions, bulk_err = correct_for_unknown_cell_content(
286
+ bulk, pseudobulk_sig_cpm, final_fractions, bias_factors
287
+ )
288
+ return final_fractions, bulk_err
274
289
 
275
290
 
276
291
  def correct_for_unknown_cell_content(
277
292
  bulk: pd.Series, pseudo_signature_cpm: pd.DataFrame, estimates: pd.Series, bias_factors: pd.Series
278
- ) -> pd.Series:
293
+ ) -> tuple[pd.Series, pd.Series]:
279
294
  r"""Performs correction for unknown cell content using the pseudo signature and bulk data.
280
295
 
281
296
  Reconstructs the bulk expression profiles through the estimated cell fractions (weighted by the mRNA bias factors) and cell-type-specific expression profiles (i.e. signature).
@@ -306,13 +321,17 @@ def correct_for_unknown_cell_content(
306
321
 
307
322
  Returns
308
323
  -------
309
- pd.Series: The corrected cell fractions, indexed by cell type. Adds an "Unknown" cell type.
324
+ estimates_fix
325
+ The corrected cell fractions, indexed by cell type. Includes an "Unknown" cell type for the unknown cellular content.
326
+ bulk_err
327
+ The difference (per gene) between the actual bulk expression and the reconstructed bulk expression (i.e., `bulk - bulk_est`).
328
+
310
329
  """
311
330
  if estimates.sum() == 0:
312
331
  estimates_fix = estimates
313
332
  # analysis fails if all cell fractions are zero, so we set the unknown cell content to ß
314
333
  estimates_fix["Unknown"] = 0
315
- return estimates_fix
334
+ return estimates_fix, pd.Series(index=bulk.index)
316
335
 
317
336
  signature_genes = pseudo_signature_cpm.index
318
337
  bulk = bulk.loc[signature_genes]
@@ -325,6 +344,8 @@ def correct_for_unknown_cell_content(
325
344
  bulk_est = pd.Series(np.dot(signature, (estimates.T * bias_factors).T))
326
345
  bulk_est.index = signature.index
327
346
 
347
+ bulk_err = bulk - bulk_est
348
+
328
349
  # Calculate the unknown cellular content ad the difference of
329
350
  # per-sample overall expression levels in the true vs. reconstructed
330
351
  # bulk RNA-seq data, divided by the overall expression in the true bulk
@@ -335,4 +356,4 @@ def correct_for_unknown_cell_content(
335
356
  estimates_fix = estimates / estimates.sum() * (1 - ukn_cc)
336
357
  estimates_fix["Unknown"] = abs(ukn_cc)
337
358
 
338
- return estimates_fix
359
+ return estimates_fix, bulk_err
@@ -23,3 +23,4 @@ def test_rectangle():
23
23
  assert isinstance(estimations, pd.DataFrame)
24
24
  assert isinstance(signatures, RectangleSignatureResult)
25
25
  assert isinstance(signatures.unkn_gene_corr, pd.Series)
26
+ assert isinstance(signatures.unkn_bulk_err, pd.DataFrame)
@@ -77,8 +77,9 @@ def test_correct_for_unknown_cell_content(small_data, quantiseq_data):
77
77
  fractions = _calculate_dwls(sig, bulk)
78
78
  biasfact = (pseudo_signature > 0).sum(axis=0)
79
79
  biasfact = biasfact / biasfact.min()
80
- result = correct_for_unknown_cell_content(bulk, pseudo_signature, fractions, biasfact)
80
+ result, bulk_err = correct_for_unknown_cell_content(bulk, pseudo_signature, fractions, biasfact)
81
81
  assert len(fractions) + 1 == len(result)
82
+ assert len(bulk_err) > 0
82
83
 
83
84
 
84
85
  def test_solve_dampened_wsl(quantiseq_data):
@@ -106,9 +107,10 @@ def test_deconvolute_no_hierarchy(small_data, quantiseq_data):
106
107
  signature = build_rectangle_signatures(adata, "cell_type", p=0.9, lfc=0.1, optimize_cutoffs=False)
107
108
  bulk, _, _ = quantiseq_data
108
109
 
109
- estimations = deconvolution(signature, bulk.T)
110
+ estimations, bulk_err = deconvolution(signature, bulk.T)
110
111
  assert np.allclose(estimations.sum(axis=1), 1)
111
112
  assert estimations.shape == (8, 4)
113
+ assert len(bulk_err) == 8
112
114
 
113
115
 
114
116
  def test_deconvolute_sparse_no_hierarchy(small_data, quantiseq_data):
@@ -118,7 +120,7 @@ def test_deconvolute_sparse_no_hierarchy(small_data, quantiseq_data):
118
120
  signature = build_rectangle_signatures(adata, "cell_type", p=0.9, lfc=0.1, optimize_cutoffs=False)
119
121
  bulk, _, _ = quantiseq_data
120
122
 
121
- expected = deconvolution(signature, bulk.T)
123
+ expected, bulk_err_exp = deconvolution(signature, bulk.T)
122
124
 
123
125
  sc_counts = sc_counts.astype(pd.SparseDtype("int"))
124
126
  csr_sparse_matrix = sc_counts.sparse.to_coo().tocsr()
@@ -127,5 +129,6 @@ def test_deconvolute_sparse_no_hierarchy(small_data, quantiseq_data):
127
129
  )
128
130
  signature_sparse = build_rectangle_signatures(adata_sparse, "cell_type", p=0.9, lfc=0.1, optimize_cutoffs=False)
129
131
 
130
- estimations = deconvolution(signature_sparse, bulk.T)
132
+ estimations, bulk_err = deconvolution(signature_sparse, bulk.T)
131
133
  assert expected.equals(estimations)
134
+ assert bulk_err_exp.equals(bulk_err)
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