source: orange-bioinformatics/widgets/OWGOEnrichmentAnalysis.py @ 1340:345de524a5b2

Revision 1340:345de524a5b2, 45.3 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Fixed "View term on AmiGO website" action.

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