Skip to content

Commit 69b1b70

Browse files
Merge pull request #5 from Ecogenomics/gtdb_training_set
feat: added subsampling to 100 genomes max per species
2 parents 4dd143f + a378487 commit 69b1b70

1 file changed

Lines changed: 42 additions & 8 deletions

File tree

‎scripts/gtdb_training_set.py‎

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import logging
2424
import argparse
2525
import gzip
26+
import random
2627
from collections import defaultdict
2728
from dataclasses import dataclass
2829

@@ -65,25 +66,37 @@ def __init__(self):
6566
# genomes to retain without needing to pass QC
6667
self.QC_EXCEPTIONS = set(['G015697925']) # s__Pinguicoccus supinus representative genome
6768

69+
# maximum number of genomes to select per species
70+
self.MAX_PER_SPECIES = 100
71+
6872
self.log = logging.getLogger('timestamp')
6973

70-
def parse_metadata_file(self, gtdb_metadata_file: str) -> dict:
74+
def parse_metadata_file(self, gtdb_metadata_file: str) -> tuple[dict, dict]:
7175
"""Parse manual ground truth file."""
7276

7377
genome_data = {}
78+
gtdb_sp_rid = {}
7479
open_file = gzip.open if gtdb_metadata_file.endswith('.gz') else open
7580
with open_file(gtdb_metadata_file, 'rt') as f:
7681
header = f.readline().strip().split('\t')
82+
7783
gid_idx = header.index("accession")
7884
gtdb_taxonomy_idx = header.index('gtdb_taxonomy')
7985
ncbi_taxonomy_idx = header.index('ncbi_taxonomy')
8086

87+
gtdb_rep_idx = header.index('gtdb_representative')
88+
8189
for line in f:
8290
tokens = line.strip().split('\t')
8391
gid = canonical_gid(tokens[gid_idx])
8492
genome_data[gid] = GenomeData(tokens[gtdb_taxonomy_idx], tokens[ncbi_taxonomy_idx])
8593

86-
return genome_data
94+
is_rep = tokens[gtdb_rep_idx].lower().startswith('t')
95+
if is_rep:
96+
gtdb_sp = [t.strip() for t in tokens[gtdb_taxonomy_idx].split(';')][-1]
97+
gtdb_sp_rid[gtdb_sp] = gid
98+
99+
return genome_data, gtdb_sp_rid
87100

88101
def parse_taxonomy_file(self, taxonomy_file: str) -> dict:
89102
"""Parse taxonomy file."""
@@ -175,12 +188,33 @@ def run(self, gtdb_bac_metadata_file: str,
175188

176189
# parse genomes from GTDB metadata files
177190
self.log.info('Parsing GTDB metadata files:')
178-
gtdb_bac_gids = self.parse_metadata_file(gtdb_bac_metadata_file)
179-
gtdb_ar_gids = self.parse_metadata_file(gtdb_ar_metadata_file)
191+
gtdb_bac_gids, gtdb_bac_sp_rid = self.parse_metadata_file(gtdb_bac_metadata_file)
192+
gtdb_ar_gids, gtdb_ar_sp_rid = self.parse_metadata_file(gtdb_ar_metadata_file)
180193
gtdb_gids = {**gtdb_bac_gids, **gtdb_ar_gids}
181-
self.log.info(f' - identified {len(gtdb_bac_gids):,} bacterial genomes')
182-
self.log.info(f' - identified {len(gtdb_ar_gids):,} archaeal genomes')
183-
self.log.info(f' - identified {len(gtdb_gids):,} total genomes')
194+
gtdb_sp_rid = {**gtdb_bac_sp_rid, **gtdb_ar_sp_rid}
195+
self.log.info(f' - identified {len(gtdb_bac_gids):,} bacterial genomes from {len(gtdb_bac_sp_rid):,} species')
196+
self.log.info(f' - identified {len(gtdb_ar_gids):,} archaeal genomes from {len(gtdb_ar_sp_rid):,} species')
197+
self.log.info(f' - identified {len(gtdb_gids):,} total genomes from {len(gtdb_sp_rid):,} species')
198+
199+
# subsample to maximum number of genomes per species
200+
self.log.info(f'Sampling to {self.MAX_PER_SPECIES} genomes per species:')
201+
202+
sp_gids = defaultdict(set)
203+
for gid, genome_data in gtdb_gids.items():
204+
gtdb_sp = [t.strip() for t in genome_data.gtdb_taxonomy.split(';')][-1]
205+
sp_gids[gtdb_sp].add(gid)
206+
207+
gtdb_gids_sampled = {}
208+
for sp, gids in sp_gids.items():
209+
if len(gids) > self.MAX_PER_SPECIES:
210+
rid = gtdb_sp_rid[sp]
211+
gids.remove(rid)
212+
gids = [rid] + random.sample(list(gids), self.MAX_PER_SPECIES - 1)
213+
214+
for gid in gids:
215+
gtdb_gids_sampled[gid] = gtdb_gids[gid]
216+
217+
self.log.info(f' - retained {len(gtdb_gids_sampled):,} genomes for training')
184218

185219
# read NCBI taxonomy for genomes
186220
self.log.info('Parsing NCBI taxonomy file:')
@@ -198,7 +232,7 @@ def run(self, gtdb_bac_metadata_file: str,
198232

199233
# combine all training genomes
200234
self.log.info('Combining all genomes to use for training:')
201-
training_gids = {**gtdb_gids, **gtdb_reassigned_gids}
235+
training_gids = {**gtdb_gids_sampled, **gtdb_reassigned_gids}
202236
self.log.info(f' - identified {len(training_gids):,} total genomes for training')
203237

204238
# sanity check that all genomes that are a QC expection are accounted for

0 commit comments

Comments
 (0)