drep 3.6.2__tar.gz → 4.0.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.
- {drep-3.6.2 → drep-4.0.0}/PKG-INFO +2 -1
- drep-4.0.0/README.md +64 -0
- drep-4.0.0/drep/VERSION +1 -0
- {drep-3.6.2 → drep-4.0.0}/drep/argumentParser.py +47 -11
- {drep-3.6.2 → drep-4.0.0}/drep/d_analyze.py +57 -26
- {drep-3.6.2 → drep-4.0.0}/drep/d_bonus.py +5 -3
- {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/cluster_utils.py +23 -58
- {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/compare_utils.py +226 -13
- {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/controller.py +48 -6
- {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/external.py +79 -3
- {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/greedy_clustering.py +23 -4
- drep-4.0.0/drep/d_cluster/union_find.py +543 -0
- {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/utils.py +14 -13
- {drep-3.6.2 → drep-4.0.0}/drep/d_evaluate.py +21 -0
- {drep-3.6.2 → drep-4.0.0}/drep/d_filter.py +1 -1
- {drep-3.6.2 → drep-4.0.0}/drep.egg-info/PKG-INFO +2 -1
- {drep-3.6.2 → drep-4.0.0}/drep.egg-info/SOURCES.txt +1 -0
- {drep-3.6.2 → drep-4.0.0}/drep.egg-info/requires.txt +1 -0
- {drep-3.6.2 → drep-4.0.0}/helper_scripts/ScaffoldLevel_dRep.py +24 -1
- {drep-3.6.2 → drep-4.0.0}/setup.py +1 -0
- drep-3.6.2/README.md +0 -50
- drep-3.6.2/drep/VERSION +0 -1
- {drep-3.6.2 → drep-4.0.0}/bin/dRep +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep/WorkDirectory.py +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep/__init__.py +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep/controller.py +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep/d_adjust.py +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep/d_choose.py +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/__init__.py +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/parsers.py +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep/d_workflows.py +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep.egg-info/dependency_links.txt +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep.egg-info/not-zip-safe +0 -0
- {drep-3.6.2 → drep-4.0.0}/drep.egg-info/top_level.txt +0 -0
- {drep-3.6.2 → drep-4.0.0}/helper_scripts/parse_stb.py +0 -0
- {drep-3.6.2 → drep-4.0.0}/pyproject.toml +0 -0
- {drep-3.6.2 → drep-4.0.0}/setup.cfg +0 -0
- {drep-3.6.2 → drep-4.0.0}/tests/test_suite.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: drep
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: De-replication of microbial genomes assembled from multiple samples
|
|
5
5
|
Home-page: https://github.com/MrOlm/drep
|
|
6
6
|
Author: Matt Olm
|
|
@@ -13,4 +13,5 @@ Requires-Dist: matplotlib
|
|
|
13
13
|
Requires-Dist: biopython
|
|
14
14
|
Requires-Dist: scikit-learn
|
|
15
15
|
Requires-Dist: tqdm
|
|
16
|
+
Requires-Dist: setuptools
|
|
16
17
|
Requires-Dist: pytest
|
drep-4.0.0/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# dRep
|
|
2
|
+
|
|
3
|
+
[](https://pepy.tech/project/drep)
|
|
4
|
+
[](https://pepy.tech/project/drep)
|
|
5
|
+
|
|
6
|
+
dRep is a python program for rapidly comparing large numbers of genomes. dRep can also "de-replicate" a genome set by identifying groups of highly similar genomes and choosing the best representative genome for each genome set.
|
|
7
|
+
|
|
8
|
+
Manual, installation instructions, and API are at available at
|
|
9
|
+
[ReadTheDocs](https://drep.readthedocs.io/en/latest/)
|
|
10
|
+
|
|
11
|
+
Publication is available at
|
|
12
|
+
[ISMEJ](https://doi.org/10.1038/ismej.2017.126)
|
|
13
|
+
|
|
14
|
+
Open source pre-print publication is available at
|
|
15
|
+
[bioRxiv](https://doi.org/10.1101/108142)
|
|
16
|
+
|
|
17
|
+
## ⚡ New in v4
|
|
18
|
+
|
|
19
|
+
dRep v4 uses [skani](https://github.com/bluenote-1577/skani) for **both** primary and secondary genome comparisons by default, replacing v3's default of MASH (primary) + fastANI (secondary). skani is much faster than that pair, and it *streams* its comparisons instead of building an all-vs-all matrix in memory — so `dereplicate` runs far quicker and its memory footprint grows roughly linearly with genome count rather than quadratically.
|
|
20
|
+
|
|
21
|
+
**Whole-pipeline `dRep dereplicate` on 10,000 genomes** — identical inputs and settings:
|
|
22
|
+
|
|
23
|
+
| | v3 defaults (MASH → fastANI) | v4 defaults (skani) |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Wall-clock time | 6 h 15 min | **14.5 min** (~26× faster) |
|
|
26
|
+
| Peak memory (RSS) | ~13 GB | ~7 GB |
|
|
27
|
+
|
|
28
|
+
*Benchmarked on an Apple M1 Pro with `-p 10`; the memory advantage widens further at larger genome counts.*
|
|
29
|
+
|
|
30
|
+
## Installation with pip
|
|
31
|
+
```
|
|
32
|
+
$ pip install drep
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
### Genome comparison:
|
|
38
|
+
```
|
|
39
|
+
$ dRep compare output_directory -g path/to/genomes/*.fasta
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Genome de-replication:
|
|
43
|
+
```
|
|
44
|
+
$ dRep dereplicate output_directory -g path/to/genomes/*.fasta
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Make sure dependencies are properly installed:
|
|
48
|
+
```
|
|
49
|
+
$ dRep check_dependencies
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Dependencies
|
|
53
|
+
### Near Essential
|
|
54
|
+
* [skani](https://github.com/bluenote-1577/skani) - Makes primary clusters and performs the default secondary comparison (v0.2+ confirmed works)
|
|
55
|
+
* [CheckM](http://ecogenomics.github.io/CheckM/) - Determines contamination and completeness of genomes (v1.0.7 confirmed works). Only needed for `dereplicate`; skip it with `--genomeInfo` or `--ignoreGenomeQuality`
|
|
56
|
+
|
|
57
|
+
### Optional
|
|
58
|
+
|
|
59
|
+
* [Mash](https://genomebiology.biomedcentral.com/articles/10.1186/s13059-016-0997-x>) - Only needed for `--primary_algorithm MASH` (v1.1.1 confirmed works)
|
|
60
|
+
* [MUMmer](http://mummer.sourceforge.net/) - Only needed for the ANIm comparison methods (v3.23 confirmed works)
|
|
61
|
+
* [fastANI](https://github.com/ParBLiSS/FastANI) - An alternative fast secondary clustering algorithm
|
|
62
|
+
* [gANI (aka ANIcalculator)](https://ani.jgi-psf.org/html/download.php?) - Performs gANI comparison method (v1.0 confirmed works)
|
|
63
|
+
* [Prodigal](http://prodigal.ornl.gov/) - Used be both checkM and gANI (v2.6.3 confirmed works)
|
|
64
|
+
* [NSimScan](https://pubmed.ncbi.nlm.nih.gov/27153714/) - Only needed for goANI algorithm (open source version of gANI)
|
drep-4.0.0/drep/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4.0.0
|
|
@@ -44,7 +44,7 @@ def printHelp():
|
|
|
44
44
|
print(' ...::: dRep v' + VERSION + ' :::...''')
|
|
45
45
|
print('''\
|
|
46
46
|
|
|
47
|
-
Matt Olm. MIT License. Banfield Lab, UC Berkeley. 2017 (last updated
|
|
47
|
+
Matt Olm. MIT License. Banfield Lab, UC Berkeley. 2017 (last updated 2026)
|
|
48
48
|
|
|
49
49
|
See https://drep.readthedocs.io/en/latest/index.html for documentation
|
|
50
50
|
Choose one of the operations below for more detailed help.
|
|
@@ -96,8 +96,8 @@ def parse_args(args):
|
|
|
96
96
|
or eukaryotes or things where checkM scoring does not work. Will only \
|
|
97
97
|
choose genomes based on length and N50", action='store_true')
|
|
98
98
|
Iflags.add_argument('--genomeInfo', help='location of .csv file containing quality \
|
|
99
|
-
information on the genomes. Must contain: ["genome"(
|
|
100
|
-
of that genome), "completeness"(0-100 value for completeness of the genome), \
|
|
99
|
+
information on the genomes. Must contain: ["genome"(filename of .fasta file \
|
|
100
|
+
of that genome, including extension e.g. genome.fasta), "completeness"(0-100 value for completeness of the genome), \
|
|
101
101
|
"contamination"(0-100 value of the contamination of the genome)]')
|
|
102
102
|
Iflags.add_argument("--checkM_method", help="Either lineage_wf (more accurate) \
|
|
103
103
|
or taxonomy_wf (faster)", choices={'taxonomy_wf', 'lineage_wf'}, \
|
|
@@ -114,16 +114,43 @@ def parse_args(args):
|
|
|
114
114
|
|
|
115
115
|
Clustflags = cluster_parent.add_argument_group('GENOME COMPARISON OPTIONS')
|
|
116
116
|
Clustflags.add_argument("--S_algorithm", help="R|Algorithm for secondary clustering comaprisons:\n" \
|
|
117
|
+
+ "skani = (DEFAULT) Kmer-based approach; fastest and most accurate.\n" \
|
|
118
|
+
+ " When paired with --primary_algorithm skani, secondary reuses\n" \
|
|
119
|
+
+ " the comparisons already done during primary clustering.\n" \
|
|
117
120
|
+ "fastANI = Kmer-based approach; very fast\n" \
|
|
118
|
-
+ "
|
|
119
|
-
+ "ANImf = (DEFAULT) Align whole genomes with nucmer; filter alignment; compare aligned regions\n" \
|
|
121
|
+
+ "ANImf = Align whole genomes with nucmer; filter alignment; compare aligned regions\n" \
|
|
120
122
|
+ "ANIn = Align whole genomes with nucmer; compare aligned regions\n" \
|
|
121
123
|
+ "gANI = Identify and align ORFs; compare aligned ORFS\n" \
|
|
122
124
|
+ "goANI = Open source version of gANI; requires nsmimscan\n",
|
|
123
|
-
default='
|
|
125
|
+
default='skani', choices={'ANIn', 'gANI', 'ANImf', 'goANI', 'fastANI', 'skani'})
|
|
126
|
+
Clustflags.add_argument("--primary_algorithm", help="R|Program to use for primary clustering.\n" \
|
|
127
|
+
+ "skani = (DEFAULT) skani triangle --sparse. Only above-threshold pairs\n" \
|
|
128
|
+
+ " are produced, so there is no N^2 matrix in RAM or on disk, and\n" \
|
|
129
|
+
+ " a skani --S_algorithm can reuse these comparisons instead of\n" \
|
|
130
|
+
+ " recomputing them.\n" \
|
|
131
|
+
+ "MASH = all-vs-all Mash. Pre-v4 behavior; builds the full N^2 table.",
|
|
132
|
+
default='skani', choices={'MASH', 'skani'})
|
|
133
|
+
Clustflags.add_argument("--no_reuse_primary_comparisons", dest='reuse_primary_comparisons',
|
|
134
|
+
help="Re-run skani during secondary clustering instead of reusing the "
|
|
135
|
+
"comparisons already computed during primary clustering. Only "
|
|
136
|
+
"relevant with --primary_algorithm skani and a skani --S_algorithm, "
|
|
137
|
+
"where the two stages otherwise compute the same ANI values twice. "
|
|
138
|
+
"Reuse is exact, so this is mostly a debugging escape hatch.",
|
|
139
|
+
action='store_false', default=True)
|
|
140
|
+
Clustflags.add_argument("--primary_skani_min_af",
|
|
141
|
+
help="Minimum percent of a genome that must align for a pair to form a "
|
|
142
|
+
"primary-clustering edge (--primary_algorithm skani only). skani's ANI "
|
|
143
|
+
"is measured within aligned regions only, so without this filter genomes "
|
|
144
|
+
"sharing just a small conserved region become edges and single linkage "
|
|
145
|
+
"chains them into one huge cluster. The default reproduces the MASH "
|
|
146
|
+
"partition closely; lower it only if you have very fragmented genomes "
|
|
147
|
+
"and understand the chaining risk.",
|
|
148
|
+
default=15, type=float)
|
|
124
149
|
Clustflags.add_argument("-ms", "--MASH_sketch", help="MASH sketch size", default=1000)
|
|
125
|
-
Clustflags.add_argument("--SkipMash", help="Skip
|
|
126
|
-
|
|
150
|
+
Clustflags.add_argument("--SkipMash", help="Skip primary clustering entirely and run secondary\
|
|
151
|
+
clustering on all genomes at once. (Named for when primary clustering was\
|
|
152
|
+
always MASH; it applies to whichever --primary_algorithm is in use.)",
|
|
153
|
+
action='store_true')
|
|
127
154
|
Clustflags.add_argument("--SkipSecondary", help="Skip secondary clustering, just perform MASH\
|
|
128
155
|
clustering", action='store_true')
|
|
129
156
|
Clustflags.add_argument("--skani_extra",
|
|
@@ -146,10 +173,19 @@ def parse_args(args):
|
|
|
146
173
|
+ "total = 2*(aligned length) / (sum of total genome lengths)\n" \
|
|
147
174
|
+ "larger = max((aligned length / genome 1), (aligned_length / genome2))\n",
|
|
148
175
|
choices=['total', 'larger'], default='larger')
|
|
149
|
-
Compflags.add_argument("--clusterAlg", help="Algorithm used to cluster genomes
|
|
150
|
-
to scipy.cluster.hierarchy.linkage", default='average',
|
|
176
|
+
Compflags.add_argument("--clusterAlg", help="Algorithm used to cluster genomes during SECONDARY\
|
|
177
|
+
clustering (passed to scipy.cluster.hierarchy.linkage)", default='average',
|
|
178
|
+
choices={'single', 'complete', 'average', 'weighted', 'centroid', 'median', 'ward'})
|
|
179
|
+
Compflags.add_argument("--primary_clusterAlg", help="R|Algorithm used to cluster genomes during PRIMARY\n" \
|
|
180
|
+
"(MASH/skani) clustering. The default 'single' is equivalent to connected\n" \
|
|
181
|
+
"components and is computed with a fast, low-memory streaming algorithm that\n" \
|
|
182
|
+
"scales to very large genome sets. Any other choice falls back to the classic\n" \
|
|
183
|
+
"dense scipy path (see --classic_primary_clustering).", default='single',
|
|
151
184
|
choices={'single', 'complete', 'average', 'weighted', 'centroid', 'median', 'ward'})
|
|
152
|
-
Compflags.add_argument("--
|
|
185
|
+
Compflags.add_argument("--classic_primary_clustering", help="Force the classic dense (scipy) primary\
|
|
186
|
+
clustering path instead of the streaming single-linkage algorithm. Uses much more\
|
|
187
|
+
RAM at scale but reproduces pre-v4 behavior and allows non-single linkage methods\
|
|
188
|
+
and the primary dendrogram plot.",
|
|
153
189
|
action='store_true', default=False)
|
|
154
190
|
|
|
155
191
|
GRflags = cluster_parent.add_argument_group('GREEDY CLUSTERING OPTIONS\n'
|
|
@@ -138,6 +138,7 @@ def mash_dendrogram_from_wd(wd, plot_dir=False):
|
|
|
138
138
|
Cdb = wd.get_db('Cdb', return_none=False)
|
|
139
139
|
Pcluster = wd.get_primary_linkage()
|
|
140
140
|
Plinkage = Pcluster['linkage']
|
|
141
|
+
Plinkage_db = Pcluster.get('db')
|
|
141
142
|
clust_args = wd.arguments['cluster']
|
|
142
143
|
PL_thresh = clust_args.get('P_ani', False)
|
|
143
144
|
if PL_thresh != False:
|
|
@@ -150,10 +151,19 @@ def mash_dendrogram_from_wd(wd, plot_dir=False):
|
|
|
150
151
|
logging.error("Skipping plot 1 - cannot generate with multiround_primary_clustering enabled")
|
|
151
152
|
return
|
|
152
153
|
|
|
154
|
+
if Plinkage is None or isinstance(Plinkage, str):
|
|
155
|
+
logging.error("Skipping plot 1 - no primary linkage matrix was computed (too many genomes, or a streaming primary algorithm was used)")
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
# Leaf labels have to come from whatever the linkage was built on. The sparse
|
|
159
|
+
# skani Mdb only holds above-threshold pairs, so a genome with no relatives is
|
|
160
|
+
# absent from it and labels derived from Mdb would not match the linkage.
|
|
161
|
+
names = list(Plinkage_db.columns) if Plinkage_db is not None else None
|
|
162
|
+
|
|
153
163
|
# Make the plot
|
|
154
164
|
logging.info("Plotting primary dendrogram")
|
|
155
165
|
plot_MASH_dendrogram(Mdb, Cdb, Plinkage, threshold = PL_thresh,\
|
|
156
|
-
plot_dir = plot_dir)
|
|
166
|
+
plot_dir = plot_dir, names = names)
|
|
157
167
|
|
|
158
168
|
def plot_secondary_dendrograms_from_wd(wd, plot_dir, **kwargs):
|
|
159
169
|
'''
|
|
@@ -614,7 +624,7 @@ def plot_ANIn_vs_len(Mdb,Ndb,exclude_zero_MASH=True):
|
|
|
614
624
|
CLUSETER PLOTS
|
|
615
625
|
"""
|
|
616
626
|
|
|
617
|
-
def plot_MASH_dendrogram(Mdb, Cdb, linkage, threshold=False, plot_dir=False):
|
|
627
|
+
def plot_MASH_dendrogram(Mdb, Cdb, linkage, threshold=False, plot_dir=False, names=None):
|
|
618
628
|
'''
|
|
619
629
|
Make a dendrogram of the primary clustering
|
|
620
630
|
|
|
@@ -624,6 +634,11 @@ def plot_MASH_dendrogram(Mdb, Cdb, linkage, threshold=False, plot_dir=False):
|
|
|
624
634
|
linkage: Result of scipy.cluster.hierarchy.linkage
|
|
625
635
|
threshold (optional): Line to plot on x-axis
|
|
626
636
|
plot_dir (optional): Location to store plot
|
|
637
|
+
names (optional): Leaf labels, in the order the linkage was built from.
|
|
638
|
+
Required when Mdb does not contain every genome -- the sparse skani
|
|
639
|
+
Mdb only holds above-threshold pairs, so a genome with no relatives
|
|
640
|
+
never appears in it and deriving labels from Mdb would silently
|
|
641
|
+
mismatch the linkage.
|
|
627
642
|
|
|
628
643
|
Returns:
|
|
629
644
|
Makes and shows plot
|
|
@@ -633,8 +648,9 @@ def plot_MASH_dendrogram(Mdb, Cdb, linkage, threshold=False, plot_dir=False):
|
|
|
633
648
|
if Mdb['genome1'].dtype.name == 'category':
|
|
634
649
|
logging.error("WARNING: Primary dendrogram labels may be shuffled! Load as csv to prevent this")
|
|
635
650
|
|
|
636
|
-
|
|
637
|
-
|
|
651
|
+
if names is None:
|
|
652
|
+
db = Mdb.pivot(index="genome1", columns="genome2", values="similarity")
|
|
653
|
+
names = list(db.columns)
|
|
638
654
|
name2cluster = Cdb.set_index('genome')['primary_cluster'].to_dict()
|
|
639
655
|
name2color = gen_color_dictionary(names, name2cluster)
|
|
640
656
|
|
|
@@ -1016,7 +1032,9 @@ def fancy_dendrogram(linkage,names,name2color=False,threshold=False,self_thresh=
|
|
|
1016
1032
|
ax = plt.gca()
|
|
1017
1033
|
xlbls = ax.get_ymajorticklabels()
|
|
1018
1034
|
for lbl in xlbls:
|
|
1019
|
-
|
|
1035
|
+
color = name2color[lbl.get_text()]
|
|
1036
|
+
lbl.set_color('black')
|
|
1037
|
+
lbl.set_bbox(dict(facecolor=color, alpha=0.7, edgecolor='none', pad=2))
|
|
1020
1038
|
|
|
1021
1039
|
# Add the threshold
|
|
1022
1040
|
if threshold:
|
|
@@ -1062,7 +1080,7 @@ def gen_color_list(names,name2cluster):
|
|
|
1062
1080
|
try:
|
|
1063
1081
|
cluster2color[cluster] = cm(1.*int(cluster)/NUM_COLORS)
|
|
1064
1082
|
except:
|
|
1065
|
-
cluster2color[cluster] = cm(1.*
|
|
1083
|
+
cluster2color[cluster] = cm(1.*float(str(cluster).split('_')[1])/NUM_COLORS)
|
|
1066
1084
|
|
|
1067
1085
|
#2. generate list of colors
|
|
1068
1086
|
colors = []
|
|
@@ -1071,6 +1089,32 @@ def gen_color_list(names,name2cluster):
|
|
|
1071
1089
|
|
|
1072
1090
|
return colors
|
|
1073
1091
|
|
|
1092
|
+
# UC Berkeley palette. The point of coloring clusters is to tell neighbouring
|
|
1093
|
+
# ones apart, not to identify a cluster by its color, so a handful of distinct
|
|
1094
|
+
# colors cycled is strictly more readable than giving every cluster its own
|
|
1095
|
+
# barely-distinguishable shade.
|
|
1096
|
+
CLUSTER_COLORS = [
|
|
1097
|
+
'#003262', # Berkeley Blue
|
|
1098
|
+
'#FDB515', # California Gold
|
|
1099
|
+
'#3B7EA1', # Founders Rock
|
|
1100
|
+
]
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
def _cluster_sort_key(cluster):
|
|
1104
|
+
'''
|
|
1105
|
+
Order clusters naturally so that cycling colors lands adjacent clusters on
|
|
1106
|
+
different colors. Handles primary clusters ('2') and secondary clusters
|
|
1107
|
+
('2_10'), sorting numerically where possible: 2_2 before 2_10, not after.
|
|
1108
|
+
'''
|
|
1109
|
+
key = []
|
|
1110
|
+
for part in str(cluster).split('_'):
|
|
1111
|
+
try:
|
|
1112
|
+
key.append((0, float(part), ''))
|
|
1113
|
+
except ValueError:
|
|
1114
|
+
key.append((1, 0.0, part))
|
|
1115
|
+
return key
|
|
1116
|
+
|
|
1117
|
+
|
|
1074
1118
|
def gen_color_dictionary(names, name2cluster):
|
|
1075
1119
|
'''
|
|
1076
1120
|
Make the dictionary name2color
|
|
@@ -1082,27 +1126,14 @@ def gen_color_dictionary(names, name2cluster):
|
|
|
1082
1126
|
Returns:
|
|
1083
1127
|
dict: name -> color
|
|
1084
1128
|
'''
|
|
1085
|
-
#
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
cluster2color = {}
|
|
1092
|
-
clusters = set(name2cluster.values())
|
|
1093
|
-
NUM_COLORS = len(clusters)
|
|
1094
|
-
for cluster in clusters:
|
|
1095
|
-
try:
|
|
1096
|
-
cluster2color[cluster] = cm(1.*int(cluster)/NUM_COLORS)
|
|
1097
|
-
except:
|
|
1098
|
-
cluster2color[cluster] = cm(1.*int(str(cluster).split('_')[1])/NUM_COLORS)
|
|
1099
|
-
|
|
1100
|
-
#2. name to color
|
|
1101
|
-
name2color = {}
|
|
1102
|
-
for name in names:
|
|
1103
|
-
name2color[name] = cluster2color[name2cluster[name]]
|
|
1129
|
+
# Cycle a small palette in cluster order. This is deterministic: the previous
|
|
1130
|
+
# implementation shuffled an unseeded colormap, so the same analysis produced
|
|
1131
|
+
# different colors on every run.
|
|
1132
|
+
clusters = sorted(set(name2cluster.values()), key=_cluster_sort_key)
|
|
1133
|
+
cluster2color = {c: CLUSTER_COLORS[i % len(CLUSTER_COLORS)]
|
|
1134
|
+
for i, c in enumerate(clusters)}
|
|
1104
1135
|
|
|
1105
|
-
return
|
|
1136
|
+
return {name: cluster2color[name2cluster[name]] for name in names}
|
|
1106
1137
|
|
|
1107
1138
|
def _comp_cluster(c):
|
|
1108
1139
|
'''
|
|
@@ -118,9 +118,11 @@ def find_program(dep):
|
|
|
118
118
|
works = False
|
|
119
119
|
if loc != None:
|
|
120
120
|
try:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
result = subprocess.run([loc, '-h'], capture_output=True)
|
|
122
|
+
# Some tools (e.g. older fastANI) exit with code 1 on -h even when working
|
|
123
|
+
if len(result.stdout) > 0 or len(result.stderr) > 0:
|
|
124
|
+
works = True
|
|
125
|
+
except Exception:
|
|
124
126
|
pass
|
|
125
127
|
|
|
126
128
|
return loc, works
|
|
@@ -6,7 +6,6 @@ import numpy as np
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
import scipy.cluster
|
|
8
8
|
from scipy.spatial import distance as ssd
|
|
9
|
-
import networkx as nx
|
|
10
9
|
|
|
11
10
|
import drep.d_cluster.utils
|
|
12
11
|
|
|
@@ -88,41 +87,18 @@ def iteratre_clusters(Bdb, Cdb, id='primary_cluster'):
|
|
|
88
87
|
yield d, cluster
|
|
89
88
|
|
|
90
89
|
|
|
91
|
-
def
|
|
92
|
-
# Filter distances below threshold first
|
|
93
|
-
filtered_edges = db[db['dist'] <= linkage_cutoff]
|
|
94
|
-
|
|
95
|
-
# Create graph directly from filtered edges
|
|
96
|
-
G = nx.Graph()
|
|
97
|
-
G.add_edges_from(filtered_edges[['genome1', 'genome2']].values)
|
|
98
|
-
|
|
99
|
-
# Log that we're using the optimized method
|
|
100
|
-
logging.debug("Using low-RAM optimized clustering method with {0} edges".format(len(filtered_edges)))
|
|
101
|
-
|
|
102
|
-
# Find connected components
|
|
103
|
-
clusters = {}
|
|
104
|
-
for cluster_id, component in enumerate(nx.connected_components(G)):
|
|
105
|
-
for genome in component:
|
|
106
|
-
clusters[genome] = cluster_id + 1
|
|
107
|
-
|
|
108
|
-
# Add isolated nodes (if needed)
|
|
109
|
-
all_genomes = set(db['genome1']).union(set(db['genome2']))
|
|
110
|
-
for genome in all_genomes:
|
|
111
|
-
if genome not in clusters:
|
|
112
|
-
clusters[genome] = len(clusters)
|
|
113
|
-
|
|
114
|
-
# Return clusters and a special value indicating we used the optimized method
|
|
115
|
-
return clusters, "optimized_method_used"
|
|
116
|
-
|
|
117
|
-
def cluster_hierarchical(db, linkage_method= 'single', linkage_cutoff= 0.10, low_ram=False):
|
|
90
|
+
def cluster_hierarchical(db, linkage_method= 'single', linkage_cutoff= 0.10):
|
|
118
91
|
'''
|
|
119
92
|
Perform hierarchical clustering on a symmetrical distiance matrix
|
|
120
93
|
|
|
94
|
+
Note this builds a dense matrix and is O(N^2) in memory. Single-linkage
|
|
95
|
+
primary clustering goes through drep.d_cluster.union_find instead, which is
|
|
96
|
+
equivalent but does not need the matrix.
|
|
97
|
+
|
|
121
98
|
Args:
|
|
122
99
|
db: result of db.pivot usually
|
|
123
100
|
linkage_method: passed to scipy.cluster.hierarchy.fcluster
|
|
124
101
|
linkage_cutoff: distance to draw the clustering line (default = .1)
|
|
125
|
-
low_ram: whether to use the memory-efficient algorithm
|
|
126
102
|
|
|
127
103
|
Returns:
|
|
128
104
|
list: [Cdb, linkage]
|
|
@@ -130,32 +106,21 @@ def cluster_hierarchical(db, linkage_method= 'single', linkage_cutoff= 0.10, low
|
|
|
130
106
|
# Save names
|
|
131
107
|
names = list(db.columns)
|
|
132
108
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
logging.error(names)
|
|
152
|
-
sys.exit()
|
|
153
|
-
linkage = scipy.cluster.hierarchy.linkage(arr, method= linkage_method)
|
|
154
|
-
|
|
155
|
-
# Form clusters
|
|
156
|
-
fclust = scipy.cluster.hierarchy.fcluster(linkage,linkage_cutoff, \
|
|
157
|
-
criterion='distance')
|
|
158
|
-
# Make Cdb
|
|
159
|
-
Cdb = drep.d_cluster.utils._gen_cdb_from_fclust(fclust,names)
|
|
160
|
-
|
|
161
|
-
return Cdb, linkage
|
|
109
|
+
# Generate linkage dataframe
|
|
110
|
+
arr = np.asarray(db)
|
|
111
|
+
try:
|
|
112
|
+
arr = ssd.squareform(arr)
|
|
113
|
+
except:
|
|
114
|
+
logging.error("The database passed in is not symmetrical!")
|
|
115
|
+
logging.error(arr)
|
|
116
|
+
logging.error(names)
|
|
117
|
+
sys.exit()
|
|
118
|
+
linkage = scipy.cluster.hierarchy.linkage(arr, method= linkage_method)
|
|
119
|
+
|
|
120
|
+
# Form clusters
|
|
121
|
+
fclust = scipy.cluster.hierarchy.fcluster(linkage,linkage_cutoff, \
|
|
122
|
+
criterion='distance')
|
|
123
|
+
# Make Cdb
|
|
124
|
+
Cdb = drep.d_cluster.utils._gen_cdb_from_fclust(fclust,names)
|
|
125
|
+
|
|
126
|
+
return Cdb, linkage
|