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.
- nova_clone-0.1.0/LICENSE +28 -0
- nova_clone-0.1.0/PKG-INFO +298 -0
- nova_clone-0.1.0/README.md +275 -0
- nova_clone-0.1.0/nova_clone/__init__.py +1 -0
- nova_clone-0.1.0/nova_clone/algorithm/__init__.py +2 -0
- nova_clone-0.1.0/nova_clone/algorithm/_build_intbc_graph.py +135 -0
- nova_clone-0.1.0/nova_clone/algorithm/_call_clones_from_coresets.py +235 -0
- nova_clone-0.1.0/nova_clone/algorithm/_clone_call_ops.py +258 -0
- nova_clone-0.1.0/nova_clone/algorithm/_clone_calling_algorithm.py +1571 -0
- nova_clone-0.1.0/nova_clone/algorithm/_compute_cellbc_vs_coreset_overlap_matrix.py +143 -0
- nova_clone-0.1.0/nova_clone/algorithm/_compute_coreset_co_assignment_matrix.py +170 -0
- nova_clone-0.1.0/nova_clone/algorithm/_find_maximal_coresets.py +428 -0
- nova_clone-0.1.0/nova_clone/algorithm/_keep_reliable_edges.py +202 -0
- nova_clone-0.1.0/nova_clone/algorithm/_make_coresets_disjoint.py +114 -0
- nova_clone-0.1.0/nova_clone/algorithm/_prune_coresets.py +164 -0
- nova_clone-0.1.0/nova_clone/algorithm/_prune_custom_intbcs.py +150 -0
- nova_clone-0.1.0/nova_clone/algorithm/_prune_likely_homoplastic_intbcs.py +162 -0
- nova_clone-0.1.0/nova_clone/algorithm/_prune_likely_sequencing_errors.py +201 -0
- nova_clone-0.1.0/nova_clone/algorithm/_prune_low_cellbc_count_intbcs.py +154 -0
- nova_clone-0.1.0/nova_clone/algorithm/_prune_low_umi_count_intbcs.py +152 -0
- nova_clone-0.1.0/nova_clone/algorithm/_types.py +150 -0
- nova_clone-0.1.0/nova_clone/algorithm/_utils.py +53 -0
- nova_clone-0.1.0/nova_clone/algorithm/cassiopeia/__init__.py +1 -0
- nova_clone-0.1.0/nova_clone/algorithm/cassiopeia/_call_lineage_groups.py +270 -0
- nova_clone-0.1.0/nova_clone/caching/__init__.py +24 -0
- nova_clone-0.1.0/nova_clone/caching/_cached.py +175 -0
- nova_clone-0.1.0/nova_clone/caching/_cached_computation.py +369 -0
- nova_clone-0.1.0/nova_clone/caching/_cached_parallel_computation.py +428 -0
- nova_clone-0.1.0/nova_clone/caching/_common.py +200 -0
- nova_clone-0.1.0/nova_clone/data/__init__.py +1 -0
- nova_clone-0.1.0/nova_clone/qc/__init__.py +179 -0
- nova_clone-0.1.0/nova_clone/qc/_clone_intbc_sets.py +234 -0
- nova_clone-0.1.0/nova_clone/qc/_compare_clone_calls.py +218 -0
- nova_clone-0.1.0/nova_clone/qc/_data_retention.py +175 -0
- nova_clone-0.1.0/nova_clone/qc/_homoplasy.py +81 -0
- nova_clone-0.1.0/nova_clone/qc/_inter_clonal_kinship.py +60 -0
- nova_clone-0.1.0/nova_clone/qc/_intra_clonal_kinship.py +172 -0
- nova_clone-0.1.0/nova_clone/qc/_multiplet_rate.py +158 -0
- nova_clone-0.1.0/nova_clone/qc/_number_of_clones.py +153 -0
- nova_clone-0.1.0/nova_clone/qc/_pre_qc.py +485 -0
- nova_clone-0.1.0/nova_clone/simulation/HDBSCAN_method_adjusted.py +68 -0
- nova_clone-0.1.0/nova_clone/simulation/__init__.py +0 -0
- nova_clone-0.1.0/nova_clone/simulation/_caching.py +40 -0
- nova_clone-0.1.0/nova_clone/simulation/benchmarking.py +1221 -0
- nova_clone-0.1.0/nova_clone/simulation/clone_to_sequences.py +408 -0
- nova_clone-0.1.0/nova_clone/simulation/creating_tree.py +416 -0
- nova_clone-0.1.0/nova_clone/simulation/evaluation.py +344 -0
- nova_clone-0.1.0/nova_clone/simulation/jaccard_hierarchical_by_intBC.py +78 -0
- nova_clone-0.1.0/nova_clone/simulation/plot_benchmark_lines.py +226 -0
- nova_clone-0.1.0/nova_clone/simulation/slice_benchmark.py +640 -0
- nova_clone-0.1.0/nova_clone/simulation/slice_benchmark_clone_calling.py +68 -0
- nova_clone-0.1.0/nova_clone.egg-info/PKG-INFO +298 -0
- nova_clone-0.1.0/nova_clone.egg-info/SOURCES.txt +56 -0
- nova_clone-0.1.0/nova_clone.egg-info/dependency_links.txt +1 -0
- nova_clone-0.1.0/nova_clone.egg-info/requires.txt +16 -0
- nova_clone-0.1.0/nova_clone.egg-info/top_level.txt +1 -0
- nova_clone-0.1.0/pyproject.toml +34 -0
- nova_clone-0.1.0/setup.cfg +4 -0
nova_clone-0.1.0/LICENSE
ADDED
|
@@ -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
|