source: orange-bioinformatics/Orange/bioinformatics/widgets/OWGOEnrichmentAnalysis.py @ 1625:cefeb35cbfc9

Revision 1625:cefeb35cbfc9, 45.8 KB checked in by mitar, 2 years ago (diff)

Moving files around.

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 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 Browser"):
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            self.annotations = None
502            gc.collect() # Force run garbage collection.
503            code = self.annotationFiles[org].split(".")[-3]
504            self.annotations = obiGO.Annotations(code, genematcher=obiGene.GMDirect(), progressCallback=lambda value: pb.advance())
505            i+=1
506            self.loadedAnnotationCode = org
507            count = defaultdict(int)
508            geneSets = defaultdict(set)
509
510            for anno in self.annotations.annotations:
511                count[anno.evidence]+=1
512                geneSets[anno.evidence].add(anno.geneName)
513            for etype in obiGO.evidenceTypesOrdered:
514                self.evidenceCheckBoxDict[etype].setEnabled(bool(count[etype]))
515                self.evidenceCheckBoxDict[etype].setText(etype+": %i annots(%i genes)" % (count[etype], len(geneSets[etype])))
516        if finish:
517            pb.finish()
518           
519    def SetGeneMatcher(self):
520        if self.annotations:
521            taxid = self.annotations.taxid
522            matchers = []
523            for matcher, use in zip([obiGene.GMGO, obiGene.GMKEGG, obiGene.GMNCBI, obiGene.GMAffy], self.geneMatcherSettings):
524                if use:
525                    try:
526                        if taxid == "352472":
527                            matchers.extend([matcher(taxid), obiGene.GMDicty(),
528                                            [matcher(taxid), obiGene.GMDicty()]])
529                            # The reason machers are duplicated is that we want `matcher` or `GMDicty` to
530                            # match genes by them self if possible. Only use the joint matcher if they fail.   
531                        else:
532                            matchers.append(matcher(taxid))
533                    except Exception, ex:
534                        print ex
535            self.annotations.genematcher = obiGene.matcher(matchers)
536            self.annotations.genematcher.set_targets(self.annotations.geneNames)
537           
538    def Enrichment(self, pb=None):
539        pb = OWGUI.ProgressBar(self, 100) if pb is None else pb
540        if not self.annotations.ontology:
541            self.annotations.ontology = self.ontology
542           
543        if isinstance(self.annotations.genematcher, obiGene.GMDirect):
544            self.SetGeneMatcher()
545        self.error(1)
546        self.warning([0, 1])
547        try:   
548            if self.useAttrNames:
549                clusterGenes = [v.name for v in self.clusterDataset.domain.variables]
550                self.information(0)
551            else:
552                geneAttr = self.candidateGeneAttrs[min(self.geneAttrIndex, len(self.candidateGeneAttrs)-1)]
553                clusterGenes = [str(ex[geneAttr]) for ex in self.clusterDataset if not ex[geneAttr].isSpecial()]
554                if any("," in gene for gene in clusterGenes):
555                    self.information(0, "Separators detected in cluster gene names. Assuming multiple genes per example.")
556                    clusterGenes = reduce(list.__add__, (genes.split(",") for genes in clusterGenes))
557                else:
558                    self.information(0)
559        except Exception, ex:
560            self.error(1, "Failed to extract gene names from input dataset! %s" % str(ex))
561            return {}
562        genesCount = len(clusterGenes)
563        genesSetCount = len(set(clusterGenes))
564       
565        self.clusterGenes = clusterGenes = self.annotations.GetGeneNamesTranslator(clusterGenes).values()
566       
567#        self.clusterGenes = clusterGenes = filter(lambda g: g in self.annotations.aliasMapper or g in self.annotations.additionalAliases, clusterGenes)
568        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))
569       
570        referenceGenes = None
571        if self.referenceDataset:
572            try:
573                if self.useAttrNames:
574                    referenceGenes = [v.name for v in self.referenceDataset.domain.variables]
575                    self.information(1)
576                else:
577                    referenceGenes = [str(ex[geneAttr]) for ex in self.referenceDataset if not ex[geneAttr].isSpecial()]
578                    if any("," in gene for gene in clusterGenes):
579                        self.information(1, "Separators detected in reference gene names. Assuming multiple genes per example.")
580                        referenceGenes = reduce(list.__add__, (genes.split(",") for genes in referenceGenes))
581                    else:
582                        self.information(1)
583
584                refc = len(referenceGenes)
585#                referenceGenes = filter(lambda g: g in self.annotations.aliasMapper or g in self.annotations.additionalAliases, referenceGenes)
586                referenceGenes = self.annotations.GetGeneNamesTranslator(referenceGenes).values()
587                self.referenceRadioBox.buttons[1].setText("Reference set (%i genes, %i matched)" % (refc, len(referenceGenes)))
588                self.referenceRadioBox.buttons[1].setDisabled(False)
589                self.information(2)
590            except Exception, er:
591                if not self.referenceDataset:
592                    self.information(2, "Unable to extract gene names from reference dataset. Using entire genome for reference")
593                else:
594                    self.referenceRadioBox.buttons[1].setText("Reference set")
595                    self.referenceRadioBox.buttons[1].setDisabled(True)
596                referenceGenes = self.annotations.geneNames
597                self.useReferenceDataset = 0
598        else:
599            self.useReferenceDataset = 0
600        if not self.useReferenceDataset:
601            self.information(2)
602            self.information(1)
603            referenceGenes = self.annotations.geneNames
604        self.referenceGenes = referenceGenes
605        evidences = []
606        for etype in obiGO.evidenceTypesOrdered:
607            if getattr(self, "useEvidence"+etype):
608                evidences.append(etype)
609        aspect = ["P", "C", "F"][self.aspectIndex]
610       
611        if clusterGenes:
612            self.terms = terms = self.annotations.GetEnrichedTerms(clusterGenes, referenceGenes, evidences, aspect=aspect,
613                                                                   prob=self.probFunctions[self.probFunc], useFDR=self.useFDR,
614                                                                   progressCallback=lambda value:pb.advance() )
615        else:
616            self.terms = terms = {}
617        if not self.terms:
618            self.warning(0, "No enriched terms found.")
619        else:
620            self.warning(0)
621           
622        pb.finish()
623        self.treeStructDict = {}
624        ids = self.terms.keys()
625       
626        self.treeStructRootKey = None
627       
628        parents = {}
629        for id in ids:
630            parents[id] = set([term for typeId, term in self.ontology[id].related])
631           
632        children = {}
633        for term in self.terms:
634            children[term] = set([id for id in ids if term in parents[id]])
635           
636        for term in self.terms:
637            self.treeStructDict[term] = TreeNode(self.terms[term], children[term])
638            if not self.ontology[term].related and not getattr(self.ontology[term], "is_obsolete", False):
639                self.treeStructRootKey = term
640        return terms
641       
642    def FilterGraph(self, graph):
643        if self.filterByPValue:
644            graph = obiGO.filterByPValue(graph, self.maxPValue)
645        if self.filterByNumOfInstances:
646            graph = dict(filter(lambda (id,(genes, p, rc)):len(genes)>=self.minNumOfInstances, graph.items()))
647        return graph
648
649    def FilterAndDisplayGraph(self):
650        if self.clusterDataset:
651            self.graph = self.FilterGraph(self.originalGraph)
652            if self.originalGraph and not self.graph:
653                self.warning(1, "All found terms were filtered out.")
654            else:
655                self.warning(1)
656            self.ClearGraph()
657            self.DisplayGraph()
658
659    def SetGraph(self, graph=None):
660        self.originalGraph = graph
661        if graph:
662            self.FilterAndDisplayGraph()
663        else:
664            self.graph = {}
665            self.ClearGraph()
666
667    def ClearGraph(self):
668        self.listView.clear()
669        self.listViewItems=[]
670        self.sigTerms.clear()
671
672    def DisplayGraph(self):
673        fromParentDict = {}
674        self.termListViewItemDict = {}
675        self.listViewItems=[]
676        enrichment = lambda t:float(len(t[0])) / t[2] * (float(len(self.referenceGenes))/len(self.clusterGenes))
677        maxFoldEnrichment = max([enrichment(term) for term in self.graph.values()] or [1])
678        def addNode(term, parent, parentDisplayNode):
679            if (parent, term) in fromParentDict:
680                return
681            if term in self.graph:
682                displayNode = GOTreeWidgetItem(self.ontology[term], self.graph[term], len(self.clusterGenes), len(self.referenceGenes), maxFoldEnrichment, parentDisplayNode)
683                displayNode.goId = term
684                self.listViewItems.append(displayNode)
685                if term in self.termListViewItemDict:
686                    self.termListViewItemDict[term].append(displayNode)
687                else:
688                    self.termListViewItemDict[term] = [displayNode]
689                fromParentDict[(parent, term)] = True
690                parent = term
691            else:
692                displayNode = parentDisplayNode
693           
694            for c in self.treeStructDict[term].children:
695                addNode(c, parent, displayNode)
696               
697        if self.treeStructDict:
698            addNode(self.treeStructRootKey, None, self.listView)
699
700        terms = self.graph.items()
701        terms = sorted(terms, key=lambda item: item[1][1])
702        self.sigTableTermsSorted = [t[0] for t in terms]
703       
704        self.sigTerms.clear()
705        for i, (id, (genes, p_value, refCount)) in enumerate(terms):
706            text = [self.ontology[id].name, str(len(genes)), str(refCount), "%.4f" % p_value, " ,".join(genes), "%.2f" % enrichment((genes, p_value, refCount))]
707            item = GOTreeWidgetItem(self.ontology[id], (genes, p_value, refCount), len(self.clusterGenes),
708                                    len(self.referenceGenes), maxFoldEnrichment, self.sigTerms)
709            item.goId = id
710               
711        self.listView.expandAll()
712        for i in range(4):
713            self.listView.resizeColumnToContents(i)
714            self.sigTerms.resizeColumnToContents(i)
715        self.sigTerms.resizeColumnToContents(5)
716        width = min(self.listView.columnWidth(0), 350)
717        self.listView.setColumnWidth(0, width)
718        self.sigTerms.setColumnWidth(0, width)
719       
720    def ViewSelectionChanged(self):
721        if self.selectionChanging:
722            return
723       
724        self.selectionChanging = 1
725        self.selectedTerms = []
726        selected = self.listView.selectedItems()
727        self.selectedTerms = list(set([lvi.term.id for lvi in selected]))
728        self.ExampleSelection()
729        self.selectionChanging = 0
730       
731       
732    def TableSelectionChanged(self):
733        if self.selectionChanging:
734            return
735       
736        self.selectionChanging = 1
737        self.selectedTerms = []
738        selectedIds = set([self.sigTerms.itemFromIndex(index).goId for index in self.sigTerms.selectedIndexes()])
739       
740        for i in range(self.sigTerms.topLevelItemCount()):
741            item = self.sigTerms.topLevelItem(i)
742            selected = item.goId in selectedIds
743            term = item.goId
744           
745            if selected:
746                self.selectedTerms.append(term)
747               
748            for lvi in self.termListViewItemDict[term]:
749                try:
750                    lvi.setSelected(selected)
751                    if selected: lvi.setExpanded(True)
752                except RuntimeError:    ##Underlying C/C++ object deleted (why??)
753                    pass
754               
755        self.ExampleSelection()
756        self.selectionChanging = 0
757           
758   
759    def UpdateAddClassButton(self):
760        self.addClassCB.setEnabled(self.selectionDisjoint == 1)
761       
762    def ExampleSelection(self):
763        selectedExamples = []
764        unselectedExamples = []
765        selectedGenes = []
766       
767        #change by Marko. don't do anything if there is no3 dataset
768        if not self.clusterDataset:
769            return
770       
771        selectedGenes = reduce(set.union, [v[0] for id, v in self.graph.items() if id in self.selectedTerms], set())
772        evidences = []
773        for etype in obiGO.evidenceTypesOrdered:
774            if getattr(self, "useEvidence"+etype):
775                evidences.append(etype)
776        allTerms = self.annotations.GetAnnotatedTerms(selectedGenes, 
777                          directAnnotationOnly=self.selectionDirectAnnotation, 
778                          evidenceCodes=evidences)
779           
780        if self.selectionDisjoint:
781            count = defaultdict(int)
782            for term in self.selectedTerms:
783                for g in allTerms.get(term, []):
784                    count[g]+=1
785            ccount = 1 if self.selectionDisjoint==1 else len(self.selectedTerms)
786            selectedGenes = [gene for gene, c in count.items() if c==ccount and gene in selectedGenes]
787        else:
788            selectedGenes = reduce(set.union, [allTerms.get(term, []) for term in self.selectedTerms], set())
789
790        if self.useAttrNames:
791            vars = [self.clusterDataset.domain[gene] for gene in set(selectedGenes)]
792            newDomain = orange.Domain(vars, self.clusterDataset.domain.classVar)
793            newdata = orange.ExampleTable(newDomain, self.clusterDataset)
794            self.send("Selected Examples", newdata)
795            self.send("Unselected Examples", None)
796        elif self.candidateGeneAttrs:
797            geneAttr = self.candidateGeneAttrs[min(self.geneAttrIndex, len(self.candidateGeneAttrs)-1)]
798            if self.selectionDisjoint == 1:
799                goVar = orange.EnumVariable("GO Term", values=list(self.selectedTerms))
800                newDomain = orange.Domain(self.clusterDataset.domain.variables, goVar)
801                newDomain.addmetas(self.clusterDataset.domain.getmetas())
802#            else:
803#                goVar = orange.StringVariable("GO Terms")
804#                newDomain = orange.Domain(self.clusterDataset.domain)
805#                newDomain.addmeta(orange.newmetaid(), goVar)
806           
807           
808            for ex in self.clusterDataset:
809                if not ex[geneAttr].isSpecial() and any(gene in selectedGenes for gene in str(ex[geneAttr]).split(",")):
810                    if self.selectionDisjoint == 1 and self.selectionAddTermAsClass:
811                        terms = filter(lambda term: any(gene in self.graph[term][0] for gene in str(ex[geneAttr]).split(",")) , self.selectedTerms)
812                        term = sorted(terms)[0]
813                        ex =  orange.Example(newDomain, ex)
814                        ex[goVar] = goVar(term)
815#                        ex.setclass(newClass(term))
816                    selectedExamples.append(ex)
817                else:
818                    unselectedExamples.append(ex)
819                   
820            if selectedExamples:
821                selectedExamples = orange.ExampleTable(selectedExamples)
822            else:
823                selectedExamples = None
824               
825            if unselectedExamples:
826                unselectedExamples = orange.ExampleTable(unselectedExamples)
827            else:
828                unselectedExamples = None
829           
830            self.send("Selected Examples", selectedExamples)
831            self.send("Unselected Examples", unselectedExamples)
832
833    def ShowInfo(self):
834        dialog = QDialog(self)
835        dialog.setModal(False)
836        dialog.setLayout(QVBoxLayout())
837        label = QLabel(dialog)
838        label.setText("Ontology:\n"+self.ontology.header if self.ontology else "Ontology not loaded!")
839        dialog.layout().addWidget(label)
840
841        label = QLabel(dialog)
842        label.setText("Annotations:\n"+self.annotations.header.replace("!", "") if self.annotations else "Annotations not loaded!")
843        dialog.layout().addWidget(label)
844        dialog.show()
845       
846    def sendReport(self):
847        self.reportSettings("Settings", [("Organism", self.annotationCodes[min(self.annotationIndex, len(self.annotationCodes) - 1)]),
848                                         ("Significance test", ("Binomial" if self.probFunc == 0 else "Hypergeometric") + (" with FDR" if self.useFDR else ""))])
849        self.reportSettings("Filter", ([("Min cluster size", self.minNumOfInstances)] if self.filterByNumOfInstances else []) + \
850                                      ([("Max p-value", self.maxPValue)] if self.filterByPValue else []))
851
852        def treeDepth(item):
853            return 1 + max([treeDepth(item.child(i)) for i in range(item.childCount())] +[0])
854       
855        def printTree(item, level, treeDepth):
856            text = '<tr>' + '<td width=16px></td>' * level
857            text += '<td colspan="%i">%s: %s</td>' % (treeDepth - level, item.term.id, item.term.name)
858            text += ''.join('<td>%s</td>' % item.text(i) for i in range(1, 4) + [5]) + '</tr>\n'
859            for i in range(item.childCount()):
860                text += printTree(item.child(i), level + 1, treeDepth)
861            return text
862       
863        treeDepth = max([treeDepth(self.listView.topLevelItem(i)) for i in range(self.listView.topLevelItemCount())] + [0])
864       
865        tableText = '<table>\n<tr>' + ''.join('<th>%s</th>' % s for s in ["Term:", "List:", "Reference:", "P-value:", "Enrichment:"]) + '</tr>'
866       
867        treeText = '<table>\n' +  '<th colspan="%i">%s</th>' % (treeDepth, "Term:") 
868        treeText += ''.join('<th>%s</th>' % s for s in ["List:", "Reference:", "P-value:", "Enrichment:"]) + '</tr>'
869       
870        for index in range(self.sigTerms.topLevelItemCount()):
871            item = self.sigTerms.topLevelItem(index)
872            tableText += printTree(item, 0, 1) 
873        tableText += '</table>' 
874       
875        for index in range(self.listView.topLevelItemCount()):
876            item = self.listView.topLevelItem(index)
877            treeText += printTree(item, 0, treeDepth)
878       
879        self.reportSection("Enriched Terms")
880        self.reportRaw(tableText)
881       
882        self.reportSection("Enriched Terms in the Ontology Tree")
883        self.reportRaw(treeText)
884       
885    def onStateChanged(self, stateType, id, text):
886        if stateType == "Warning":
887            self.listView._userMessage = text
888            self.listView.viewport().update()
889           
890    def onDeleteWidget(self):
891        """ Called before the widget is removed from the canvas.
892        """
893        self.annotations = None
894        self.ontology = None
895        gc.collect() # Force collection
896       
897
898class GOTreeWidgetItem(QTreeWidgetItem):
899    def __init__(self, term, enrichmentResult, nClusterGenes, nRefGenes, maxFoldEnrichment, parent):
900        QTreeWidgetItem.__init__(self, parent)
901        self.term = term
902        self.enrichmentResult = enrichmentResult
903        self.nClusterGenes = nClusterGenes
904        self.nRefGenes = nRefGenes
905        self.maxFoldEnrichment = maxFoldEnrichment
906        self.enrichment = enrichment = lambda t:float(len(t[0])) / t[2] * (float(nRefGenes)/nClusterGenes)
907        self.setText(0, term.name)
908        fmt = "%" + str(-int(math.log(nClusterGenes))) + "i (%.2f%%)"
909        self.setText(1, fmt % (len(enrichmentResult[0]), 100.0*len(self.enrichmentResult[0])/nClusterGenes))
910        fmt = "%" + str(-int(math.log(nRefGenes))) + "i (%.2f%%)"
911        self.setText(2, fmt % (enrichmentResult[2], 100.0*enrichmentResult[2]/nRefGenes))
912        self.setText(3, "%.4f" % enrichmentResult[1])
913        self.setText(4, ", ".join(enrichmentResult[0]))
914        self.setText(5, "%.2f" % (enrichment(enrichmentResult)))
915        self.setToolTip(0, "<p>" + term.__repr__()[6:].strip().replace("\n", "<br>"))
916        self.sortByData = [term.name, len(self.enrichmentResult[0]), enrichmentResult[2], enrichmentResult[1], ", ".join(enrichmentResult[0]), enrichment(enrichmentResult)]
917
918    def data(self, col, role):
919        if role == Qt.UserRole:
920            return QVariant(self.enrichment(self.enrichmentResult) / self.maxFoldEnrichment)
921        else:
922            return QTreeWidgetItem.data(self, col, role)
923
924    def __lt__(self, other):
925        col = self.treeWidget().sortColumn()
926        return self.sortByData[col] < other.sortByData[col]
927   
928class EnrichmentColumnItemDelegate(QItemDelegate):
929    def paint(self, painter, option, index):
930        self.drawBackground(painter, option, index)
931        value, ok = index.data(Qt.UserRole).toDouble()
932        if ok:
933            painter.save()
934            painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))
935            painter.drawRect(option.rect)
936            painter.setBrush(QBrush(Qt.blue, Qt.SolidPattern))
937            painter.drawRect(option.rect.x(), option.rect.y(), value*(option.rect.width()-1), option.rect.height()-1)
938            painter.restore()
939        else:
940            QItemDelegate.paint(self, painter, option, index)
941       
942       
943class GeneMatcherDialog(OWWidget):
944    items = [("useGO", "Use gene names from Gene Ontology annotations"),
945             ("useKEGG", "Use gene names from KEGG Genes database"),
946             ("useNCBI", "Use gene names from NCBI Gene info database"),
947             ("useAffy", "Use Affymetrix platform reference ids")]
948    settingsList = [item[0] for item in items]
949    def __init__(self, parent=None, defaults=[True, False, False, False], enabled=[False, True, True, True], **kwargs):
950        OWWidget.__init__(self, parent, **kwargs)
951        for item, default in zip(self.items, defaults):
952            setattr(self, item[0], default)
953           
954        self.loadSettings()
955        for item, enable in zip(self.items, enabled):
956            cb = OWGUI.checkBox(self, self, *item)
957            cb.setEnabled(enable)
958           
959        box = OWGUI.widgetBox(self, orientation="horizontal")
960        OWGUI.button(box, self, "OK", callback=self.accept)
961        OWGUI.button(box, self, "Cancel", callback=self.reject)
962       
963       
964if __name__=="__main__":
965    import sys
966    app = QApplication(sys.argv)
967    w=OWGOEnrichmentAnalysis()
968    data = orange.ExampleTable("../../../doc/datasets/brown-selected.tab")
969    w.show()
970    w.SetClusterDataset(data)
971    app.exec_()
972    w.saveSettings()
973       
Note: See TracBrowser for help on using the repository browser.