source: orange-bioinformatics/widgets/OWGOEnrichmentAnalysis.py @ 1328:3f31a534a2c2

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