pyham 2.0.0__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.
pyham/TreeProfile.py ADDED
@@ -0,0 +1,408 @@
1
+ from .abstractgene import HOG
2
+ import logging
3
+ import json
4
+ from ete4 import Tree
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class TreeProfile(object):
10
+ """
11
+ Object that map on each node of the Ham species tree the related evolutionary information such as the number of
12
+ genes the related genome contains or the number of evolutionary events (duplications, genes loss or gain of genes)
13
+ that occurs between the parent node and itself. This can be either applied to the full Ham comparative genomic setup
14
+ or for a specific HOG.
15
+
16
+ Attributes:
17
+ | pyham (:obj:`pyham.pyham.Ham`): :obj:`set` of HOG ids used by the FilterOrthoXMLParser.
18
+ | hog (:obj:`pyham.abstractgene.HOG`, optional): If specified, compute TreeProfile on a single HOGs. Defaults is None.
19
+ | treemap (:obj:`ete3.Etree`): Ete3 Etree object containing the taxonomy of interest with annotated node.
20
+ """
21
+
22
+ def __init__(self, ham, hog=None):
23
+
24
+ self.ham = ham
25
+ self.hog = hog
26
+
27
+ if hog is None:
28
+ self.treemap = self.compute_tree_profile_full()
29
+
30
+ elif isinstance(hog, HOG):
31
+ self.treemap = self.computeTP_hog(hog)
32
+ else:
33
+ raise TypeError("Invalid argument {} for HOG".format(hog))
34
+
35
+ def computeTP_hog(self, hog):
36
+ """
37
+ Create the treeMap object (ete3 Etree object with annotated nodes) that contained the following features for
38
+ each nodes (representing an AbstractGene):
39
+
40
+ - nbr_genes: numbers of HOG/Gene the genome contains. For leaves, it also counts the singletons.
41
+ - dupl: numbers of HOGs that have arose by duplication in between this node and its parent.
42
+ - retained: numbers of HOGs that have stay retained (in term of copy numbers) in between this node and
43
+ its parent.
44
+ - lost: numbers of HOGs that have been lost in between this node and its parent.
45
+
46
+ Returns:
47
+ TreeMap
48
+ """
49
+
50
+ # copy the required taxonomy using query hog as root level
51
+ treeMap = hog.genome.taxon.copy()
52
+
53
+
54
+ # create a dictionary that map node with related hogs/genes
55
+ levelGroups = {}
56
+ for lvl in treeMap.traverse():
57
+ levelGroups[lvl.name]=[]
58
+
59
+ # add all of subhog to the related level in levelGroups
60
+ for subhog in hog.get_all_descendant_hogs():
61
+ if subhog.genome.name in levelGroups:
62
+ levelGroups[subhog.genome.name].append(subhog)
63
+
64
+ # add empty extant genome to levelGroups
65
+ for extantGenome in treeMap.leaves():
66
+ levelGroups[extantGenome.name] = []
67
+
68
+ # add genes to related extant genome in levelGroups
69
+ for species, genes in hog.get_all_descendant_genes_clustered_by_species().items():
70
+ levelGroups[species.name] = genes
71
+
72
+ for lvl in treeMap.traverse():
73
+ lvl.add_prop("nbr_genes", len(levelGroups[lvl.name]))
74
+ lvl.add_prop("gain", None)
75
+
76
+ if not lvl.is_root:
77
+ cpt_dupl = 0
78
+ cpt_ident = 0
79
+ set_dup_parent = set()
80
+
81
+ for h in levelGroups[lvl.name]:
82
+ if h.arose_by_duplication != False:
83
+ cpt_dupl += 1
84
+ set_dup_parent.add(h.parent)
85
+ else:
86
+ cpt_ident += 1
87
+
88
+ cpt_lost = 0
89
+ for h_up in levelGroups[lvl.up.name]:
90
+ if len(h_up.children) != 0:
91
+ found = False
92
+ for child in h_up.children:
93
+ if child.genome.taxon.name == lvl.name:
94
+ found = True
95
+ if found is False:
96
+ cpt_lost += 1
97
+ else:
98
+ cpt_lost += 1
99
+
100
+ cpt_duplication = max(0, cpt_dupl - len(list(set_dup_parent)))
101
+ nbr_ev = cpt_lost + cpt_duplication
102
+
103
+
104
+ else:
105
+ cpt_dupl = None
106
+ cpt_ident = None
107
+ cpt_lost = None
108
+ cpt_duplication = None
109
+ nbr_ev = None
110
+
111
+ lvl.add_prop("dupl", cpt_dupl)
112
+ lvl.add_prop("lost", cpt_lost)
113
+ lvl.add_prop("retained", cpt_ident)
114
+ lvl.add_prop("nbr_events", nbr_ev)
115
+ lvl.add_prop("duplication", cpt_duplication)
116
+
117
+ return treeMap
118
+
119
+ def compute_tree_profile_full(self):
120
+ """
121
+ Create the treeMap object (ete3 Etree object with annotated nodes) that contained the following features for
122
+ each nodes (the root node contains only nbr_genes):
123
+
124
+ - nbr_genes: numbers of HOG/Gene the genome contains. For leaves, it also counts the singletons.
125
+ - dupl: numbers of HOGs that have arose by duplication in between this node and its parent.
126
+ - lost: numbers of HOGs that have been lost in between this node and its parent.
127
+ - gain: numbers of HOGs that have "emerged" at this node.
128
+ - retained: numbers of HOGs that have stay retained (in term of copy numbers) in between this node and
129
+ its parent.
130
+
131
+ In order to get all those node informations, the HOGMap between each node and its parent is computed.
132
+
133
+ Returns:
134
+ TreeMap
135
+ """
136
+
137
+ def _add_annot(node: Tree, nbr, dupl, lost, gain, retained, duplication, nbr_ev):
138
+ node.add_prop("nbr_genes", nbr)
139
+ node.add_prop("nbr_events", nbr_ev)
140
+ node.add_prop("dupl", dupl)
141
+ node.add_prop("lost", lost)
142
+ node.add_prop("gain", gain)
143
+ node.add_prop("retained", retained)
144
+ node.add_prop("duplication", duplication)
145
+
146
+
147
+ treeMap = self.ham.taxonomy.tree.copy()
148
+ for node in treeMap.traverse():
149
+ if node.is_root:
150
+ node_genome = self.ham.get_ancestral_genome_by_name(node.name)
151
+ _add_annot(node, len(node_genome.genes), None, None, None, None, None, None)
152
+
153
+ else:
154
+ node_genome_up = self.ham._get_ancestral_genome_by_name(node.up.name)
155
+
156
+ if node.is_leaf:
157
+ node_genome = self.ham._get_extant_genome_by_name(name=node.name)
158
+ nbr = node_genome.get_number_genes(singleton=True)
159
+
160
+ else:
161
+ node_genome = self.ham._get_ancestral_genome_by_name(node.name)
162
+ nbr = node_genome.get_number_genes()
163
+
164
+ hogmap = self.ham._get_HOGMap({node_genome, node_genome_up})
165
+
166
+ nbr_duplicate = 0
167
+ for gs in hogmap.DUPLICATE.values():
168
+ nbr_duplicate += len(gs)
169
+
170
+ nbr_ev = hogmap.number_duplication + len(hogmap.LOSS) + len(hogmap.GAIN)
171
+
172
+ _add_annot(node, nbr, nbr_duplicate, len(hogmap.LOSS), len(hogmap.GAIN), len(hogmap.RETAINED.keys()), hogmap.number_duplication, nbr_ev)
173
+
174
+ return treeMap
175
+
176
+ def export(self, output, layout_function=None, display_internal_histogram=True):
177
+
178
+ """
179
+ Method to export the tree profile object as figure (available format .SVG, .PDF, .PNG).
180
+
181
+ -- Some magic going there --
182
+
183
+ Args:
184
+ | output (:obj:`str`): output file name. The extension of output will set the format of the figure (SVG, .PDF, .PNG)
185
+ | layout_function (:obj:`function`, optional): custom layout_fn for ete3 TreeStyle.
186
+ | display_internal_histogram (:obj:`Boolean`, optional): Display internal node as histogram or raw text with numbers. Defaults to True.
187
+ """
188
+
189
+ from ete4 import TreeStyle, TextFace, NodeStyle, BarChartFace
190
+
191
+ # maximum number of genes per node in this treeMap
192
+ max_genes = max([d for d in self.treemap.traverse()], key=lambda x:x.nbr_genes).nbr_genes
193
+
194
+ if self.hog is None:
195
+ _color_scheme = ["#41c1c2","#bdc3c7","#f39c12","#27ae60","#e74c3c"]
196
+ _label_legend = ["Genes","Retained","Duplicated","Novel","Lost"]
197
+ _values_legend = [max_genes,max_genes,max_genes,max_genes,max_genes]
198
+ w_legend = 50 # todo calculate base on len(_values)
199
+ else:
200
+ _color_scheme = ["#41c1c2", "#bdc3c7", "#f39c12", "#e74c3c"]
201
+ _label_legend = ["Genes", "Retained", "Duplicated", "Lost"]
202
+ _values_legend = [max_genes, max_genes, max_genes, max_genes]
203
+ w_legend = 40 # todo calculate base on len(_values)
204
+
205
+ def _layout(node):
206
+
207
+ if self.hog is None:
208
+ _label = [str(node.nbr_genes),str(node.retained),str(node.dupl),str(node.gain),str(node.lost)]
209
+ else:
210
+ _label = [str(node.nbr_genes), str(node.retained), str(node.dupl), str(node.lost)]
211
+
212
+ def _add_face(name_feature, value_feature, cnum=1, pos="branch-right"):
213
+ node.add_face(TextFace("{}: {}".format(name_feature, value_feature)), column=cnum, position=pos)
214
+
215
+ def _add_faces(cNbr=1, posNbr="branch-right", cAttr=2, posAtt="branch-right"):
216
+
217
+ _add_face("#genes", node.nbr_genes, cnum=cNbr, pos=posNbr)
218
+
219
+ if node.retained is not None:
220
+ _add_face("#Retained", node.retained, cnum=cAttr, pos=posAtt)
221
+
222
+ if node.dupl is not None:
223
+ _add_face("#Duplicated", node.dupl, cnum=cAttr, pos=posAtt)
224
+
225
+ if node.gain is not None:
226
+ _add_face("#Novel", node.gain, cnum=cAttr, pos=posAtt)
227
+
228
+ if node.lost is not None:
229
+ _add_face("#Lost", node.lost, cnum=cAttr, pos=posAtt)
230
+
231
+ if node.is_leaf:
232
+ if display_internal_histogram:
233
+ if self.hog is None:
234
+ values = [node.nbr_genes,node.retained,node.dupl,node.gain,node.lost]
235
+ w_plot = 50
236
+ else:
237
+ values = [node.nbr_genes, node.retained, node.dupl, node.lost]
238
+ w_plot = 40
239
+ node.add_face(BarChartFace(values, deviations=None, width=w_plot, height=25, colors=_color_scheme, labels=_label, min_value=0, max_value=max_genes, label_fsize=6, scale_fsize=6),column=1, position = "branch-right")
240
+ else:
241
+ _add_faces()
242
+
243
+ else:
244
+
245
+ if display_internal_histogram:
246
+ if node.is_root:
247
+ node.add_face(BarChartFace([node.nbr_genes], deviations=None, width=10, height=25, colors=["#41c1c2"], labels=[str(node.nbr_genes)], min_value=0, max_value=max_genes, label_fsize=6, scale_fsize=6),column=0, position = "branch-bottom")
248
+ else:
249
+ if self.hog is None:
250
+ values = [node.nbr_genes,node.retained,node.dupl,node.gain,node.lost]
251
+ w_plot = 50
252
+ else:
253
+ values = [node.nbr_genes,node.retained,node.dupl,node.lost]
254
+ w_plot = 40
255
+ node.add_face(BarChartFace(values, deviations=None, width=w_plot, height=25, colors=_color_scheme, labels=_label, min_value=0, max_value=max_genes, label_fsize=6, scale_fsize=6),column=1, position = "branch-top")
256
+
257
+
258
+
259
+ else:
260
+ _add_faces(cNbr=0, posNbr="branch-top", cAttr=0, posAtt="branch-bottom")
261
+
262
+ ts = TreeStyle()
263
+
264
+ if layout_function is not None:
265
+ ts.layout_fn = layout_function
266
+ else:
267
+ ts.layout_fn = _layout
268
+ ts.legend.add_face(BarChartFace(_values_legend, deviations=None, width=w_legend, height=25, colors=_color_scheme, labels=_label_legend, min_value=0, max_value=max_genes, label_fsize=6, scale_fsize=6),column=0)
269
+ ts.legend_position = 3
270
+
271
+ self.treemap.render(output, tree_style=ts)
272
+
273
+ def export_as_html(self, output):
274
+
275
+ """
276
+ Method to export the tree profile object as an interactive tool embedded into a html file.
277
+
278
+
279
+ Args:
280
+ | output (:obj:`str`): output file name. If filename finish by .html, a double click load the file into your default browser.
281
+ """
282
+
283
+ def visit_custom(node, data):
284
+
285
+ current = {
286
+ "name": node.props['name'],
287
+ "numberGenes": node.props['nbr_genes'],
288
+ "numberEvents": node.props['nbr_events'],
289
+
290
+ "length": 0.01,
291
+ "collapsed": "false",
292
+ "evolutionaryEvents": {
293
+ "retained": None,
294
+ "duplicated": None,
295
+ "gained": None,
296
+ "lost": None,
297
+ "duplication": None
298
+ }
299
+ }
300
+
301
+ if node.is_root:
302
+ current['evolutionaryEvents'] = False
303
+
304
+ else:
305
+ current['evolutionaryEvents']["retained"] = node.props['retained']
306
+ current['evolutionaryEvents']["duplicated"] = node.props['dupl']
307
+ current['evolutionaryEvents']["gained"] = node.props['gain']
308
+ current['evolutionaryEvents']["lost"] = node.props['lost']
309
+ current['evolutionaryEvents']["duplication"] = node.props['duplication']
310
+
311
+ if not node.is_leaf:
312
+ current['children'] = []
313
+
314
+ for child in node.children:
315
+ current['children'].append(visit_custom(child, data))
316
+
317
+ return current
318
+
319
+ template = '''
320
+ <!DOCTYPE html>
321
+ <html>
322
+ <head>
323
+ <title>Phylo.io</title>
324
+ <meta charset="UTF-8">
325
+ <script src="https://peterolson.github.com/BigInteger.js/BigInteger.min.js"></script>
326
+ <script type="text/javascript" src="https://cdn.rawgit.com/DessimozLab/phylo-io/5e89fafc3b1746b22da33c20b2af621d5807b6fb/www/js/jquery-2.1.4.min.js"></script>
327
+ <script type="text/javascript" src="https://cdn.rawgit.com/DessimozLab/phylo-io/9eae3d2af33f1f75bff78e393909dccfdd400650/www/js/treecompare.js"></script>
328
+ <script type="text/javascript" src="https://underscorejs.org/underscore-min.js"></script>
329
+ <script type="text/javascript" src="https://cdn.rawgit.com/DessimozLab/phylo-io/5e89fafc3b1746b22da33c20b2af621d5807b6fb/www/js/d3.min.js"></script>
330
+ <script type="text/javascript" src="https://cdn.rawgit.com/DessimozLab/phylo-io/5e89fafc3b1746b22da33c20b2af621d5807b6fb/www/js/bootstrap.min.js"></script>
331
+ <script type="text/javascript" src="https://cdn.rawgit.com/DessimozLab/phylo-io/5e89fafc3b1746b22da33c20b2af621d5807b6fb/www/js/FileSaver.min.js"></script>
332
+ <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/solid.css" integrity="sha384-v2Tw72dyUXeU3y4aM2Y0tBJQkGfplr39mxZqlTBDUZAb9BGoC40+rdFCG0m10lXk" crossorigin="anonymous">
333
+ <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/fontawesome.css" integrity="sha384-q3jl8XQu1OpdLgGFvNRnPdj5VIlCvgsDQTQB6owSOHWlAurxul7f+JpUOVdAiJ5P" crossorigin="anonymous">
334
+ <link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/DessimozLab/phylo-io/5e89fafc3b1746b22da33c20b2af621d5807b6fb/www/css/bootstrap.min.css">
335
+ <link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/DessimozLab/phylo-io/5e89fafc3b1746b22da33c20b2af621d5807b6fb/www/css/bootstrap-theme.min.css">
336
+ <link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/DessimozLab/phylo-io/5e89fafc3b1746b22da33c20b2af621d5807b6fb/www/css/style.css">
337
+ <style>
338
+
339
+ text {{stroke: none;}}
340
+
341
+ #help_modal_button {{
342
+ position: fixed;
343
+ right: 10px;
344
+ margin-right: 10px; /*magic number */
345
+ bottom: 10px;
346
+ z-index: 99;
347
+ }}
348
+
349
+ </style>
350
+ </head>
351
+ <body id="phylo">
352
+
353
+ <!-- Modal -->
354
+ <div class="modal fade bs-example-modal-lg" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
355
+ <div class="modal-dialog modal-lg" role="document">
356
+ <div class="modal-content">
357
+ <div class="modal-header">
358
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
359
+ <h4 class="modal-title" id="myModalLabel">Tree profile help</h4>
360
+ </div>
361
+ <div class="modal-body">
362
+
363
+ <embed src="https://cdn.rawgit.com/DessimozLab/pyham/fc01fb94/help.pdf" frameborder="0" width="100%" height="400px">
364
+ </div>
365
+ <div class="modal-footer">
366
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
367
+ </div>
368
+ </div>
369
+ </div>
370
+ </div>
371
+
372
+
373
+ <!-- Button trigger modal -->
374
+ <button id="help_modal_button" type="button" class="btn btn-sm" data-toggle="modal" data-target="#myModal">
375
+ Help
376
+ </button>
377
+
378
+
379
+
380
+ <div id="vis-container1" style="width: 100%; height: 100%;">
381
+ <div id="scale-1"> </div>
382
+ </div>
383
+ <script type="text/javascript">
384
+ var treecomp = TreeCompare().init({{
385
+ enableScale: true,
386
+ scaleColor: "black",
387
+ showHistogramValues: true,
388
+ showHistogramSummaryValue: true
389
+ }});
390
+ treeData = '{json_data}';
391
+ var tree1 = treecomp.addTree(treeData, undefined, "single");
392
+ treecomp.viewTree(tree1.name, "vis-container1", "scale-1");
393
+ treecomp.addMainLegend(tree1.name);
394
+ </script>
395
+ </body>
396
+ </html>
397
+ '''
398
+
399
+ data = {}
400
+
401
+ data = visit_custom(self.treemap, data)
402
+
403
+ data = json.dumps(data)
404
+
405
+ template = template.format(json_data=data)
406
+
407
+ with open(output, 'w') as outfile:
408
+ outfile.write(template)
pyham/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ from .parsers import *
2
+ from .ham import *
3
+ from .abstractgene import *
4
+ from .taxonomy import *
5
+ from .genome import *
6
+ from .utils import *
7
+ from .mapper import *
8
+ from .TreeProfile import *
9
+ from .iham import *
10
+ from .validation import *
11
+
12
+ try:
13
+ from ._version import version as __version__
14
+ except ImportError:
15
+ from importlib.metadata import version, PackageNotFoundError
16
+
17
+ try:
18
+ __version__ = version("pyham")
19
+ except PackageNotFoundError:
20
+ __version__ = "unknown"
pyham/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '2.0.0'
22
+ __version_tuple__ = version_tuple = (2, 0, 0)
23
+
24
+ __commit_id__ = commit_id = 'g3ae35e1c8'