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.
Files changed (38) hide show
  1. {drep-3.6.2 → drep-4.0.0}/PKG-INFO +2 -1
  2. drep-4.0.0/README.md +64 -0
  3. drep-4.0.0/drep/VERSION +1 -0
  4. {drep-3.6.2 → drep-4.0.0}/drep/argumentParser.py +47 -11
  5. {drep-3.6.2 → drep-4.0.0}/drep/d_analyze.py +57 -26
  6. {drep-3.6.2 → drep-4.0.0}/drep/d_bonus.py +5 -3
  7. {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/cluster_utils.py +23 -58
  8. {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/compare_utils.py +226 -13
  9. {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/controller.py +48 -6
  10. {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/external.py +79 -3
  11. {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/greedy_clustering.py +23 -4
  12. drep-4.0.0/drep/d_cluster/union_find.py +543 -0
  13. {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/utils.py +14 -13
  14. {drep-3.6.2 → drep-4.0.0}/drep/d_evaluate.py +21 -0
  15. {drep-3.6.2 → drep-4.0.0}/drep/d_filter.py +1 -1
  16. {drep-3.6.2 → drep-4.0.0}/drep.egg-info/PKG-INFO +2 -1
  17. {drep-3.6.2 → drep-4.0.0}/drep.egg-info/SOURCES.txt +1 -0
  18. {drep-3.6.2 → drep-4.0.0}/drep.egg-info/requires.txt +1 -0
  19. {drep-3.6.2 → drep-4.0.0}/helper_scripts/ScaffoldLevel_dRep.py +24 -1
  20. {drep-3.6.2 → drep-4.0.0}/setup.py +1 -0
  21. drep-3.6.2/README.md +0 -50
  22. drep-3.6.2/drep/VERSION +0 -1
  23. {drep-3.6.2 → drep-4.0.0}/bin/dRep +0 -0
  24. {drep-3.6.2 → drep-4.0.0}/drep/WorkDirectory.py +0 -0
  25. {drep-3.6.2 → drep-4.0.0}/drep/__init__.py +0 -0
  26. {drep-3.6.2 → drep-4.0.0}/drep/controller.py +0 -0
  27. {drep-3.6.2 → drep-4.0.0}/drep/d_adjust.py +0 -0
  28. {drep-3.6.2 → drep-4.0.0}/drep/d_choose.py +0 -0
  29. {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/__init__.py +0 -0
  30. {drep-3.6.2 → drep-4.0.0}/drep/d_cluster/parsers.py +0 -0
  31. {drep-3.6.2 → drep-4.0.0}/drep/d_workflows.py +0 -0
  32. {drep-3.6.2 → drep-4.0.0}/drep.egg-info/dependency_links.txt +0 -0
  33. {drep-3.6.2 → drep-4.0.0}/drep.egg-info/not-zip-safe +0 -0
  34. {drep-3.6.2 → drep-4.0.0}/drep.egg-info/top_level.txt +0 -0
  35. {drep-3.6.2 → drep-4.0.0}/helper_scripts/parse_stb.py +0 -0
  36. {drep-3.6.2 → drep-4.0.0}/pyproject.toml +0 -0
  37. {drep-3.6.2 → drep-4.0.0}/setup.cfg +0 -0
  38. {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.6.2
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
+ [![Downloads](https://pepy.tech/badge/drep)](https://pepy.tech/project/drep)
4
+ [![Downloads](https://pepy.tech/badge/drep/week)](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)
@@ -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 2025)
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"(basename of .fasta file \
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
- + "skani = Even faster Kmer-based approacht\n" \
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='fastANI', choices={'ANIn', 'gANI', 'ANImf', 'goANI', 'fastANI', 'skani'})
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 MASH clustering,\
126
- just do secondary clustering on all genomes", action='store_true')
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 (passed\
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("--low_ram_primary_clustering", help="Use a memory-efficient algorithm for primary clustering. This only affects primary clustering and not secondary clustering.",
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
- db = Mdb.pivot(index="genome1", columns="genome2", values="similarity")
637
- names = list(db.columns)
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
- lbl.set_color(name2color[lbl.get_text()])
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.*int(str(cluster).split('_')[1])/NUM_COLORS)
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
- #cm = _rand_cmap(len(set(name2cluster.values()))+1,type='bright')
1086
- vals = np.linspace(0,1,len(set(name2cluster.values()))+1)
1087
- np.random.shuffle(vals)
1088
- cm = plt.cm.colors.ListedColormap(plt.cm.jet(vals))
1089
-
1090
- # 1. generate cluster to color
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 name2color
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
- o = subprocess.check_output([loc, '-h'],stderr= subprocess.STDOUT)
122
- works = True
123
- except:
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 cluster_threshold_graph_optimized(db, linkage_cutoff=0.10, linkage_method='single'):
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
- if low_ram:
134
- # Convert to long format for optimized clustering
135
- db_long = db.stack().reset_index()
136
- db_long.columns = ['genome1', 'genome2', 'dist']
137
- clusters, _ = cluster_threshold_graph_optimized(db_long, linkage_cutoff, linkage_method)
138
-
139
- # Convert clusters to Cdb format
140
- Cdb = pd.DataFrame({'genome': list(clusters.keys()),
141
- 'cluster': list(clusters.values())})
142
- return Cdb, _
143
- else:
144
- # Generate linkage dataframe
145
- arr = np.asarray(db)
146
- try:
147
- arr = ssd.squareform(arr)
148
- except:
149
- logging.error("The database passed in is not symmetrical!")
150
- logging.error(arr)
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