nova-clone 0.1.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 (58) hide show
  1. nova_clone-0.1.0/LICENSE +28 -0
  2. nova_clone-0.1.0/PKG-INFO +298 -0
  3. nova_clone-0.1.0/README.md +275 -0
  4. nova_clone-0.1.0/nova_clone/__init__.py +1 -0
  5. nova_clone-0.1.0/nova_clone/algorithm/__init__.py +2 -0
  6. nova_clone-0.1.0/nova_clone/algorithm/_build_intbc_graph.py +135 -0
  7. nova_clone-0.1.0/nova_clone/algorithm/_call_clones_from_coresets.py +235 -0
  8. nova_clone-0.1.0/nova_clone/algorithm/_clone_call_ops.py +258 -0
  9. nova_clone-0.1.0/nova_clone/algorithm/_clone_calling_algorithm.py +1571 -0
  10. nova_clone-0.1.0/nova_clone/algorithm/_compute_cellbc_vs_coreset_overlap_matrix.py +143 -0
  11. nova_clone-0.1.0/nova_clone/algorithm/_compute_coreset_co_assignment_matrix.py +170 -0
  12. nova_clone-0.1.0/nova_clone/algorithm/_find_maximal_coresets.py +428 -0
  13. nova_clone-0.1.0/nova_clone/algorithm/_keep_reliable_edges.py +202 -0
  14. nova_clone-0.1.0/nova_clone/algorithm/_make_coresets_disjoint.py +114 -0
  15. nova_clone-0.1.0/nova_clone/algorithm/_prune_coresets.py +164 -0
  16. nova_clone-0.1.0/nova_clone/algorithm/_prune_custom_intbcs.py +150 -0
  17. nova_clone-0.1.0/nova_clone/algorithm/_prune_likely_homoplastic_intbcs.py +162 -0
  18. nova_clone-0.1.0/nova_clone/algorithm/_prune_likely_sequencing_errors.py +201 -0
  19. nova_clone-0.1.0/nova_clone/algorithm/_prune_low_cellbc_count_intbcs.py +154 -0
  20. nova_clone-0.1.0/nova_clone/algorithm/_prune_low_umi_count_intbcs.py +152 -0
  21. nova_clone-0.1.0/nova_clone/algorithm/_types.py +150 -0
  22. nova_clone-0.1.0/nova_clone/algorithm/_utils.py +53 -0
  23. nova_clone-0.1.0/nova_clone/algorithm/cassiopeia/__init__.py +1 -0
  24. nova_clone-0.1.0/nova_clone/algorithm/cassiopeia/_call_lineage_groups.py +270 -0
  25. nova_clone-0.1.0/nova_clone/caching/__init__.py +24 -0
  26. nova_clone-0.1.0/nova_clone/caching/_cached.py +175 -0
  27. nova_clone-0.1.0/nova_clone/caching/_cached_computation.py +369 -0
  28. nova_clone-0.1.0/nova_clone/caching/_cached_parallel_computation.py +428 -0
  29. nova_clone-0.1.0/nova_clone/caching/_common.py +200 -0
  30. nova_clone-0.1.0/nova_clone/data/__init__.py +1 -0
  31. nova_clone-0.1.0/nova_clone/qc/__init__.py +179 -0
  32. nova_clone-0.1.0/nova_clone/qc/_clone_intbc_sets.py +234 -0
  33. nova_clone-0.1.0/nova_clone/qc/_compare_clone_calls.py +218 -0
  34. nova_clone-0.1.0/nova_clone/qc/_data_retention.py +175 -0
  35. nova_clone-0.1.0/nova_clone/qc/_homoplasy.py +81 -0
  36. nova_clone-0.1.0/nova_clone/qc/_inter_clonal_kinship.py +60 -0
  37. nova_clone-0.1.0/nova_clone/qc/_intra_clonal_kinship.py +172 -0
  38. nova_clone-0.1.0/nova_clone/qc/_multiplet_rate.py +158 -0
  39. nova_clone-0.1.0/nova_clone/qc/_number_of_clones.py +153 -0
  40. nova_clone-0.1.0/nova_clone/qc/_pre_qc.py +485 -0
  41. nova_clone-0.1.0/nova_clone/simulation/HDBSCAN_method_adjusted.py +68 -0
  42. nova_clone-0.1.0/nova_clone/simulation/__init__.py +0 -0
  43. nova_clone-0.1.0/nova_clone/simulation/_caching.py +40 -0
  44. nova_clone-0.1.0/nova_clone/simulation/benchmarking.py +1221 -0
  45. nova_clone-0.1.0/nova_clone/simulation/clone_to_sequences.py +408 -0
  46. nova_clone-0.1.0/nova_clone/simulation/creating_tree.py +416 -0
  47. nova_clone-0.1.0/nova_clone/simulation/evaluation.py +344 -0
  48. nova_clone-0.1.0/nova_clone/simulation/jaccard_hierarchical_by_intBC.py +78 -0
  49. nova_clone-0.1.0/nova_clone/simulation/plot_benchmark_lines.py +226 -0
  50. nova_clone-0.1.0/nova_clone/simulation/slice_benchmark.py +640 -0
  51. nova_clone-0.1.0/nova_clone/simulation/slice_benchmark_clone_calling.py +68 -0
  52. nova_clone-0.1.0/nova_clone.egg-info/PKG-INFO +298 -0
  53. nova_clone-0.1.0/nova_clone.egg-info/SOURCES.txt +56 -0
  54. nova_clone-0.1.0/nova_clone.egg-info/dependency_links.txt +1 -0
  55. nova_clone-0.1.0/nova_clone.egg-info/requires.txt +16 -0
  56. nova_clone-0.1.0/nova_clone.egg-info/top_level.txt +1 -0
  57. nova_clone-0.1.0/pyproject.toml +34 -0
  58. nova_clone-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Yosef Lab
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,298 @@
1
+ Metadata-Version: 2.4
2
+ Name: nova-clone
3
+ Version: 0.1.0
4
+ Summary: Clone calling algorithm and QC
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: editdistance
9
+ Requires-Dist: ipython
10
+ Requires-Dist: matplotlib
11
+ Requires-Dist: multiset
12
+ Requires-Dist: networkx
13
+ Requires-Dist: numpy
14
+ Requires-Dist: pandas
15
+ Requires-Dist: scikit-learn
16
+ Requires-Dist: seaborn
17
+ Requires-Dist: tqdm
18
+ Provides-Extra: benchmark
19
+ Requires-Dist: cassiopeia-lineage; extra == "benchmark"
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # NovaClone
25
+
26
+ NovaClone is a network-based *clone calling* algorithm for datasets generated through static or evolving transgene-integration events such as lineage tracing experiments.
27
+
28
+ ## Overview
29
+
30
+ NovaClone assigns cells to clones in a principled, noise-aware way and provides QC metrics for evaluating dataset quality and clone calling results.
31
+ NovaClone works with barcode tables and supports multi-sample datasets.
32
+
33
+ ## Features
34
+
35
+ - Network-based clone calling algorithm robust to sequencing errors, missing data, multiplets, background noise and homoplasy.
36
+ - Applicable to static and evolving transgene-integration barcoding.
37
+ - Built-in QC metrics (pre- and post-clone-calling)
38
+ - Fast Python implementation.
39
+ - Simple API.
40
+ - Default parameters should work well for most datasets.
41
+
42
+ ## Installation
43
+
44
+ ```
45
+ git clone git@github.com:YosefLab/nova-clone.git
46
+ cd nova-clone
47
+
48
+ # Option 1 (conda)
49
+ conda env create -f environment.yml
50
+ conda activate nova_clone-env
51
+
52
+ # Option 2 (pip)
53
+ pip install -r requirements.txt
54
+ ```
55
+
56
+ You may verify your installation by running the test suite:
57
+
58
+ ```
59
+ python -m pytest tests
60
+ ```
61
+
62
+ ## System requirements
63
+
64
+ This package requires Python version >= 3.10.
65
+
66
+ ## Input format
67
+
68
+ NovaClone expects a dictionary mapping sample names to their **intBC (integration barcode) table**, where each intBC table is a pandas DataFrame with columns:
69
+
70
+ - **cellBC** — cell barcode
71
+ - **intBC** — integration barcode (transgene insertion site)
72
+ - **UMI_count** — UMI count or evidence score for that integration event
73
+
74
+ Example:
75
+
76
+ ```
77
+ intBC_table_per_sample = {"sample0": intBC_table, "sample1": another_intBC_table}
78
+ ```
79
+ Example `intBC_table`:
80
+ | Row number | cellBC | intBC | UMI_count |
81
+ |---|---|---|---|
82
+ | 1 | AAACCCATCAAACGAA | AAACCCATCAAACGAA | 27 |
83
+ | 2 | AAACCCATCAAACGAA | ACCAGTCGTATTGA | 2 |
84
+ | 3 | TTGCTACGCGTATCGC | ACCAGTCGTATTGT | 14 |
85
+
86
+ ## Quick start
87
+
88
+ The typical workflow is:
89
+ (0) Read your data and build the intBC_table_per_sample dictionary that NovaClone expects.
90
+ (1) Run NovaClone's pre-QC on your intBC_table_per_sample, which will provide quality metrics and highlight potential issues with your data *before* the NovaClone is run.
91
+ (2) Run NovaClone on your intBC_table_per_sample.
92
+ (3) Run QC on the clone call found by NovaClone. NOTE: you can run this QC on clone calls from any other algorithm you are using, the QC is not specific to the NovaClone algorithm.
93
+
94
+ ```
95
+ import pandas as pd
96
+ from nova_clone import clone_calling_algorithm
97
+ from nova_clone.qc import pre_qc_complete_analysis, qc_complete_analysis
98
+
99
+ # load your intBC table(s)
100
+ intBC_table_sample0 = pd.read_csv('intBC_table_sample0.csv')
101
+ intBC_table_sample1 = pd.read_csv('intBC_table_sample1.csv')
102
+ intBC_table_per_sample = {"sample0": intBC_table_sample0, "sample1": intBC_table_sample1} # The sample names can be anything you want e.g. "sample0", "right_lung", etc.
103
+
104
+ # run pre-QC
105
+ pre_qc_complete_analysis(intBC_table_per_sample)
106
+
107
+ # run clone calling
108
+ clone_calling_results = clone_calling_algorithm(intBC_table_per_sample)
109
+
110
+ # run post-QC
111
+ qc_complete_analysis(intBC_table_per_sample, clone_call=clone_calling_results.clone_call)
112
+ ```
113
+
114
+ See [Example.ipynb](notebooks/Example.ipynb) for a self-contained example on the macsGESTALT data, and [clone_calling_macsGESTALT_fig2Res.ipynb](notebooks/macsGESTALT_data/clone_calling_macsGESTALT_fig2Res.ipynb) for a longer version of the example which reproduces the figures in our paper.
115
+
116
+ ## Output
117
+
118
+ NovaClone's `clone_calling_algorithm` returns a `results` tuple which contains the `clone_call`. The main entries of interest are thus:
119
+
120
+ - `results.clone_call.cell_to_clones_mapping (Dict[str, Set[str]])`: Dictionary mapping cellBCs to clone IDs. A cellBCs mapping to more than one clone is a multiplet, and a cellBC mapping to no clone is an empty cellBC or a dropped cellBC (e.g. due to low quality).
121
+ - `results.clone_call.clone_to_cells_mapping (Dict[str, Set[str]])`: Dictionary clone IDs to cellBCs.
122
+ - `results.clone_call.clone_intbc_sets (Dict[str, Set[str]])`: Dictionary mapping clone IDs to its set of IntBCs.
123
+ - `results.clone_call.cell_intbc_sets (Dict[str, Set[str]])`: Dictionary mapping cellBCs to its set of IntBCs.
124
+
125
+ ## Full API
126
+
127
+ While we expect the default settings to work well on most datasets (as we show in our paper), here is the full list of parameters.
128
+
129
+ ```
130
+ def clone_calling_algorithm(
131
+ ##### Public parameters (defaults should work well on most datasets) #####
132
+ # Phase 1 parameters
133
+ intbc_table_per_sample: Dict[str, pd.DataFrame],
134
+ min_intbc_umi_count: int = 10,
135
+ custom_intbc_filtering_function: Callable[[str], bool] = lambda intbc: False,
136
+ min_intbc_cellbc_count: int = 10,
137
+ edit_distance_cutoff: int = 4,
138
+ # Phase 2 parameters
139
+ min_cells_assigned: int = 10,
140
+ min_intbc_fraction: float = 0.05,
141
+ edit_distance_cutoff_phase_2: Optional[int] = None, # If `None`, defaults to `edit_distance_cutoff` value
142
+ # Recursive step parameters
143
+ recursive: bool = False,
144
+ recursive_min_fraction_uniquely_mapped_cells_to_recurse: float = 0.7,
145
+ recursive_min_cells_assigned: int = 10,
146
+ # Single-intBC clone recovery
147
+ recover_single_intbc_clones: bool = False,
148
+ ) -> CloneCallingAlgorithmReturnType:
149
+ """
150
+ Args:
151
+ intbc_table_per_sample: Each value should be a pandas DataFrame with the following columns:
152
+ "cellBC" (str), "intBC" (str), "UMI_count" (int). For example, a row might be:
153
+ ["AAACCCAAGAGCCTGA", "ACCTCATGATATTC", 45]
154
+ indicating that the intBC "ACCTCATGATATTC" was observed 45 times in the cellBC "AAACCCAAGAGCCTGA".
155
+ There should be one intBC table per sample; the sample name acts as the key of the dictionary.
156
+ min_intbc_umi_count: IntBCs with less than this number of UMI_counts will be discarded by the algorithm.
157
+ The default value is 10, which is effective at removing sequencing errors and background RNA and
158
+ sequencing errors. For some datasets, such as the macsGESTALT which we analyze, we lower this
159
+ threshold to two UMIs, as described in the main text. More generally, we recommend to set the UMI
160
+ cutoff by considering a histogram of UMI counts across all (cellBC, intBC) pairs (rows in the input
161
+ table) and use the leftmost mode (in case the count distribution is multi-modal) or a low quantile
162
+ (otherwise).
163
+ custom_intbc_filtering_function: IntBCs for which this function gives
164
+ `True` will be discarded.
165
+ min_intbc_cellbc_count: IntBCs appearing in less than this number of
166
+ cellBCs will be discarded.
167
+ edit_distance_cutoff: Edit distance cutoff. E.g. if you expect there to
168
+ exist sequencing errors at edit distance 4 but not 5, please set this to 4.
169
+ To be precise about how this is used: When building clonal signatures (phase 1 of
170
+ the algorithm), we identify putative sequencing errors by checking for
171
+ intBCs `x` frequently co-occurring with another (more frequent) intBC
172
+ `y` at an edit distance <= edit_distance_cutoff. If so, we remove `x`.
173
+ Similarly, in the second phase of the algorithm, when we reconstruct
174
+ the intBC set for each clone de novo, we discard each intBC with an
175
+ edit distance <= edit_distance_cutoff to some more abundant intBC already
176
+ kept in the clone's intBC set.
177
+ We allow setting the cutoff for phase 2 differently with `edit_distance_cutoff_phase_2`.
178
+ min_cells_assigned: Coresets with less than this number of mapped cells
179
+ will be dropped. If you wish to recover smaller clones, you can lower
180
+ this, however, note that since all \intBCs are supported by at least
181
+ min_intbc_cellbc_count cellBCs (by default 10, and we do not recommend
182
+ changing this), all clones will have at least 10 mapped cellBCs.
183
+ min_intbc_fraction:
184
+ Following up on the above argument, we can also specify the
185
+ minimum percentage of cells in the clone containing the intBC
186
+ needed to include the intBC in the clone's intBC set. We set this
187
+ to 5% by default to recover the intBC tail too. Set this to e.g.
188
+ 20% if you only wish to retain only high-frequency intBCs.
189
+ edit_distance_cutoff_phase_2: See description of parameter edit_distance_cutoff.
190
+ If `None` (default) this is set equal to edit_distance_cutoff. This should work
191
+ well in most cases. Reasons for setting this value to something different than
192
+ `edit_distance_cutoff` are (i) increase retention of intBCs after clone calling;
193
+ note that while removing sequencing errors during the first phase (finding clonal
194
+ signatures) is critical (or else we might overmerge clones), when building intBC
195
+ sets de novo is it less critical because in the worst case, sequencing errors
196
+ lead to "duplicate" characters which will likely get dropped due to low frequency
197
+ via `min_intbc_fraction` anyway, and (ii) the user might want to curate the intBCs
198
+ in each clone themselves, in which case they can set edit_distance_cutoff_phase_2=0
199
+ to turn off this second sequencing error filter.
200
+ recursive: If True, then we will recursively call the algorithm on each
201
+ clone, thus resolving the subclonal structure derived by the
202
+ hierarchy of integrations. When using this option, we recommend
203
+ (and will enforce) using:
204
+ min_intbc_fraction <= 0.01
205
+ recursive_min_fraction_uniquely_mapped_cells_to_recurse: If recursive
206
+ is True, then this determines the min fraction of cells that have
207
+ to be uniquely mapped to a clone's subclones before we accept the
208
+ subclonal decomposition of that clone. This thus serves as a
209
+ stopping criteria. For example, a clone might have two resolvable
210
+ subclones comprising 35% and 31% of the clone's cells respectively,
211
+ with the remaining 34% unmapped (i.e. discarded). Hence, if this
212
+ argument is set to 0.7 (i.e. 70%), then we would NOT accept this
213
+ recursive decomposition and just retain the original clone. Instead,
214
+ if the subclones comprised 35% and 35% of the clone's cells, then
215
+ we would exactly meet the 70% threshold and accept the recursive
216
+ decomposition.
217
+ recursive_min_cells_assigned: If recursive
218
+ is True, a lower bound on the size of the subclones we want to find.
219
+ Subclones tend to be smaller than the parent clone, so it is a good
220
+ idea to set this lower than the original
221
+ min_cells_assigned.
222
+ recover_single_intbc_clones: If `True`, single-intBC clones will also be
223
+ recovered. Use this if your data contains single-intBC clones. There
224
+ is an inherent tradeoff in trying to recover single-intBC clones: since
225
+ these clones have weaker statistical support, unless you are certain
226
+ that your data has negligible levels of homoplasy, you might end up
227
+ accidentally overmerging clones. If evolving tracer marks are
228
+ available, we recommend using these to confirm that the single-intBC
229
+ clones are high quality, and if there is overmerging we recommend
230
+ splitting into different clones based on evolving lineage marks.
231
+
232
+ Returns:
233
+ A `results` NamedTuple of type CloneCallingAlgorithmReturnType, which contains
234
+ the clone call in the field `clone_call`. The `clone_call` itself is also a
235
+ NamedTuple; the most important fields are:
236
+ - `results.clone_call.cell_to_clones_mapping (Dict[str, Set[str]])`:
237
+ Dictionary mapping cellBCs to clone IDs. A cellBCs mapping to more than one
238
+ clone is a multiplet, and a cellBC mapping to no clone is an empty cellBC or
239
+ a dropped cellBC (e.g. due to low quality).
240
+ - `results.clone_call.clone_to_cells_mapping (Dict[str, Set[str]])`:
241
+ Dictionary clone IDs to cellBCs.
242
+ - `results.clone_call.clone_intbc_sets (Dict[str, Set[str]])`:
243
+ Dictionary mapping clone IDs to its set of IntBCs.
244
+ - `results.clone_call.cell_intbc_sets (Dict[str, Set[str]])`:
245
+ Dictionary mapping cellBCs to its set of IntBCs.
246
+ NOTE: the cellBCs are prefixed with the sample name in the `results` (to avoid
247
+ cellBC collision between different samples.)
248
+ """
249
+ ```
250
+
251
+ ### Return type
252
+
253
+ In addition to the `clone_call`, the `results` returned by NovaClone's `clone_calling_algorithm` also contains other information such as the recursive clone call (when NovaClone is run with `recursive=True`).
254
+ Below is the exact definition of the return type.
255
+
256
+ ```
257
+ CellBCType = str
258
+ IntBCType = str
259
+ UMICountType = int
260
+ CloneType = str
261
+
262
+
263
+ class CloneCallType(NamedTuple):
264
+ clone_to_cells_mapping: Dict[CloneType, Set[CellBCType]]
265
+ cell_to_clones_mapping: Dict[CellBCType, Set[CloneType]]
266
+ clone_intbc_sets: Dict[CloneType, Set[IntBCType]]
267
+ cell_intbc_sets: Dict[CellBCType, Set[IntBCType]]
268
+
269
+
270
+ class CloneCallingAlgorithmReturnType(NamedTuple):
271
+ """
272
+ Object returned by `clone_calling_algorithm`
273
+
274
+ Attributes:
275
+ clone_call: The clone call
276
+ recursive_clone_call: The recursive clone call.
277
+ recursive_clone_call_integration_hierarchy: Describes the subclonal
278
+ structure of each clone in a succint way, and hence allows easy
279
+ parsing of the recursive_clone_call. E.g.:
280
+ {
281
+ "clone_0": ({"AAA", "BBB"},
282
+ {
283
+ "clone_0": ({"CCC"},
284
+ None),
285
+ "clone_1": ({"DDD"},
286
+ None),
287
+ }),
288
+ }
289
+ metadata: General metadata for debugging etc.
290
+ total_time: Total time taken
291
+ """
292
+
293
+ clone_call: CloneCallType
294
+ recursive_clone_call: Dict[CloneType, "CloneCallingAlgorithmReturnType"]
295
+ recursive_clone_call_integration_hierarchy: Any = None
296
+ total_time: Optional[float] = None
297
+ metadata: Any = None
298
+ ```
@@ -0,0 +1,275 @@
1
+ # NovaClone
2
+
3
+ NovaClone is a network-based *clone calling* algorithm for datasets generated through static or evolving transgene-integration events such as lineage tracing experiments.
4
+
5
+ ## Overview
6
+
7
+ NovaClone assigns cells to clones in a principled, noise-aware way and provides QC metrics for evaluating dataset quality and clone calling results.
8
+ NovaClone works with barcode tables and supports multi-sample datasets.
9
+
10
+ ## Features
11
+
12
+ - Network-based clone calling algorithm robust to sequencing errors, missing data, multiplets, background noise and homoplasy.
13
+ - Applicable to static and evolving transgene-integration barcoding.
14
+ - Built-in QC metrics (pre- and post-clone-calling)
15
+ - Fast Python implementation.
16
+ - Simple API.
17
+ - Default parameters should work well for most datasets.
18
+
19
+ ## Installation
20
+
21
+ ```
22
+ git clone git@github.com:YosefLab/nova-clone.git
23
+ cd nova-clone
24
+
25
+ # Option 1 (conda)
26
+ conda env create -f environment.yml
27
+ conda activate nova_clone-env
28
+
29
+ # Option 2 (pip)
30
+ pip install -r requirements.txt
31
+ ```
32
+
33
+ You may verify your installation by running the test suite:
34
+
35
+ ```
36
+ python -m pytest tests
37
+ ```
38
+
39
+ ## System requirements
40
+
41
+ This package requires Python version >= 3.10.
42
+
43
+ ## Input format
44
+
45
+ NovaClone expects a dictionary mapping sample names to their **intBC (integration barcode) table**, where each intBC table is a pandas DataFrame with columns:
46
+
47
+ - **cellBC** — cell barcode
48
+ - **intBC** — integration barcode (transgene insertion site)
49
+ - **UMI_count** — UMI count or evidence score for that integration event
50
+
51
+ Example:
52
+
53
+ ```
54
+ intBC_table_per_sample = {"sample0": intBC_table, "sample1": another_intBC_table}
55
+ ```
56
+ Example `intBC_table`:
57
+ | Row number | cellBC | intBC | UMI_count |
58
+ |---|---|---|---|
59
+ | 1 | AAACCCATCAAACGAA | AAACCCATCAAACGAA | 27 |
60
+ | 2 | AAACCCATCAAACGAA | ACCAGTCGTATTGA | 2 |
61
+ | 3 | TTGCTACGCGTATCGC | ACCAGTCGTATTGT | 14 |
62
+
63
+ ## Quick start
64
+
65
+ The typical workflow is:
66
+ (0) Read your data and build the intBC_table_per_sample dictionary that NovaClone expects.
67
+ (1) Run NovaClone's pre-QC on your intBC_table_per_sample, which will provide quality metrics and highlight potential issues with your data *before* the NovaClone is run.
68
+ (2) Run NovaClone on your intBC_table_per_sample.
69
+ (3) Run QC on the clone call found by NovaClone. NOTE: you can run this QC on clone calls from any other algorithm you are using, the QC is not specific to the NovaClone algorithm.
70
+
71
+ ```
72
+ import pandas as pd
73
+ from nova_clone import clone_calling_algorithm
74
+ from nova_clone.qc import pre_qc_complete_analysis, qc_complete_analysis
75
+
76
+ # load your intBC table(s)
77
+ intBC_table_sample0 = pd.read_csv('intBC_table_sample0.csv')
78
+ intBC_table_sample1 = pd.read_csv('intBC_table_sample1.csv')
79
+ intBC_table_per_sample = {"sample0": intBC_table_sample0, "sample1": intBC_table_sample1} # The sample names can be anything you want e.g. "sample0", "right_lung", etc.
80
+
81
+ # run pre-QC
82
+ pre_qc_complete_analysis(intBC_table_per_sample)
83
+
84
+ # run clone calling
85
+ clone_calling_results = clone_calling_algorithm(intBC_table_per_sample)
86
+
87
+ # run post-QC
88
+ qc_complete_analysis(intBC_table_per_sample, clone_call=clone_calling_results.clone_call)
89
+ ```
90
+
91
+ See [Example.ipynb](notebooks/Example.ipynb) for a self-contained example on the macsGESTALT data, and [clone_calling_macsGESTALT_fig2Res.ipynb](notebooks/macsGESTALT_data/clone_calling_macsGESTALT_fig2Res.ipynb) for a longer version of the example which reproduces the figures in our paper.
92
+
93
+ ## Output
94
+
95
+ NovaClone's `clone_calling_algorithm` returns a `results` tuple which contains the `clone_call`. The main entries of interest are thus:
96
+
97
+ - `results.clone_call.cell_to_clones_mapping (Dict[str, Set[str]])`: Dictionary mapping cellBCs to clone IDs. A cellBCs mapping to more than one clone is a multiplet, and a cellBC mapping to no clone is an empty cellBC or a dropped cellBC (e.g. due to low quality).
98
+ - `results.clone_call.clone_to_cells_mapping (Dict[str, Set[str]])`: Dictionary clone IDs to cellBCs.
99
+ - `results.clone_call.clone_intbc_sets (Dict[str, Set[str]])`: Dictionary mapping clone IDs to its set of IntBCs.
100
+ - `results.clone_call.cell_intbc_sets (Dict[str, Set[str]])`: Dictionary mapping cellBCs to its set of IntBCs.
101
+
102
+ ## Full API
103
+
104
+ While we expect the default settings to work well on most datasets (as we show in our paper), here is the full list of parameters.
105
+
106
+ ```
107
+ def clone_calling_algorithm(
108
+ ##### Public parameters (defaults should work well on most datasets) #####
109
+ # Phase 1 parameters
110
+ intbc_table_per_sample: Dict[str, pd.DataFrame],
111
+ min_intbc_umi_count: int = 10,
112
+ custom_intbc_filtering_function: Callable[[str], bool] = lambda intbc: False,
113
+ min_intbc_cellbc_count: int = 10,
114
+ edit_distance_cutoff: int = 4,
115
+ # Phase 2 parameters
116
+ min_cells_assigned: int = 10,
117
+ min_intbc_fraction: float = 0.05,
118
+ edit_distance_cutoff_phase_2: Optional[int] = None, # If `None`, defaults to `edit_distance_cutoff` value
119
+ # Recursive step parameters
120
+ recursive: bool = False,
121
+ recursive_min_fraction_uniquely_mapped_cells_to_recurse: float = 0.7,
122
+ recursive_min_cells_assigned: int = 10,
123
+ # Single-intBC clone recovery
124
+ recover_single_intbc_clones: bool = False,
125
+ ) -> CloneCallingAlgorithmReturnType:
126
+ """
127
+ Args:
128
+ intbc_table_per_sample: Each value should be a pandas DataFrame with the following columns:
129
+ "cellBC" (str), "intBC" (str), "UMI_count" (int). For example, a row might be:
130
+ ["AAACCCAAGAGCCTGA", "ACCTCATGATATTC", 45]
131
+ indicating that the intBC "ACCTCATGATATTC" was observed 45 times in the cellBC "AAACCCAAGAGCCTGA".
132
+ There should be one intBC table per sample; the sample name acts as the key of the dictionary.
133
+ min_intbc_umi_count: IntBCs with less than this number of UMI_counts will be discarded by the algorithm.
134
+ The default value is 10, which is effective at removing sequencing errors and background RNA and
135
+ sequencing errors. For some datasets, such as the macsGESTALT which we analyze, we lower this
136
+ threshold to two UMIs, as described in the main text. More generally, we recommend to set the UMI
137
+ cutoff by considering a histogram of UMI counts across all (cellBC, intBC) pairs (rows in the input
138
+ table) and use the leftmost mode (in case the count distribution is multi-modal) or a low quantile
139
+ (otherwise).
140
+ custom_intbc_filtering_function: IntBCs for which this function gives
141
+ `True` will be discarded.
142
+ min_intbc_cellbc_count: IntBCs appearing in less than this number of
143
+ cellBCs will be discarded.
144
+ edit_distance_cutoff: Edit distance cutoff. E.g. if you expect there to
145
+ exist sequencing errors at edit distance 4 but not 5, please set this to 4.
146
+ To be precise about how this is used: When building clonal signatures (phase 1 of
147
+ the algorithm), we identify putative sequencing errors by checking for
148
+ intBCs `x` frequently co-occurring with another (more frequent) intBC
149
+ `y` at an edit distance <= edit_distance_cutoff. If so, we remove `x`.
150
+ Similarly, in the second phase of the algorithm, when we reconstruct
151
+ the intBC set for each clone de novo, we discard each intBC with an
152
+ edit distance <= edit_distance_cutoff to some more abundant intBC already
153
+ kept in the clone's intBC set.
154
+ We allow setting the cutoff for phase 2 differently with `edit_distance_cutoff_phase_2`.
155
+ min_cells_assigned: Coresets with less than this number of mapped cells
156
+ will be dropped. If you wish to recover smaller clones, you can lower
157
+ this, however, note that since all \intBCs are supported by at least
158
+ min_intbc_cellbc_count cellBCs (by default 10, and we do not recommend
159
+ changing this), all clones will have at least 10 mapped cellBCs.
160
+ min_intbc_fraction:
161
+ Following up on the above argument, we can also specify the
162
+ minimum percentage of cells in the clone containing the intBC
163
+ needed to include the intBC in the clone's intBC set. We set this
164
+ to 5% by default to recover the intBC tail too. Set this to e.g.
165
+ 20% if you only wish to retain only high-frequency intBCs.
166
+ edit_distance_cutoff_phase_2: See description of parameter edit_distance_cutoff.
167
+ If `None` (default) this is set equal to edit_distance_cutoff. This should work
168
+ well in most cases. Reasons for setting this value to something different than
169
+ `edit_distance_cutoff` are (i) increase retention of intBCs after clone calling;
170
+ note that while removing sequencing errors during the first phase (finding clonal
171
+ signatures) is critical (or else we might overmerge clones), when building intBC
172
+ sets de novo is it less critical because in the worst case, sequencing errors
173
+ lead to "duplicate" characters which will likely get dropped due to low frequency
174
+ via `min_intbc_fraction` anyway, and (ii) the user might want to curate the intBCs
175
+ in each clone themselves, in which case they can set edit_distance_cutoff_phase_2=0
176
+ to turn off this second sequencing error filter.
177
+ recursive: If True, then we will recursively call the algorithm on each
178
+ clone, thus resolving the subclonal structure derived by the
179
+ hierarchy of integrations. When using this option, we recommend
180
+ (and will enforce) using:
181
+ min_intbc_fraction <= 0.01
182
+ recursive_min_fraction_uniquely_mapped_cells_to_recurse: If recursive
183
+ is True, then this determines the min fraction of cells that have
184
+ to be uniquely mapped to a clone's subclones before we accept the
185
+ subclonal decomposition of that clone. This thus serves as a
186
+ stopping criteria. For example, a clone might have two resolvable
187
+ subclones comprising 35% and 31% of the clone's cells respectively,
188
+ with the remaining 34% unmapped (i.e. discarded). Hence, if this
189
+ argument is set to 0.7 (i.e. 70%), then we would NOT accept this
190
+ recursive decomposition and just retain the original clone. Instead,
191
+ if the subclones comprised 35% and 35% of the clone's cells, then
192
+ we would exactly meet the 70% threshold and accept the recursive
193
+ decomposition.
194
+ recursive_min_cells_assigned: If recursive
195
+ is True, a lower bound on the size of the subclones we want to find.
196
+ Subclones tend to be smaller than the parent clone, so it is a good
197
+ idea to set this lower than the original
198
+ min_cells_assigned.
199
+ recover_single_intbc_clones: If `True`, single-intBC clones will also be
200
+ recovered. Use this if your data contains single-intBC clones. There
201
+ is an inherent tradeoff in trying to recover single-intBC clones: since
202
+ these clones have weaker statistical support, unless you are certain
203
+ that your data has negligible levels of homoplasy, you might end up
204
+ accidentally overmerging clones. If evolving tracer marks are
205
+ available, we recommend using these to confirm that the single-intBC
206
+ clones are high quality, and if there is overmerging we recommend
207
+ splitting into different clones based on evolving lineage marks.
208
+
209
+ Returns:
210
+ A `results` NamedTuple of type CloneCallingAlgorithmReturnType, which contains
211
+ the clone call in the field `clone_call`. The `clone_call` itself is also a
212
+ NamedTuple; the most important fields are:
213
+ - `results.clone_call.cell_to_clones_mapping (Dict[str, Set[str]])`:
214
+ Dictionary mapping cellBCs to clone IDs. A cellBCs mapping to more than one
215
+ clone is a multiplet, and a cellBC mapping to no clone is an empty cellBC or
216
+ a dropped cellBC (e.g. due to low quality).
217
+ - `results.clone_call.clone_to_cells_mapping (Dict[str, Set[str]])`:
218
+ Dictionary clone IDs to cellBCs.
219
+ - `results.clone_call.clone_intbc_sets (Dict[str, Set[str]])`:
220
+ Dictionary mapping clone IDs to its set of IntBCs.
221
+ - `results.clone_call.cell_intbc_sets (Dict[str, Set[str]])`:
222
+ Dictionary mapping cellBCs to its set of IntBCs.
223
+ NOTE: the cellBCs are prefixed with the sample name in the `results` (to avoid
224
+ cellBC collision between different samples.)
225
+ """
226
+ ```
227
+
228
+ ### Return type
229
+
230
+ In addition to the `clone_call`, the `results` returned by NovaClone's `clone_calling_algorithm` also contains other information such as the recursive clone call (when NovaClone is run with `recursive=True`).
231
+ Below is the exact definition of the return type.
232
+
233
+ ```
234
+ CellBCType = str
235
+ IntBCType = str
236
+ UMICountType = int
237
+ CloneType = str
238
+
239
+
240
+ class CloneCallType(NamedTuple):
241
+ clone_to_cells_mapping: Dict[CloneType, Set[CellBCType]]
242
+ cell_to_clones_mapping: Dict[CellBCType, Set[CloneType]]
243
+ clone_intbc_sets: Dict[CloneType, Set[IntBCType]]
244
+ cell_intbc_sets: Dict[CellBCType, Set[IntBCType]]
245
+
246
+
247
+ class CloneCallingAlgorithmReturnType(NamedTuple):
248
+ """
249
+ Object returned by `clone_calling_algorithm`
250
+
251
+ Attributes:
252
+ clone_call: The clone call
253
+ recursive_clone_call: The recursive clone call.
254
+ recursive_clone_call_integration_hierarchy: Describes the subclonal
255
+ structure of each clone in a succint way, and hence allows easy
256
+ parsing of the recursive_clone_call. E.g.:
257
+ {
258
+ "clone_0": ({"AAA", "BBB"},
259
+ {
260
+ "clone_0": ({"CCC"},
261
+ None),
262
+ "clone_1": ({"DDD"},
263
+ None),
264
+ }),
265
+ }
266
+ metadata: General metadata for debugging etc.
267
+ total_time: Total time taken
268
+ """
269
+
270
+ clone_call: CloneCallType
271
+ recursive_clone_call: Dict[CloneType, "CloneCallingAlgorithmReturnType"]
272
+ recursive_clone_call_integration_hierarchy: Any = None
273
+ total_time: Optional[float] = None
274
+ metadata: Any = None
275
+ ```
@@ -0,0 +1 @@
1
+ from .algorithm import clone_calling_algorithm
@@ -0,0 +1,2 @@
1
+ from ._clone_calling_algorithm import clone_calling_algorithm
2
+ from ._clone_call_ops import merge_clones, drop_clones