source: orange-bioinformatics/orangecontrib/bio/widgets/OWMAPlot.py @ 1874:b3e32cc5cf6f

Revision 1874:b3e32cc5cf6f, 18.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 7 months ago (diff)

Added new style widget meta descriptions.

RevLine 
[1242]1"""<name>MA Plot</name>
2<description>Normalize expression array data on a MA - plot</description>
[1726]3<icon>icons/MAPlot.svg</icon>
[1242]4"""
5
[1632]6from __future__ import absolute_import
[1286]7
8from functools import partial
9
[1632]10import numpy
11       
12from Orange.OrangeWidgets import OWConcurrent
13from Orange.OrangeWidgets import OWGUI
14from Orange.OrangeWidgets.OWWidget import *
15from Orange.OrangeWidgets.OWGraph import *
16
17from .. import obiExpression
18
[1874]19NAME = "MA Plot"
20DESCRIPTION = "Normalize expression array data on a MA - plot"
21ICON = "icons/MAPlot.svg"
22PRIORITY = 5000
23
24INPUTS = [("Expression array", Orange.data.Table, "setData")]
25OUTPUTS = [("Normalized expression array", Orange.data.Table, Default),
26           ("Filtered expression array", Orange.data.Table)]
27
28REPLACES = ["_bioinformatics.widgets.OWMAPlot.OWMAPlot"]
29
30
[1286]31class ProgressBarDiscard(QObject):
32    def __init__(self, parent, redirect):
33        QObject.__init__(self, parent)
34        self.redirect = redirect
35        self._delay = False
36       
37    @pyqtSignature("progressBarSet(float)")
38    def progressBarSet(self, value):
39        # Discard OWBaseWidget.progressBarSet call, because it calls qApp.processEvents
40        #which can result in 'event queue climbing' and max. recursion error if GUI thread
41        #gets another advance signal before it finishes with this one
42        if not self._delay:
43            try:
44                self._delay = True
45                self.redirect.progressBarSet(value)
46            finally:
47                self._delay = False
48       
[1244]49       
[1242]50class OWMAPlot(OWWidget):
[1260]51    settingsList = ["appendZScore", "appendRIValues"]
[1284]52    contextHandlers = {"": DomainContextHandler("", ["selectedGroup", "zCutoff"])}
[1242]53   
54    CENTER_METHODS = [("Average", obiExpression.MA_center_average),
55                      ("Lowess (fast - interpolated)", obiExpression.MA_center_lowess_fast),
56                      ("Lowess", obiExpression.MA_center_lowess)]
57   
58    MERGE_METHODS = [("Average", numpy.ma.average),
[1264]59                     ("Median", numpy.ma.median),
60                     ("Geometric mean", obiExpression.geometric_mean)]
[1242]61   
62    def __init__(self, parent=None, signalManager=None, name="Normalize Expression Array"):
63        OWWidget.__init__(self, parent, signalManager, name, wantGraph=True)
64       
65        self.inputs = [("Expression array", ExampleTable, self.setData)]
[1262]66        self.outputs = [("Normalized expression array", ExampleTable, Default), ("Filtered expression array", ExampleTable)]
[1242]67       
68        self.selectedGroup = 0
69        self.selectedCenterMethod = 0
70        self.selectedMergeMethod = 0
71        self.zCutoff = 1.96
72        self.appendZScore = False
[1260]73        self.appendRIValues = False
[1242]74        self.autoCommit = False
75       
76        self.loadSettings()
77        ## GUI
78        self.infoBox = OWGUI.widgetLabel(OWGUI.widgetBox(self.controlArea, "Info", addSpace=True),
79                                         "No data on input.")
80       
81        box = OWGUI.widgetBox(self.controlArea, "Split by", addSpace=True)
82        self.groupCombo = OWGUI.comboBox(box, self, "selectedGroup", 
83                                         callback=self.onGroupSelection
84                                         )
85       
86        self.centerCombo = OWGUI.comboBox(self.controlArea, self, "selectedCenterMethod",
87                                          box="Center Fold-change Using",
88                                          items=[name for name, _ in self.CENTER_METHODS],
89                                          callback=self.onCenterMethodChange,
90                                          addSpace=True
91                                          )
92       
93        self.mergeCombo = OWGUI.comboBox(self.controlArea, self, "selectedMergeMethod",
94                                         box="Merge Replicates",
95                                         items=[name for name, _ in self.MERGE_METHODS],
96                                         tooltip="Select the method for replicate merging",
97                                         callback=self.onMergeMethodChange,
98                                         addSpace=True
99                                         )
100       
101        box = OWGUI.doubleSpin(self.controlArea, self, "zCutoff", 0.0, 3.0, 0.01,
102                               box="Z-Score Cutoff",
103                               callback=[self.replotMA, self.commitIf])
104       
105        OWGUI.separator(self.controlArea)
106       
107        box = OWGUI.widgetBox(self.controlArea, "Ouput")
108        OWGUI.checkBox(box, self, "appendZScore", "Append Z-Scores",
109                       tooltip="Append calculated Z-Scores to output",
[1260]110                       callback=self.commitIf
111                       )
112       
113        OWGUI.checkBox(box, self, "appendRIValues", "Append Log Ratio and Intensity values",
114                       tooltip="Append calculated Log Ratio and Intensity values to output data",
115                       callback=self.commitIf
[1242]116                       )
117       
118        cb = OWGUI.checkBox(box, self, "autoCommit", "Commit on change",
119                       tooltip="Commit data on any change",
120                       callback=self.commitIf
121                       )
122       
123        b = OWGUI.button(box, self, "Commit", callback=self.commit)
124        OWGUI.setStopper(self, b, cb, "changedFlag", callback=self.commit)
125       
126        self.connect(self.graphButton, SIGNAL("clicked()"), self.saveGraph)
127       
128        OWGUI.rubber(self.controlArea)
129        self.graph = OWGraph(self.mainArea)
130        self.graph.setAxisTitle(QwtPlot.xBottom, "Intensity: log<sub>10</sub>(R*G)")
131        self.graph.setAxisTitle(QwtPlot.yLeft, "Log ratio: log<sub>2</sub>(R/G)")
[1260]132        self.graph.showFilledSymbols = True
[1242]133        self.mainArea.layout().addWidget(self.graph)
134        self.groups = []
135        self.split_data = None, None
136        self.merged_splits = None, None
[1260]137        self.centered = None, None
[1242]138        self.changedFlag = False
[1260]139        self.data = None
[1242]140       
141        self.resize(800, 600)
142       
143    def onFinished(self, status):
[1244]144        self.setEnabled(True)
[1242]145   
[1244]146    def onUnhandledException(self, ex_info):
[1284]147        self.setEnabled(True)
[1244]148        print >> sys.stderr, "Unhandled exception in non GUI thread"
[1250]149       
[1244]150        ex_type, ex_val, tb = ex_info
[1260]151        if ex_type == numpy.linalg.LinAlgError and False:
[1244]152            self.error(0, "Linear algebra error: %s" % repr(ex_val))
153        else:
154            sys.excepthook(*ex_info)
155   
[1242]156    def onGroupSelection(self):
[1260]157        if self.data:
158            self.updateInfoBox()
159            self.splitData()
160            self.runNormalization()
[1242]161       
162    def onCenterMethodChange(self):
[1260]163        if self.data:
164            self.runNormalization()
[1242]165       
166    def onMergeMethodChange(self):
[1260]167        if self.data:
168            self.splitData()
169            self.runNormalization()
[1242]170       
[1252]171    def proposeGroups(self, data):
172        col_labels = [attr.attributes.items() for attr in data.domain.attributes]
173        col_labels = sorted(reduce(set.union, col_labels, set()))
174        col_labels = [(key, value, 1) for key, value in col_labels]
175       
176        attrs = [attr for attr in data.domain.variables + data.domain.getmetas().values() \
177                 if attr.varType == orange.VarTypes.Discrete]
178       
179        row_labels = [(attr.name, value, 0) for attr in attrs for value in attr.values]
180       
[1280]181        def filterSingleValues(labels):
182            ret = []
183            for name, value, axis in labels:
184                match = [(n, v, a) for n, v, a in labels if n == name]
185                if len(match) > 1:
186                    ret.append((name, value, axis))
187            return ret
188           
189        col_labels = filterSingleValues(col_labels)
190        row_labels = filterSingleValues(row_labels)
191       
[1252]192        return col_labels + row_labels
193   
[1242]194    def setData(self, data):
195        self.closeContext("")
196        self.data = data
[1357]197        self.error([0 ,1])
[1242]198        if data is not None:
[1244]199            self.infoBox.setText("%i genes on input" % len(data))
[1252]200            self.groups = self.proposeGroups(data)
[1242]201            self.groupCombo.clear()
[1252]202            self.groupCombo.addItems(["%s: %s" % (key, value) for key, value, axis in self.groups])
203           
[1357]204            if not self.groups:
205                self.error(1, "Input data has no class attribute or attribute labels!")
206                self.clear()
207                return
208           
[1252]209            self.openContext("", data)
[1242]210            self.selectedGroup = min(self.selectedGroup, len(self.groups) - 1)
[1252]211           
212            self.updateInfoBox()
[1242]213            self.splitData()
214            self.runNormalization()
215        else:
216            self.clear()
217       
218    def clear(self):
219        self.groups = []
[1260]220        self.data = None
221        self.centered = None, None
[1242]222        self.split_data = None, None
223        self.merged_splits = None, None
[1272]224        self.graph.removeDrawingCurves()
[1252]225        self.infoBox.setText("No data on input")
[1260]226        self.send("Normalized expression array", None)
227        self.send("Filtered expression array", None)
[1242]228       
[1252]229    def updateInfoBox(self):
230        genes = self.getGeneNames()
231        self.infoBox.setText("%i genes on input" % len(self.data))
[1242]232       
[1252]233    def getSelectedGroup(self):
234        return self.groups[self.selectedGroup]
235   
236    def getSelectedGroupSplit(self):
237        key, value, axis = self.getSelectedGroup()
238        other_values = [v for k, v, a in self.groups if k == key and a == axis and v != value]
239        return [(key, value), (key, other_values)], axis
240   
241    def getGeneNames(self):
242        key, value, axis = self.getSelectedGroup()
243        if axis == 0:
244            genes = [str(ex[key]) for ex in self.data]
245        else:
246            genes = [attr.name for attr in self.data.domain.attributes]
247           
248        return genes
249   
250    def splitData(self): 
251        groups, axis = self.getSelectedGroupSplit()
252        self.split_ind = [obiExpression.select_indices(self.data, key, value, axis) for key, value in groups]
253        self.split_data = obiExpression.split_data(self.data, groups, axis)
[1242]254       
255    def getMerged(self):
256        split1, split2 = self.split_data
257        (array1, _, _), (array2, _, _) = split1.toNumpyMA(), split2.toNumpyMA()
[1252]258       
259        _, _, axis = self.getSelectedGroup()
[1242]260        merge_function = self.MERGE_METHODS[self.selectedMergeMethod][1]
[1252]261       
262        merged1 = obiExpression.merge_replicates(array1, axis, merge_function=merge_function)
263        merged2 = obiExpression.merge_replicates(array2, axis, merge_function=merge_function)
[1242]264        self.merged_splits = merged1, merged2
265       
266        return self.merged_splits
267       
268    def runNormalization(self):
269        self.progressBarInit()
270        self.progressBarSet(0.0)
271        G, R = self.getMerged()
272        self.progressBarSet(5.0)
273       
274        center_method = self.CENTER_METHODS[self.selectedCenterMethod][1]
[1260]275       
[1242]276        # TODO: progess bar , lowess can take a long time
277        if self.selectedCenterMethod in [1, 2]: #Lowess
278            Gc, Rc = center_method(G, R, f = 1./min(500., len(G)/100), iter=1)
279        else:
280            Gc, Rc = center_method(G, R)
281        self.progressBarSet(70.0)
282        self.centered = Gc, Rc
283        self.z_scores = obiExpression.MA_zscore(Gc, Rc, 1./3.)
284        self.progressBarSet(100.0)
285        self.plotMA(Gc, Rc, self.z_scores, self.zCutoff)
286        self.progressBarFinished()
287       
288    def runNormalizationAsync(self):
[1250]289        """ Run MA centering and z_score estimation in a separate thread
290        """
[1244]291        self.error(0)
[1242]292        self.progressBarInit()
293        self.progressBarSet(0.0)
294        G, R = self.getMerged()
[1284]295#        self.progressBarSet(5.0)
[1242]296       
[1272]297        center_method = self.CENTER_METHODS[self.selectedCenterMethod][1]
298        use_lowess = self.selectedCenterMethod in [1, 2]
[1242]299       
[1272]300        def run(progressCallback = lambda value: None): # the function to run in a thread
[1284]301#            progressCallback(5.0)
[1272]302            if use_lowess:
[1284]303                Gc, Rc = center_method(G, R, f=2./3., iter=1, progressCallback=lambda val: progressCallback(val/2))
[1272]304            else:
305                Gc, Rc = center_method(G, R)
306            progressCallback(50)
[1284]307            z_scores = obiExpression.MA_zscore(Gc, Rc, 1./3., progressCallback= lambda val: progressCallback(50 + val/2))
[1242]308           
[1272]309            return Gc, Rc, z_scores
310       
[1286]311        self.progressDiscard = ProgressBarDiscard(self, self)
312         
[1272]313        async = self.asyncCall(run, name="Normalization",
314                               onResult=self.onResults,
315                               onError=self.onUnhandledException)
[1286]316        self.connect(async, SIGNAL("progressChanged(float)"), self.progressDiscard.progressBarSet, Qt.QueuedConnection)
[1280]317        self.setEnabled(False)
[1272]318        async.__call__(progressCallback=async.emitProgressChanged)
[1242]319           
320    ## comment out this line if threading creates any problems
321    runNormalization = runNormalizationAsync
322   
[1272]323    def onResults(self, (Gc, Rc, z_scores)):
324        """ Handle the results of centering and z-scoring
325        """
326        assert(QThread.currentThread() is self.thread())
327        self.setEnabled(True)
[1286]328        qApp.processEvents()
[1272]329        self.progressBarFinished()
330        self.centered = Gc, Rc
331        self.z_scores = z_scores
332        self.plotMA(Gc, Rc, z_scores, self.zCutoff)
333        self.commit()
334       
[1244]335   
[1242]336    def plotMA(self, G, R, z_scores, z_cuttof):
337        ratio, intensity = obiExpression.ratio_intensity(G, R)
338       
339        filter = numpy.isfinite(ratio) & numpy.isfinite(intensity) & numpy.isfinite(z_scores)
340        for array in [ratio, intensity, z_scores]:
341            if numpy.ma.is_masked(array):
[1260]342                filter &= -array.mask
[1242]343       
[1268]344        filtered_ind = numpy.ma.where(filter)
[1242]345        ratio = numpy.take(ratio, filtered_ind)
346        intensity = numpy.take(intensity, filtered_ind)
347        z_scores = numpy.take(z_scores, filtered_ind)
348       
[1268]349        red_ind = numpy.ma.where(numpy.ma.abs(z_scores) >= z_cuttof)
350        blue_ind = numpy.ma.where(numpy.ma.abs(z_scores) < z_cuttof)
[1242]351       
352        red_xdata, red_ydata = intensity[red_ind], ratio[red_ind]
353        blue_xdata, blue_ydata = intensity[blue_ind], ratio[blue_ind]
354        self.graph.removeDrawingCurves()
[1260]355       
[1242]356        c = self.graph.addCurve("", Qt.black, Qt.black, xData=[0.0, 1.0], yData=[0.0, 0.0], style=QwtPlotCurve.Lines, symbol=QwtSymbol.NoSymbol)
357        c.setAxis(QwtPlot.xTop, QwtPlot.yLeft)
358       
[1260]359        self.graph.addCurve("Z >= %.2f" % z_cuttof, QColor(Qt.red), Qt.red, brushAlpha=100, size=6, enableLegend=True, xData=list(red_xdata), yData=list(red_ydata), autoScale=True)
360        self.graph.addCurve("Z < %.2f" % z_cuttof, QColor(Qt.blue), Qt.blue, brushAlpha=100, size=6, enableLegend=True, xData=list(blue_xdata), yData=list(blue_ydata), autoScale=True)
[1242]361       
362        self.graph.setAxisScale(QwtPlot.xTop, 0.0, 1.0)
[1252]363         
[1242]364        self.graph.replot()
365       
[1244]366       
[1242]367    def replotMA(self):
[1260]368        if self.data and self.centered:
369            Gc, Rc = self.centered
370            self.plotMA(Gc, Rc, self.z_scores, self.zCutoff)
[1242]371       
[1244]372       
[1242]373    def commitIf(self):
374        if self.autoCommit and self.changedFlag:
375            self.commit()
376        else:
377            self.changedFlag = True
378           
[1244]379           
[1242]380    def commit(self):
[1260]381        if not self.data:
382            return
383       
[1242]384        G, R = self.merged_splits
385        Gc, Rc = self.centered
386        ind1, ind2 = self.split_ind
387       
388        gfactor = Gc / G
389       
390        domain = orange.Domain(self.data.domain.attributes, self.data.domain.classVar)
391        domain.addmetas(self.data.domain.getmetas())
[1252]392       
393        _, _, axis = self.getSelectedGroup()
394        if self.appendZScore and axis == 1:
[1242]395            attr = orange.FloatVariable("Z-Score")
396            if not hasattr(self, "z_score_mid"):
397                self.z_score_mid = orange.newmetaid()
398            mid = self.z_score_mid
399            domain.addmeta(mid, attr)
400           
[1260]401        if self.appendRIValues and axis == 1:
402            r_attr = orange.FloatVariable("Log Ratio")
403            i_attr = orange.FloatVariable("Intensity")
404            if not hasattr(self, "_r_mid"):
405                self._r_mid = orange.newmetaid()
406                self._i_mid = orange.newmetaid()
407            r_mid, i_mid = self._r_mid, self._i_mid
408            domain.addmeta(r_mid, r_attr)
409            domain.addmeta(i_mid, i_attr)
410           
[1242]411        data = orange.ExampleTable(domain, self.data)
[1260]412       
413        def finite_nonmasked(g):
414            return numpy.isfinite(g) and g is not numpy.ma.masked
415       
[1252]416        if axis == 0:
417            for i, gf in zip(ind1, gfactor):
418                for attr in domain.attributes:
[1260]419                    if not data[i][attr].isSpecial() and finite_nonmasked(gf):
420                        data[i][attr] = data[i][attr] * gf
[1252]421        else:
[1260]422            ratio, intensity = obiExpression.ratio_intensity(*self.centered) #*self.merged_splits)
423            for ex, gf, r, inten, z in zip(data, gfactor, ratio, intensity, self.z_scores):
[1252]424                for i in ind1:
[1260]425                    if not ex[i].isSpecial() and finite_nonmasked(gf):
[1252]426                        ex[i] = float(ex[i]) * gf
427                if self.appendZScore:
[1260]428                    ex[attr] = z if finite_nonmasked(z) else "?"
429                   
430                if self.appendRIValues:
431                    ex[r_attr] = r if finite_nonmasked(r) else "?"
432                    ex[i_attr] = inten if finite_nonmasked(inten) else "?"
[1252]433       
[1260]434        filtered_ind = list(numpy.ma.abs(self.z_scores.filled(0)) >= self.zCutoff)
[1252]435        if axis == 0:
436            attrs = [attr for attr, filtered in zip(domain.attributes, filtered_ind) if filtered]
437            filtered_domain = orange.Domain(attrs, domain.classVar)
438            filtered_domain.addmetas(domain.getmetas())
439            filtered_data = orange.ExampleTable(filtered_domain, data)
440        else:
441            filtered_data = data.select([int(b) for b in filtered_ind])
[1242]442           
443        self.send("Normalized expression array", data)
444        self.send("Filtered expression array", filtered_data)
445       
[1244]446       
[1242]447    def saveGraph(self):
[1632]448        from Orange.OrangeWidgets.OWDlgs import OWChooseImageSizeDlg
[1242]449        dlg = OWChooseImageSizeDlg(self.graph, parent=self)
450        dlg.exec_()
451       
[1244]452       
[1242]453if __name__ == "__main__":
454    app = QApplication(sys.argv)
455    w= OWMAPlot()
[1272]456    data = orange.ExampleTable(os.path.expanduser("~/GDS1210.tab"))
457#    data = orange.ExampleTable(os.path.expanduser("../../../doc/datasets/brown-selected.tab"))
[1242]458    w.setData(data)
459    w.show()
460    app.exec_()
461       
[1632]462       
Note: See TracBrowser for help on using the repository browser.