drep 3.4.5__tar.gz → 3.6.1__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 (36) hide show
  1. {drep-3.4.5 → drep-3.6.1}/PKG-INFO +9 -1
  2. {drep-3.4.5 → drep-3.6.1}/README.md +1 -1
  3. drep-3.6.1/drep/VERSION +1 -0
  4. {drep-3.4.5 → drep-3.6.1}/drep/__init__.py +1 -1
  5. {drep-3.4.5 → drep-3.6.1}/drep/argumentParser.py +8 -2
  6. {drep-3.4.5 → drep-3.6.1}/drep/d_analyze.py +10 -10
  7. {drep-3.4.5 → drep-3.6.1}/drep/d_bonus.py +1 -1
  8. {drep-3.4.5 → drep-3.6.1}/drep/d_cluster/cluster_utils.py +58 -19
  9. {drep-3.4.5 → drep-3.6.1}/drep/d_cluster/compare_utils.py +10 -1
  10. {drep-3.4.5 → drep-3.6.1}/drep/d_cluster/external.py +104 -0
  11. {drep-3.4.5 → drep-3.6.1}/drep/d_cluster/utils.py +3 -1
  12. {drep-3.4.5 → drep-3.6.1}/drep/d_filter.py +38 -24
  13. {drep-3.4.5 → drep-3.6.1}/drep.egg-info/PKG-INFO +9 -1
  14. {drep-3.4.5 → drep-3.6.1}/drep.egg-info/SOURCES.txt +3 -1
  15. {drep-3.4.5 → drep-3.6.1}/helper_scripts/ScaffoldLevel_dRep.py +106 -45
  16. {drep-3.4.5 → drep-3.6.1}/helper_scripts/parse_stb.py +48 -39
  17. drep-3.6.1/pyproject.toml +3 -0
  18. drep-3.6.1/tests/test_suite.py +30 -0
  19. drep-3.4.5/drep/VERSION +0 -1
  20. {drep-3.4.5 → drep-3.6.1}/bin/dRep +0 -0
  21. {drep-3.4.5 → drep-3.6.1}/drep/WorkDirectory.py +0 -0
  22. {drep-3.4.5 → drep-3.6.1}/drep/controller.py +0 -0
  23. {drep-3.4.5 → drep-3.6.1}/drep/d_adjust.py +0 -0
  24. {drep-3.4.5 → drep-3.6.1}/drep/d_choose.py +0 -0
  25. {drep-3.4.5 → drep-3.6.1}/drep/d_cluster/__init__.py +0 -0
  26. {drep-3.4.5 → drep-3.6.1}/drep/d_cluster/controller.py +0 -0
  27. {drep-3.4.5 → drep-3.6.1}/drep/d_cluster/greedy_clustering.py +0 -0
  28. {drep-3.4.5 → drep-3.6.1}/drep/d_cluster/parsers.py +0 -0
  29. {drep-3.4.5 → drep-3.6.1}/drep/d_evaluate.py +0 -0
  30. {drep-3.4.5 → drep-3.6.1}/drep/d_workflows.py +0 -0
  31. {drep-3.4.5 → drep-3.6.1}/drep.egg-info/dependency_links.txt +0 -0
  32. {drep-3.4.5 → drep-3.6.1}/drep.egg-info/not-zip-safe +0 -0
  33. {drep-3.4.5 → drep-3.6.1}/drep.egg-info/requires.txt +0 -0
  34. {drep-3.4.5 → drep-3.6.1}/drep.egg-info/top_level.txt +0 -0
  35. {drep-3.4.5 → drep-3.6.1}/setup.cfg +0 -0
  36. {drep-3.4.5 → drep-3.6.1}/setup.py +0 -0
@@ -1,8 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: drep
3
- Version: 3.4.5
3
+ Version: 3.6.1
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
7
7
  Author-email: mattolm@berkeley.edu
8
8
  License: MIT
9
+ Requires-Dist: numpy
10
+ Requires-Dist: pandas
11
+ Requires-Dist: seaborn
12
+ Requires-Dist: matplotlib
13
+ Requires-Dist: biopython
14
+ Requires-Dist: scikit-learn
15
+ Requires-Dist: tqdm
16
+ Requires-Dist: pytest
@@ -9,7 +9,7 @@ Manual, installation instructions, and API are at available at
9
9
  [ReadTheDocs](https://drep.readthedocs.io/en/latest/)
10
10
 
11
11
  Publication is available at
12
- [ISMEJ](http://www.nature.com/ismej/journal/vaop/ncurrent/full/ismej2017126a.html)
12
+ [ISMEJ](https://doi.org/10.1038/ismej.2017.126)
13
13
 
14
14
  Open source pre-print publication is available at
15
15
  [bioRxiv](https://doi.org/10.1101/108142)
@@ -0,0 +1 @@
1
+ 3.6.1
@@ -2,7 +2,7 @@
2
2
 
3
3
  from subprocess import call
4
4
  import os
5
- from Bio import SeqIO
5
+ # from Bio import SeqIO
6
6
  import shutil
7
7
  import multiprocessing
8
8
  import multiprocessing.dummy
@@ -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 2023)
47
+ Matt Olm. MIT License. Banfield Lab, UC Berkeley. 2017 (last updated 2025)
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.
@@ -115,16 +115,20 @@ def parse_args(args):
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
117
  + "fastANI = Kmer-based approach; very fast\n" \
118
+ + "skani = Even faster Kmer-based approacht\n" \
118
119
  + "ANImf = (DEFAULT) Align whole genomes with nucmer; filter alignment; compare aligned regions\n" \
119
120
  + "ANIn = Align whole genomes with nucmer; compare aligned regions\n" \
120
121
  + "gANI = Identify and align ORFs; compare aligned ORFS\n" \
121
122
  + "goANI = Open source version of gANI; requires nsmimscan\n",
122
- default='fastANI', choices={'ANIn', 'gANI', 'ANImf', 'goANI', 'fastANI'})
123
+ default='fastANI', choices={'ANIn', 'gANI', 'ANImf', 'goANI', 'fastANI', 'skani'})
123
124
  Clustflags.add_argument("-ms", "--MASH_sketch", help="MASH sketch size", default=1000)
124
125
  Clustflags.add_argument("--SkipMash", help="Skip MASH clustering,\
125
126
  just do secondary clustering on all genomes", action='store_true')
126
127
  Clustflags.add_argument("--SkipSecondary", help="Skip secondary clustering, just perform MASH\
127
128
  clustering", action='store_true')
129
+ Clustflags.add_argument("--skani_extra",
130
+ help="Extra arguments to pass to skani triangle",
131
+ default="", type=str)
128
132
  Clustflags.add_argument("--n_PRESET", help="R|Presets to pass to nucmer\n" \
129
133
  + "tight = only align highly conserved regions\n" \
130
134
  + "normal = default ANIn parameters", choices=['normal', 'tight'],
@@ -145,6 +149,8 @@ def parse_args(args):
145
149
  Compflags.add_argument("--clusterAlg", help="Algorithm used to cluster genomes (passed\
146
150
  to scipy.cluster.hierarchy.linkage", default='average',
147
151
  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.",
153
+ action='store_true', default=False)
148
154
 
149
155
  GRflags = cluster_parent.add_argument_group('GREEDY CLUSTERING OPTIONS\n'
150
156
  'These decrease RAM use and runtime at the expense of a minor loss in '
@@ -728,11 +728,11 @@ def plot_winner_scoring_complex(Wdb, Sdb, Cdb, Gdb, plot_dir= False, **kwargs):
728
728
  for variable in x['variable'].unique():
729
729
  vals += [v for v in x['value'][x['variable'] == variable].tolist()]
730
730
 
731
- # Add un-normalized values to barplots
732
- i = 0
733
- for p in g.patches:
734
- g.annotate("{0:.1f}".format(vals[i]), (p.get_width(), p.get_y()+(p.get_height()/1.1) ), fontsize=8)
735
- i += 1
731
+ # # Add un-normalized values to barplots
732
+ # i = 0
733
+ # for p in g.patches:
734
+ # g.annotate("{0:.1f}".format(vals[i]), (p.get_width(), p.get_y()+(p.get_height()/1.1) ), fontsize=8)
735
+ # i += 1
736
736
 
737
737
  plt.title('Scoring of cluster {0}'.format(cluster))
738
738
  plt.xlabel('Normalized Score')
@@ -1378,11 +1378,11 @@ def _make_scoring_plot(db, bars,**kwargs):
1378
1378
  for variable in x['variable'].unique():
1379
1379
  vals += [v for v in x['value'][x['variable'] == variable].tolist()]
1380
1380
 
1381
- # Add un-normalized values to barplots
1382
- i = 0
1383
- for p in g.patches:
1384
- g.annotate("{0:.1f}".format(vals[i]), (p.get_width(), p.get_y()+(p.get_height()/1.1) ), fontsize=8)
1385
- i += 1
1381
+ # # Add un-normalized values to barplots
1382
+ # i = 0
1383
+ # for p in g.patches:
1384
+ # g.annotate("{0:.1f}".format(vals[i]), (p.get_width(), p.get_y()+(p.get_height()/1.1) ), fontsize=8)
1385
+ # i += 1
1386
1386
 
1387
1387
  # Add taxonomy if available
1388
1388
  axes = plt.gca()
@@ -98,7 +98,7 @@ def check_dependencies(print_out=False):
98
98
  For all possible dependencies, see if you can find them
99
99
  '''
100
100
  for dep in ['mash', 'nucmer', 'checkm', 'ANIcalculator', 'prodigal', 'centrifuge',
101
- 'nsimscan', 'fastANI']:
101
+ 'nsimscan', 'fastANI', 'skani']:
102
102
  loc, works = find_program(dep)
103
103
  works_message = {True:'all good', False:'!!! ERROR !!!'}[works]
104
104
  message = '{0:.<40} {1:15} (location = {2})'.format(dep, works_message, loc)
@@ -6,6 +6,7 @@ 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
9
10
 
10
11
  import drep.d_cluster.utils
11
12
 
@@ -87,7 +88,33 @@ def iteratre_clusters(Bdb, Cdb, id='primary_cluster'):
87
88
  yield d, cluster
88
89
 
89
90
 
90
- def cluster_hierarchical(db, linkage_method= 'single', linkage_cutoff= 0.10):
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):
91
118
  '''
92
119
  Perform hierarchical clustering on a symmetrical distiance matrix
93
120
 
@@ -95,6 +122,7 @@ def cluster_hierarchical(db, linkage_method= 'single', linkage_cutoff= 0.10):
95
122
  db: result of db.pivot usually
96
123
  linkage_method: passed to scipy.cluster.hierarchy.fcluster
97
124
  linkage_cutoff: distance to draw the clustering line (default = .1)
125
+ low_ram: whether to use the memory-efficient algorithm
98
126
 
99
127
  Returns:
100
128
  list: [Cdb, linkage]
@@ -102,21 +130,32 @@ def cluster_hierarchical(db, linkage_method= 'single', linkage_cutoff= 0.10):
102
130
  # Save names
103
131
  names = list(db.columns)
104
132
 
105
- # Generate linkage dataframe
106
- arr = np.asarray(db)
107
- try:
108
- arr = ssd.squareform(arr)
109
- except:
110
- logging.error("The database passed in is not symmetrical!")
111
- logging.error(arr)
112
- logging.error(names)
113
- sys.exit()
114
- linkage = scipy.cluster.hierarchy.linkage(arr, method= linkage_method)
115
-
116
- # Form clusters
117
- fclust = scipy.cluster.hierarchy.fcluster(linkage,linkage_cutoff, \
118
- criterion='distance')
119
- # Make Cdb
120
- Cdb = drep.d_cluster.utils._gen_cdb_from_fclust(fclust,names)
121
-
122
- return Cdb, linkage
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
@@ -264,6 +264,7 @@ def cluster_mash_database(db, **kwargs):
264
264
  Keyword arguments:
265
265
  clusterAlg: how to cluster database (default = single)
266
266
  P_ani: threshold to cluster at (default = 0.9)
267
+ low_ram_primary_clustering: whether to use memory-efficient algorithm
267
268
 
268
269
  Returns:
269
270
  list: [Cdb, [linkage, linkage_db, arguments]]
@@ -273,12 +274,13 @@ def cluster_mash_database(db, **kwargs):
273
274
  # Load key words
274
275
  P_Lmethod = kwargs.get('clusterAlg','single')
275
276
  P_Lcutoff = 1 - kwargs.get('P_ani',.9)
277
+ low_ram = kwargs.get('low_ram_primary_clustering', False)
276
278
 
277
279
  # Do the actual clustering
278
280
  db['dist'] = 1 - db['similarity']
279
281
  linkage_db = db.pivot(index="genome1", columns="genome2", values="dist")
280
282
  Cdb, linkage = drep.d_cluster.cluster_utils.cluster_hierarchical(linkage_db, linkage_method= P_Lmethod, \
281
- linkage_cutoff= P_Lcutoff)
283
+ linkage_cutoff= P_Lcutoff, low_ram=low_ram)
282
284
  Cdb = Cdb.rename(columns={'cluster':'primary_cluster'})
283
285
  Cdb['primary_cluster'] = Cdb['primary_cluster'].astype(int)
284
286
 
@@ -290,6 +292,7 @@ def cluster_mash_database(db, **kwargs):
290
292
  return Cdb, cluster_ret
291
293
 
292
294
  def secondary_clustering(Bdb, Cdb, algorithm, data_folder, **kwargs):
295
+
293
296
  if kwargs.get('greedy_secondary_clustering', False) != True:
294
297
  Ndb = pd.DataFrame()
295
298
  for bdb, name in iteratre_clusters(Bdb, Cdb, id='primary_cluster'):
@@ -368,6 +371,12 @@ def compare_genomes(bdb, algorithm, data_folder, **kwargs):
368
371
  df = drep.d_cluster.external.run_pairwise_fastANI(genome_list, working_data_folder, **kwargs)
369
372
  return df
370
373
 
374
+ elif algorithm == 'skani':
375
+ genome_list = bdb['location'].tolist()
376
+ working_data_folder = os.path.join(data_folder, 'skani_files/')
377
+ df = drep.d_cluster.external.run_pairwise_skani(genome_list, working_data_folder, **kwargs)
378
+ return df
379
+
371
380
  elif algorithm == 'gANI':
372
381
  # Figure out prodigal folder
373
382
  wd = kwargs.get('wd', False)
@@ -115,6 +115,88 @@ def run_pairwise_fastANI(genome_list, outdir, **kwargs):
115
115
  logging.error("CRITICAL ERROR WITH SECONDARY CLUSTERING CODE {0}; SKIPPING".format(code))
116
116
  return pd.DataFrame()
117
117
 
118
+ def run_pairwise_skani(genome_list, outdir, **kwargs):
119
+ code = drep.d_cluster.utils._randomString(stringLength=10)
120
+ extra_cmd = kwargs.get('skani_extra', "")
121
+
122
+ # Make folders
123
+ if not os.path.exists(outdir):
124
+ os.makedirs(outdir)
125
+ tmp_dir = os.path.join(outdir, 'tmp/')
126
+ if not os.path.exists(tmp_dir):
127
+ os.makedirs(tmp_dir)
128
+
129
+ # Make genome list
130
+ glist = os.path.join(tmp_dir, 'genomeList')
131
+ glist = _make_glist(genome_list, glist)
132
+
133
+ # Gen command
134
+ exe_loc = drep.get_exe('skani')
135
+ out_base = os.path.join(outdir, 'skani_out_{0}'.format(code))
136
+ #cmd = [exe_loc, '--ql', glist, '--rl', glist, '-o', out_base, '--matrix', '-t', str(p), "--minFraction", str(0)]
137
+ cmd = [exe_loc, "triangle", "-t", "1", '-o', out_base, '--full-matrix', '-l', glist, '--detailed', '-s', "1", '--min-af', "0"]
138
+ if extra_cmd != "":
139
+ cmd += extra_cmd.split(' ')
140
+
141
+ logging.debug(' '.join(cmd) + ' ' + code)
142
+
143
+ # Run command
144
+ if ('wd' in kwargs) and (kwargs.get('debug', False)):
145
+ logdir = kwargs.get('wd').get_dir('cmd_logs')
146
+ else:
147
+ logdir = False
148
+ drep.thread_cmds([cmd], shell=False, logdir=logdir, t=1)
149
+
150
+ # Load results
151
+ fdb = load_skani(out_base)
152
+
153
+ return fdb
154
+
155
+ def load_triangle_matrix(file_path):
156
+ with open(file_path, 'r') as f:
157
+ lines = f.readlines()
158
+
159
+ # Get the number of genomes
160
+ num_genomes = int(lines[0].strip())
161
+
162
+ # Initialize lists to store data
163
+ reference = []
164
+ query = []
165
+ ani = []
166
+
167
+ # Parse the matrix
168
+ for i in range(1, num_genomes + 1):
169
+ row = lines[i].strip().split('\t')
170
+ ref_genome = row[0]
171
+ for j in range(1, i + 1):
172
+ query_genome = lines[j].strip().split('\t')[0]
173
+ ani_value = float(row[j])
174
+ reference.append(ref_genome)
175
+ query.append(query_genome)
176
+ ani.append(ani_value)
177
+
178
+ # Create a DataFrame
179
+ df = pd.DataFrame({'reference': reference, 'querry': query, 'ani': ani})
180
+ return df
181
+
182
+ def load_matrix_to_dataframe(file_path):
183
+ with open(file_path, 'r') as file:
184
+ lines = file.readlines()
185
+
186
+ num_genomes = int(lines[0].strip())
187
+ genomes = [line.split('\t')[0] for line in lines[1:num_genomes + 1]]
188
+
189
+ data = []
190
+ for i in range(num_genomes):
191
+ for j in range(num_genomes):
192
+ genome1 = genomes[i]
193
+ genome2 = genomes[j]
194
+ ani = float(lines[i + 1].split('\t')[j + 1])
195
+ data.append([genome1, genome2, ani])
196
+
197
+ df = pd.DataFrame(data, columns=['reference', 'querry', 'ani'])
198
+ return df
199
+
118
200
  def fastani_one_vs_many(one, many, genome_rep_file, outdir, **kwargs):
119
201
  p = kwargs.get('processors', 6)
120
202
  code = drep.d_cluster.utils._randomString(stringLength=10)
@@ -147,6 +229,28 @@ def load_fastani(file):
147
229
 
148
230
  return fdb
149
231
 
232
+ def load_skani(file):
233
+ # Load the ani triangle
234
+ adb = load_triangle_matrix(file)
235
+ adb['ani'] = adb['ani'] / 100
236
+
237
+ # Make symnetrical
238
+ db = adb.rename(columns={'reference':'querry', 'querry':'reference'})
239
+ db = db[db['reference'] != db['querry']]
240
+ adb = pd.concat([adb, db], ignore_index=True).reset_index(drop=True)
241
+
242
+ # Load the af triangle
243
+ tdb = load_matrix_to_dataframe(file + '.af').rename(columns={'ani':'alignment_coverage'})
244
+
245
+ # Merge
246
+ assert len(adb) == len(tdb)
247
+ fdb = pd.merge(adb, tdb, how='inner')
248
+
249
+ # parse
250
+ for c in ['reference', 'querry']:
251
+ fdb[c] = [drep.d_cluster.utils._get_genome_name_from_fasta(x) for x in fdb[c]]
252
+
253
+ return fdb
150
254
 
151
255
  def _fix_fastani(odb):
152
256
  # Add back missing genomes
@@ -103,6 +103,8 @@ def estimate_time(comps, alg):
103
103
  time = comps * .5
104
104
  elif alg == 'fastANI':
105
105
  time = comps * 0.00667
106
+ elif alg == 'skani':
107
+ time = comps * 0.00667
106
108
  return time
107
109
 
108
110
 
@@ -352,7 +354,7 @@ def gen_goANI_cmd(file, g1, g2, exe):
352
354
  return []
353
355
 
354
356
  else:
355
- cmd = [exe,'--om','TABX', g1, g2, file]
357
+ cmd = [exe,'--om','TABX', '--rpq', '1', g1, g2, file]
356
358
  return cmd
357
359
 
358
360
  def process_deltafiles(deltafiles, org_lengths, logger=None, **kwargs):
@@ -11,9 +11,6 @@ import gzip
11
11
  import pandas as pd
12
12
  import os
13
13
  import sys
14
- import shutil
15
- import multiprocessing
16
- from Bio import SeqIO
17
14
 
18
15
  import drep
19
16
  import drep.WorkDirectory
@@ -449,32 +446,43 @@ def calc_n50(loc):
449
446
  Calculate the N50 of a .fasta file
450
447
 
451
448
  Args:
452
- fasta_loc: location of .fasta file.
449
+ loc: location of .fasta file.
453
450
 
454
451
  Returns:
455
452
  int: N50 of .fasta file.
456
453
  '''
457
454
  lengths = []
455
+ is_header = True
458
456
 
459
-
460
- if loc[-3:] == '.gz':
461
- with gzip.open(loc, "rt") as handle:
462
- for seq_record in SeqIO.parse(handle, "fasta"):
463
- lengths.append(len(seq_record))
457
+ if loc.endswith('.gz'):
458
+ open_func = gzip.open
459
+ mode = 'rt'
464
460
  else:
465
- for seq_record in SeqIO.parse(loc, "fasta"):
466
- lengths.append(len(seq_record))
467
-
468
- # for seq_record in SeqIO.parse(loc, "fasta"):
469
- # lengths.append(len(seq_record))
470
-
471
- half = sum(lengths)/2
461
+ open_func = open
462
+ mode = 'r'
463
+
464
+ with open_func(loc, mode) as handle:
465
+ seq_length = 0
466
+ for line in handle:
467
+ if line.startswith('>'):
468
+ if not is_header:
469
+ lengths.append(seq_length)
470
+ seq_length = 0
471
+ is_header = True
472
+ else:
473
+ seq_length += len(line.strip())
474
+ is_header = False
475
+ if not is_header:
476
+ lengths.append(seq_length)
477
+
478
+ half = sum(lengths) / 2
472
479
  tally = 0
473
480
  for l in sorted(lengths, reverse=True):
474
481
  tally += l
475
482
  if tally > half:
476
483
  return l
477
484
 
485
+
478
486
  def run_prodigal(genome_list, out_dir, **kwargs):
479
487
  '''
480
488
  Run prodigal on a set of genomes, store the output in the out_dir
@@ -647,23 +655,29 @@ def _iterate_checkm_groups(genome_folder_whole, checkm_outf_whole, checkm_group_
647
655
 
648
656
  def calc_fasta_length(fasta_loc):
649
657
  '''
650
- Calculate the length of the .fasta file and retun length
658
+ Calculate the length of the .fasta file and return length
651
659
 
652
660
  Args:
653
661
  fasta_loc: location of .fasta file
654
662
 
655
663
  Returns:
656
- int: total length of all .fasta files
664
+ int: total length of all sequences in the .fasta file
657
665
  '''
658
666
  total = 0
659
667
 
660
- if fasta_loc[-3:] == '.gz':
661
- with gzip.open(fasta_loc, "rt") as handle:
662
- for seq_record in SeqIO.parse(handle, "fasta"):
663
- total += len(seq_record)
668
+ if fasta_loc.endswith('.gz'):
669
+ open_func = gzip.open
670
+ mode = 'rt'
664
671
  else:
665
- for seq_record in SeqIO.parse(fasta_loc, "fasta"):
666
- total += len(seq_record)
672
+ open_func = open
673
+ mode = 'r'
674
+
675
+ with open_func(fasta_loc, mode) as handle:
676
+ for line in handle:
677
+ if line.startswith('>'):
678
+ pass
679
+ else:
680
+ total += len(line.strip())
667
681
 
668
682
  return total
669
683
 
@@ -1,8 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: drep
3
- Version: 3.4.5
3
+ Version: 3.6.1
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
7
7
  Author-email: mattolm@berkeley.edu
8
8
  License: MIT
9
+ Requires-Dist: numpy
10
+ Requires-Dist: pandas
11
+ Requires-Dist: seaborn
12
+ Requires-Dist: matplotlib
13
+ Requires-Dist: biopython
14
+ Requires-Dist: scikit-learn
15
+ Requires-Dist: tqdm
16
+ Requires-Dist: pytest
@@ -1,4 +1,5 @@
1
1
  README.md
2
+ pyproject.toml
2
3
  setup.py
3
4
  bin/dRep
4
5
  drep/VERSION
@@ -28,4 +29,5 @@ drep/d_cluster/greedy_clustering.py
28
29
  drep/d_cluster/parsers.py
29
30
  drep/d_cluster/utils.py
30
31
  helper_scripts/ScaffoldLevel_dRep.py
31
- helper_scripts/parse_stb.py
32
+ helper_scripts/parse_stb.py
33
+ tests/test_suite.py
@@ -8,7 +8,6 @@
8
8
 
9
9
 
10
10
  import os
11
- import sys
12
11
  import gzip
13
12
  import glob
14
13
  import textwrap
@@ -18,15 +17,13 @@ import argparse
18
17
  import pandas as pd
19
18
 
20
19
  from shutil import copyfile
21
- import subprocess
22
20
  from subprocess import call
23
21
  from collections import defaultdict
24
22
 
25
23
  from tqdm import tqdm
26
24
 
27
- from Bio import SeqIO
28
-
29
25
  import drep
26
+ import drep.d_filter
30
27
 
31
28
  def version():
32
29
  versionFile = open(os.path.join(drep.__path__[0], 'VERSION'))
@@ -128,8 +125,8 @@ class ANImCommand(abstractCommand):
128
125
 
129
126
  if method == 'simple':
130
127
  # Get the organism lengths
131
- qlen = fasta_length(self.config['querry'])
132
- rlen = fasta_length(self.config['reference'])
128
+ qlen = drep.d_filter.calc_fasta_length(self.config['querry'])
129
+ rlen = drep.d_filter.calc_fasta_length(self.config['reference'])
133
130
 
134
131
  # Parse the reults
135
132
  delta = get_delta_loc(self.config['prefix'])
@@ -156,7 +153,6 @@ class ANImCommand(abstractCommand):
156
153
 
157
154
  def __str__(self):
158
155
  ''' Show the command parameters '''
159
- return pprint.pformat(self.config)
160
156
 
161
157
  def gen_mummer_cmd(**kwargs):
162
158
  '''
@@ -660,16 +656,27 @@ def load_scaffold2length(fasta):
660
656
  Return a database of scaffold to length
661
657
  '''
662
658
  table = defaultdict(list)
659
+ is_header = True
663
660
 
664
- if fasta[-3:] == '.gz':
665
- with gzip.open(fasta, "rt") as handle:
666
- for seq_record in SeqIO.parse(handle, "fasta"):
667
- table['scaffold'].append(seq_record.id)
668
- table['length'].append(len(seq_record))
661
+ if fasta.endswith('.gz'):
662
+ open_func = gzip.open
663
+ mode = 'rt'
669
664
  else:
670
- for seq_record in SeqIO.parse(str(fasta), "fasta"):
671
- table['scaffold'].append(seq_record.id)
672
- table['length'].append(len(seq_record))
665
+ open_func = open
666
+ mode = 'r'
667
+
668
+ with open_func(fasta, mode) as handle:
669
+ for line in handle:
670
+ if line.startswith('>'):
671
+ is_header = True
672
+ scaffold_id = line[1:].strip().split(' ')[0]
673
+ elif is_header:
674
+ table['scaffold'].append(scaffold_id)
675
+ table['length'].append(0)
676
+ is_header = False
677
+ else:
678
+ table['length'][-1] += len(line.strip())
679
+
673
680
  return pd.DataFrame(table)
674
681
 
675
682
  def add_cov_ani(Ddb, s2l):
@@ -712,28 +719,46 @@ def split_into_chunks(file, max_len, temp_folder):
712
719
  chunks = []
713
720
 
714
721
  chunk_number = 0
715
- records = []
716
722
  chunk_len = 0
717
- for r in SeqIO.parse(file, "fasta"):
718
- records.append(r)
719
- chunk_len += len(r)
723
+ chunk_records = []
724
+ record_id = ''
725
+ record_seq = ''
726
+
727
+ with open(file, 'r') as handle:
728
+ for line in handle:
729
+ if line.startswith('>'):
730
+ if chunk_len + len(record_seq) > max_len and len(chunk_records) > 0:
731
+ # Save this chunk
732
+ new_loc = newfile_base + '_CHUNK_{0}.fasta'.format(chunk_number)
733
+ with open(new_loc, 'w') as chunk_file:
734
+ chunk_file.write(''.join(chunk_records))
735
+ chunks.append(new_loc)
736
+
737
+ chunk_len = 0
738
+ chunk_number += 1
739
+ chunk_records = []
740
+
741
+ if record_id:
742
+ chunk_records.append('>' + record_id + '\n' + record_seq + '\n')
743
+ chunk_len += len(record_seq)
744
+
745
+ record_id = line[1:].strip().split(' ')[0]
746
+ record_seq = ''
747
+ else:
748
+ record_seq += line.strip()
749
+
750
+ # Add the last record
751
+ if record_id:
752
+ chunk_records.append('>' + record_id + '\n' + record_seq + '\n')
753
+ chunk_len += len(record_seq)
720
754
 
721
- if chunk_len > max_len:
722
- # Save this chunk
755
+ # Save the final chunk
756
+ if len(chunk_records) > 0:
723
757
  new_loc = newfile_base + '_CHUNK_{0}.fasta'.format(chunk_number)
724
- SeqIO.write(records, new_loc, "fasta")
758
+ with open(new_loc, 'w') as chunk_file:
759
+ chunk_file.write(''.join(chunk_records))
725
760
  chunks.append(new_loc)
726
761
 
727
- chunk_len = 0
728
- chunk_number += 1
729
- records = []
730
-
731
- # Save the final chunk
732
- if len(records) > 0:
733
- new_loc = newfile_base + '_CHUNK_{0}.fasta'.format(chunk_number)
734
- SeqIO.write(records, new_loc, "fasta")
735
- chunks.append(new_loc)
736
-
737
762
  return chunks
738
763
 
739
764
  def compare_wrapper(current, new, scaffDb, temp_folder, **kwargs):
@@ -875,23 +900,38 @@ def reconsile(Sdb, current, new, newFile, temp_folder, **kwargs):
875
900
 
876
901
  return Rdb
877
902
 
878
-
879
903
  def write_new(ori_loc, new_loc, to_rm, verbose=False):
880
904
  '''
881
905
  Write a new .fasta file removing certain scaffolds
882
906
  '''
883
- records = []
884
- if ori_loc[-3:] == '.gz':
885
- with gzip.open(ori_loc, "rt") as handle:
886
- for r in SeqIO.parse(handle, "fasta"):
887
- records.append(r)
907
+ if ori_loc.endswith('.gz'):
908
+ open_func = gzip.open
909
+ mode = 'rt'
888
910
  else:
889
- for r in SeqIO.parse(ori_loc, "fasta"):
890
- records.append(r)
911
+ open_func = open
912
+ mode = 'r'
913
+
914
+ ori_len = 0
915
+ count = 0
916
+ with open_func(ori_loc, mode) as handle, open(new_loc, 'w') as out_handle:
917
+ record_id = ''
918
+ record_seq = ''
919
+ for line in handle:
920
+ if line.startswith('>'):
921
+ ori_len += 1
922
+ if record_id and record_id not in to_rm:
923
+ out_handle.write('>' + record_id + '\n' + record_seq + '\n')
924
+ count += 1
925
+ record_id = line[1:].strip().split(' ')[0]
926
+ record_seq = ''
927
+ else:
928
+ record_seq += line.strip()
929
+
930
+ # Write the last record
931
+ if record_id and record_id not in to_rm:
932
+ out_handle.write('>' + record_id + '\n' + record_seq + '\n')
933
+ count += 1
891
934
 
892
- ori_len = len(records)
893
- records = (r for r in records if r.id not in to_rm)
894
- count = SeqIO.write(records, new_loc, "fasta")
895
935
  if verbose:
896
936
  print("Removed {0} scaffolds from {1}".format(ori_len - count, ori_loc))
897
937
 
@@ -957,9 +997,30 @@ class test_dRepScaffolds():
957
997
  to_rm = ['N5_271_010G1_scaffold_119']
958
998
  new_loc = self.test_dir + 'new_file.fasta'
959
999
 
960
- ori_len = len([r for r in SeqIO.parse(self.fastas[0], "fasta")])
1000
+ def count_fasta_records(fasta_loc):
1001
+ count = 0
1002
+ record_flag = False
1003
+ if fasta_loc.endswith('.gz'):
1004
+ open_func = gzip.open
1005
+ mode = 'rt'
1006
+ else:
1007
+ open_func = open
1008
+ mode = 'r'
1009
+
1010
+ with open_func(fasta_loc, mode) as handle:
1011
+ for line in handle:
1012
+ if line.startswith('>'):
1013
+ count += 1
1014
+ record_flag = True
1015
+ elif not record_flag:
1016
+ # In case the file starts with sequences without headers
1017
+ count += 1
1018
+ record_flag = True
1019
+ return count
1020
+
1021
+ ori_len = count_fasta_records(self.fastas[0])
961
1022
  write_new(self.fastas[0], new_loc, to_rm, verbose=False)
962
- new_len = len([r for r in SeqIO.parse(new_loc, "fasta")])
1023
+ new_len = count_fasta_records(new_loc)
963
1024
 
964
1025
  assert new_len + 1 == ori_len
965
1026
 
@@ -3,50 +3,55 @@
3
3
  import os
4
4
  import sys
5
5
  import gzip
6
- import logging
7
6
  import textwrap
8
7
  import argparse
9
8
 
10
- from Bio import SeqIO
11
-
12
9
  def extract_bins(fasta, stb_file, out_base):
13
10
  if (out_base != '') & (out_base[-1] != '/'):
14
11
  out_base += '_'
15
12
 
16
13
  # Make scaffold to bin dictionary
17
14
  stb = {}
18
- stb_reader = open(stb_file)
19
- for line in stb_reader:
20
- line = line.strip()
21
-
22
- if line.startswith('#') or line.startswith('scaffold_name'):
23
- continue
15
+ with open(stb_file) as stb_reader:
16
+ for line in stb_reader:
17
+ line = line.strip()
24
18
 
25
- scaffold = line.split('\t')[0].strip()
26
- bin = line.split('\t')[1].strip()
27
- stb[scaffold] = bin
19
+ if line.startswith('#') or line.startswith('scaffold_name'):
20
+ continue
28
21
 
29
- # Do it with append
30
- if fasta[-3:] == '.gz':
31
- handle = gzip.open(fasta, "rt")
32
- seqs = SeqIO.parse(handle, "fasta")
22
+ scaffold, bin = line.split('\t')[:2]
23
+ stb[scaffold.strip()] = bin.strip()
33
24
 
25
+ # Parse the FASTA file and write to bins
26
+ if fasta.endswith('.gz'):
27
+ open_func = gzip.open
28
+ mode = 'rt'
34
29
  else:
35
- seqs = SeqIO.parse(fasta, "fasta")
36
-
37
- for seq_record in seqs:
38
- id = str(seq_record.id).strip()
39
- if id not in stb:
40
- # print("{0} not in stb".format(id))
41
- continue
42
- fasta = stb[id]
30
+ open_func = open
31
+ mode = 'r'
32
+
33
+ with open_func(fasta, mode) as handle:
34
+ record_id = ''
35
+ record_seq = ''
36
+ for line in handle:
37
+ if line.startswith('>'):
38
+ if record_id and record_id in stb:
39
+ bin = stb[record_id]
40
+ with open(f"{out_base}{bin}.fa", 'a') as nw:
41
+ nw.write(f">{record_id}\n{record_seq}\n")
42
+ record_id = line[1:].strip().split(' ')[0]
43
+ record_seq = ''
44
+ else:
45
+ record_seq += line.strip()
46
+
47
+ # Write the last record
48
+ if record_id and record_id in stb:
49
+ bin = stb[record_id]
50
+ with open(f"{out_base}{bin}.fa", 'a') as nw:
51
+ nw.write(f">{record_id}\n{record_seq}\n")
43
52
 
44
- nw = open("{0}{1}.fa".format(out_base, fasta), 'a')
45
- nw.write('\n'.join([">{0}".format(id), str(seq_record.seq), '']))
46
- nw.close()
47
-
48
- if fasta[-3:] == '.gz':
49
- handle.close()
53
+ import os
54
+ import gzip
50
55
 
51
56
  def gen_stb(fastas):
52
57
  if ((len(fastas) == 1) & (not fastas[0].endswith('.gz'))):
@@ -67,18 +72,22 @@ def gen_stb(fastas):
67
72
  stb = {}
68
73
  for fasta in fastas:
69
74
  bin = os.path.basename(fasta)
70
- if bin[-3:] == '.gz':
71
- with gzip.open(fasta, "rt") as handle:
72
- for seq_record in SeqIO.parse(handle, "fasta"):
73
- id = str(seq_record.id).strip()
74
- stb[id] = bin
75
+ if fasta.endswith('.gz'):
76
+ open_func = gzip.open
77
+ mode = 'rt'
75
78
  else:
76
- iter = SeqIO.parse(fasta, "fasta")
77
- for seq_record in iter:
78
- id = str(seq_record.id).strip()
79
- stb[id] = bin
79
+ open_func = open
80
+ mode = 'r'
81
+
82
+ with open_func(fasta, mode) as handle:
83
+ for line in handle:
84
+ if line.startswith('>'):
85
+ id = line[1:].strip().split(' ')[0]
86
+ stb[id] = bin
87
+
80
88
  return stb
81
89
 
90
+
82
91
  def print_stb(stb, output):
83
92
  file = open(output,'w')
84
93
  for key in sorted(stb, key=stb.get):
@@ -0,0 +1,3 @@
1
+ [tool.pytest.ini_options]
2
+ testpaths = ["tests"]
3
+ addopts = "-v"
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python
2
+
3
+ ###############################################################################
4
+ #
5
+ # test_suite.py - process several E. coli genomes to verify operation of dRep
6
+ #
7
+ ###############################################################################
8
+
9
+ import tests.test_rerun
10
+ import tests.test_unit
11
+ import tests.test_dereplicate
12
+ import tests.test_filter
13
+ import tests.test_cluster
14
+ import tests.test_choose
15
+ import tests.test_analyze
16
+ import tests.test_bonus
17
+ import tests.test_taxonomy
18
+
19
+ if __name__ == '__main__':
20
+ # tests.test_rerun.test_rerun().run()
21
+ # tests.test_unit.test_unit().run()
22
+ tests.test_dereplicate.test_dereplicate().run()
23
+ # tests.test_filter.test_filter().run()
24
+ # tests.test_cluster.test_cluster().run()
25
+ # tests.test_choose.test_choose().run()
26
+ # tests.test_analyze.test_analyze().run()
27
+ # tests.test_bonus.test_bonus().run()
28
+ # tests.test_taxonomy.test_taxonomy().run()
29
+
30
+ print("Everything seems to be working swimmingly!")
drep-3.4.5/drep/VERSION DELETED
@@ -1 +0,0 @@
1
- 3.4.5
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes