source: orange-bioinformatics/orangecontrib/bio/widgets/OWGOEnrichmentAnalysis.py @ 1873:0810c5708cc5

Revision 1873:0810c5708cc5, 47.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 6 months ago (diff)

Moved '_bioinformatics' into orangecontrib namespace.

Line 
1"""
2<name>GO Browser</name>
3<description>Enrichment analysis for Gene Ontology terms.</description>
4<contact>Ales Erjavec</contact>
5<icon>icons/GOBrowser.svg</icon>
6<priority>2020</priority>
7"""
8
9from __future__ import absolute_import, with_statement
10
11from collections import defaultdict
12from functools import partial
13import gc
14import sys, os, tarfile, math
15from os.path import join as p_join
16
17from Orange.orng import orngServerFiles
18from Orange.orng.orngDataCaching import data_hints
19from Orange.OrangeWidgets import OWGUI
20from Orange.OrangeWidgets.OWWidget import *
21
22from .. import obiGene, obiGO, obiProb, obiTaxonomy
23
24dataDir = orngServerFiles.localpath("GO")
25
26def listAvailable():
27    files = orngServerFiles.listfiles("GO")
28    ret = {}
29    for file in files:
30        tags = orngServerFiles.info("GO", file)["tags"]
31        td = dict([tuple(tag.split(":")) for tag in tags if tag.startswith("#") and ":" in tag])
32        if "association" in file.lower():
33            ret[td.get("#organism", file)] = file
34    orgMap = {"352472":"44689"}
35    essential = ["gene_association.%s.tar.gz" % obiGO.from_taxid(id) for id in obiTaxonomy.essential_taxids() if obiGO.from_taxid(id)]
36    essentialNames = [obiTaxonomy.name(id) for id in obiTaxonomy.essential_taxids() if obiGO.from_taxid(id)]
37    ret.update(zip(essentialNames, essential))
38    return ret
39
40class _disablegc(object):
41    def __enter__(self):
42        gc.disable()
43    def __exit__(self, *args):
44        gc.enable()
45
46def getOrgFileName(org):
47    from Orange.orng import orngServerFiles
48    files = orngServerFiles.listfiles("go")
49    return [f for f in files if org in f].pop()
50
51class TreeNode(object):
52    def __init__(self, tuple, children):
53        self.tuple = tuple
54        self.children = children
55
56class GOTreeWidget(QTreeWidget):
57    def contextMenuEvent(self, event):
58        QTreeWidget.contextMenuEvent(self, event)
59##        print event.x(), event.y()
60        term = self.itemAt(event.pos()).term
61        self._currMenu = QMenu()
62        self._currAction = self._currMenu.addAction("View term on AmiGO website")
63##        self.connect(self, SIGNAL("triggered(QAction*)"), partial(self.BrowserAction, term))
64        self.connect(self._currAction, SIGNAL("triggered()"), lambda :self.BrowserAction(term))
65        self._currMenu.popup(event.globalPos())
66
67    def BrowserAction(self, term):
68        import webbrowser
69        if isinstance(term, obiGO.Term):
70            term = term.id
71        webbrowser.open("http://amigo.geneontology.org/cgi-bin/amigo/term-details.cgi?term="+term)
72       
73    def paintEvent(self, event):
74        QTreeWidget.paintEvent(self, event)
75        if getattr(self, "_userMessage", None):
76            painter = QPainter(self.viewport())
77            font = QFont(self.font())
78            font.setPointSize(15)
79            painter.setFont(font)
80            painter.drawText(self.viewport().geometry(), Qt.AlignCenter, self._userMessage)
81            painter.end()
82
83
84class OWGOEnrichmentAnalysis(OWWidget):
85    settingsList = ["annotationIndex", "useReferenceDataset", "aspectIndex",
86                    "geneAttrIndex", "geneMatcherSettings",
87                    "filterByNumOfInstances", "minNumOfInstances",
88                    "filterByPValue", "maxPValue", "selectionDirectAnnotation",
89                    "selectionDisjoint", "selectionType",
90                    "selectionAddTermAsClass", "useAttrNames", "probFunc",
91                    "useFDR"
92                    ]
93
94    contextHandlers = {"": DomainContextHandler(
95                                "",
96                                ["geneAttrIndex", "useAttrNames",
97                                 "annotationIndex", "geneMatcherSettings"],
98                                matchValues=1)
99                       }
100
101    def __init__(self, parent=None, signalManager=None, name="GO Browser"):
102        OWWidget.__init__(self, parent, signalManager, name)
103        self.inputs = [("Cluster Examples", ExampleTable,
104                        self.SetClusterDataset, Default),
105                       ("Reference Examples", ExampleTable,
106                        self.SetReferenceDataset, Single + NonDefault)]
107
108        self.outputs = [("Selected Examples", ExampleTable, Default),
109                        ("Unselected Examples", ExampleTable, Default),
110                        ("Example With Unknown Genes", ExampleTable, Default),
111                        ("Enrichment Report", ExampleTable)]
112
113        self.annotationIndex = 0
114        self.autoFindBestOrg = False
115        self.useReferenceDataset = 0
116        self.aspectIndex = 0
117        self.geneAttrIndex = 0
118        self.useAttrNames = False
119        self.geneMatcherSettings = [True, False, False, False]
120        self.filterByNumOfInstances = False
121        self.minNumOfInstances = 1
122        self.filterByPValue = True
123        self.maxPValue = 0.1
124        self.probFunc = 0
125        self.useFDR = True
126        self.selectionDirectAnnotation = 0
127        self.selectionDisjoint = 0
128        self.selectionAddTermAsClass = 0
129        self.selectionChanging = 0
130       
131        # check usage of all evidences
132        for etype in obiGO.evidenceTypesOrdered:
133            varName = "useEvidence" + etype
134            if varName not in self.settingsList: 
135                self.settingsList.append(varName)
136            code = compile("self.%s = True" % (varName), ".", "single")
137            exec(code)
138        self.annotationCodes = []
139       
140        self.loadSettings()
141       
142        #############
143        ##GUI
144        #############
145        self.tabs = OWGUI.tabWidget(self.controlArea)
146        ##Input tab
147        self.inputTab = OWGUI.createTabPage(self.tabs, "Input")
148        box = OWGUI.widgetBox(self.inputTab, "Info")
149        self.infoLabel = OWGUI.widgetLabel(box, "No data on input\n")
150       
151        OWGUI.button(box, self, "Ontology/Annotation Info", callback=self.ShowInfo,
152                     tooltip="Show information on loaded ontology and annotations",
153                     debuggingEnabled=0)
154        box = OWGUI.widgetBox(self.inputTab, "Organism", addSpace=True)
155        self.annotationComboBox = OWGUI.comboBox(box, self, "annotationIndex",
156                            items = self.annotationCodes, callback=self.Update,
157                            tooltip="Select organism", debuggingEnabled=0)
158       
159        # freeze until annotation combo box is updateded with available annotations.
160        if self.signalManager:
161            self.signalManager.freeze(self).push()
162        QTimer.singleShot(0, self.UpdateOrganismComboBox)
163       
164        self.geneAttrIndexCombo = OWGUI.comboBox(self.inputTab, self, "geneAttrIndex",
165                            box="Gene names", callback=self.Update,
166                            tooltip="Use this attribute to extract gene names from input data")
167        OWGUI.checkBox(self.geneAttrIndexCombo.box, self, "useAttrNames", "Use data attributes names",
168                       disables=[(-1, self.geneAttrIndexCombo)], callback=self.Update, 
169                       tooltip="Use attribute names for gene names")
170        OWGUI.button(self.geneAttrIndexCombo.box, self, "Gene matcher settings", 
171                     callback=self.UpdateGeneMatcher, 
172                     tooltip="Open gene matching settings dialog", 
173                     debuggingEnabled=0)
174       
175        self.referenceRadioBox = OWGUI.radioButtonsInBox(self.inputTab, self, "useReferenceDataset", 
176                                                         ["Entire genome", "Reference set (input)"],
177                                                         tooltips=["Use entire genome for reference",
178                                                                   "Use genes from Referece Examples input signal as reference"],
179                                                         box="Reference", callback=self.Update)
180        self.referenceRadioBox.buttons[1].setDisabled(True)
181        OWGUI.radioButtonsInBox(self.inputTab, self, "aspectIndex", ["Biological process",
182                                                                     "Cellular component",
183                                                                     "Molecular function"], 
184                                box="Aspect", callback=self.Update)
185       
186        self.geneAttrIndexCombo.setDisabled(bool(self.useAttrNames))
187       
188        ##Filter tab
189        self.filterTab = OWGUI.createTabPage(self.tabs, "Filter")
190        box = OWGUI.widgetBox(self.filterTab, "Filter GO Term Nodes", addSpace=True)
191        OWGUI.checkBox(box, self, "filterByNumOfInstances", "Genes",
192                       callback=self.FilterAndDisplayGraph, 
193                       tooltip="Filter by number of input genes mapped to a term")
194        OWGUI.spin(OWGUI.indentedBox(box), self, 'minNumOfInstances', 1, 100, 
195                   step=1, label='#:', labelWidth=15, 
196                   callback=self.FilterAndDisplayGraph, 
197                   callbackOnReturn=True, 
198                   tooltip="Min. number of input genes mapped to a term")
199       
200        OWGUI.checkBox(box, self, "filterByPValue", "Significance",
201                       callback=self.FilterAndDisplayGraph, 
202                       tooltip="Filter by term p-value")
203        OWGUI.doubleSpin(OWGUI.indentedBox(box), self, 'maxPValue', 1e-8, 1, 
204                         step=1e-8,  label='p:', labelWidth=15, 
205                         callback=self.FilterAndDisplayGraph, 
206                         callbackOnReturn=True, 
207                         tooltip="Max term p-value")
208        box = OWGUI.widgetBox(box, "Significance test")
209        OWGUI.radioButtonsInBox(box, self, "probFunc", ["Binomial", "Hypergeometric"], 
210                                tooltips=["Use binomial distribution test", 
211                                          "Use hypergeometric distribution test"], 
212                                callback=self.Update)
213        OWGUI.checkBox(box, self, "useFDR", "Use FDR (False Discovery Rate)", 
214                       callback=self.Update, 
215                       tooltip="Use False Discovery Rate correction")
216        box = OWGUI.widgetBox(self.filterTab, "Evidence codes in annotation", 
217                              addSpace=True)
218        self.evidenceCheckBoxDict = {}
219        for etype in obiGO.evidenceTypesOrdered:
220            self.evidenceCheckBoxDict[etype] = OWGUI.checkBox(box, self, "useEvidence"+etype, etype,
221                                            callback=self.Update, tooltip=obiGO.evidenceTypes[etype])
222       
223        ##Select tab
224        self.selectTab = OWGUI.createTabPage(self.tabs, "Select")
225        box = OWGUI.radioButtonsInBox(self.selectTab, self, "selectionDirectAnnotation", 
226                                      ["Directly or Indirectly", "Directly"], 
227                                      box="Annotated genes", 
228                                      callback=self.ExampleSelection)
229       
230        box = OWGUI.widgetBox(self.selectTab, "Output", addSpace=True)
231        OWGUI.radioButtonsInBox(box, self, "selectionDisjoint", 
232                                btnLabels=["All selected genes", 
233                                           "Term-specific genes", 
234                                           "Common term genes"], 
235                                tooltips=["Outputs genes annotated to all selected GO terms", 
236                                          "Outputs genes that appear in only one of selected GO terms", 
237                                          "Outputs genes common to all selected GO terms"], 
238                                callback=[self.ExampleSelection,
239                                          self.UpdateAddClassButton])
240        self.addClassCB = OWGUI.checkBox(box, self, "selectionAddTermAsClass",
241                                         "Add GO Term as class", 
242                                         callback=self.ExampleSelection)
243
244        # ListView for DAG, and table for significant GOIDs
245        self.DAGcolumns = ['GO term', 'Cluster', 'Reference', 'p value', 'Genes', 'Enrichment']
246       
247        self.splitter = QSplitter(Qt.Vertical, self.mainArea)
248        self.mainArea.layout().addWidget(self.splitter)
249
250        # list view
251        self.listView = GOTreeWidget(self.splitter)
252        self.listView.setSelectionMode(QAbstractItemView.ExtendedSelection)
253        self.listView.setAllColumnsShowFocus(1)
254        self.listView.setColumnCount(len(self.DAGcolumns))
255        self.listView.setHeaderLabels(self.DAGcolumns)
256       
257        self.listView.header().setClickable(True)
258        self.listView.header().setSortIndicatorShown(True)
259        self.listView.setSortingEnabled(True)
260        self.listView.setItemDelegateForColumn(5, EnrichmentColumnItemDelegate(self))
261        self.listView.setRootIsDecorated(True)
262
263       
264        self.connect(self.listView, SIGNAL("itemSelectionChanged()"), self.ViewSelectionChanged)
265       
266        # table of significant GO terms
267        self.sigTerms = QTreeWidget(self.splitter)
268        self.sigTerms.setColumnCount(len(self.DAGcolumns))
269        self.sigTerms.setHeaderLabels(self.DAGcolumns)
270        self.sigTerms.setSortingEnabled(True)
271        self.sigTerms.setSelectionMode(QAbstractItemView.ExtendedSelection)
272       
273        self.connect(self.sigTerms, SIGNAL("itemSelectionChanged()"), self.TableSelectionChanged)
274        self.splitter.show()
275
276        self.sigTableTermsSorted = []
277        self.graph = {}
278       
279        self.loadedAnnotationCode = "---"
280       
281        self.inputTab.layout().addStretch(1)
282        self.filterTab.layout().addStretch(1)
283        self.selectTab.layout().addStretch(1)
284       
285        self.resize(1000, 800)
286
287        self.clusterDataset = None
288        self.referenceDataset = None
289        self.ontology = None
290        self.annotations = None
291        self.treeStructRootKey = None
292        self.probFunctions = [obiProb.Binomial(), obiProb.Hypergeometric()]
293        self.selectedTerms = []
294       
295        self.connect(self, SIGNAL("widgetStateChanged(QString, int, QString)"), self.onStateChanged)
296       
297    def UpdateOrganismComboBox(self):
298        try:
299            if self.annotationCodes and len(self.annotationCodes) > self.annotationIndex:
300                currAnnotationCode = self.annotationCodes[self.annotationIndex]
301            else:
302                currAnnotationCode = None
303            self.progressBarInit()
304            with orngServerFiles.DownloadProgress.setredirect(self.progressBarSet):
305                self.annotationFiles = listAvailable()
306            self.progressBarFinished()
307            self.annotationCodes = sorted(self.annotationFiles.keys())
308            self.annotationComboBox.clear()
309            self.annotationComboBox.addItems(self.annotationCodes)
310            self.annotationComboBox.setCurrentIndex(self.annotationIndex)
311        finally:
312            if self.signalManager:
313                self.signalManager.freeze(self).pop()
314
315    def UpdateGeneMatcher(self):
316        dialog = GeneMatcherDialog(self, defaults=self.geneMatcherSettings, modal=True)
317        if dialog.exec_():
318            self.geneMatcherSettings = [getattr(dialog, item[0]) for item in dialog.items]
319            if self.annotations:
320                self.SetGeneMatcher()
321                if self.clusterDataset:
322                    self.Update()
323               
324    def Update(self):
325        if self.clusterDataset:
326            pb = OWGUI.ProgressBar(self, 100)
327            self.Load(pb=pb)
328            self.FilterUnknownGenes()
329            graph = self.Enrichment(pb=pb)
330            self.SetGraph(graph)
331
332    def UpdateGOAndAnnotation(self, tags=[]):
333        from .OWUpdateGenomicsDatabases import OWUpdateGenomicsDatabases
334        w = OWUpdateGenomicsDatabases(parent = self, searchString=" ".join(tags))
335        w.setModal(True)
336        w.show()
337        self.UpdateAnnotationComboBox()
338
339    def UpdateAnnotationComboBox(self):
340        if self.annotationCodes:
341            curr = self.annotationCodes[min(self.annotationIndex, len(self.annotationCodes)-1)]
342        else:
343            curr = None
344        self.annotationFiles = listAvailable()
345        self.annotationCodes = self.annotationFiles.keys()
346        index = curr and self.annotationCodes.index(curr) or 0
347        self.annotationComboBox.clear()
348        self.annotationComboBox.addItems(self.annotationCodes)
349        self.annotationComboBox.setCurrentIndex(index)
350        if not self.annotationCodes:
351            self.error(0, "No downloaded annotations!!\nClick the update button and update annotationa for at least one organism!")
352        else:
353            self.error(0)
354
355    def SetGenesComboBox(self):
356        self.candidateGeneAttrs = self.clusterDataset.domain.variables + self.clusterDataset.domain.getmetas().values()
357        self.candidateGeneAttrs = filter(lambda v: v.varType in [orange.VarTypes.String,
358                                                                 orange.VarTypes.Other,
359                                                                 orange.VarTypes.Discrete], 
360                                         self.candidateGeneAttrs)
361        self.geneAttrIndexCombo.clear()
362        self.geneAttrIndexCombo.addItems([a.name for a in  self.candidateGeneAttrs])
363
364    def FindBestGeneAttrAndOrganism(self):
365        if self.autoFindBestOrg: 
366            organismGenes = dict([(o,set(go.getCachedGeneNames(o))) for o in self.annotationCodes])
367        else:
368            currCode = self.annotationCodes[min(self.annotationIndex, len(self.annotationCodes)-1)]
369            filename = p_join(dataDir, self.annotationFiles[currCode])
370            try:
371                f = tarfile.open(filename)
372                info = [info for info in f.getmembers() if info.name.startswith("gene_names")].pop()
373                geneNames = cPickle.loads(f.extractfile(info).read().replace("\r\n", "\n"))
374            except Exception, ex:
375                geneNames = cPickle.loads(open(p_join(filename, "gene_names.pickle")).read().replace("\r\n", "\n"))
376            organismGenes = {currCode: set(geneNames)}
377        candidateGeneAttrs = self.clusterDataset.domain.attributes + self.clusterDataset.domain.getmetas().values()
378        candidateGeneAttrs = filter(lambda v: v.varType in [orange.VarTypes.String, 
379                                                            orange.VarTypes.Other, 
380                                                            orange.VarTypes.Discrete], 
381                                    candidateGeneAttrs)
382        attrNames = [v.name for v in self.clusterDataset.domain.variables]
383        cn = {}
384        for attr in candidateGeneAttrs:
385            vals = [str(e[attr]) for e in self.clusterDataset]
386            if any("," in val for val in vals):
387                vals = reduce(list.__add__, (val.split(",") for val in vals))
388            for organism, s in organismGenes.items():
389                l = filter(lambda a: a in s, vals)
390                cn[(attr,organism)] = len(set(l))
391        for organism, s in organismGenes.items():
392            l = filter(lambda a: a in s, attrNames)
393            cn[("_var_names_", organism)] = len(set(l))
394           
395        cn = cn.items()
396        cn.sort(lambda a,b:-cmp(a[1],b[1]))
397        ((bestAttr, organism), count) = cn[0]
398        if bestAttr=="_var_names_" and count<=len(attrNames)/10.0 or \
399           bestAttr!="_var_names_" and count<=len(self.clusterDataset)/10.0:
400            return
401       
402        self.annotationIndex = self.annotationCodes.index(organism)
403        if bestAttr=="_var_names_":
404            self.useAttrNames = True
405            self.geneAttrIndex = 0
406        else:
407            self.useAttrNames = False
408            self.geneAttrIndex = candidateGeneAttrs.index(bestAttr)
409
410    def SetClusterDataset(self, data=None):
411        if not self.annotationCodes:
412            QTimer.singleShot(200, lambda: self.SetClusterDataset(data))
413            return
414        self.closeContext()
415        self.clusterDataset = data
416        self.infoLabel.setText("\n")
417        if data:
418            self.SetGenesComboBox()
419            try:
420                taxid = data_hints.get_hint(data, "taxid", "")
421                code = obiGO.from_taxid(taxid)
422                filename = "gene_association.%s.tar.gz" % code
423                if filename in self.annotationFiles.values():
424                    self.annotationIndex = \
425                            [i for i, name in enumerate(self.annotationCodes) \
426                             if self.annotationFiles[name] == filename].pop()
427            except Exception:
428                pass
429            self.useAttrNames = data_hints.get_hint(data, "genesinrows",
430                                                    self.useAttrNames)
431            self.openContext("", data)
432            self.Update()
433        else:
434            self.infoLabel.setText("No data on input\n")
435            self.warning(0)
436            self.warning(1)
437            self.openContext("", None)
438            self.ClearGraph()
439            self.send("Selected Examples", None)
440            self.send("Unselected Examples", None)
441            self.send("Example With Unknown Genes", None)
442            self.send("Enrichment Report", None)
443
444    def SetReferenceDataset(self, data=None):
445        self.referenceDataset = data
446        self.referenceRadioBox.buttons[1].setDisabled(not bool(data))
447        self.referenceRadioBox.buttons[1].setText("Reference set")
448        if self.clusterDataset and self.useReferenceDataset:
449            self.useReferenceDataset = 0 if not data else 1
450            graph = self.Enrichment()
451            self.SetGraph(graph)
452        elif self.clusterDataset:
453            self.UpdateReferenceSetButton()
454
455    def UpdateReferenceSetButton(self):
456        allgenes, refgenes = None, None
457        if self.referenceDataset:
458            try:
459                allgenes = self.GenesFromExampleTable(self.referenceDataset)
460            except Exception:
461                allgenes = []
462            refgenes, unknown = self.FilterAnnotatedGenes(allgenes)
463        self.referenceRadioBox.buttons[1].setDisabled(not bool(allgenes))
464        self.referenceRadioBox.buttons[1].setText("Reference set " + ("(%i genes, %i matched)" % (len(allgenes), len(refgenes)) if allgenes and refgenes else ""))
465
466    def GenesFromExampleTable(self, data):
467        if self.useAttrNames:
468            genes = [v.name for v in data.domain.variables]
469        else:
470            attr = self.candidateGeneAttrs[min(self.geneAttrIndex, len(self.candidateGeneAttrs) - 1)]
471            genes = [str(ex[attr]) for ex in data if not ex[attr].isSpecial()]
472            if any("," in gene for gene in genes):
473                self.information(0, "Separators detected in gene names. Assuming multiple genes per example.")
474                genes = reduce(list.__add__, (genes.split(",") for genes in genes))
475        return genes
476       
477    def FilterAnnotatedGenes(self, genes):
478        matchedgenes = self.annotations.GetGeneNamesTranslator(genes).values()
479        return matchedgenes, [gene for gene in genes if gene not in matchedgenes]
480       
481    def FilterUnknownGenes(self):
482        if not self.useAttrNames and self.candidateGeneAttrs:
483            geneAttr = self.candidateGeneAttrs[min(self.geneAttrIndex, len(self.candidateGeneAttrs)-1)]
484            examples = []
485            for ex in self.clusterDataset:
486                if not any(self.annotations.genematcher.match(n.strip()) for n in str(ex[geneAttr]).split(",")):
487                    examples.append(ex)
488
489            self.send("Example With Unknown Genes", examples and orange.ExampleTable(examples) or None)
490        else:
491            self.send("Example With Unknown Genes", None)
492
493    def Load(self, pb=None):
494        go_files, tax_files = orngServerFiles.listfiles("GO"), orngServerFiles.listfiles("Taxonomy")
495        calls = []
496        pb, finish = (OWGUI.ProgressBar(self, 0), True) if pb is None else (pb, False)
497        count = 0
498        if not tax_files:
499            calls.append(("Taxonomy", "ncbi_taxnomy.tar.gz"))
500            count += 1
501        org = self.annotationCodes[min(self.annotationIndex, len(self.annotationCodes)-1)]
502        if org != self.loadedAnnotationCode:
503            count += 1
504            if self.annotationFiles[org] not in go_files:
505                calls.append(("GO", self.annotationFiles[org]))
506                count += 1
507               
508        if "gene_ontology_edit.obo.tar.gz" not in go_files:
509            calls.append(("GO", "gene_ontology_edit.obo.tar.gz"))
510            count += 1
511        if not self.ontology:
512            count += 1
513        pb.iter += count*100
514       
515        for i, args in enumerate(calls):
516            orngServerFiles.localpath_download(*args, **dict(callback=pb.advance))
517           
518        i = len(calls)
519        if not self.ontology:
520            self.ontology = obiGO.Ontology(progressCallback=lambda value: pb.advance())
521            i+=1
522        if org != self.loadedAnnotationCode:
523            self.annotations = None
524            gc.collect() # Force run garbage collection.
525            code = self.annotationFiles[org].split(".")[-3]
526            self.annotations = obiGO.Annotations(code, genematcher=obiGene.GMDirect(), progressCallback=lambda value: pb.advance())
527            i+=1
528            self.loadedAnnotationCode = org
529            count = defaultdict(int)
530            geneSets = defaultdict(set)
531
532            for anno in self.annotations.annotations:
533                count[anno.evidence]+=1
534                geneSets[anno.evidence].add(anno.geneName)
535            for etype in obiGO.evidenceTypesOrdered:
536                self.evidenceCheckBoxDict[etype].setEnabled(bool(count[etype]))
537                self.evidenceCheckBoxDict[etype].setText(etype+": %i annots(%i genes)" % (count[etype], len(geneSets[etype])))
538        if finish:
539            pb.finish()
540           
541    def SetGeneMatcher(self):
542        if self.annotations:
543            taxid = self.annotations.taxid
544            matchers = []
545            for matcher, use in zip([obiGene.GMGO, obiGene.GMKEGG, obiGene.GMNCBI, obiGene.GMAffy], self.geneMatcherSettings):
546                if use:
547                    try:
548                        if taxid == "352472":
549                            matchers.extend([matcher(taxid), obiGene.GMDicty(),
550                                            [matcher(taxid), obiGene.GMDicty()]])
551                            # The reason machers are duplicated is that we want `matcher` or `GMDicty` to
552                            # match genes by them self if possible. Only use the joint matcher if they fail.   
553                        else:
554                            matchers.append(matcher(taxid))
555                    except Exception, ex:
556                        print ex
557            self.annotations.genematcher = obiGene.matcher(matchers)
558            self.annotations.genematcher.set_targets(self.annotations.geneNames)
559           
560    def Enrichment(self, pb=None):
561        pb = OWGUI.ProgressBar(self, 100) if pb is None else pb
562        if not self.annotations.ontology:
563            self.annotations.ontology = self.ontology
564           
565        if isinstance(self.annotations.genematcher, obiGene.GMDirect):
566            self.SetGeneMatcher()
567        self.error(1)
568        self.warning([0, 1])
569        try:   
570            if self.useAttrNames:
571                clusterGenes = [v.name for v in self.clusterDataset.domain.variables]
572                self.information(0)
573            else:
574                geneAttr = self.candidateGeneAttrs[min(self.geneAttrIndex, len(self.candidateGeneAttrs)-1)]
575                clusterGenes = [str(ex[geneAttr]) for ex in self.clusterDataset if not ex[geneAttr].isSpecial()]
576                if any("," in gene for gene in clusterGenes):
577                    self.information(0, "Separators detected in cluster gene names. Assuming multiple genes per example.")
578                    clusterGenes = reduce(list.__add__, (genes.split(",") for genes in clusterGenes))
579                else:
580                    self.information(0)
581        except Exception, ex:
582            self.error(1, "Failed to extract gene names from input dataset! %s" % str(ex))
583            return {}
584        genesCount = len(clusterGenes)
585        genesSetCount = len(set(clusterGenes))
586       
587        self.clusterGenes = clusterGenes = self.annotations.GetGeneNamesTranslator(clusterGenes).values()
588       
589#        self.clusterGenes = clusterGenes = filter(lambda g: g in self.annotations.aliasMapper or g in self.annotations.additionalAliases, clusterGenes)
590        self.infoLabel.setText("%i unique genes on input\n%i (%.1f%%) genes with known annotations" % (genesSetCount, len(clusterGenes), 100.0*len(clusterGenes)/genesSetCount if genesSetCount else 0.0))
591       
592        referenceGenes = None
593        if self.referenceDataset:
594            try:
595                if self.useAttrNames:
596                    referenceGenes = [v.name for v in self.referenceDataset.domain.variables]
597                    self.information(1)
598                else:
599                    referenceGenes = [str(ex[geneAttr]) for ex in self.referenceDataset if not ex[geneAttr].isSpecial()]
600                    if any("," in gene for gene in clusterGenes):
601                        self.information(1, "Separators detected in reference gene names. Assuming multiple genes per example.")
602                        referenceGenes = reduce(list.__add__, (genes.split(",") for genes in referenceGenes))
603                    else:
604                        self.information(1)
605
606                refc = len(referenceGenes)
607#                referenceGenes = filter(lambda g: g in self.annotations.aliasMapper or g in self.annotations.additionalAliases, referenceGenes)
608                referenceGenes = self.annotations.GetGeneNamesTranslator(referenceGenes).values()
609                self.referenceRadioBox.buttons[1].setText("Reference set (%i genes, %i matched)" % (refc, len(referenceGenes)))
610                self.referenceRadioBox.buttons[1].setDisabled(False)
611                self.information(2)
612            except Exception, er:
613                if not self.referenceDataset:
614                    self.information(2, "Unable to extract gene names from reference dataset. Using entire genome for reference")
615                else:
616                    self.referenceRadioBox.buttons[1].setText("Reference set")
617                    self.referenceRadioBox.buttons[1].setDisabled(True)
618                referenceGenes = self.annotations.geneNames
619                self.useReferenceDataset = 0
620        else:
621            self.useReferenceDataset = 0
622        if not self.useReferenceDataset:
623            self.information(2)
624            self.information(1)
625            referenceGenes = self.annotations.geneNames
626        self.referenceGenes = referenceGenes
627        evidences = []
628        for etype in obiGO.evidenceTypesOrdered:
629            if getattr(self, "useEvidence"+etype):
630                evidences.append(etype)
631        aspect = ["P", "C", "F"][self.aspectIndex]
632       
633        if clusterGenes:
634            self.terms = terms = self.annotations.GetEnrichedTerms(clusterGenes, referenceGenes, evidences, aspect=aspect,
635                                                                   prob=self.probFunctions[self.probFunc], useFDR=self.useFDR,
636                                                                   progressCallback=lambda value:pb.advance() )
637        else:
638            self.terms = terms = {}
639        if not self.terms:
640            self.warning(0, "No enriched terms found.")
641        else:
642            self.warning(0)
643           
644        pb.finish()
645        self.treeStructDict = {}
646        ids = self.terms.keys()
647       
648        self.treeStructRootKey = None
649       
650        parents = {}
651        for id in ids:
652            parents[id] = set([term for typeId, term in self.ontology[id].related])
653           
654        children = {}
655        for term in self.terms:
656            children[term] = set([id for id in ids if term in parents[id]])
657           
658        for term in self.terms:
659            self.treeStructDict[term] = TreeNode(self.terms[term], children[term])
660            if not self.ontology[term].related and not getattr(self.ontology[term], "is_obsolete", False):
661                self.treeStructRootKey = term
662        return terms
663       
664    def FilterGraph(self, graph):
665        if self.filterByPValue:
666            graph = obiGO.filterByPValue(graph, self.maxPValue)
667        if self.filterByNumOfInstances:
668            graph = dict(filter(lambda (id,(genes, p, rc)):len(genes)>=self.minNumOfInstances, graph.items()))
669        return graph
670
671    def FilterAndDisplayGraph(self):
672        if self.clusterDataset:
673            self.graph = self.FilterGraph(self.originalGraph)
674            if self.originalGraph and not self.graph:
675                self.warning(1, "All found terms were filtered out.")
676            else:
677                self.warning(1)
678            self.ClearGraph()
679            self.DisplayGraph()
680
681    def SetGraph(self, graph=None):
682        self.originalGraph = graph
683        if graph:
684            self.FilterAndDisplayGraph()
685        else:
686            self.graph = {}
687            self.ClearGraph()
688
689    def ClearGraph(self):
690        self.listView.clear()
691        self.listViewItems=[]
692        self.sigTerms.clear()
693
694    def DisplayGraph(self):
695        fromParentDict = {}
696        self.termListViewItemDict = {}
697        self.listViewItems = []
698        enrichment = lambda t:float(len(t[0])) / t[2] * (float(len(self.referenceGenes))/len(self.clusterGenes))
699        maxFoldEnrichment = max([enrichment(term) for term in self.graph.values()] or [1])
700
701        def addNode(term, parent, parentDisplayNode):
702            if (parent, term) in fromParentDict:
703                return
704            if term in self.graph:
705                displayNode = GOTreeWidgetItem(self.ontology[term], self.graph[term], len(self.clusterGenes), len(self.referenceGenes), maxFoldEnrichment, parentDisplayNode)
706                displayNode.goId = term
707                self.listViewItems.append(displayNode)
708                if term in self.termListViewItemDict:
709                    self.termListViewItemDict[term].append(displayNode)
710                else:
711                    self.termListViewItemDict[term] = [displayNode]
712                fromParentDict[(parent, term)] = True
713                parent = term
714            else:
715                displayNode = parentDisplayNode
716
717            for c in self.treeStructDict[term].children:
718                addNode(c, parent, displayNode)
719
720        if self.treeStructDict:
721            addNode(self.treeStructRootKey, None, self.listView)
722
723        terms = self.graph.items()
724        terms = sorted(terms, key=lambda item: item[1][1])
725        self.sigTableTermsSorted = [t[0] for t in terms]
726
727        self.sigTerms.clear()
728        for i, (t_id, (genes, p_value, refCount)) in enumerate(terms):
729            item = GOTreeWidgetItem(self.ontology[t_id],
730                                    (genes, p_value, refCount),
731                                    len(self.clusterGenes),
732                                    len(self.referenceGenes),
733                                    maxFoldEnrichment,
734                                    self.sigTerms)
735            item.goId = t_id
736
737        self.listView.expandAll()
738        for i in range(4):
739            self.listView.resizeColumnToContents(i)
740            self.sigTerms.resizeColumnToContents(i)
741        self.sigTerms.resizeColumnToContents(5)
742        width = min(self.listView.columnWidth(0), 350)
743        self.listView.setColumnWidth(0, width)
744        self.sigTerms.setColumnWidth(0, width)
745
746        # Create and send the enrichemnt report table.
747        termsDomain = orange.Domain(
748            [orange.StringVariable("GO Term Id"),
749             orange.StringVariable("GO Term Name"),
750             orange.FloatVariable("Cluster Frequency"),
751             orange.FloatVariable("Reference Frequency"),
752             orange.FloatVariable("P-value"),
753             orange.FloatVariable("Enrichment"),
754             orange.StringVariable("Genes")
755             ], None)
756
757        terms = [[t_id,
758                  self.ontology[t_id].name,
759                  float(len(genes)) / len(self.clusterGenes),
760                  float(r_count) / len(self.referenceGenes),
761                  p_value,
762                  float(len(genes)) / len(self.clusterGenes) * \
763                  float(len(self.referenceGenes)) / r_count,
764                  ",".join(genes)
765                  ]
766                 for t_id, (genes, p_value, r_count) in terms]
767
768        if terms:
769            termsTable = orange.ExampleTable(termsDomain, terms)
770        else:
771            termsTable = orange.ExampleTable(termsDomain)
772        self.send("Enrichment Report", termsTable)
773
774    def ViewSelectionChanged(self):
775        if self.selectionChanging:
776            return
777
778        self.selectionChanging = 1
779        self.selectedTerms = []
780        selected = self.listView.selectedItems()
781        self.selectedTerms = list(set([lvi.term.id for lvi in selected]))
782        self.ExampleSelection()
783        self.selectionChanging = 0
784
785    def TableSelectionChanged(self):
786        if self.selectionChanging:
787            return
788       
789        self.selectionChanging = 1
790        self.selectedTerms = []
791        selectedIds = set([self.sigTerms.itemFromIndex(index).goId for index in self.sigTerms.selectedIndexes()])
792       
793        for i in range(self.sigTerms.topLevelItemCount()):
794            item = self.sigTerms.topLevelItem(i)
795            selected = item.goId in selectedIds
796            term = item.goId
797           
798            if selected:
799                self.selectedTerms.append(term)
800               
801            for lvi in self.termListViewItemDict[term]:
802                try:
803                    lvi.setSelected(selected)
804                    if selected: lvi.setExpanded(True)
805                except RuntimeError:    ##Underlying C/C++ object deleted (why??)
806                    pass
807               
808        self.ExampleSelection()
809        self.selectionChanging = 0
810           
811   
812    def UpdateAddClassButton(self):
813        self.addClassCB.setEnabled(self.selectionDisjoint == 1)
814       
815    def ExampleSelection(self):
816        selectedExamples = []
817        unselectedExamples = []
818        selectedGenes = []
819       
820        #change by Marko. don't do anything if there is no3 dataset
821        if not self.clusterDataset:
822            return
823       
824        selectedGenes = reduce(set.union, [v[0] for id, v in self.graph.items() if id in self.selectedTerms], set())
825        evidences = []
826        for etype in obiGO.evidenceTypesOrdered:
827            if getattr(self, "useEvidence"+etype):
828                evidences.append(etype)
829        allTerms = self.annotations.GetAnnotatedTerms(selectedGenes, 
830                          directAnnotationOnly=self.selectionDirectAnnotation, 
831                          evidenceCodes=evidences)
832           
833        if self.selectionDisjoint:
834            count = defaultdict(int)
835            for term in self.selectedTerms:
836                for g in allTerms.get(term, []):
837                    count[g]+=1
838            ccount = 1 if self.selectionDisjoint==1 else len(self.selectedTerms)
839            selectedGenes = [gene for gene, c in count.items() if c==ccount and gene in selectedGenes]
840        else:
841            selectedGenes = reduce(set.union, [allTerms.get(term, []) for term in self.selectedTerms], set())
842
843        if self.useAttrNames:
844            vars = [self.clusterDataset.domain[gene] for gene in set(selectedGenes)]
845            newDomain = orange.Domain(vars, self.clusterDataset.domain.classVar)
846            newdata = orange.ExampleTable(newDomain, self.clusterDataset)
847            self.send("Selected Examples", newdata)
848            self.send("Unselected Examples", None)
849        elif self.candidateGeneAttrs:
850            geneAttr = self.candidateGeneAttrs[min(self.geneAttrIndex, len(self.candidateGeneAttrs)-1)]
851            if self.selectionDisjoint == 1:
852                goVar = orange.EnumVariable("GO Term", values=list(self.selectedTerms))
853                newDomain = orange.Domain(self.clusterDataset.domain.variables, goVar)
854                newDomain.addmetas(self.clusterDataset.domain.getmetas())
855#            else:
856#                goVar = orange.StringVariable("GO Terms")
857#                newDomain = orange.Domain(self.clusterDataset.domain)
858#                newDomain.addmeta(orange.newmetaid(), goVar)
859           
860           
861            for ex in self.clusterDataset:
862                if not ex[geneAttr].isSpecial() and any(gene in selectedGenes for gene in str(ex[geneAttr]).split(",")):
863                    if self.selectionDisjoint == 1 and self.selectionAddTermAsClass:
864                        terms = filter(lambda term: any(gene in self.graph[term][0] for gene in str(ex[geneAttr]).split(",")) , self.selectedTerms)
865                        term = sorted(terms)[0]
866                        ex =  orange.Example(newDomain, ex)
867                        ex[goVar] = goVar(term)
868#                        ex.setclass(newClass(term))
869                    selectedExamples.append(ex)
870                else:
871                    unselectedExamples.append(ex)
872                   
873            if selectedExamples:
874                selectedExamples = orange.ExampleTable(selectedExamples)
875            else:
876                selectedExamples = None
877               
878            if unselectedExamples:
879                unselectedExamples = orange.ExampleTable(unselectedExamples)
880            else:
881                unselectedExamples = None
882
883            self.send("Selected Examples", selectedExamples)
884            self.send("Unselected Examples", unselectedExamples)
885
886    def ShowInfo(self):
887        dialog = QDialog(self)
888        dialog.setModal(False)
889        dialog.setLayout(QVBoxLayout())
890        label = QLabel(dialog)
891        label.setText("Ontology:\n"+self.ontology.header if self.ontology else "Ontology not loaded!")
892        dialog.layout().addWidget(label)
893
894        label = QLabel(dialog)
895        label.setText("Annotations:\n"+self.annotations.header.replace("!", "") if self.annotations else "Annotations not loaded!")
896        dialog.layout().addWidget(label)
897        dialog.show()
898       
899    def sendReport(self):
900        self.reportSettings("Settings", [("Organism", self.annotationCodes[min(self.annotationIndex, len(self.annotationCodes) - 1)]),
901                                         ("Significance test", ("Binomial" if self.probFunc == 0 else "Hypergeometric") + (" with FDR" if self.useFDR else ""))])
902        self.reportSettings("Filter", ([("Min cluster size", self.minNumOfInstances)] if self.filterByNumOfInstances else []) + \
903                                      ([("Max p-value", self.maxPValue)] if self.filterByPValue else []))
904
905        def treeDepth(item):
906            return 1 + max([treeDepth(item.child(i)) for i in range(item.childCount())] +[0])
907       
908        def printTree(item, level, treeDepth):
909            text = '<tr>' + '<td width=16px></td>' * level
910            text += '<td colspan="%i">%s: %s</td>' % (treeDepth - level, item.term.id, item.term.name)
911            text += ''.join('<td>%s</td>' % item.text(i) for i in range(1, 4) + [5]) + '</tr>\n'
912            for i in range(item.childCount()):
913                text += printTree(item.child(i), level + 1, treeDepth)
914            return text
915       
916        treeDepth = max([treeDepth(self.listView.topLevelItem(i)) for i in range(self.listView.topLevelItemCount())] + [0])
917       
918        tableText = '<table>\n<tr>' + ''.join('<th>%s</th>' % s for s in ["Term:", "List:", "Reference:", "P-value:", "Enrichment:"]) + '</tr>'
919       
920        treeText = '<table>\n' +  '<th colspan="%i">%s</th>' % (treeDepth, "Term:") 
921        treeText += ''.join('<th>%s</th>' % s for s in ["List:", "Reference:", "P-value:", "Enrichment:"]) + '</tr>'
922       
923        for index in range(self.sigTerms.topLevelItemCount()):
924            item = self.sigTerms.topLevelItem(index)
925            tableText += printTree(item, 0, 1) 
926        tableText += '</table>' 
927       
928        for index in range(self.listView.topLevelItemCount()):
929            item = self.listView.topLevelItem(index)
930            treeText += printTree(item, 0, treeDepth)
931       
932        self.reportSection("Enriched Terms")
933        self.reportRaw(tableText)
934       
935        self.reportSection("Enriched Terms in the Ontology Tree")
936        self.reportRaw(treeText)
937       
938    def onStateChanged(self, stateType, id, text):
939        if stateType == "Warning":
940            self.listView._userMessage = text
941            self.listView.viewport().update()
942           
943    def onDeleteWidget(self):
944        """ Called before the widget is removed from the canvas.
945        """
946        self.annotations = None
947        self.ontology = None
948        gc.collect() # Force collection
949       
950
951class GOTreeWidgetItem(QTreeWidgetItem):
952    def __init__(self, term, enrichmentResult, nClusterGenes, nRefGenes, maxFoldEnrichment, parent):
953        QTreeWidgetItem.__init__(self, parent)
954        self.term = term
955        self.enrichmentResult = enrichmentResult
956        self.nClusterGenes = nClusterGenes
957        self.nRefGenes = nRefGenes
958        self.maxFoldEnrichment = maxFoldEnrichment
959        self.enrichment = enrichment = lambda t:float(len(t[0])) / t[2] * (float(nRefGenes)/nClusterGenes)
960        self.setText(0, term.name)
961        fmt = "%" + str(-int(math.log(nClusterGenes))) + "i (%.2f%%)"
962        self.setText(1, fmt % (len(enrichmentResult[0]), 100.0*len(self.enrichmentResult[0])/nClusterGenes))
963        fmt = "%" + str(-int(math.log(nRefGenes))) + "i (%.2f%%)"
964        self.setText(2, fmt % (enrichmentResult[2], 100.0*enrichmentResult[2]/nRefGenes))
965        self.setText(3, "%.4f" % enrichmentResult[1])
966        self.setText(4, ", ".join(enrichmentResult[0]))
967        self.setText(5, "%.2f" % (enrichment(enrichmentResult)))
968        self.setToolTip(0, "<p>" + term.__repr__()[6:].strip().replace("\n", "<br>"))
969        self.sortByData = [term.name, len(self.enrichmentResult[0]), enrichmentResult[2], enrichmentResult[1], ", ".join(enrichmentResult[0]), enrichment(enrichmentResult)]
970
971    def data(self, col, role):
972        if role == Qt.UserRole:
973            return QVariant(self.enrichment(self.enrichmentResult) / self.maxFoldEnrichment)
974        else:
975            return QTreeWidgetItem.data(self, col, role)
976
977    def __lt__(self, other):
978        col = self.treeWidget().sortColumn()
979        return self.sortByData[col] < other.sortByData[col]
980   
981class EnrichmentColumnItemDelegate(QItemDelegate):
982    def paint(self, painter, option, index):
983        self.drawBackground(painter, option, index)
984        value, ok = index.data(Qt.UserRole).toDouble()
985        if ok:
986            painter.save()
987            painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))
988            painter.drawRect(option.rect)
989            painter.setBrush(QBrush(Qt.blue, Qt.SolidPattern))
990            painter.drawRect(option.rect.x(), option.rect.y(), value*(option.rect.width()-1), option.rect.height()-1)
991            painter.restore()
992        else:
993            QItemDelegate.paint(self, painter, option, index)
994       
995       
996class GeneMatcherDialog(OWWidget):
997    items = [("useGO", "Use gene names from Gene Ontology annotations"),
998             ("useKEGG", "Use gene names from KEGG Genes database"),
999             ("useNCBI", "Use gene names from NCBI Gene info database"),
1000             ("useAffy", "Use Affymetrix platform reference ids")]
1001    settingsList = [item[0] for item in items]
1002    def __init__(self, parent=None, defaults=[True, False, False, False], enabled=[False, True, True, True], **kwargs):
1003        OWWidget.__init__(self, parent, **kwargs)
1004        for item, default in zip(self.items, defaults):
1005            setattr(self, item[0], default)
1006           
1007        self.loadSettings()
1008        for item, enable in zip(self.items, enabled):
1009            cb = OWGUI.checkBox(self, self, *item)
1010            cb.setEnabled(enable)
1011           
1012        box = OWGUI.widgetBox(self, orientation="horizontal")
1013        OWGUI.button(box, self, "OK", callback=self.accept)
1014        OWGUI.button(box, self, "Cancel", callback=self.reject)
1015       
1016       
1017if __name__=="__main__":
1018    import sys
1019    app = QApplication(sys.argv)
1020    w=OWGOEnrichmentAnalysis()
1021    data = orange.ExampleTable("brown-selected.tab")
1022    w.show()
1023    w.SetClusterDataset(data)
1024    app.exec_()
1025    w.saveSettings()
1026       
Note: See TracBrowser for help on using the repository browser.