drep 3.7.1__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.7.1 → drep-4.0.0}/PKG-INFO +2 -9
- drep-4.0.0/README.md +64 -0
- drep-4.0.0/drep/VERSION +1 -0
- {drep-3.7.1 → drep-4.0.0}/drep/argumentParser.py +44 -8
- {drep-3.7.1 → drep-4.0.0}/drep/d_analyze.py +53 -24
- {drep-3.7.1 → drep-4.0.0}/drep/d_cluster/cluster_utils.py +23 -58
- {drep-3.7.1 → drep-4.0.0}/drep/d_cluster/compare_utils.py +209 -11
- {drep-3.7.1 → drep-4.0.0}/drep/d_cluster/controller.py +37 -5
- {drep-3.7.1 → drep-4.0.0}/drep/d_cluster/external.py +76 -1
- {drep-3.7.1 → 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.7.1 → drep-4.0.0}/drep/d_cluster/utils.py +14 -13
- {drep-3.7.1 → drep-4.0.0}/drep.egg-info/PKG-INFO +2 -9
- {drep-3.7.1 → drep-4.0.0}/drep.egg-info/SOURCES.txt +1 -0
- {drep-3.7.1 → drep-4.0.0}/drep.egg-info/requires.txt +0 -1
- {drep-3.7.1 → drep-4.0.0}/helper_scripts/ScaffoldLevel_dRep.py +24 -1
- {drep-3.7.1 → drep-4.0.0}/setup.py +0 -1
- drep-3.7.1/README.md +0 -50
- drep-3.7.1/drep/VERSION +0 -1
- {drep-3.7.1 → drep-4.0.0}/bin/dRep +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/WorkDirectory.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/__init__.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/controller.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/d_adjust.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/d_bonus.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/d_choose.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/d_cluster/__init__.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/d_cluster/parsers.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/d_evaluate.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/d_filter.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep/d_workflows.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep.egg-info/dependency_links.txt +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep.egg-info/not-zip-safe +0 -0
- {drep-3.7.1 → drep-4.0.0}/drep.egg-info/top_level.txt +0 -0
- {drep-3.7.1 → drep-4.0.0}/helper_scripts/parse_stb.py +0 -0
- {drep-3.7.1 → drep-4.0.0}/pyproject.toml +0 -0
- {drep-3.7.1 → drep-4.0.0}/setup.cfg +0 -0
- {drep-3.7.1 → drep-4.0.0}/tests/test_suite.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
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,12 +13,5 @@ Requires-Dist: matplotlib
|
|
|
13
13
|
Requires-Dist: biopython
|
|
14
14
|
Requires-Dist: scikit-learn
|
|
15
15
|
Requires-Dist: tqdm
|
|
16
|
-
Requires-Dist: networkx
|
|
17
16
|
Requires-Dist: setuptools
|
|
18
17
|
Requires-Dist: pytest
|
|
19
|
-
Dynamic: author
|
|
20
|
-
Dynamic: author-email
|
|
21
|
-
Dynamic: home-page
|
|
22
|
-
Dynamic: license
|
|
23
|
-
Dynamic: requires-dist
|
|
24
|
-
Dynamic: summary
|
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
|
|
@@ -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
|
|
|
@@ -1073,6 +1089,32 @@ def gen_color_list(names,name2cluster):
|
|
|
1073
1089
|
|
|
1074
1090
|
return colors
|
|
1075
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
|
+
|
|
1076
1118
|
def gen_color_dictionary(names, name2cluster):
|
|
1077
1119
|
'''
|
|
1078
1120
|
Make the dictionary name2color
|
|
@@ -1084,27 +1126,14 @@ def gen_color_dictionary(names, name2cluster):
|
|
|
1084
1126
|
Returns:
|
|
1085
1127
|
dict: name -> color
|
|
1086
1128
|
'''
|
|
1087
|
-
#
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
cluster2color = {}
|
|
1094
|
-
clusters = set(name2cluster.values())
|
|
1095
|
-
NUM_COLORS = len(clusters)
|
|
1096
|
-
for cluster in clusters:
|
|
1097
|
-
try:
|
|
1098
|
-
cluster2color[cluster] = cm(1.*int(cluster)/NUM_COLORS)
|
|
1099
|
-
except:
|
|
1100
|
-
cluster2color[cluster] = cm(1.*float(str(cluster).split('_')[1])/NUM_COLORS)
|
|
1101
|
-
|
|
1102
|
-
#2. name to color
|
|
1103
|
-
name2color = {}
|
|
1104
|
-
for name in names:
|
|
1105
|
-
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)}
|
|
1106
1135
|
|
|
1107
|
-
return
|
|
1136
|
+
return {name: cluster2color[name2cluster[name]] for name in names}
|
|
1108
1137
|
|
|
1109
1138
|
def _comp_cluster(c):
|
|
1110
1139
|
'''
|
|
@@ -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
|