rectanglepy 1.4.2__tar.gz → 1.5.0__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 (59) hide show
  1. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.bumpversion.cfg +1 -1
  2. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.readthedocs.yaml +1 -1
  3. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/PKG-INFO +1 -1
  4. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/pyproject.toml +1 -1
  5. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/src/rectanglepy/__init__.py +2 -1
  6. rectanglepy-1.5.0/src/rectanglepy/parameters.py +11 -0
  7. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/src/rectanglepy/pp/create_signature.py +51 -12
  8. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/src/rectanglepy/rectangle.py +6 -1
  9. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/tests/test_pp.py +49 -0
  10. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.cruft.json +0 -0
  11. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.editorconfig +0 -0
  12. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  13. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  14. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  15. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  16. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.github/workflows/build.yaml +0 -0
  17. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.github/workflows/release.yaml +0 -0
  18. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.github/workflows/release_testpypi.yaml +0 -0
  19. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.github/workflows/test.yaml +0 -0
  20. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.gitignore +0 -0
  21. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/.pre-commit-config.yaml +0 -0
  22. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/CHANGELOG.md +0 -0
  23. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/CLA.md +0 -0
  24. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/LICENSE +0 -0
  25. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/LICENSE-Commercial.md +0 -0
  26. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/README.md +0 -0
  27. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/Makefile +0 -0
  28. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/_static/.gitkeep +0 -0
  29. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/_static/rec_logo.001.png +0 -0
  30. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/_templates/.gitkeep +0 -0
  31. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/_templates/autosummary/class.rst +0 -0
  32. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/api.md +0 -0
  33. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/changelog.md +0 -0
  34. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/conf.py +0 -0
  35. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/contributing.md +0 -0
  36. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/extensions/typed_returns.py +0 -0
  37. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/index.md +0 -0
  38. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/installation.md +0 -0
  39. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/make.bat +0 -0
  40. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/notebooks/example.ipynb +0 -0
  41. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/references.bib +0 -0
  42. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/references.md +0 -0
  43. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/docs/tutorials.md +0 -0
  44. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/src/rectanglepy/data/hao1_annotations_small.zip +0 -0
  45. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/src/rectanglepy/data/hao1_counts_small.zip +0 -0
  46. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/src/rectanglepy/data/small_fino_bulks.zip +0 -0
  47. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/src/rectanglepy/pp/__init__.py +0 -0
  48. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/src/rectanglepy/pp/rectangle_signature.py +0 -0
  49. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/src/rectanglepy/tl/__init__.py +0 -0
  50. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/src/rectanglepy/tl/deconvolution.py +0 -0
  51. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/tests/data/TIL10_signature.txt +0 -0
  52. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/tests/data/bulk_small.csv +0 -0
  53. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/tests/data/cell_annotations_small.txt +0 -0
  54. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/tests/data/quanTIseq_SimRNAseq_mixture_smaller.csv +0 -0
  55. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/tests/data/quanTIseq_SimRNAseq_read_fractions_small.txt +0 -0
  56. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/tests/data/sc_object_small.csv +0 -0
  57. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/tests/data/signature_hao1.csv +0 -0
  58. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/tests/test_rectangle.py +0 -0
  59. {rectanglepy-1.4.2 → rectanglepy-1.5.0}/tests/test_tl.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 1.4.2
2
+ current_version = 1.5.0
3
3
  tag = True
4
4
  commit = True
5
5
 
@@ -1,7 +1,7 @@
1
1
  # https://docs.readthedocs.io/en/stable/config-file/v2.html
2
2
  version: 2
3
3
  build:
4
- os: ubuntu-20.04
4
+ os: ubuntu-24.04
5
5
  tools:
6
6
  python: "3.10"
7
7
  sphinx:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rectanglepy
3
- Version: 1.4.2
3
+ Version: 1.5.0
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
@@ -4,7 +4,7 @@ requires = ["hatchling"]
4
4
 
5
5
  [project]
6
6
  name = "rectanglepy"
7
- version = "1.4.2"
7
+ version = "1.5.0"
8
8
  description = "Hierarchical deconvolution of bulk transcriptomics"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,8 +1,9 @@
1
1
  from importlib.metadata import version
2
2
 
3
3
  from . import pp, tl
4
+ from .parameters import RectangleAdvancedParameters
4
5
  from .rectangle import load_tutorial_data, rectangle
5
6
 
6
- __all__ = ["pp", "tl", "load_tutorial_data", "rectangle"]
7
+ __all__ = ["pp", "tl", "RectangleAdvancedParameters", "load_tutorial_data", "rectangle"]
7
8
 
8
9
  __version__ = version("rectanglepy")
@@ -0,0 +1,11 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause OR LicenseRef-Rectangle-Commercial
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class RectangleAdvancedParameters:
8
+ """Advanced parameters for Rectangle internals."""
9
+
10
+ number_of_bootstraps: int = 7
11
+ grid_search_split_size: int = 50
@@ -13,6 +13,7 @@ from scipy.cluster.hierarchy import fcluster, linkage
13
13
  from scipy.stats import pearsonr
14
14
  from sklearn.metrics import silhouette_score
15
15
 
16
+ from rectanglepy.parameters import RectangleAdvancedParameters
16
17
  from rectanglepy.tl.deconvolution import solve_qp
17
18
 
18
19
  from .rectangle_signature import RectangleSignatureResult
@@ -129,11 +130,16 @@ def _filter_de_analysis_results(de_analysis_result, p, logfc):
129
130
 
130
131
 
131
132
  def _run_deseq2(
132
- countsig: pd.DataFrame, sc_data, annotations: pd.Series, n_cpus: int = None, gene_expression_threshold=0.5
133
+ countsig: pd.DataFrame,
134
+ sc_data,
135
+ annotations: pd.Series,
136
+ n_cpus: int = None,
137
+ gene_expression_threshold=0.5,
138
+ number_of_bootstraps: int = 7,
133
139
  ) -> dict[str | int, pd.DataFrame]:
134
140
  results = {}
135
141
  inference = DefaultInference(n_cpus=n_cpus)
136
- bootstrapped_signature = _create_bootstrap_signature(countsig, sc_data, annotations)
142
+ bootstrapped_signature = _create_bootstrap_signature(countsig, sc_data, annotations, number_of_bootstraps)
137
143
  np.random.seed(42)
138
144
  for _i, cell_type in enumerate(countsig.columns):
139
145
  bootstrapped_signature_copy = bootstrapped_signature.copy()
@@ -167,12 +173,11 @@ def _run_deseq2(
167
173
  return results
168
174
 
169
175
 
170
- def _create_bootstrap_signature(countsig, sc_data, annotations) -> pd.DataFrame:
176
+ def _create_bootstrap_signature(countsig, sc_data, annotations, number_of_bootstraps: int = 7) -> pd.DataFrame:
171
177
  if scipy.sparse.issparse(sc_data):
172
178
  sc_data = sc_data.toarray()
173
179
  celltypes = countsig.columns
174
180
  bootstrapped_signature = pd.DataFrame()
175
- number_of_bootstraps = 7
176
181
  samples_per_bootstrap = 500
177
182
  np.random.seed(42)
178
183
  for celltype in celltypes:
@@ -198,14 +203,30 @@ def _de_analysis(
198
203
  n_cpus: int = None,
199
204
  genes=None,
200
205
  gene_expression_threshold=0.5,
206
+ advanced_parameters: RectangleAdvancedParameters = None,
201
207
  ) -> tuple[Series, dict[str, [str]] :, DataFrame | None]:
202
208
  logger.info("Starting DE analysis")
203
- deseq_results = _run_deseq2(pseudo_count_sig, sc_data, annotations, n_cpus, gene_expression_threshold)
209
+ advanced_parameters = advanced_parameters or RectangleAdvancedParameters()
210
+ deseq_results = _run_deseq2(
211
+ pseudo_count_sig,
212
+ sc_data,
213
+ annotations,
214
+ n_cpus,
215
+ gene_expression_threshold,
216
+ advanced_parameters.number_of_bootstraps,
217
+ )
204
218
  optimization_results = None
205
219
 
206
220
  if optimize_cutoffs:
207
221
  logger.info("Optimizing cutoff parameters p and lfc")
208
- optimization_results = _optimize_parameters(sc_data, annotations, pseudo_count_sig, deseq_results, genes)
222
+ optimization_results = _optimize_parameters(
223
+ sc_data,
224
+ annotations,
225
+ pseudo_count_sig,
226
+ deseq_results,
227
+ genes,
228
+ advanced_parameters.grid_search_split_size,
229
+ )
209
230
  p, lfc = optimization_results.iloc[0, 0:2]
210
231
  logger.info(f"Optimization done\n Best cutoffs p: {p} and lfc: {lfc}")
211
232
 
@@ -287,6 +308,7 @@ def build_rectangle_signatures(
287
308
  lfc=1.5,
288
309
  n_cpus: int = None,
289
310
  gene_expression_threshold=0.5,
311
+ advanced_parameters: RectangleAdvancedParameters = None,
290
312
  ) -> RectangleSignatureResult:
291
313
  r"""Builds rectangle signatures based on single-cell count data and annotations.
292
314
 
@@ -303,7 +325,7 @@ def build_rectangle_signatures(
303
325
  raw
304
326
  A flag indicating whether to use the raw Anndata data. Defaults to False.
305
327
  optimize_cutoffs
306
- Indicates whether to optimize the p-value and log fold change cutoffs using gridsearch. Defaults to True.
328
+ Indicates whether to optimize the log fold change cutoffs using gridsearch. Defaults to True.
307
329
  p
308
330
  The p-value threshold for the DE analysis (only used if optimize_cutoffs is False).
309
331
  lfc
@@ -312,11 +334,14 @@ def build_rectangle_signatures(
312
334
  The number of cpus to use for the DE analysis. Defaults to the number of cpus available.
313
335
  gene_expression_threshold
314
336
  The gene expression threshold for the DE analysis. How many cells need to express a gene to be considered in DGE
337
+ advanced_parameters
338
+ Optional advanced Rectangle parameters. Defaults are used when not provided.
315
339
 
316
340
  Returns
317
341
  -------
318
342
  The result of the rectangle signature analysis which is of type RectangleSignatureResult.
319
343
  """
344
+ advanced_parameters = advanced_parameters or RectangleAdvancedParameters()
320
345
  annotations = adata.obs[cell_type_col]
321
346
  adata = adata[:, adata.X.sum(axis=0) > len(annotations.value_counts())]
322
347
  assert adata.var_names.is_unique, "Duplicate gene found in adata"
@@ -344,7 +369,16 @@ def build_rectangle_signatures(
344
369
  m_rna_biasfactors = _create_bias_factors(pseudo_sig_counts, sc_counts, annotations)
345
370
 
346
371
  marker_genes, marker_genes_per_cell_type, optimization_result = _de_analysis(
347
- pseudo_sig_counts, sc_counts, annotations, p, lfc, optimize_cutoffs, n_cpus, genes, gene_expression_threshold
372
+ pseudo_sig_counts,
373
+ sc_counts,
374
+ annotations,
375
+ p,
376
+ lfc,
377
+ optimize_cutoffs,
378
+ n_cpus,
379
+ genes,
380
+ gene_expression_threshold,
381
+ advanced_parameters,
348
382
  )
349
383
  pseudo_sig_cpm = _convert_to_cpm(pseudo_sig_counts)
350
384
  logger.info("Starting rectangle cluster analysis")
@@ -371,6 +405,7 @@ def build_rectangle_signatures(
371
405
  lfc,
372
406
  False,
373
407
  gene_expression_threshold=gene_expression_threshold,
408
+ advanced_parameters=advanced_parameters,
374
409
  )
375
410
  clustered_signature = _convert_to_cpm(clustered_signature)
376
411
  return RectangleSignatureResult(
@@ -400,7 +435,12 @@ def _create_pseudo_count_sig(sc_counts: np.ndarray, annotations: pd.Series, var_
400
435
 
401
436
 
402
437
  def _optimize_parameters(
403
- sc_data: pd.DataFrame, annotations: pd.Series, pseudo_signature_counts: pd.DataFrame, de_results, genes=None
438
+ sc_data: pd.DataFrame,
439
+ annotations: pd.Series,
440
+ pseudo_signature_counts: pd.DataFrame,
441
+ de_results,
442
+ genes=None,
443
+ grid_search_split_size: int = 50,
404
444
  ) -> pd.DataFrame:
405
445
  # search space for p and lfc
406
446
  lfcs = [x / 100 for x in range(160, 230, 10)]
@@ -408,7 +448,7 @@ def _optimize_parameters(
408
448
 
409
449
  results = []
410
450
  logger.info("generating pseudo bulks")
411
- bulks, real_fractions = _generate_pseudo_bulks(sc_data, annotations, genes)
451
+ bulks, real_fractions = _generate_pseudo_bulks(sc_data, annotations, genes, grid_search_split_size)
412
452
  for p in ps:
413
453
  for lfc in lfcs:
414
454
  try:
@@ -441,9 +481,8 @@ def _assess_parameter_fit(
441
481
  return rsme, pearson_r
442
482
 
443
483
 
444
- def _generate_pseudo_bulks(sc_data, annotations, genes=None):
484
+ def _generate_pseudo_bulks(sc_data, annotations, genes=None, split_size: int = 50):
445
485
  number_of_bulks = 50
446
- split_size = 50
447
486
  bulks = []
448
487
  real_fractions = []
449
488
  np.random.seed(42)
@@ -7,6 +7,7 @@ from anndata import AnnData
7
7
  from loguru import logger
8
8
  from pandas import DataFrame
9
9
 
10
+ from .parameters import RectangleAdvancedParameters
10
11
  from .pp import RectangleSignatureResult, build_rectangle_signatures
11
12
  from .tl import deconvolution
12
13
 
@@ -24,6 +25,7 @@ def rectangle(
24
25
  lfc=1.5,
25
26
  n_cpus: int = None,
26
27
  gene_expression_threshold=0.5,
28
+ advanced_parameters: RectangleAdvancedParameters = None,
27
29
  ) -> tuple[DataFrame, RectangleSignatureResult]:
28
30
  r"""All in one deconvolution method. Creates signatures and deconvolutes the bulk data. Has options for subsampling and consensus runs.
29
31
 
@@ -40,7 +42,7 @@ def rectangle(
40
42
  raw
41
43
  A flag indicating whether to use the raw Anndata data.
42
44
  optimize_cutoffs
43
- Indicates whether to optimize the p-value and log fold change cutoffs using gridsearch.
45
+ Indicates whether to optimize the log fold change cutoffs using gridsearch.
44
46
  p
45
47
  The p-value threshold for the DE analysis (only used if optimize_cutoffs is False).
46
48
  lfc
@@ -51,6 +53,8 @@ def rectangle(
51
53
  A flag indicating whether to correct for mRNA bias. Defaults to True.
52
54
  gene_expression_threshold : float
53
55
  The threshold for gene expression. Genes with expression below this threshold are removed from the analysis.
56
+ advanced_parameters
57
+ Optional advanced Rectangle parameters. Defaults are used when not provided.
54
58
 
55
59
  Returns
56
60
  -------
@@ -71,6 +75,7 @@ def rectangle(
71
75
  lfc=lfc,
72
76
  n_cpus=n_cpus,
73
77
  gene_expression_threshold=gene_expression_threshold,
78
+ advanced_parameters=advanced_parameters,
74
79
  )
75
80
 
76
81
  estimations, bulk_err = deconvolution(signatures, bulks, correct_mrna_bias, n_cpus)
@@ -6,6 +6,7 @@ import pytest
6
6
  from anndata import AnnData
7
7
 
8
8
  import rectanglepy as rectangle
9
+ from rectanglepy import RectangleAdvancedParameters
9
10
  from rectanglepy.pp.create_signature import (
10
11
  _assess_parameter_fit,
11
12
  _calculate_cluster_range,
@@ -18,6 +19,7 @@ from rectanglepy.pp.create_signature import (
18
19
  _de_analysis,
19
20
  _generate_pseudo_bulks,
20
21
  _get_fcluster_assignments,
22
+ _optimize_parameters,
21
23
  _run_deseq2,
22
24
  build_rectangle_signatures,
23
25
  )
@@ -154,6 +156,7 @@ def test_generate_pseudo_bulks(small_data):
154
156
  sc_counts = sc_counts.astype("int")
155
157
  adata = AnnData(sc_counts.T, obs=annotations.to_frame(name="cell_type"))
156
158
  result, _ = _generate_pseudo_bulks(adata.X.T, annotations, adata.var_names)
159
+ custom_result, _ = _generate_pseudo_bulks(adata.X.T, annotations, adata.var_names, split_size=10)
157
160
 
158
161
  sc_data = sc_counts.astype(pd.SparseDtype("int"))
159
162
  csr_sparse_matrix = sc_data.sparse.to_coo().tocsr()
@@ -162,11 +165,40 @@ def test_generate_pseudo_bulks(small_data):
162
165
  result_sparse, _ = _generate_pseudo_bulks(adata_sparse.X.T, annotations, adata_sparse.var_names)
163
166
 
164
167
  assert len(result) == 1000 and len(result.columns) == 50
168
+ assert custom_result.shape == result.shape
169
+ assert not np.allclose(result, custom_result)
165
170
  # first gene should have all 0s
166
171
  assert result.iloc[0, :].sum() == 0
167
172
  assert np.allclose(result, result_sparse)
168
173
 
169
174
 
175
+ def test_optimize_parameters_uses_grid_search_split_size(monkeypatch):
176
+ seen = {}
177
+
178
+ def fake_generate_pseudo_bulks(sc_data, annotations, genes=None, split_size=50):
179
+ seen["split_size"] = split_size
180
+ bulks = pd.DataFrame([[1.0]], index=["gene"], columns=["bulk"])
181
+ real_fractions = pd.DataFrame([[1.0]], index=["cell_type"], columns=["bulk"])
182
+ return bulks, real_fractions
183
+
184
+ def fake_assess_parameter_fit(lfc, p, bulks, real_fractions, pseudo_signature_counts, de_results):
185
+ return 0.0, 1.0
186
+
187
+ monkeypatch.setattr(rectangle.pp.create_signature, "_generate_pseudo_bulks", fake_generate_pseudo_bulks)
188
+ monkeypatch.setattr(rectangle.pp.create_signature, "_assess_parameter_fit", fake_assess_parameter_fit)
189
+
190
+ results = _optimize_parameters(
191
+ pd.DataFrame(),
192
+ pd.Series(dtype=str),
193
+ pd.DataFrame(),
194
+ {},
195
+ grid_search_split_size=13,
196
+ )
197
+
198
+ assert seen["split_size"] == 13
199
+ assert results.iloc[0]["pearson_r"] == 1.0
200
+
201
+
170
202
  def test_asses_fit(small_data):
171
203
  sc_counts, annotations, bulk = small_data
172
204
  sc_counts = sc_counts.astype("int")
@@ -217,3 +249,20 @@ def test_create_bootstrap_signature(small_data):
217
249
  bootstrap = _create_bootstrap_signature(sc_pseudo, adata.X.T, annotations)
218
250
 
219
251
  assert len(bootstrap.columns) == len(sc_pseudo.columns) * bootstraps_per_cell
252
+
253
+
254
+ def test_create_bootstrap_signature_with_advanced_parameter(small_data):
255
+ bootstraps_per_cell = 3
256
+ advanced_parameters = RectangleAdvancedParameters(number_of_bootstraps=bootstraps_per_cell)
257
+ sc_counts, annotations, bulk = small_data
258
+ sc_counts = sc_counts.astype("int")
259
+ sc_pseudo = sc_counts.groupby(annotations.values, axis=1).sum()
260
+ adata = AnnData(sc_counts.T, obs=annotations.to_frame(name="cell_type"))
261
+ bootstrap = _create_bootstrap_signature(
262
+ sc_pseudo,
263
+ adata.X.T,
264
+ annotations,
265
+ advanced_parameters.number_of_bootstraps,
266
+ )
267
+
268
+ assert len(bootstrap.columns) == len(sc_pseudo.columns) * bootstraps_per_cell
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