moose-classifier 0.10__py3-none-any.whl

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.
lineages.py ADDED
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Builds taxonomy lineages
4
+ """
5
+ import io
6
+ import logging
7
+ import os
8
+ import urllib.request
9
+
10
+ ROOT = 'root'
11
+
12
+
13
+ class Node:
14
+ def __init__(self, tax_id):
15
+ self.tax_id = tax_id
16
+ self.children = []
17
+ self.name = ''
18
+
19
+ def __rank_tree__(self, parent_rank, lineages={}):
20
+ '''Creates a dictionary of sets with keys being ranks pointing
21
+ to a set of parent ranks via tree traversal.
22
+ '''
23
+ if self.rank != 'no rank' and self.rank != parent_rank:
24
+ if self.rank in lineages:
25
+ lineages[self.rank].add(parent_rank)
26
+ else:
27
+ lineages[self.rank] = set()
28
+ parent_rank = self.rank
29
+ for c in self.children:
30
+ c.__rank_tree__(parent_rank, lineages)
31
+ return lineages
32
+
33
+ def __repr__(self):
34
+ return '{} "{}" [{}]'.format(self.tax_id, self.name, self.rank)
35
+
36
+ def add_child(self, child):
37
+ self.children.append(child)
38
+
39
+ def expand_no_ranks(self, suffix, ranks, parent=ROOT):
40
+ if self.rank == 'no rank':
41
+ self.rank = parent + suffix
42
+ if self.rank not in ranks:
43
+ ranks.insert(ranks.index(parent) + 1, self.rank)
44
+ for c in self.children:
45
+ c.expand_no_ranks(suffix, ranks, self.rank)
46
+
47
+ def prune(self, keep):
48
+ cut = self.tax_id not in keep
49
+ for c in list(self.children):
50
+ if c.prune(keep):
51
+ self.children.remove(c)
52
+ else:
53
+ cut = False
54
+ return cut
55
+
56
+ def ranks(self, ranks=set()):
57
+ ranks.add(self.rank)
58
+ for c in self.children:
59
+ c.ranks(ranks)
60
+ return ranks
61
+
62
+ def get_lineages(self, ranks, lineages=None, lin=None):
63
+ if lineages is None:
64
+ lineages = []
65
+ if lin is None:
66
+ lin = {}
67
+ if self.rank in ranks:
68
+ lin.update({self.rank: self.tax_id})
69
+ lineages.append(
70
+ {'tax_id': self.tax_id,
71
+ 'tax_name': self.name,
72
+ 'rank': self.rank,
73
+ **lin})
74
+ for c in self.children:
75
+ c.get_lineages(ranks, lineages, lin.copy())
76
+ return lineages
77
+
78
+
79
+ class Tree(dict):
80
+ '''
81
+ Builds and returns all the nodes as a dictionary object
82
+ '''
83
+ def __init__(self, nodes, names=None):
84
+ for tax_id, parent_id, rank in nodes:
85
+ if tax_id in self:
86
+ node = self[tax_id]
87
+ else:
88
+ node = Node(tax_id)
89
+ self[tax_id] = node
90
+
91
+ node.rank = rank
92
+
93
+ if tax_id == parent_id: # top node, no parent
94
+ self.root = node
95
+ continue
96
+
97
+ if parent_id in self:
98
+ parent = self[parent_id]
99
+ else:
100
+ parent = Node(parent_id)
101
+ self[parent_id] = parent
102
+
103
+ parent.add_child(node)
104
+
105
+ for tax_id, name in names:
106
+ self[tax_id].name = name
107
+
108
+ self.ranks = self.__ranks__()
109
+
110
+ def __ranks__(self):
111
+ '''
112
+ Determines rank order
113
+ '''
114
+ lineages = self.root.__rank_tree__(self.root.rank)
115
+ for l in lineages.values():
116
+ l.discard(self.root.rank)
117
+ ranks = []
118
+ if self.root.rank != 'no rank':
119
+ ranks.append(self.root.rank)
120
+ while lineages:
121
+ next_rank = sorted(lineages, key=lambda x: len(lineages[x]))[0]
122
+ ranks.append(next_rank)
123
+ del lineages[next_rank]
124
+ for v in lineages.values():
125
+ v.discard(next_rank)
126
+ return ranks
127
+
128
+ def include_root(self):
129
+ if self.ranks[0] != ROOT:
130
+ self.root.rank = ROOT
131
+ self.ranks.insert(0, ROOT)
132
+
133
+ def expand_ranks(self, suffix):
134
+ self.include_root()
135
+ self.root.expand_no_ranks(suffix, self.ranks)
136
+
137
+ def get_lineages(self):
138
+ return self.root.get_lineages(self.ranks)
139
+
140
+
141
+ def get_taxdmp(url):
142
+ logging.info('downloading ' + url)
143
+ tar, headers = urllib.request.urlretrieve(url, os.path.basename(url))
144
+ logging.debug(str(headers).strip())
145
+ return tar
146
+
147
+
148
+ def get_data(taxdmp, name_class='scientific name'):
149
+ nodes = io.TextIOWrapper(taxdmp.extractfile('nodes.dmp'))
150
+ nodes = (n.strip().replace('\t', '').split('|') for n in nodes)
151
+ nodes = (n[:3] for n in nodes) # tax_id,parent,rank
152
+ names = io.TextIOWrapper(taxdmp.extractfile('names.dmp'))
153
+ names = (n.strip().replace('\t', '').split('|') for n in names)
154
+ names = (n for n in names if n[3] == name_class)
155
+ names = (n[:2] for n in names) # tax_id,name
156
+ return nodes, names
157
+
158
+
159
+ def setup_logging(namespace):
160
+ loglevel = {
161
+ 0: logging.ERROR,
162
+ 1: logging.WARNING,
163
+ 2: logging.INFO,
164
+ 3: logging.DEBUG,
165
+ }.get(namespace.verbosity, logging.DEBUG)
166
+ if namespace.verbosity > 1:
167
+ logformat = '%(levelname)s taxtree %(message)s'
168
+ else:
169
+ logformat = 'taxtree %(message)s'
170
+ logging.basicConfig(stream=namespace.log, format=logformat, level=loglevel)