source: orange/Orange/OrangeWidgets/Evaluate/OWLiftCurve.py @ 11096:cf7d2ae9d22b

Revision 11096:cf7d2ae9d22b, 17.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 19 months ago (diff)

Added new svg icons for the widgets/categories.

Line 
1"""
2<name>Lift Curve</name>
3<description>Displays a lift curve based on evaluation of classifiers.</description>
4<contact>Tomaz Curk</contact>
5<icon>icons/LiftCurve.svg</icon>
6<priority>1020</priority>
7"""
8from OWColorPalette import ColorPixmap
9from OWWidget import *
10from OWGraph import *
11from OWGUI import *
12from OWROC import *
13
14import orngStat, orngEval
15import statc, math
16from Orange.evaluation.testing import TEST_TYPE_SINGLE
17
18class singleClassLiftCurveGraph(singleClassROCgraph):
19    def __init__(self, parent = None, name = None, title = ""):
20        singleClassROCgraph.__init__(self, parent, name)
21
22        self.enableYRaxis(1)
23        self.setXaxisTitle("P Rate")
24        self.setAxisAutoScale(QwtPlot.yRight)
25        self.setAxisAutoScale(QwtPlot.yLeft)
26        self.setYLaxisTitle("TP")
27        self.setShowYRaxisTitle(1)
28        self.setYRaxisTitle("Cost")
29
30        self.setShowMainTitle(1)
31        self.setMainTitle(title)
32        self.averagingMethod = 'merge'
33
34    def computeCurve(self, res, classIndex=-1, keepConcavities=1):
35        return orngStat.computeLiftCurve(res, classIndex)
36
37    def setNumberOfClassifiersIterationsAndClassifierColors(self, classifierNames, iterationsNum, classifierColor):
38        singleClassROCgraph.setNumberOfClassifiersIterationsAndClassifierColors(self, classifierNames, iterationsNum, classifierColor)
39        self.performanceLineCKey.setYAxis(QwtPlot.yRight)
40        self.performanceLineCKey.setSymbol(QwtSymbol())
41
42    def setTestSetData(self, splitByIterations, targetClass):
43        self.splitByIterations = splitByIterations
44        ## generate the "base" unmodified Lift curves
45        self.targetClass = targetClass
46        iteration = 0
47
48        for isplit in splitByIterations:
49            # unmodified Lift curve
50            P, N, curves = self.computeCurve(isplit, self.targetClass)
51            self.setIterationCurves(iteration, curves)
52            iteration += 1
53
54    ## the lift curve is the average curve from the selected test sets
55    ## no other average curves here
56    def calcAverageCurves(self):
57        ##
58        ## self.averagingMethod == 'merge':
59        mergedIterations = orngEval.ExperimentResults(1, self.splitByIterations[0].classifierNames, self.splitByIterations[0].classValues, self.splitByIterations[0].weights, classifiers=self.splitByIterations[0].classifiers, loaded=self.splitByIterations[0].loaded)
60        i = 0
61        for show, isplit in zip(self.showIterations, self.splitByIterations):
62            if show:
63                for te in isplit.results:
64                    mergedIterations.results.append( te )
65            i += 1
66        self.mergedConvexHullData = []
67        if len(mergedIterations.results) > 0:
68            self.P, self.N, curves = self.computeCurve(mergedIterations, self.targetClass, 1)
69            _, _, convexCurves = self.computeCurve(mergedIterations, self.targetClass, 0)
70            classifier = 0
71            for c in curves:
72                x = [px for (px, py, pf) in c]
73                y = [py for (px, py, pf) in c]
74                curve = self.mergedCKeys[classifier]
75                curve.setData(x, y)
76                classifier += 1
77            classifier = 0
78            for c in convexCurves:
79                self.mergedConvexHullData.append(c) ## put all points of all curves into one big array
80                x = [px for (px, py, pf) in c]
81                y = [py for (px, py, pf) in c]
82                curve = self.mergedConvexCKeys[classifier]
83                curve.setData(x, y)
84                classifier += 1
85
86            self.diagonalCKey.setData([0.0, 1.0], [0.0, self.P])
87        else:
88            for c in range(len(self.mergedCKeys)):
89                self.mergedCKeys[c].setData([], [])
90                self.mergedConvexCKeys[c].setData([], [])
91
92    ## always set to 'merge' mode
93    def setAveragingMethod(self, m):
94        self.averagingMethod = 'merge'
95        self.updateCurveDisplay()
96
97    ## performance line
98    def calcUpdatePerformanceLine(self):
99        ## now draw the closest line to the curve
100        b = (self.averagingMethod == 'merge') and self.showPerformanceLine
101        self.removeMarkers()
102        costx = []
103        costy = []
104
105        firstGlobalMinP = 1
106        globalMinCost = 0
107        globalMinCostPoints = []
108
109        for (x, TP, fp) in self.hullCurveDataForPerfLine:
110            first = 1
111            minc = 0
112            localMinCostPoints = []
113            for (cNum, (threshold, FPrate)) in fp:
114                if TP > self.P:
115                    import warnings
116                    warnings.warn("The sky is falling!!")
117                cost = self.pvalue*(1.0 - TP/(self.P or 1))*self.FNcost + (1.0 - self.pvalue)*FPrate*self.FPcost
118                if first or cost < minc:
119                    first = 0
120                    minc = cost
121                    localMinCostPoints = [ (x, minc, threshold, cNum) ]
122                else:
123                    if cost == minc:
124                        localMinCostPoints.append( (x, minc, threshold, cNum) )
125
126            if firstGlobalMinP or minc < globalMinCost:
127                firstGlobalMinP = 0
128                globalMinCost = minc
129                globalMinCostPoints = [l for l in localMinCostPoints]
130            else:
131                if minc == globalMinCost:
132                    globalMinCostPoints.extend(localMinCostPoints)
133
134            costx.append(x)
135            costy.append(minc)
136
137        if self.performanceLineCKey: #self.curve(self.performanceLineCKey):
138            self.performanceLineCKey.setData(costx, costy)
139            self.performanceLineCKey.setVisible(b)
140        self.replot()
141#        self.update()
142
143        nOnMinc = {}
144        for (x, minc, threshold, cNum) in globalMinCostPoints:
145            s = "c:%.1f, th:%1.3f %s" % (minc, threshold, self.classifierNames[cNum])
146            marker = self.addMarker(s, 0, 0)
147            marker.setAxis(QwtPlot.xBottom, QwtPlot.yRight)
148            onYCn = nOnMinc.get(str(x), 0)
149
150            lminc = self.invTransform(QwtPlot.yLeft, self.transform(QwtPlot.yRight, minc)) ## ugly
151            if onYCn > 0:
152                lminc = lminc - onYCn*0.05
153                nOnMinc[str(x)] = nOnMinc[str(x)] + 1
154                marker.setSymbol(QwtSymbol())
155            else:
156                nOnMinc[str(x)] = 1
157                marker.setSymbol(self.performanceLineSymbol)
158
159            lminc = self.invTransform(QwtPlot.yRight, self.transform(QwtPlot.yLeft, lminc)) ## ugly ugly
160
161            marker.setXValue(x)
162            marker.setYValue(lminc)
163            if x >= 0.90:
164                marker.setLabelAlignment(Qt.AlignLeft)
165            else:
166                marker.setLabelAlignment(Qt.AlignRight)
167
168            marker.setVisible(b)
169
170    def setPointWidth(self, v):
171        self.performanceLineSymbol.setSize(v, v)
172        for marker in [item for item in self.itemList() if isinstance(item, QwtPlotMarker)]:
173            marker.setSymbol(self.performanceLineSymbol)
174        self.replot()
175#        self.update()
176
177class OWLiftCurve(OWROC):
178    settingsList = ["PointWidth", "CurveWidth", "ShowDiagonal",
179                    "ConvexHullCurveWidth", "HullColor", "ShowConvexHull", "ShowConvexCurves", "EnablePerformance"]
180    contextHandlers = {"": EvaluationResultsContextHandler("", "targetClass", "selectedClassifiers")}
181
182    def __init__(self, parent=None, signalManager = None):
183        OWWidget.__init__(self, parent, signalManager, "Lift Curve Analysis", 1)
184
185        # inputs
186        self.inputs=[("Evaluation Results", orngTest.ExperimentResults, self.results, Default)]
187
188        # default settings
189        self.PointWidth = 7
190        self.CurveWidth = 3
191        self.ConvexCurveWidth = 1
192        self.ShowDiagonal = TRUE
193        self.ConvexHullCurveWidth = 3
194        self.HullColor = str(QColor(Qt.yellow).name())
195        self.ShowConvexHull = TRUE
196        self.ShowConvexCurves = FALSE
197        self.EnablePerformance = TRUE
198        self.classifiers = []
199        self.selectedClassifiers = []
200
201        #load settings
202        self.loadSettings()
203
204### Moved here to override the saved settings since the controls do not exist any more
205        self.CurveWidth = 3
206        self.ConvexCurveWidth = 1
207        self.ConvexHullCurveWidth = 3
208
209        # temp variables
210        self.dres = None
211        self.classifierColor = None
212        self.numberOfClasses  = 0
213        self.targetClass = None
214        self.numberOfClassifiers = 0
215        self.numberOfIterations = 0
216        self.graphs = []
217        self.maxp = 1000
218        self.defaultPerfLinePValues = []
219
220        # performance analysis (temporary values
221        self.FPcost = 500.0
222        self.FNcost = 500.0
223        self.pvalue = 50.0 ##0.400
224
225        # list of values (remember for each class)
226        self.FPcostList = []
227        self.FNcostList = []
228        self.pvalueList = []
229
230        # GUI
231        #self.grid.expand(3, 3)
232        import sip
233        sip.delete(self.mainArea.layout())
234        self.graphsGridLayoutQGL = QGridLayout(self.mainArea)
235        self.mainArea.setLayout(self.graphsGridLayoutQGL)
236
237        # save each ROC graph in separate file
238        self.connect(self.graphButton, SIGNAL("clicked()"), self.saveToFile)
239
240        ## general tab
241        self.tabs = OWGUI.tabWidget(self.controlArea)
242        self.generalTab = OWGUI.createTabPage(self.tabs, "General")
243
244       
245        ## target class
246        self.classCombo = OWGUI.comboBox(self.generalTab, self, 'targetClass', box='Target class', items=[], callback=self.target)
247        OWGUI.separator(self.generalTab)
248
249        ## classifiers selection (classifiersQLB)
250        self.classifiersQVGB = OWGUI.widgetBox(self.generalTab, "Classifiers", addSpace=True)
251        self.classifiersQLB = OWGUI.listBox(self.classifiersQVGB, self, "selectedClassifiers", selectionMode = QListWidget.MultiSelection, callback = self.classifiersSelectionChange)
252        self.unselectAllClassifiersQLB = OWGUI.button(self.classifiersQVGB, self, "(Un)select all", callback = self.SUAclassifiersQLB)
253##        OWGUI.checkBox(self.classifiersQVGB, self, 'ShowConvexHull', 'Show convex lift hull', tooltip='', callback=self.setShowConvexHull)
254##        OWGUI.checkBox(self.classifiersQVGB, self, 'ShowDiagonal', 'Show diagonal', tooltip='', callback=self.setShowDiagonal)
255
256        # show Lift Curve convex hull
257        OWGUI.checkBox(self.generalTab, self, 'ShowConvexHull', 'Show lift convex hull', tooltip='', callback=self.setShowConvexHull)
258               
259
260        # performance analysis
261        self.performanceTab = OWGUI.createTabPage(self.tabs, "Analysis")
262        self.performanceTabCosts = OWGUI.widgetBox(self.performanceTab)
263        OWGUI.checkBox(self.performanceTabCosts, self, 'EnablePerformance', 'Show cost function', tooltip='', callback=self.setShowPerformanceAnalysis)
264
265        ## FP and FN cost ranges
266        mincost = 1; maxcost = 1000; stepcost = 5;
267        self.maxpsum = 100; self.minp = 1; self.maxp = self.maxpsum - self.minp ## need it also in self.pvaluesUpdated
268        stepp = 1.0
269
270        OWGUI.widgetLabel(self.performanceTabCosts, "False positive cost")
271        OWGUI.hSlider(OWGUI.indentedBox(self.performanceTabCosts), self, 'FPcost', minValue=mincost, maxValue=maxcost, step=stepcost, callback=self.costsChanged, ticks=50)
272        OWGUI.widgetLabel(self.performanceTabCosts, "False negative cost")
273        OWGUI.hSlider(OWGUI.indentedBox(self.performanceTabCosts), self, 'FNcost', minValue=mincost, maxValue=maxcost, step=stepcost, callback=self.costsChanged, ticks=50)
274
275        OWGUI.widgetLabel(self.performanceTabCosts, "Prior target class probability [%]")
276        ptc = OWGUI.indentedBox(self.performanceTabCosts)
277        OWGUI.hSlider(ptc, self, 'pvalue', minValue=self.minp, maxValue=self.maxp, step=stepp, callback=self.pvaluesUpdated, ticks=5, labelFormat="%2.1f")
278        OWGUI.separator(ptc)
279        OWGUI.button(ptc, self, 'Compute from data', self.setDefaultPValues) ## reset p values to default
280
281
282        ## test set selection (testSetsQLB)
283        self.testSetsQVGB = OWGUI.widgetBox(self.performanceTab, "Test sets")
284        self.testSetsQLB = OWGUI.listBox(self.testSetsQVGB, self, selectionMode = QListWidget.MultiSelection, callback = self.testSetsSelectionChange)
285        self.unselectAllTestSetsQLB = OWGUI.button(self.testSetsQVGB, self, "(Un)select all", callback = self.SUAtestSetsQLB)
286
287        # settings tab
288        self.settingsTab = OWGUI.createTabPage(self.tabs, "Settings")
289        OWGUI.hSlider(self.settingsTab, self, 'PointWidth', box='Point width', minValue=0, maxValue=9, step=1, callback=self.setPointWidth, ticks=1)
290        OWGUI.hSlider(self.settingsTab, self, 'CurveWidth', box='Lift curve width', minValue=1, maxValue=5, step=1, callback=self.setCurveWidth, ticks=1)
291        OWGUI.hSlider(self.settingsTab, self, 'ConvexHullCurveWidth', box='Lift curve convex hull', minValue=2, maxValue=9, step=1, callback=self.setConvexHullCurveWidth, ticks=1)
292        OWGUI.checkBox(self.settingsTab, self, 'ShowDiagonal', 'Show diagonal', tooltip='', callback=self.setShowDiagonal)
293        OWGUI.rubber(self.settingsTab)
294##        self.SettingsTab.addStretch(100)
295
296#        OWGUI.rubber(self.controlArea)
297        self.resize(770, 530)
298
299    def sendReport(self):
300        # need to reimport - Qt provides something stupid instead
301        from __builtin__ import hex
302        self.reportSettings("Settings",
303                            [("Classifiers", ", ".join('<font color="#%s">%s</font>' % ("".join(("0"+hex(x)[2:])[-2:] for x in self.classifierColor[cNum].getRgb()[:3]), str(item.text()))
304                                                        for cNum, item in enumerate(self.classifiersQLB.item(i) for i in range(self.classifiersQLB.count()))
305                                                          if item.isSelected())),
306                             ("Target class", self.classCombo.itemText(self.targetClass)
307                                              if self.targetClass is not None else
308                                              "N/A"),
309                             ("Costs", "FP=%i, FN=%i" % (self.FPcost, self.FNcost)),
310                             ("Prior target class probability", "%i%%" % self.pvalue)
311                            ])
312        if self.targetClass is not None:
313            self.reportRaw("<br/>")
314            self.reportImage(self.graphs[self.targetClass].saveToFileDirect, QSize(500, 400))
315
316
317    def calcAllClassGraphs(self):
318        for (cl, g) in enumerate(self.graphs):
319            g.setNumberOfClassifiersIterationsAndClassifierColors(self.dres.classifierNames, self.numberOfIterations, self.classifierColor)
320            g.setTestSetData(self.dresSplitByIterations, cl)
321            g.setShowConvexHull(self.ShowConvexHull)
322            g.setShowPerformanceLine(self.EnablePerformance)
323
324            ## user settings
325            g.setPointWidth(self.PointWidth)
326            g.setCurveWidth(self.CurveWidth)
327            g.setShowDiagonal(self.ShowDiagonal)
328            g.setConvexHullCurveWidth(self.ConvexHullCurveWidth)
329            g.setHullColor(QColor(self.HullColor))
330
331    def results(self, dres):
332        self.closeContext()
333
334        self.FPcostList = []
335        self.FNcostList = []
336        self.pvalueList = []
337
338        self.classCombo.clear()
339        self.removeGraphs()
340        self.testSetsQLB.clear()
341        self.classifiersQLB.clear()
342
343        self.warning([0, 1])
344
345        if dres is not None and dres.class_values is None:
346            self.warning(1, "Lift Curve cannot be used for regression results.")
347            dres = None
348
349        self.dres = dres
350
351        if not dres:
352            self.targetClass = None
353            self.openContext("", dres)
354            return
355
356        if dres and dres.test_type != TEST_TYPE_SINGLE:
357            self.warning(0, "Lift curve is supported only for single-target prediction problems.")
358            return
359
360        self.defaultPerfLinePValues = []
361        if self.dres <> None:
362            ## classQLB
363            self.numberOfClasses = len(self.dres.classValues)
364            self.graphs = []
365
366            for i in range(self.numberOfClasses):
367                self.FPcostList.append( 500)
368                self.FNcostList.append( 500)
369                graph = singleClassLiftCurveGraph(self.mainArea, "", "Predicted class: " + self.dres.classValues[i])
370                self.graphs.append( graph )
371                self.classCombo.addItem(self.dres.classValues[i])
372
373            ## classifiersQLB
374            self.classifierColor = []
375            self.numberOfClassifiers = self.dres.numberOfLearners
376            if self.numberOfClassifiers > 1:
377                allCforHSV = self.numberOfClassifiers - 1
378            else:
379                allCforHSV = self.numberOfClassifiers
380            for i in range(self.numberOfClassifiers):
381                newColor = QColor()
382                newColor.setHsv(i*255/allCforHSV, 255, 255)
383                self.classifierColor.append( newColor )
384
385            ## testSetsQLB
386            self.dresSplitByIterations = orngStat.splitByIterations(self.dres)
387            self.numberOfIterations = len(self.dresSplitByIterations)
388
389            self.calcAllClassGraphs()
390
391            ## classifiersQLB
392            for i in range(self.numberOfClassifiers):
393                newColor = self.classifierColor[i]
394                self.classifiersQLB.addItem(QListWidgetItem(ColorPixmap(newColor), self.dres.classifierNames[i]))
395            self.classifiersQLB.selectAll()
396
397            ## testSetsQLB
398            self.testSetsQLB.addItems([str(i) for i in range(self.numberOfIterations)])
399            self.testSetsQLB.selectAll()
400
401            ## calculate default pvalues
402            reminder = self.maxp
403            for f in orngStat.classProbabilitiesFromRes(self.dres):
404                v = int(round(f * self.maxp))
405                reminder -= v
406                if reminder < 0:
407                    v = v+reminder
408                self.defaultPerfLinePValues.append(v)
409                self.pvalueList.append( v)
410
411            self.targetClass = 0 ## select first target
412            self.target()
413        else:
414            self.classifierColor = None
415        self.openContext("", self.dres)
416        self.performanceTabCosts.setEnabled(1)
417        self.setDefaultPValues()
418
419if __name__ == "__main__":
420    a = QApplication(sys.argv)
421    owdm = OWLiftCurve()
422    owdm.show()
423    a.exec_()
424
425
Note: See TracBrowser for help on using the repository browser.