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.
- __init__.py +6 -0
- classify.py +1204 -0
- lineages.py +170 -0
- moose_classifier-0.10.dist-info/LICENSE +674 -0
- moose_classifier-0.10.dist-info/METADATA +1111 -0
- moose_classifier-0.10.dist-info/RECORD +9 -0
- moose_classifier-0.10.dist-info/WHEEL +5 -0
- moose_classifier-0.10.dist-info/entry_points.txt +2 -0
- moose_classifier-0.10.dist-info/top_level.txt +3 -0
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)
|