source: orange/Orange/OrangeWidgets/Classify/OWNomogram.py @ 11096:cf7d2ae9d22b

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

Added new svg icons for the widgets/categories.

Line 
1"""
2<name>Nomogram</name>
3<description>Nomogram viewer for Naive Bayesian, logistic regression or CN2 (EVC only) classifiers.</description>
4<icon>icons/Nomogram.svg</icon>
5<contact>Martin Mozina (martin.mozina(@at@)fri.uni-lj.si)</contact>
6<priority>2500</priority>
7"""
8
9#
10# Nomogram is a Orange widget for
11# for visualization of the knowledge
12# obtained with Naive Bayes or logistic regression classifier
13#
14import math
15import orange
16import OWGUI
17from OWWidget import *
18from OWNomogramGraph import *
19from orngDataCaching import *
20import orngLR
21
22
23aproxZero = 0.0001
24
25def getStartingPoint(d, min):
26    if d == 0:
27        return min
28    elif min<0:
29        curr_num = numpy.arange(-min+d, step=d)
30        curr_num = curr_num[len(curr_num)-1]
31        curr_num = -curr_num
32    elif min - d <= 0:
33        curr_num = 0
34    else:
35        curr_num = numpy.arange(min-d, step=d)
36        curr_num = curr_num[len(curr_num)-1]
37    return curr_num
38
39def getRounding(d):
40    if d == 0:
41        return 2
42    rndFac = math.floor(math.log10(d));
43    if rndFac<-2:
44        rndFac = int(-rndFac)
45    else:
46        rndFac = 2
47    return rndFac
48
49def avg(l):
50    return sum(l)/len(l)
51
52
53class OWNomogram(OWWidget):
54    settingsList = ["alignType", "verticalSpacing", "contType", "verticalSpacingContinuous", "yAxis", "probability", "confidence_check", "confidence_percent", "histogram", "histogram_size", "sort_type"]
55    contextHandlers = {"": DomainContextHandler("", ["TargetClassIndex"], matchValues=1)}
56
57    def __init__(self,parent=None, signalManager = None):
58        OWWidget.__init__(self, parent, signalManager, "Nomogram", 1)
59
60        #self.setWFlags(Qt.WResizeNoErase | Qt.WRepaintNoErase) #this works like magic.. no flicker during repaint!
61        self.parent = parent
62#        self.setWFlags(self.getWFlags()+Qt.WStyle_Maximize)
63
64        self.callbackDeposit = [] # deposit for OWGUI callback functions
65        self.alignType = 0
66        self.contType = 0
67        self.yAxis = 0
68        self.probability = 0
69        self.verticalSpacing = 60
70        self.verticalSpacingContinuous = 100
71        self.diff_between_ordinal = 30
72        self.fontSize = 9
73        self.lineWidth = 1
74        self.histogram = 0
75        self.histogram_size = 10
76        self.data = None
77        self.cl = None
78        self.confidence_check = 0
79        self.confidence_percent = 95
80        self.sort_type = 0
81
82        self.loadSettings()
83
84        self.pointsName = ["Total", "Total"]
85        self.totalPointsName = ["Probability", "Probability"]
86        self.bnomogram = None
87
88
89        self.inputs=[("Classifier", orange.Classifier, self.classifier)]
90
91
92        self.TargetClassIndex = 0
93        self.targetCombo = OWGUI.comboBox(self.controlArea, self, "TargetClassIndex", " Target Class ", addSpace=True, tooltip='Select target (prediction) class in the model.', callback = self.setTarget)
94
95        self.alignRadio = OWGUI.radioButtonsInBox(self.controlArea, self,  'alignType', ['Align left', 'Align by zero influence'], box='Attribute placement',
96                                                  tooltips=['Attributes in nomogram are left aligned', 'Attributes are not aligned, top scale represents true (normalized) regression coefficient value'],
97                                                  addSpace=True,
98                                                  callback=self.showNomogram)
99        self.verticalSpacingLabel = OWGUI.spin(self.alignRadio, self, 'verticalSpacing', 15, 200, label = 'Vertical spacing:',  orientation = 0, tooltip='Define space (pixels) between adjacent attributes.', callback = self.showNomogram)
100
101        self.ContRadio = OWGUI.radioButtonsInBox(self.controlArea, self, 'contType',   ['1D projection', '2D curve'], 'Continuous attributes',
102                                tooltips=['Continuous attribute are presented on a single scale', 'Two dimensional space is used to present continuous attributes in nomogram.'],
103                                addSpace=True,
104                                callback=[lambda:self.verticalSpacingContLabel.setDisabled(not self.contType), self.showNomogram])
105
106        self.verticalSpacingContLabel = OWGUI.spin(OWGUI.indentedBox(self.ContRadio, sep=OWGUI.checkButtonOffsetHint(self.ContRadio.buttons[-1])), self, 'verticalSpacingContinuous', 15, 200, label = "Height", orientation=0, tooltip='Define space (pixels) between adjacent 2d presentation of attributes.', callback = self.showNomogram)
107        self.verticalSpacingContLabel.setDisabled(not self.contType)
108
109        self.yAxisRadio = OWGUI.radioButtonsInBox(self.controlArea, self, 'yAxis', ['Point scale', 'Log odds ratios'], 'Scale',
110                                tooltips=['values are normalized on a 0-100 point scale','values on top axis show log-linear contribution of attribute to full model'],
111                                addSpace=True,
112                                callback=self.showNomogram)
113
114        layoutBox = OWGUI.widgetBox(self.controlArea, "Display", orientation=1, addSpace=True)
115
116        self.probabilityCheck = OWGUI.checkBox(layoutBox, self, 'probability', 'Show prediction',  tooltip='', callback = self.setProbability)
117
118        self.CICheck, self.CILabel = OWGUI.checkWithSpin(layoutBox, self, 'Confidence intervals (%):', min=1, max=99, step = 1, checked='confidence_check', value='confidence_percent', checkCallback=self.showNomogram, spinCallback = self.showNomogram)
119
120        self.histogramCheck, self.histogramLabel = OWGUI.checkWithSpin(layoutBox, self, 'Show histogram, size', min=1, max=30, checked='histogram', value='histogram_size', step = 1, tooltip='-(TODO)-', checkCallback=self.showNomogram, spinCallback = self.showNomogram)
121
122        OWGUI.separator(layoutBox)
123        self.sortOptions = ["No sorting", "Absolute importance", "Positive influence", "Negative influence"]
124        self.sortBox = OWGUI.comboBox(layoutBox, self, "sort_type", label="Sort by ", items=self.sortOptions, callback = self.sortNomogram, orientation="horizontal")
125
126
127        OWGUI.rubber(self.controlArea)
128
129        self.connect(self.graphButton, SIGNAL("clicked()"), self.menuItemPrinter)
130
131
132
133        #add a graph widget
134        self.header = OWNomogramHeader(None, self.mainArea)
135        self.header.setFixedHeight(60)
136        self.header.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
137        self.header.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
138        self.graph = OWNomogramGraph(self.bnomogram, self.mainArea)
139        self.graph.setMinimumWidth(200)
140        self.graph.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
141        self.footer = OWNomogramHeader(None, self.mainArea)
142        self.footer.setFixedHeight(60*2+10)
143        self.footer.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
144        self.footer.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
145
146        self.mainArea.layout().addWidget(self.header)
147        self.mainArea.layout().addWidget(self.graph)
148        self.mainArea.layout().addWidget(self.footer)
149        self.resize(700,500)
150        #self.repaint()
151        #self.update()
152
153        # mouse pressed flag
154        self.mousepr = False
155
156    def sendReport(self):
157        if self.cl:
158            tclass = self.cl.domain.classVar.values[self.TargetClassIndex]
159        else:
160            tclass = "N/A"
161        self.reportSettings("Information",
162                            [("Target class", tclass),
163                             self.confidence_check and ("Confidence intervals", "%i%%" % self.confidence_percent),
164                             ("Sorting", self.sortOptions[self.sort_type] if self.sort_type else "None")])
165        if self.cl:
166            canvases = header, graph, footer = self.header.scene(), self.graph.scene(), self.footer.scene()
167            painter = QPainter()
168            buffer = QPixmap(max(c.width() for c in canvases), sum(c.height() for c in canvases))
169            painter.begin(buffer)
170            painter.fillRect(buffer.rect(), QBrush(QColor(255, 255, 255)))
171            header.render(painter, QRectF(0, 0, header.width(), header.height()), QRectF(0, 0, header.width(), header.height()))
172            graph.render(painter, QRectF(0, header.height(), graph.width(), graph.height()), QRectF(0, 0, graph.width(), graph.height()))
173            footer.render(painter, QRectF(0, header.height()+graph.height(), footer.width(), footer.height()), QRectF(0, 0, footer.width(), footer.height()))
174            painter.end()
175            self.reportImage(lambda filename: buffer.save(filename, os.path.splitext(filename)[1][1:]))
176
177       
178    # Input channel: the Bayesian classifier
179    def nbClassifier(self, cl):
180        # this subroutine computes standard error of estimated beta. Note that it is used only for discrete data,
181        # continuous data have a different computation.
182        def errOld(e, priorError, key, data):
183            inf = 0.0
184            sume = e[0]+e[1]
185            for d in data:
186                if d[at]==key:
187                    inf += (e[0]*e[1]/sume/sume)
188            inf = max(inf, aproxZero)
189            var = max(1/inf - priorError*priorError, 0)
190            return (math.sqrt(var))
191
192        def err(condDist, att, value, targetClass, priorError, data):
193            sumE = sum(condDist)
194            valueE = condDist[targetClass]
195            distAtt = orange.Distribution(att, data)
196            inf = distAtt[value]*(valueE/sumE)*(1-valueE/sumE)
197            inf = max(inf, aproxZero)
198            var = max(1/inf - priorError*priorError, 0)
199            return (math.sqrt(var))
200
201        classVal = cl.domain.classVar
202        att = cl.domain.attributes
203
204        # calculate prior probability
205        dist1 = max(aproxZero, 1-cl.distribution[classVal[self.TargetClassIndex]])
206        dist0 = max(aproxZero, cl.distribution[classVal[self.TargetClassIndex]])
207        prior = dist0/dist1
208        if self.data:
209            sumd = dist1+dist0
210            priorError = math.sqrt(1/((dist1*dist0/sumd/sumd)*len(self.data)))
211        else:
212            priorError = 0
213
214        if self.bnomogram:
215            self.bnomogram.destroy_and_init(self, AttValue("Constant", math.log(prior), error = priorError))
216        else:
217            self.bnomogram = BasicNomogram(self, AttValue("Constant", math.log(prior), error = priorError))
218
219        if self.data:
220            stat = getCached(self.data, orange.DomainBasicAttrStat, (self.data,))
221
222        for at in range(len(att)):
223            a = None
224            if att[at].varType == orange.VarTypes.Discrete:
225                if att[at].ordered:
226                    a = AttrLineOrdered(att[at].name, self.bnomogram)
227                else:
228                    a = AttrLine(att[at].name, self.bnomogram)
229                for cd in cl.conditionalDistributions[at].keys():
230                    # calculuate thickness
231                    conditional0 = max(cl.conditionalDistributions[at][cd][classVal[self.TargetClassIndex]], aproxZero)
232                    conditional1 = max(1-cl.conditionalDistributions[at][cd][classVal[self.TargetClassIndex]], aproxZero)
233                    beta = math.log(conditional0/conditional1/prior)
234                    if self.data:
235                        #thickness = int(round(4.*float(len(self.data.filter({att[at].name:str(cd)})))/float(len(self.data))))
236                        thickness = float(len(self.data.filter({att[at].name:str(cd)})))/float(len(self.data))
237                        se = err(cl.conditionalDistributions[at][cd], att[at], cd, classVal[self.TargetClassIndex], priorError, self.data) # standar error of beta
238                    else:
239                        thickness = 0
240                        se = 0
241
242                    a.addAttValue(AttValue(str(cd), beta, lineWidth=thickness, error = se))
243
244            else:
245                a = AttrLineCont(att[at].name, self.bnomogram)
246                numOfPartitions = 50
247
248                if self.data:
249                    maxAtValue = stat[at].max
250                    minAtValue = stat[at].min
251                else:
252                    maxAtValue = cl.conditionalDistributions[at].keys()[len(cl.conditionalDistributions[at].keys())-1]
253                    minAtValue = cl.conditionalDistributions[at].keys()[0]
254
255                d = maxAtValue-minAtValue
256                d = getDiff(d/numOfPartitions)
257
258                # get curr_num = starting point for continuous att. sampling
259                curr_num = getStartingPoint(d, minAtValue)
260                rndFac = getRounding(d)
261
262                values = []
263                for i in range(2*numOfPartitions):
264                    if curr_num+i*d>=minAtValue and curr_num+i*d<=maxAtValue:
265                        # get thickness
266                        if self.data:
267                            thickness = float(len(self.data.filter({att[at].name:(curr_num+i*d-d/2, curr_num+i*d+d/2)})))/len(self.data)
268                        else:
269                            thickness = 0.0
270                        d_filter = filter(lambda x: x>curr_num+i*d-d/2 and x<curr_num+i*d+d/2, cl.conditionalDistributions[at].keys())
271                        if len(d_filter)>0:
272                            cd = cl.conditionalDistributions[at]
273                            conditional0 = avg([cd[f][classVal[self.TargetClassIndex]] for f in d_filter])
274                            conditional0 = min(1-aproxZero,max(aproxZero,conditional0))
275                            conditional1 = 1-conditional0
276                            try:
277                                # compute error of loess in logistic space
278                                var = avg([cd[f].variances[self.TargetClassIndex] for f in d_filter])
279                                standard_error= math.sqrt(var)
280                                rightError0 = (conditional0+standard_error)/max(conditional1-standard_error, aproxZero)
281                                leftError0  =  max(conditional0-standard_error, aproxZero)/(conditional1+standard_error)
282                                se = (math.log(rightError0) - math.log(leftError0))/2
283                                se = math.sqrt(math.pow(se,2)+math.pow(priorError,2))
284
285                                # add value to set of values
286                                a.addAttValue(AttValue(str(round(curr_num+i*d,rndFac)),
287                                                       math.log(conditional0/conditional1/prior),
288                                                       lineWidth=thickness,
289                                                       error = se))
290                            except:
291                                pass
292                a.continuous = True
293                # invert values:
294            # if there are more than 1 value in the attribute, add it to the nomogram
295            if a and len(a.attValues)>1:
296                self.bnomogram.addAttribute(a)
297
298        self.alignRadio.setDisabled(False)
299        self.graph.setScene(self.bnomogram)
300        self.bnomogram.show()
301
302    # Input channel: the logistic regression classifier
303    def lrClassifier(self, cl):
304        if self.TargetClassIndex == 0 or self.TargetClassIndex == cl.domain.classVar[0]:
305            mult = -1
306        else:
307            mult = 1
308
309        if self.bnomogram:
310            self.bnomogram.destroy_and_init(self, AttValue('Constant', mult*cl.beta[0], error = 0))
311        else:
312            self.bnomogram = BasicNomogram(self, AttValue('Constant', mult*cl.beta[0], error = 0))
313
314        # After applying feature subset selection on discrete attributes
315        # aproximate unknown error for each attribute is math.sqrt(math.pow(cl.beta_se[0],2)/len(at))
316        try:
317            aprox_prior_error = math.sqrt(math.pow(cl.beta_se[0],2)/len(cl.domain.attributes))
318        except:
319            aprox_prior_error = 0
320
321        domain = cl.continuizedDomain or cl.domain
322        if domain:
323            for at in domain.attributes:
324                at.setattr("visited",0)
325
326            for at in domain.attributes:
327                if at.getValueFrom and at.visited==0:
328                    name = at.getValueFrom.variable.name
329                    var = at.getValueFrom.variable
330                    if var.ordered:
331                        a = AttrLineOrdered(name, self.bnomogram)
332                    else:
333                        a = AttrLine(name, self.bnomogram)
334                    listOfExcludedValues = []
335                    for val in var.values:
336                        foundValue = False
337                        for same in domain.attributes:
338                            if same.visited==0 and same.getValueFrom and same.getValueFrom.variable == var and same.getValueFrom.variable.values[same.getValueFrom.transformer.value]==val:
339                                same.setattr("visited",1)
340                                a.addAttValue(AttValue(val, mult*cl.beta[same], error = cl.beta_se[same]))
341                                foundValue = True
342                        if not foundValue:
343                            listOfExcludedValues.append(val)
344                    if len(listOfExcludedValues) == 1:
345                        a.addAttValue(AttValue(listOfExcludedValues[0], 0, error = aprox_prior_error))
346                    elif len(listOfExcludedValues) == 2:
347                        a.addAttValue(AttValue("("+listOfExcludedValues[0]+","+listOfExcludedValues[1]+")", 0, error = aprox_prior_error))
348                    elif len(listOfExcludedValues) > 2:
349                        a.addAttValue(AttValue("Other", 0, error = aprox_prior_error))
350                    # if there are more than 1 value in the attribute, add it to the nomogram
351                    if len(a.attValues)>1:
352                        self.bnomogram.addAttribute(a)
353
354
355                elif at.visited==0:
356                    name = at.name
357                    var = at
358                    a = AttrLineCont(name, self.bnomogram)
359                    if self.data:
360                        bas = getCached(self.data, orange.DomainBasicAttrStat, (self.data,))
361                        maxAtValue = bas[var].max
362                        minAtValue = bas[var].min
363                    else:
364                        maxAtValue = 1.
365                        minAtValue = -1.
366                    numOfPartitions = 50.
367                    d = getDiff((maxAtValue-minAtValue)/numOfPartitions)
368
369                    # get curr_num = starting point for continuous att. sampling
370                    curr_num = getStartingPoint(d, minAtValue)
371                    rndFac = getRounding(d)
372
373                    while curr_num<maxAtValue+d:
374                        if abs(mult*curr_num*cl.beta[at])<aproxZero:
375                            a.addAttValue(AttValue("0.0", 0))
376                        else:
377                            a.addAttValue(AttValue(str(curr_num), mult*curr_num*cl.beta[at]))
378                        curr_num += d
379                    a.continuous = True
380                    at.setattr("visited", 1)
381                    # if there are more than 1 value in the attribute, add it to the nomogram
382                    if len(a.attValues)>1:
383                        self.bnomogram.addAttribute(a)
384
385        self.alignRadio.setDisabled(True)
386        self.alignType = 0
387        self.graph.setScene(self.bnomogram)
388        self.bnomogram.show()
389
390    def svmClassifier(self, cl):
391        import orngLR_Jakulin
392
393        import orngLinVis
394
395        self.error(0)
396        if self.TargetClassIndex == 0 or self.TargetClassIndex == cl.domain.classVar[0]:
397            mult = -1
398        else:
399            mult = 1
400
401        try:
402            visualizer = orngLinVis.Visualizer(self.data, cl, buckets=1, dimensions=1)
403            beta_from_cl = self.cl.estimator.classifier.classifier.beta[0] - self.cl.estimator.translator.trans[0].disp*self.cl.estimator.translator.trans[0].mult*self.cl.estimator.classifier.classifier.beta[1]
404            beta_from_cl = mult*beta_from_cl
405        except:
406            self.error(0, "orngLinVis.Visualizer error"+ str(sys.exc_info()[0])+":"+str(sys.exc_info()[1]))
407#            QMessageBox("orngLinVis.Visualizer error", str(sys.exc_info()[0])+":"+str(sys.exc_info()[1]), QMessageBox.Warning,
408#                        QMessageBox.NoButton, QMessageBox.NoButton, QMessageBox.NoButton, self).show()
409            return
410
411        if self.bnomogram:
412            self.bnomogram.destroy_and_init(self, AttValue('Constant', -mult*math.log((1.0/min(max(visualizer.probfunc(0.0),aproxZero),0.9999))-1), 0))
413        else:
414            self.bnomogram = BasicNomogram(self, AttValue('Constant', -mult*math.log((1.0/min(max(visualizer.probfunc(0.0),aproxZero),0.9999))-1), 0))
415
416        # get maximum and minimum values in visualizer.m
417        maxMap = reduce(numpy.maximum, visualizer.m)
418        minMap = reduce(numpy.minimum, visualizer.m)
419
420        coeff = 0 #
421        at_num = 1
422        correction = self.cl.coeff*self.cl.estimator.translator.trans[0].mult*self.cl.estimator.classifier.classifier.beta[1]
423        for c in visualizer.coeff_names:
424            if type(c[1])==str:
425                for i in range(len(c)):
426                    if i == 0:
427                        if self.data.domain[c[0]].ordered:
428                            a = AttrLineOrdered(c[i], self.bnomogram)
429                        else:
430                            a = AttrLine(c[i], self.bnomogram)
431                        at_num = at_num + 1
432                    else:
433                        if self.data:
434                            thickness = float(len(self.data.filter({self.data.domain[c[0]].name:str(c[i])})))/float(len(self.data))
435                        a.addAttValue(AttValue(c[i], correction*mult*visualizer.coeffs[coeff], lineWidth=thickness))
436                        coeff = coeff + 1
437            else:
438                a = AttrLineCont(c[0], self.bnomogram)
439
440                # get min and max from Data and transform coeff accordingly
441                maxNew=maxMap[coeff]
442                minNew=maxMap[coeff]
443                if self.data:
444                    bas = getCached(self.data, orange.DomainBasicAttrStat, (self.data,))
445                    maxNew = bas[c[0]].max
446                    minNew = bas[c[0]].min
447
448                # transform SVM betas to betas siutable for nomogram
449                if maxNew == minNew:
450                    beta = ((maxMap[coeff]-minMap[coeff])/aproxZero)*visualizer.coeffs[coeff]
451                else:
452                    beta = ((maxMap[coeff]-minMap[coeff])/(maxNew-minNew))*visualizer.coeffs[coeff]
453                n = -minNew+minMap[coeff]
454
455                numOfPartitions = 50
456                d = getDiff((maxNew-minNew)/numOfPartitions)
457
458                # get curr_num = starting point for continuous att. sampling
459                curr_num = getStartingPoint(d, minNew)
460                rndFac = getRounding(d)
461
462                while curr_num<maxNew+d:
463                    a.addAttValue(AttValue(str(curr_num), correction*(mult*(curr_num-minNew)*beta-minMap[coeff]*visualizer.coeffs[coeff])))
464                    curr_num += d
465
466                at_num = at_num + 1
467                coeff = coeff + 1
468                a.continuous = True
469
470            # if there are more than 1 value in the attribute, add it to the nomogram
471            if len(a.attValues)>1:
472                self.bnomogram.addAttribute(a)
473        self.cl.domain = orange.Domain(self.data.domain.classVar)
474        self.graph.setScene(self.bnomogram)
475        self.bnomogram.show()
476
477    # Input channel: the rule classifier (from CN2-EVC only)
478    def ruleClassifier(self, cl):
479        def selectSign(oper):
480            if oper == orange.ValueFilter_continuous.Less:
481                return "<"
482            elif oper == orange.ValueFilter_continuous.LessEqual:
483                return "<="
484            elif oper == orange.ValueFilter_continuous.Greater:
485                return ">"
486            elif oper == orange.ValueFilter_continuous.GreaterEqual:
487                return ">="
488            else: return "="
489
490        def getConditions(rule):
491            conds = rule.filter.conditions
492            domain = rule.filter.domain
493            ret = []
494            if len(conds)==0:
495                ret = ret + ["TRUE"]
496            for i,c in enumerate(conds):
497                if i > 0:
498                    ret[-1] += " & "
499                if type(c) == orange.ValueFilter_discrete:
500                    ret += [domain[c.position].name + "=" + str(domain[c.position].values[int(c.values[0])])]
501                elif type(c) == orange.ValueFilter_continuous:
502                    ret += [domain[c.position].name + selectSign(c.oper) + "%.3f"%c.ref]
503            return ret
504
505        self.error(1)
506        if not len(self.data.domain.classVar.values) == 2:
507            self.error(1, "Rules require binary classes")
508        classVal = cl.domain.classVar
509        att = cl.domain.attributes
510
511        if self.TargetClassIndex == 0 or self.TargetClassIndex == cl.domain.classVar[0]:
512            mult = 1.
513        else:
514            mult = -1.
515
516        # calculate prior probability (from self.TargetClassIndex)
517        if self.bnomogram:
518            self.bnomogram.destroy_and_init(self, AttValue("Constant", 0.0))
519        else:
520            self.bnomogram = BasicNomogram(self, AttValue("Constant", 0.0))
521        self.cl.setattr("rulesOrdering", [])
522        for r_i,r in enumerate(cl.rules):
523            a = AttrLine(getConditions(r), self.bnomogram)
524            self.cl.rulesOrdering.append(getConditions(r))
525            if r.classifier.defaultVal == 0:
526                sign = mult
527            else: sign = -mult
528            a.addAttValue(AttValue("yes", sign*cl.ruleBetas[r_i], lineWidth=0, error = 0.0))
529            a.addAttValue(AttValue("no", 0.0, lineWidth=0, error = 0.0))
530            self.bnomogram.addAttribute(a)
531
532        self.graph.setScene(self.bnomogram)
533        self.bnomogram.show()
534
535
536    def initClassValues(self, classValue):
537        self.targetCombo.clear()
538        self.targetCombo.addItems([str(v) for v in classValue])
539
540    def classifier(self, cl):
541        self.closeContext()
542        self.error(2) 
543
544        oldcl = self.cl
545        self.cl = None
546       
547        if cl:
548            for acceptable in (orange.BayesClassifier, orange.LogRegClassifier, orange.RuleClassifier_logit):
549                if isinstance(cl, acceptable):
550                    self.cl = cl
551                    break
552            else:
553                self.error(2, "Nomograms can be drawn for only for Bayesian classifier and logistic regression")
554                 
555        if not oldcl or not self.cl or not oldcl.domain == self.cl.domain:
556            if self.cl:
557                self.initClassValues(self.cl.domain.classVar)
558            self.TargetClassIndex = 0
559           
560        self.data = getattr(self.cl, "data", None)
561
562        if self.data and self.data.domain and not self.data.domain.classVar:
563            self.error(2, "Classless domain")
564            # Here it said "return", but let us report an error and clean up the widget
565            self.cl = self.data = None
566
567        self.openContext("", self.data)
568        if not self.data:
569            self.histogramCheck.setChecked(False)
570            self.histogramCheck.setDisabled(True)
571            self.histogramLabel.setDisabled(True)
572            self.CICheck.setChecked(False)
573            self.CICheck.setDisabled(True)
574            self.CILabel.setDisabled(True)
575        else:
576            self.histogramCheck.setEnabled(True)
577            self.histogramCheck.makeConsistent()
578            self.CICheck.setEnabled(True)
579            self.CICheck.makeConsistent()
580        self.updateNomogram()
581
582    def setTarget(self):
583        self.updateNomogram()
584
585    def updateNomogram(self):
586##        import orngSVM
587
588        def setNone():
589            for view in [self.footer, self.header, self.graph]:
590                scene = view.scene()
591                if scene:
592                    for item in scene.items():
593                        scene.removeItem(item)
594
595        if self.data and self.cl: # and not type(self.cl) == orngLR_Jakulin.MarginMetaClassifier:
596            #check domains
597            for at in self.cl.domain:
598                if at.getValueFrom and hasattr(at.getValueFrom, "variable"):
599                    if (not at.getValueFrom.variable in self.data.domain) and (not at in self.data.domain):
600                        return
601                else:
602                    if not at in self.data.domain:
603                        return
604
605        if isinstance(self.cl, orange.BayesClassifier):
606#            if len(self.cl.domain.classVar.values)>2:
607#                QMessageBox("OWNomogram:", " Please use only Bayes classifiers that are induced on data with dichotomous class!", QMessageBox.Warning,
608#                            QMessageBox.NoButton, QMessageBox.NoButton, QMessageBox.NoButton, self).show()
609#            else:
610                self.nbClassifier(self.cl)
611##        elif isinstance(self.cl, orngLR_Jakulin.MarginMetaClassifier) and self.data:
612##            self.svmClassifier(self.cl)
613        elif isinstance(self.cl, orange.RuleClassifier_logit):
614            self.ruleClassifier(self.cl)
615
616        elif isinstance(self.cl, orange.LogRegClassifier):
617            # get if there are any continuous attributes in data -> then we need data to compute margins
618            cont = False
619            if self.cl.continuizedDomain:
620                for at in self.cl.continuizedDomain.attributes:
621                    if not at.getValueFrom:
622                        cont = True
623            if self.data or not cont:
624                self.lrClassifier(self.cl)
625            else:
626                setNone()
627        else:
628            setNone()
629        if self.sort_type>0:
630            self.sortNomogram()
631
632    def sortNomogram(self):
633        def sign(x):
634            if x<0:
635                return -1;
636            return 1;
637        def compare_to_ordering_in_rules(x,y):
638            return self.cl.rulesOrdering.index(x.name) - self.cl.rulesOrdering.index(y.name)
639        def compare_to_ordering_in_data(x,y):
640            return self.data.domain.attributes.index(self.data.domain[x.name]) - self.data.domain.attributes.index(self.data.domain[y.name])
641        def compare_to_ordering_in_domain(x,y):
642            return self.cl.domain.attributes.index(self.cl.domain[x.name]) - self.cl.domain.attributes.index(self.cl.domain[y.name])
643        def compate_beta_difference(x,y):
644            return -sign(x.maxValue-x.minValue-y.maxValue+y.minValue)
645        def compare_beta_positive(x, y):
646            return -sign(x.maxValue-y.maxValue)
647        def compare_beta_negative(x, y):
648            return sign(x.minValue-y.minValue)
649
650        if not self.bnomogram:
651            return
652        if self.sort_type == 0 and hasattr(self.cl, "rulesOrdering"):
653            self.bnomogram.attributes.sort(compare_to_ordering_in_rules)
654        elif self.sort_type == 0 and self.data:
655            self.bnomogram.attributes.sort(compare_to_ordering_in_data)
656        elif self.sort_type == 0 and self.cl and self.cl.domain:
657            self.bnomogram.attributes.sort(compare_to_ordering_in_domain)
658        if self.sort_type == 1:
659            self.bnomogram.attributes.sort(compate_beta_difference)
660        elif self.sort_type == 2:
661            self.bnomogram.attributes.sort(compare_beta_positive)
662        elif self.sort_type == 3:
663            self.bnomogram.attributes.sort(compare_beta_negative)
664
665        # update nomogram
666        self.showNomogram()
667
668
669    def setProbability(self):
670        if self.probability and self.bnomogram:
671            self.bnomogram.showAllMarkers()
672        elif self.bnomogram:
673            self.bnomogram.hideAllMarkers()
674
675    def setBaseLine(self):
676        if self.bnomogram:
677            self.bnomogram.showBaseLine(True)
678
679    def menuItemPrinter(self):
680        import copy
681        canvases = header, graph, footer = self.header.scene(), self.graph.scene(), self.footer.scene()
682        # all scenes together
683        scene_confed = QGraphicsScene(0, 0, max(c.width() for c in canvases), sum(c.height() for c in canvases))
684        # add items from header
685        header_its = header.items()
686        for it in header_its:
687            scene_confed.addItem(it)
688        # add items from graph
689        graph_its = graph.items()
690        for it in graph_its:
691            scene_confed.addItem(it)
692            it.moveBy(0., header.height())
693        # add from footer
694        footer_its = footer.items()
695        for it in footer_its:
696            scene_confed.addItem(it)
697            it.moveBy(0.,header.height() + graph.height())
698        try:
699            import OWDlgs
700        except:
701            print "Missing file 'OWDlgs.py'. This file should be in OrangeWidgets folder. Unable to print/save image."
702        sizeDlg = OWDlgs.OWChooseImageSizeDlg(scene_confed, parent=self)
703        sizeDlg.exec_()
704
705        # set all items back to original canvases           
706        for it in header_its:
707            header.addItem(it)
708        for it in graph_its:
709            graph.addItem(it)
710            it.moveBy(0., -header.height())
711        for it in footer_its:
712            footer.addItem(it)
713            it.moveBy(0, - header.height() - graph.height())
714        self.showNomogram()
715
716    # Callbacks
717    def showNomogram(self):
718        if self.bnomogram and self.cl:
719            #self.bnomogram.hide()
720            self.bnomogram.show()
721            self.bnomogram.update()
722
723
724# test widget appearance
725if __name__=="__main__":
726    import orngLR, orngSVM
727
728    a=QApplication(sys.argv)
729    ow=OWNomogram()
730    a.setMainWidget(ow)
731    data = orange.ExampleTable("../../doc/datasets/heart_disease.tab")
732
733    bayes = orange.BayesLearner(data)
734    bayes.setattr("data",data)
735    ow.classifier(bayes)
736
737    # here you can test setting some stuff
738   
739    a.exec_()
740
741    # save settings
742    ow.saveSettings()
743
Note: See TracBrowser for help on using the repository browser.