# source:orange/Orange/OrangeWidgets/Classify/OWNomogram.py@9671:a7b056375472

Revision 9671:a7b056375472, 32.8 KB checked in by anze <anze.staric@…>, 2 years ago (diff)

Moved orange to Orange (part 2)

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.png</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
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'],
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.'],
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'],
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
130
131
132
138        self.graph = OWNomogramGraph(self.bnomogram, self.mainArea)
139        self.graph.setMinimumWidth(200)
140        self.graph.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
142        self.footer.setFixedHeight(60*2+10)
143        self.footer.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
144        self.footer.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
145
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:
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)))
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
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:
297
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:
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:
376                        else:
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:
384
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))
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:
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:
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))
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
680        import copy
682        # all scenes together
683        scene_confed = QGraphicsScene(0, 0, max(c.width() for c in canvases), sum(c.height() for c in canvases))
688        # add items from graph
689        graph_its = graph.items()
690        for it in graph_its:
694        footer_its = footer.items()
695        for it in footer_its:
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
708        for it in graph_its:
711        for it in footer_its:
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.