source: orange-bioinformatics/_bioinformatics/widgets/OWGOEnrichmentAnalysis.py @ 1636:10d234fdadb9

Revision 1636:10d234fdadb9, 45.9 KB checked in by mitar, 2 years ago (diff)

Restructuring because we will not be using namespaces.

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