source: orange-bioinformatics/_bioinformatics/widgets/OWMAPlot.py @ 1636:10d234fdadb9

Revision 1636:10d234fdadb9, 18.0 KB checked in by mitar, 2 years ago (diff)

Restructuring because we will not be using namespaces.

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