source: orange-bioinformatics/widgets/OWGOEnrichmentAnalysis.py @ 1330:45ced56f4661

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