def __init__(self, parent=None, signalManager=None): OWWidget.__init__(self, parent, signalManager, "Nomogram", 1) #self.setWFlags(Qt.WResizeNoErase | Qt.WRepaintNoErase) #this works like magic.. no flicker during repaint! self.parent = parent # self.setWFlags(self.getWFlags()+Qt.WStyle_Maximize) self.callbackDeposit = [] # deposit for OWGUI callback functions self.alignType = 0 self.contType = 0 self.yAxis = 0 self.probability = 0 self.verticalSpacing = 60 self.verticalSpacingContinuous = 100 self.diff_between_ordinal = 30 self.fontSize = 9 self.lineWidth = 1 self.histogram = 0 self.histogram_size = 10 self.data = None self.cl = None self.confidence_check = 0 self.confidence_percent = 95 self.sort_type = 0 self.loadSettings() self.pointsName = ["Total", "Total"] self.totalPointsName = ["Probability", "Probability"] self.bnomogram = None self.inputs = [("Classifier", orange.Classifier, self.classifier)] self.TargetClassIndex = 0 self.targetCombo = OWGUI.comboBox( self.controlArea, self, "TargetClassIndex", " Target Class ", addSpace=True, tooltip='Select target (prediction) class in the model.', callback=self.setTarget) self.alignRadio = OWGUI.radioButtonsInBox( self.controlArea, self, 'alignType', ['Align left', 'Align by zero influence'], box='Attribute placement', tooltips=[ 'Attributes in nomogram are left aligned', 'Attributes are not aligned, top scale represents true (normalized) regression coefficient value' ], addSpace=True, callback=self.showNomogram) 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) self.ContRadio = OWGUI.radioButtonsInBox( self.controlArea, self, 'contType', ['1D projection', '2D curve'], 'Continuous attributes', tooltips=[ 'Continuous attribute are presented on a single scale', 'Two dimensional space is used to present continuous attributes in nomogram.' ], addSpace=True, callback=[ lambda: self.verticalSpacingContLabel.setDisabled( not self.contType), self.showNomogram ]) 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) self.verticalSpacingContLabel.setDisabled(not self.contType) self.yAxisRadio = OWGUI.radioButtonsInBox( self.controlArea, self, 'yAxis', ['Point scale', 'Log odds ratios'], 'Scale', tooltips=[ 'values are normalized on a 0-100 point scale', 'values on top axis show log-linear contribution of attribute to full model' ], addSpace=True, callback=self.showNomogram) layoutBox = OWGUI.widgetBox(self.controlArea, "Display", orientation=1, addSpace=True) self.probabilityCheck = OWGUI.checkBox(layoutBox, self, 'probability', 'Show prediction', tooltip='', callback=self.setProbability) 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) 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) OWGUI.separator(layoutBox) self.sortOptions = [ "No sorting", "Absolute importance", "Positive influence", "Negative influence" ] self.sortBox = OWGUI.comboBox(layoutBox, self, "sort_type", label="Sort by ", items=self.sortOptions, callback=self.sortNomogram, orientation="horizontal") OWGUI.rubber(self.controlArea) self.connect(self.graphButton, SIGNAL("clicked()"), self.menuItemPrinter) #add a graph widget self.header = OWNomogramHeader(None, self.mainArea) self.header.setFixedHeight(60) self.header.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.header.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.graph = OWNomogramGraph(self.bnomogram, self.mainArea) self.graph.setMinimumWidth(200) self.graph.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.footer = OWNomogramHeader(None, self.mainArea) self.footer.setFixedHeight(60 * 2 + 10) self.footer.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.footer.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.mainArea.layout().addWidget(self.header) self.mainArea.layout().addWidget(self.graph) self.mainArea.layout().addWidget(self.footer) self.resize(700, 500) #self.repaint() #self.update() # mouse pressed flag self.mousepr = False
def __init__(self,parent=None, signalManager = None): OWWidget.__init__(self, parent, signalManager, "Nomogram", 1) #self.setWFlags(Qt.WResizeNoErase | Qt.WRepaintNoErase) #this works like magic.. no flicker during repaint! self.parent = parent # self.setWFlags(self.getWFlags()+Qt.WStyle_Maximize) self.callbackDeposit = [] # deposit for OWGUI callback functions self.alignType = 0 self.contType = 0 self.yAxis = 0 self.probability = 0 self.verticalSpacing = 60 self.verticalSpacingContinuous = 100 self.diff_between_ordinal = 30 self.fontSize = 9 self.lineWidth = 1 self.histogram = 0 self.histogram_size = 10 self.data = None self.cl = None self.confidence_check = 0 self.confidence_percent = 95 self.sort_type = 0 self.loadSettings() self.pointsName = ["Total", "Total"] self.totalPointsName = ["Probability", "Probability"] self.bnomogram = None self.inputs=[("Classifier", orange.Classifier, self.classifier)] self.TargetClassIndex = 0 self.targetCombo = OWGUI.comboBox(self.controlArea, self, "TargetClassIndex", " Target Class ", addSpace=True, tooltip='Select target (prediction) class in the model.', callback = self.setTarget) self.alignRadio = OWGUI.radioButtonsInBox(self.controlArea, self, 'alignType', ['Align left', 'Align by zero influence'], box='Attribute placement', tooltips=['Attributes in nomogram are left aligned', 'Attributes are not aligned, top scale represents true (normalized) regression coefficient value'], addSpace=True, callback=self.showNomogram) 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) self.ContRadio = OWGUI.radioButtonsInBox(self.controlArea, self, 'contType', ['1D projection', '2D curve'], 'Continuous attributes', tooltips=['Continuous attribute are presented on a single scale', 'Two dimensional space is used to present continuous attributes in nomogram.'], addSpace=True, callback=[lambda:self.verticalSpacingContLabel.setDisabled(not self.contType), self.showNomogram]) 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) self.verticalSpacingContLabel.setDisabled(not self.contType) self.yAxisRadio = OWGUI.radioButtonsInBox(self.controlArea, self, 'yAxis', ['Point scale', 'Log odds ratios'], 'Scale', tooltips=['values are normalized on a 0-100 point scale','values on top axis show log-linear contribution of attribute to full model'], addSpace=True, callback=self.showNomogram) layoutBox = OWGUI.widgetBox(self.controlArea, "Display", orientation=1, addSpace=True) self.probabilityCheck = OWGUI.checkBox(layoutBox, self, 'probability', 'Show prediction', tooltip='', callback = self.setProbability) 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) 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) OWGUI.separator(layoutBox) self.sortOptions = ["No sorting", "Absolute importance", "Positive influence", "Negative influence"] self.sortBox = OWGUI.comboBox(layoutBox, self, "sort_type", label="Sort by ", items=self.sortOptions, callback = self.sortNomogram, orientation="horizontal") OWGUI.rubber(self.controlArea) self.connect(self.graphButton, SIGNAL("clicked()"), self.menuItemPrinter) #add a graph widget self.header = OWNomogramHeader(None, self.mainArea) self.header.setFixedHeight(60) self.header.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.header.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.graph = OWNomogramGraph(self.bnomogram, self.mainArea) self.graph.setMinimumWidth(200) self.graph.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.footer = OWNomogramHeader(None, self.mainArea) self.footer.setFixedHeight(60*2+10) self.footer.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.footer.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.mainArea.layout().addWidget(self.header) self.mainArea.layout().addWidget(self.graph) self.mainArea.layout().addWidget(self.footer) self.resize(700,500) #self.repaint() #self.update() # mouse pressed flag self.mousepr = False
class OWNomogram(OWWidget): settingsList = [ "alignType", "verticalSpacing", "contType", "verticalSpacingContinuous", "yAxis", "probability", "confidence_check", "confidence_percent", "histogram", "histogram_size", "sort_type" ] contextHandlers = { "": DomainContextHandler("", ["TargetClassIndex"], matchValues=1) } def __init__(self, parent=None, signalManager=None): OWWidget.__init__(self, parent, signalManager, "Nomogram", 1) #self.setWFlags(Qt.WResizeNoErase | Qt.WRepaintNoErase) #this works like magic.. no flicker during repaint! self.parent = parent # self.setWFlags(self.getWFlags()+Qt.WStyle_Maximize) self.callbackDeposit = [] # deposit for OWGUI callback functions self.alignType = 0 self.contType = 0 self.yAxis = 0 self.probability = 0 self.verticalSpacing = 60 self.verticalSpacingContinuous = 100 self.diff_between_ordinal = 30 self.fontSize = 9 self.lineWidth = 1 self.histogram = 0 self.histogram_size = 10 self.data = None self.cl = None self.confidence_check = 0 self.confidence_percent = 95 self.sort_type = 0 self.loadSettings() self.pointsName = ["Total", "Total"] self.totalPointsName = ["Probability", "Probability"] self.bnomogram = None self.inputs = [("Classifier", orange.Classifier, self.classifier)] self.TargetClassIndex = 0 self.targetCombo = OWGUI.comboBox( self.controlArea, self, "TargetClassIndex", " Target Class ", addSpace=True, tooltip='Select target (prediction) class in the model.', callback=self.setTarget) self.alignRadio = OWGUI.radioButtonsInBox( self.controlArea, self, 'alignType', ['Align left', 'Align by zero influence'], box='Attribute placement', tooltips=[ 'Attributes in nomogram are left aligned', 'Attributes are not aligned, top scale represents true (normalized) regression coefficient value' ], addSpace=True, callback=self.showNomogram) 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) self.ContRadio = OWGUI.radioButtonsInBox( self.controlArea, self, 'contType', ['1D projection', '2D curve'], 'Continuous attributes', tooltips=[ 'Continuous attribute are presented on a single scale', 'Two dimensional space is used to present continuous attributes in nomogram.' ], addSpace=True, callback=[ lambda: self.verticalSpacingContLabel.setDisabled( not self.contType), self.showNomogram ]) 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) self.verticalSpacingContLabel.setDisabled(not self.contType) self.yAxisRadio = OWGUI.radioButtonsInBox( self.controlArea, self, 'yAxis', ['Point scale', 'Log odds ratios'], 'Scale', tooltips=[ 'values are normalized on a 0-100 point scale', 'values on top axis show log-linear contribution of attribute to full model' ], addSpace=True, callback=self.showNomogram) layoutBox = OWGUI.widgetBox(self.controlArea, "Display", orientation=1, addSpace=True) self.probabilityCheck = OWGUI.checkBox(layoutBox, self, 'probability', 'Show prediction', tooltip='', callback=self.setProbability) 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) 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) OWGUI.separator(layoutBox) self.sortOptions = [ "No sorting", "Absolute importance", "Positive influence", "Negative influence" ] self.sortBox = OWGUI.comboBox(layoutBox, self, "sort_type", label="Sort by ", items=self.sortOptions, callback=self.sortNomogram, orientation="horizontal") OWGUI.rubber(self.controlArea) self.connect(self.graphButton, SIGNAL("clicked()"), self.menuItemPrinter) #add a graph widget self.header = OWNomogramHeader(None, self.mainArea) self.header.setFixedHeight(60) self.header.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.header.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.graph = OWNomogramGraph(self.bnomogram, self.mainArea) self.graph.setMinimumWidth(200) self.graph.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.footer = OWNomogramHeader(None, self.mainArea) self.footer.setFixedHeight(60 * 2 + 10) self.footer.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.footer.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.mainArea.layout().addWidget(self.header) self.mainArea.layout().addWidget(self.graph) self.mainArea.layout().addWidget(self.footer) self.resize(700, 500) #self.repaint() #self.update() # mouse pressed flag self.mousepr = False def sendReport(self): if self.cl: tclass = self.cl.domain.classVar.values[self.TargetClassIndex] else: tclass = "N/A" self.reportSettings( "Information", [("Target class", tclass), self.confidence_check and ("Confidence intervals", "%i%%" % self.confidence_percent), ("Sorting", self.sortOptions[self.sort_type] if self.sort_type else "None")]) if self.cl: canvases = header, graph, footer = self.header.scene( ), self.graph.scene(), self.footer.scene() painter = QPainter() buffer = QPixmap(max(c.width() for c in canvases), sum(c.height() for c in canvases)) painter.begin(buffer) painter.fillRect(buffer.rect(), QBrush(QColor(255, 255, 255))) header.render(painter, QRectF(0, 0, header.width(), header.height()), QRectF(0, 0, header.width(), header.height())) graph.render( painter, QRectF(0, header.height(), graph.width(), graph.height()), QRectF(0, 0, graph.width(), graph.height())) footer.render( painter, QRectF(0, header.height() + graph.height(), footer.width(), footer.height()), QRectF(0, 0, footer.width(), footer.height())) painter.end() self.reportImage(lambda filename: buffer.save( filename, os.path.splitext(filename)[1][1:])) # Input channel: the Bayesian classifier def nbClassifier(self, cl): # this subroutine computes standard error of estimated beta. Note that it is used only for discrete data, # continuous data have a different computation. def errOld(e, priorError, key, data): inf = 0.0 sume = e[0] + e[1] for d in data: if d[at] == key: inf += (e[0] * e[1] / sume / sume) inf = max(inf, aproxZero) var = max(1 / inf - priorError * priorError, 0) return (math.sqrt(var)) def err(condDist, att, value, targetClass, priorError, data): sumE = sum(condDist) valueE = condDist[targetClass] distAtt = orange.Distribution(att, data) inf = distAtt[value] * (valueE / sumE) * (1 - valueE / sumE) inf = max(inf, aproxZero) var = max(1 / inf - priorError * priorError, 0) return (math.sqrt(var)) classVal = cl.domain.classVar att = cl.domain.attributes # calculate prior probability dist1 = max(aproxZero, 1 - cl.distribution[classVal[self.TargetClassIndex]]) dist0 = max(aproxZero, cl.distribution[classVal[self.TargetClassIndex]]) prior = dist0 / dist1 if self.data: sumd = dist1 + dist0 priorError = math.sqrt( 1 / ((dist1 * dist0 / sumd / sumd) * len(self.data))) else: priorError = 0 if self.bnomogram: self.bnomogram.destroy_and_init( self, AttValue("Constant", math.log(prior), error=priorError)) else: self.bnomogram = BasicNomogram( self, AttValue("Constant", math.log(prior), error=priorError)) if self.data: stat = getCached(self.data, orange.DomainBasicAttrStat, (self.data, )) for at in range(len(att)): a = None if att[at].varType == orange.VarTypes.Discrete: if att[at].ordered: a = AttrLineOrdered(att[at].name, self.bnomogram) else: a = AttrLine(att[at].name, self.bnomogram) for cd in cl.conditionalDistributions[at].keys(): # calculuate thickness conditional0 = max( cl.conditionalDistributions[at][cd][classVal[ self.TargetClassIndex]], aproxZero) conditional1 = max( 1 - cl.conditionalDistributions[at][cd][classVal[ self.TargetClassIndex]], aproxZero) beta = math.log(conditional0 / conditional1 / prior) if self.data: #thickness = int(round(4.*float(len(self.data.filter({att[at].name:str(cd)})))/float(len(self.data)))) thickness = float( len(self.data.filter({att[at].name: str(cd) }))) / float(len(self.data)) se = err(cl.conditionalDistributions[at][cd], att[at], cd, classVal[self.TargetClassIndex], priorError, self.data) # standar error of beta else: thickness = 0 se = 0 a.addAttValue( AttValue(str(cd), beta, lineWidth=thickness, error=se)) else: a = AttrLineCont(att[at].name, self.bnomogram) numOfPartitions = 50 if self.data: maxAtValue = stat[at].max minAtValue = stat[at].min else: maxAtValue = cl.conditionalDistributions[at].keys()[ len(cl.conditionalDistributions[at].keys()) - 1] minAtValue = cl.conditionalDistributions[at].keys()[0] d = maxAtValue - minAtValue d = getDiff(d / numOfPartitions) # get curr_num = starting point for continuous att. sampling curr_num = getStartingPoint(d, minAtValue) rndFac = getRounding(d) values = [] for i in range(2 * numOfPartitions): if curr_num + i * d >= minAtValue and curr_num + i * d <= maxAtValue: # get thickness if self.data: thickness = float( len( self.data.filter({ att[at].name: (curr_num + i * d - d / 2, curr_num + i * d + d / 2) }))) / len(self.data) else: thickness = 0.0 d_filter = filter( lambda x: x > curr_num + i * d - d / 2 and x < curr_num + i * d + d / 2, cl.conditionalDistributions[at].keys()) if len(d_filter) > 0: cd = cl.conditionalDistributions[at] conditional0 = avg([ cd[f][classVal[self.TargetClassIndex]] for f in d_filter ]) conditional0 = min(1 - aproxZero, max(aproxZero, conditional0)) conditional1 = 1 - conditional0 try: # compute error of loess in logistic space var = avg([ cd[f].variances[self.TargetClassIndex] for f in d_filter ]) standard_error = math.sqrt(var) rightError0 = (conditional0 + standard_error) / max( conditional1 - standard_error, aproxZero) leftError0 = max(conditional0 - standard_error, aproxZero) / (conditional1 + standard_error) se = (math.log(rightError0) - math.log(leftError0)) / 2 se = math.sqrt( math.pow(se, 2) + math.pow(priorError, 2)) # add value to set of values a.addAttValue( AttValue(str( round(curr_num + i * d, rndFac)), math.log(conditional0 / conditional1 / prior), lineWidth=thickness, error=se)) except: pass a.continuous = True # invert values: # if there are more than 1 value in the attribute, add it to the nomogram if a and len(a.attValues) > 1: self.bnomogram.addAttribute(a) self.alignRadio.setDisabled(False) self.graph.setScene(self.bnomogram) self.bnomogram.show() # Input channel: the logistic regression classifier def lrClassifier(self, cl): if self.TargetClassIndex == 0 or self.TargetClassIndex == cl.domain.classVar[ 0]: mult = -1 else: mult = 1 if self.bnomogram: self.bnomogram.destroy_and_init( self, AttValue('Constant', mult * cl.beta[0], error=0)) else: self.bnomogram = BasicNomogram( self, AttValue('Constant', mult * cl.beta[0], error=0)) # After applying feature subset selection on discrete attributes # aproximate unknown error for each attribute is math.sqrt(math.pow(cl.beta_se[0],2)/len(at)) try: aprox_prior_error = math.sqrt( math.pow(cl.beta_se[0], 2) / len(cl.domain.attributes)) except: aprox_prior_error = 0 domain = cl.continuizedDomain or cl.domain if domain: for at in domain.attributes: at.setattr("visited", 0) for at in domain.attributes: if at.getValueFrom and at.visited == 0: name = at.getValueFrom.variable.name var = at.getValueFrom.variable if var.ordered: a = AttrLineOrdered(name, self.bnomogram) else: a = AttrLine(name, self.bnomogram) listOfExcludedValues = [] for val in var.values: foundValue = False for same in domain.attributes: if same.visited == 0 and same.getValueFrom and same.getValueFrom.variable == var and same.getValueFrom.variable.values[ same.getValueFrom.transformer. value] == val: same.setattr("visited", 1) a.addAttValue( AttValue(val, mult * cl.beta[same], error=cl.beta_se[same])) foundValue = True if not foundValue: listOfExcludedValues.append(val) if len(listOfExcludedValues) == 1: a.addAttValue( AttValue(listOfExcludedValues[0], 0, error=aprox_prior_error)) elif len(listOfExcludedValues) == 2: a.addAttValue( AttValue("(" + listOfExcludedValues[0] + "," + listOfExcludedValues[1] + ")", 0, error=aprox_prior_error)) elif len(listOfExcludedValues) > 2: a.addAttValue( AttValue("Other", 0, error=aprox_prior_error)) # if there are more than 1 value in the attribute, add it to the nomogram if len(a.attValues) > 1: self.bnomogram.addAttribute(a) elif at.visited == 0: name = at.name var = at a = AttrLineCont(name, self.bnomogram) if self.data: bas = getCached(self.data, orange.DomainBasicAttrStat, (self.data, )) maxAtValue = bas[var].max minAtValue = bas[var].min else: maxAtValue = 1. minAtValue = -1. numOfPartitions = 50. d = getDiff((maxAtValue - minAtValue) / numOfPartitions) # get curr_num = starting point for continuous att. sampling curr_num = getStartingPoint(d, minAtValue) rndFac = getRounding(d) while curr_num < maxAtValue + d: if abs(mult * curr_num * cl.beta[at]) < aproxZero: a.addAttValue(AttValue("0.0", 0)) else: a.addAttValue( AttValue(str(curr_num), mult * curr_num * cl.beta[at])) curr_num += d a.continuous = True at.setattr("visited", 1) # if there are more than 1 value in the attribute, add it to the nomogram if len(a.attValues) > 1: self.bnomogram.addAttribute(a) self.alignRadio.setDisabled(True) self.alignType = 0 self.graph.setScene(self.bnomogram) self.bnomogram.show() def svmClassifier(self, cl): import orngLR_Jakulin import orngLinVis self.error(0) if self.TargetClassIndex == 0 or self.TargetClassIndex == cl.domain.classVar[ 0]: mult = -1 else: mult = 1 try: visualizer = orngLinVis.Visualizer(self.data, cl, buckets=1, dimensions=1) 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] beta_from_cl = mult * beta_from_cl except: self.error( 0, "orngLinVis.Visualizer error" + str(sys.exc_info()[0]) + ":" + str(sys.exc_info()[1])) # QMessageBox("orngLinVis.Visualizer error", str(sys.exc_info()[0])+":"+str(sys.exc_info()[1]), QMessageBox.Warning, # QMessageBox.NoButton, QMessageBox.NoButton, QMessageBox.NoButton, self).show() return if self.bnomogram: self.bnomogram.destroy_and_init( self, AttValue( 'Constant', -mult * math.log((1.0 / min( max(visualizer.probfunc(0.0), aproxZero), 0.9999)) - 1), 0)) else: self.bnomogram = BasicNomogram( self, AttValue( 'Constant', -mult * math.log((1.0 / min( max(visualizer.probfunc(0.0), aproxZero), 0.9999)) - 1), 0)) # get maximum and minimum values in visualizer.m maxMap = reduce(numpy.maximum, visualizer.m) minMap = reduce(numpy.minimum, visualizer.m) coeff = 0 # at_num = 1 correction = self.cl.coeff * self.cl.estimator.translator.trans[ 0].mult * self.cl.estimator.classifier.classifier.beta[1] for c in visualizer.coeff_names: if type(c[1]) == str: for i in range(len(c)): if i == 0: if self.data.domain[c[0]].ordered: a = AttrLineOrdered(c[i], self.bnomogram) else: a = AttrLine(c[i], self.bnomogram) at_num = at_num + 1 else: if self.data: thickness = float( len( self.data.filter({ self.data.domain[c[0]].name: str(c[i]) }))) / float(len(self.data)) a.addAttValue( AttValue(c[i], correction * mult * visualizer.coeffs[coeff], lineWidth=thickness)) coeff = coeff + 1 else: a = AttrLineCont(c[0], self.bnomogram) # get min and max from Data and transform coeff accordingly maxNew = maxMap[coeff] minNew = maxMap[coeff] if self.data: bas = getCached(self.data, orange.DomainBasicAttrStat, (self.data, )) maxNew = bas[c[0]].max minNew = bas[c[0]].min # transform SVM betas to betas siutable for nomogram if maxNew == minNew: beta = ((maxMap[coeff] - minMap[coeff]) / aproxZero) * visualizer.coeffs[coeff] else: beta = ((maxMap[coeff] - minMap[coeff]) / (maxNew - minNew)) * visualizer.coeffs[coeff] n = -minNew + minMap[coeff] numOfPartitions = 50 d = getDiff((maxNew - minNew) / numOfPartitions) # get curr_num = starting point for continuous att. sampling curr_num = getStartingPoint(d, minNew) rndFac = getRounding(d) while curr_num < maxNew + d: a.addAttValue( AttValue( str(curr_num), correction * (mult * (curr_num - minNew) * beta - minMap[coeff] * visualizer.coeffs[coeff]))) curr_num += d at_num = at_num + 1 coeff = coeff + 1 a.continuous = True # if there are more than 1 value in the attribute, add it to the nomogram if len(a.attValues) > 1: self.bnomogram.addAttribute(a) self.cl.domain = orange.Domain(self.data.domain.classVar) self.graph.setScene(self.bnomogram) self.bnomogram.show() # Input channel: the rule classifier (from CN2-EVC only) def ruleClassifier(self, cl): def selectSign(oper): if oper == orange.ValueFilter_continuous.Less: return "<" elif oper == orange.ValueFilter_continuous.LessEqual: return "<=" elif oper == orange.ValueFilter_continuous.Greater: return ">" elif oper == orange.ValueFilter_continuous.GreaterEqual: return ">=" else: return "=" def getConditions(rule): conds = rule.filter.conditions domain = rule.filter.domain ret = [] if len(conds) == 0: ret = ret + ["TRUE"] for i, c in enumerate(conds): if i > 0: ret[-1] += " & " if type(c) == orange.ValueFilter_discrete: ret += [ domain[c.position].name + "=" + str(domain[c.position].values[int(c.values[0])]) ] elif type(c) == orange.ValueFilter_continuous: ret += [ domain[c.position].name + selectSign(c.oper) + "%.3f" % c.ref ] return ret self.error(1) if not len(self.data.domain.classVar.values) == 2: self.error(1, "Rules require binary classes") classVal = cl.domain.classVar att = cl.domain.attributes if self.TargetClassIndex == 0 or self.TargetClassIndex == cl.domain.classVar[ 0]: mult = 1. else: mult = -1. # calculate prior probability (from self.TargetClassIndex) if self.bnomogram: self.bnomogram.destroy_and_init(self, AttValue("Constant", 0.0)) else: self.bnomogram = BasicNomogram(self, AttValue("Constant", 0.0)) self.cl.setattr("rulesOrdering", []) for r_i, r in enumerate(cl.rules): a = AttrLine(getConditions(r), self.bnomogram) self.cl.rulesOrdering.append(getConditions(r)) if r.classifier.defaultVal == 0: sign = mult else: sign = -mult a.addAttValue( AttValue("yes", sign * cl.ruleBetas[r_i], lineWidth=0, error=0.0)) a.addAttValue(AttValue("no", 0.0, lineWidth=0, error=0.0)) self.bnomogram.addAttribute(a) self.graph.setScene(self.bnomogram) self.bnomogram.show() def initClassValues(self, classValue): self.targetCombo.clear() self.targetCombo.addItems([str(v) for v in classValue]) def classifier(self, cl): self.closeContext() self.error(2) oldcl = self.cl self.cl = None if cl: for acceptable in (orange.BayesClassifier, orange.LogRegClassifier, orange.RuleClassifier_logit): if isinstance(cl, acceptable): self.cl = cl break else: self.error( 2, "Nomograms can be drawn for only for Bayesian classifier and logistic regression" ) if not oldcl or not self.cl or not oldcl.domain == self.cl.domain: if self.cl: self.initClassValues(self.cl.domain.classVar) self.TargetClassIndex = 0 self.data = getattr(self.cl, "data", None) if self.data and self.data.domain and not self.data.domain.classVar: self.error(2, "Classless domain") # Here it said "return", but let us report an error and clean up the widget self.cl = self.data = None self.openContext("", self.data) if not self.data: self.histogramCheck.setChecked(False) self.histogramCheck.setDisabled(True) self.histogramLabel.setDisabled(True) self.CICheck.setChecked(False) self.CICheck.setDisabled(True) self.CILabel.setDisabled(True) else: self.histogramCheck.setEnabled(True) self.histogramCheck.makeConsistent() self.CICheck.setEnabled(True) self.CICheck.makeConsistent() self.updateNomogram() def setTarget(self): self.updateNomogram() def updateNomogram(self): ## import orngSVM def setNone(): for view in [self.footer, self.header, self.graph]: scene = view.scene() if scene: for item in scene.items(): scene.removeItem(item) if self.data and self.cl: # and not type(self.cl) == orngLR_Jakulin.MarginMetaClassifier: #check domains for at in self.cl.domain: if at.getValueFrom and hasattr(at.getValueFrom, "variable"): if (not at.getValueFrom.variable in self.data.domain) and ( not at in self.data.domain): return else: if not at in self.data.domain: return if isinstance(self.cl, orange.BayesClassifier): # if len(self.cl.domain.classVar.values)>2: # QMessageBox("OWNomogram:", " Please use only Bayes classifiers that are induced on data with dichotomous class!", QMessageBox.Warning, # QMessageBox.NoButton, QMessageBox.NoButton, QMessageBox.NoButton, self).show() # else: self.nbClassifier(self.cl) ## elif isinstance(self.cl, orngLR_Jakulin.MarginMetaClassifier) and self.data: ## self.svmClassifier(self.cl) elif isinstance(self.cl, orange.RuleClassifier_logit): self.ruleClassifier(self.cl) elif isinstance(self.cl, orange.LogRegClassifier): # get if there are any continuous attributes in data -> then we need data to compute margins cont = False if self.cl.continuizedDomain: for at in self.cl.continuizedDomain.attributes: if not at.getValueFrom: cont = True if self.data or not cont: self.lrClassifier(self.cl) else: setNone() else: setNone() if self.sort_type > 0: self.sortNomogram() def sortNomogram(self): def sign(x): if x < 0: return -1 return 1 def compare_to_ordering_in_rules(x, y): return self.cl.rulesOrdering.index( x.name) - self.cl.rulesOrdering.index(y.name) def compare_to_ordering_in_data(x, y): return self.data.domain.attributes.index( self.data.domain[x.name]) - self.data.domain.attributes.index( self.data.domain[y.name]) def compare_to_ordering_in_domain(x, y): return self.cl.domain.attributes.index( self.cl.domain[x.name]) - self.cl.domain.attributes.index( self.cl.domain[y.name]) def compate_beta_difference(x, y): return -sign(x.maxValue - x.minValue - y.maxValue + y.minValue) def compare_beta_positive(x, y): return -sign(x.maxValue - y.maxValue) def compare_beta_negative(x, y): return sign(x.minValue - y.minValue) if not self.bnomogram: return if self.sort_type == 0 and hasattr(self.cl, "rulesOrdering"): self.bnomogram.attributes.sort(compare_to_ordering_in_rules) elif self.sort_type == 0 and self.data: self.bnomogram.attributes.sort(compare_to_ordering_in_data) elif self.sort_type == 0 and self.cl and self.cl.domain: self.bnomogram.attributes.sort(compare_to_ordering_in_domain) if self.sort_type == 1: self.bnomogram.attributes.sort(compate_beta_difference) elif self.sort_type == 2: self.bnomogram.attributes.sort(compare_beta_positive) elif self.sort_type == 3: self.bnomogram.attributes.sort(compare_beta_negative) # update nomogram self.showNomogram() def setProbability(self): if self.probability and self.bnomogram: self.bnomogram.showAllMarkers() elif self.bnomogram: self.bnomogram.hideAllMarkers() def setBaseLine(self): if self.bnomogram: self.bnomogram.showBaseLine(True) def menuItemPrinter(self): import copy canvases = header, graph, footer = self.header.scene( ), self.graph.scene(), self.footer.scene() # all scenes together scene_confed = QGraphicsScene(0, 0, max(c.width() for c in canvases), sum(c.height() for c in canvases)) # add items from header header_its = header.items() for it in header_its: scene_confed.addItem(it) # add items from graph graph_its = graph.items() for it in graph_its: scene_confed.addItem(it) it.moveBy(0., header.height()) # add from footer footer_its = footer.items() for it in footer_its: scene_confed.addItem(it) it.moveBy(0., header.height() + graph.height()) try: import OWDlgs except: print "Missing file 'OWDlgs.py'. This file should be in OrangeWidgets folder. Unable to print/save image." sizeDlg = OWDlgs.OWChooseImageSizeDlg(scene_confed, parent=self) sizeDlg.exec_() # set all items back to original canvases for it in header_its: header.addItem(it) for it in graph_its: graph.addItem(it) it.moveBy(0., -header.height()) for it in footer_its: footer.addItem(it) it.moveBy(0, -header.height() - graph.height()) self.showNomogram() # Callbacks def showNomogram(self): if self.bnomogram and self.cl: #self.bnomogram.hide() self.bnomogram.show() self.bnomogram.update()
class OWNomogram(OWWidget): settingsList = ["alignType", "verticalSpacing", "contType", "verticalSpacingContinuous", "yAxis", "probability", "confidence_check", "confidence_percent", "histogram", "histogram_size", "sort_type"] contextHandlers = {"": DomainContextHandler("", ["TargetClassIndex"], matchValues=1)} def __init__(self,parent=None, signalManager = None): OWWidget.__init__(self, parent, signalManager, "Nomogram", 1) #self.setWFlags(Qt.WResizeNoErase | Qt.WRepaintNoErase) #this works like magic.. no flicker during repaint! self.parent = parent # self.setWFlags(self.getWFlags()+Qt.WStyle_Maximize) self.callbackDeposit = [] # deposit for OWGUI callback functions self.alignType = 0 self.contType = 0 self.yAxis = 0 self.probability = 0 self.verticalSpacing = 60 self.verticalSpacingContinuous = 100 self.diff_between_ordinal = 30 self.fontSize = 9 self.lineWidth = 1 self.histogram = 0 self.histogram_size = 10 self.data = None self.cl = None self.confidence_check = 0 self.confidence_percent = 95 self.sort_type = 0 self.loadSettings() self.pointsName = ["Total", "Total"] self.totalPointsName = ["Probability", "Probability"] self.bnomogram = None self.inputs=[("Classifier", orange.Classifier, self.classifier)] self.TargetClassIndex = 0 self.targetCombo = OWGUI.comboBox(self.controlArea, self, "TargetClassIndex", " Target Class ", addSpace=True, tooltip='Select target (prediction) class in the model.', callback = self.setTarget) self.alignRadio = OWGUI.radioButtonsInBox(self.controlArea, self, 'alignType', ['Align left', 'Align by zero influence'], box='Attribute placement', tooltips=['Attributes in nomogram are left aligned', 'Attributes are not aligned, top scale represents true (normalized) regression coefficient value'], addSpace=True, callback=self.showNomogram) 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) self.ContRadio = OWGUI.radioButtonsInBox(self.controlArea, self, 'contType', ['1D projection', '2D curve'], 'Continuous attributes', tooltips=['Continuous attribute are presented on a single scale', 'Two dimensional space is used to present continuous attributes in nomogram.'], addSpace=True, callback=[lambda:self.verticalSpacingContLabel.setDisabled(not self.contType), self.showNomogram]) 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) self.verticalSpacingContLabel.setDisabled(not self.contType) self.yAxisRadio = OWGUI.radioButtonsInBox(self.controlArea, self, 'yAxis', ['Point scale', 'Log odds ratios'], 'Scale', tooltips=['values are normalized on a 0-100 point scale','values on top axis show log-linear contribution of attribute to full model'], addSpace=True, callback=self.showNomogram) layoutBox = OWGUI.widgetBox(self.controlArea, "Display", orientation=1, addSpace=True) self.probabilityCheck = OWGUI.checkBox(layoutBox, self, 'probability', 'Show prediction', tooltip='', callback = self.setProbability) 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) 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) OWGUI.separator(layoutBox) self.sortOptions = ["No sorting", "Absolute importance", "Positive influence", "Negative influence"] self.sortBox = OWGUI.comboBox(layoutBox, self, "sort_type", label="Sort by ", items=self.sortOptions, callback = self.sortNomogram, orientation="horizontal") OWGUI.rubber(self.controlArea) self.connect(self.graphButton, SIGNAL("clicked()"), self.menuItemPrinter) #add a graph widget self.header = OWNomogramHeader(None, self.mainArea) self.header.setFixedHeight(60) self.header.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.header.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.graph = OWNomogramGraph(self.bnomogram, self.mainArea) self.graph.setMinimumWidth(200) self.graph.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.footer = OWNomogramHeader(None, self.mainArea) self.footer.setFixedHeight(60*2+10) self.footer.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.footer.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.mainArea.layout().addWidget(self.header) self.mainArea.layout().addWidget(self.graph) self.mainArea.layout().addWidget(self.footer) self.resize(700,500) #self.repaint() #self.update() # mouse pressed flag self.mousepr = False def sendReport(self): if self.cl: tclass = self.cl.domain.classVar.values[self.TargetClassIndex] else: tclass = "N/A" self.reportSettings("Information", [("Target class", tclass), self.confidence_check and ("Confidence intervals", "%i%%" % self.confidence_percent), ("Sorting", self.sortOptions[self.sort_type] if self.sort_type else "None")]) if self.cl: canvases = header, graph, footer = self.header.scene(), self.graph.scene(), self.footer.scene() painter = QPainter() buffer = QPixmap(max(c.width() for c in canvases), sum(c.height() for c in canvases)) painter.begin(buffer) painter.fillRect(buffer.rect(), QBrush(QColor(255, 255, 255))) header.render(painter, QRectF(0, 0, header.width(), header.height()), QRectF(0, 0, header.width(), header.height())) graph.render(painter, QRectF(0, header.height(), graph.width(), graph.height()), QRectF(0, 0, graph.width(), graph.height())) footer.render(painter, QRectF(0, header.height()+graph.height(), footer.width(), footer.height()), QRectF(0, 0, footer.width(), footer.height())) painter.end() self.reportImage(lambda filename: buffer.save(filename, os.path.splitext(filename)[1][1:])) # Input channel: the Bayesian classifier def nbClassifier(self, cl): # this subroutine computes standard error of estimated beta. Note that it is used only for discrete data, # continuous data have a different computation. def errOld(e, priorError, key, data): inf = 0.0 sume = e[0]+e[1] for d in data: if d[at]==key: inf += (e[0]*e[1]/sume/sume) inf = max(inf, aproxZero) var = max(1/inf - priorError*priorError, 0) return (math.sqrt(var)) def err(condDist, att, value, targetClass, priorError, data): sumE = sum(condDist) valueE = condDist[targetClass] distAtt = orange.Distribution(att, data) inf = distAtt[value]*(valueE/sumE)*(1-valueE/sumE) inf = max(inf, aproxZero) var = max(1/inf - priorError*priorError, 0) return (math.sqrt(var)) classVal = cl.domain.classVar att = cl.domain.attributes # calculate prior probability dist1 = max(aproxZero, 1-cl.distribution[classVal[self.TargetClassIndex]]) dist0 = max(aproxZero, cl.distribution[classVal[self.TargetClassIndex]]) prior = dist0/dist1 if self.data: sumd = dist1+dist0 priorError = math.sqrt(1/((dist1*dist0/sumd/sumd)*len(self.data))) else: priorError = 0 if self.bnomogram: self.bnomogram.destroy_and_init(self, AttValue("Constant", math.log(prior), error = priorError)) else: self.bnomogram = BasicNomogram(self, AttValue("Constant", math.log(prior), error = priorError)) if self.data: stat = getCached(self.data, orange.DomainBasicAttrStat, (self.data,)) for at in range(len(att)): a = None if att[at].varType == orange.VarTypes.Discrete: if att[at].ordered: a = AttrLineOrdered(att[at].name, self.bnomogram) else: a = AttrLine(att[at].name, self.bnomogram) for cd in cl.conditionalDistributions[at].keys(): # calculuate thickness conditional0 = max(cl.conditionalDistributions[at][cd][classVal[self.TargetClassIndex]], aproxZero) conditional1 = max(1-cl.conditionalDistributions[at][cd][classVal[self.TargetClassIndex]], aproxZero) beta = math.log(conditional0/conditional1/prior) if self.data: #thickness = int(round(4.*float(len(self.data.filter({att[at].name:str(cd)})))/float(len(self.data)))) thickness = float(len(self.data.filter({att[at].name:str(cd)})))/float(len(self.data)) se = err(cl.conditionalDistributions[at][cd], att[at], cd, classVal[self.TargetClassIndex], priorError, self.data) # standar error of beta else: thickness = 0 se = 0 a.addAttValue(AttValue(str(cd), beta, lineWidth=thickness, error = se)) else: a = AttrLineCont(att[at].name, self.bnomogram) numOfPartitions = 50 if self.data: maxAtValue = stat[at].max minAtValue = stat[at].min else: maxAtValue = cl.conditionalDistributions[at].keys()[len(cl.conditionalDistributions[at].keys())-1] minAtValue = cl.conditionalDistributions[at].keys()[0] d = maxAtValue-minAtValue d = getDiff(d/numOfPartitions) # get curr_num = starting point for continuous att. sampling curr_num = getStartingPoint(d, minAtValue) rndFac = getRounding(d) values = [] for i in range(2*numOfPartitions): if curr_num+i*d>=minAtValue and curr_num+i*d<=maxAtValue: # get thickness if self.data: thickness = float(len(self.data.filter({att[at].name:(curr_num+i*d-d/2, curr_num+i*d+d/2)})))/len(self.data) else: thickness = 0.0 d_filter = filter(lambda x: x>curr_num+i*d-d/2 and x<curr_num+i*d+d/2, cl.conditionalDistributions[at].keys()) if len(d_filter)>0: cd = cl.conditionalDistributions[at] conditional0 = avg([cd[f][classVal[self.TargetClassIndex]] for f in d_filter]) conditional0 = min(1-aproxZero,max(aproxZero,conditional0)) conditional1 = 1-conditional0 try: # compute error of loess in logistic space var = avg([cd[f].variances[self.TargetClassIndex] for f in d_filter]) standard_error= math.sqrt(var) rightError0 = (conditional0+standard_error)/max(conditional1-standard_error, aproxZero) leftError0 = max(conditional0-standard_error, aproxZero)/(conditional1+standard_error) se = (math.log(rightError0) - math.log(leftError0))/2 se = math.sqrt(math.pow(se,2)+math.pow(priorError,2)) # add value to set of values a.addAttValue(AttValue(str(round(curr_num+i*d,rndFac)), math.log(conditional0/conditional1/prior), lineWidth=thickness, error = se)) except: pass a.continuous = True # invert values: # if there are more than 1 value in the attribute, add it to the nomogram if a and len(a.attValues)>1: self.bnomogram.addAttribute(a) self.alignRadio.setDisabled(False) self.graph.setScene(self.bnomogram) self.bnomogram.show() # Input channel: the logistic regression classifier def lrClassifier(self, cl): if self.TargetClassIndex == 0 or self.TargetClassIndex == cl.domain.classVar[0]: mult = -1 else: mult = 1 if self.bnomogram: self.bnomogram.destroy_and_init(self, AttValue('Constant', mult*cl.beta[0], error = 0)) else: self.bnomogram = BasicNomogram(self, AttValue('Constant', mult*cl.beta[0], error = 0)) # After applying feature subset selection on discrete attributes # aproximate unknown error for each attribute is math.sqrt(math.pow(cl.beta_se[0],2)/len(at)) try: aprox_prior_error = math.sqrt(math.pow(cl.beta_se[0],2)/len(cl.domain.attributes)) except: aprox_prior_error = 0 domain = cl.continuizedDomain or cl.domain if domain: for at in domain.attributes: at.setattr("visited",0) for at in domain.attributes: if at.getValueFrom and at.visited==0: name = at.getValueFrom.variable.name var = at.getValueFrom.variable if var.ordered: a = AttrLineOrdered(name, self.bnomogram) else: a = AttrLine(name, self.bnomogram) listOfExcludedValues = [] for val in var.values: foundValue = False for same in domain.attributes: if same.visited==0 and same.getValueFrom and same.getValueFrom.variable == var and same.getValueFrom.variable.values[same.getValueFrom.transformer.value]==val: same.setattr("visited",1) a.addAttValue(AttValue(val, mult*cl.beta[same], error = cl.beta_se[same])) foundValue = True if not foundValue: listOfExcludedValues.append(val) if len(listOfExcludedValues) == 1: a.addAttValue(AttValue(listOfExcludedValues[0], 0, error = aprox_prior_error)) elif len(listOfExcludedValues) == 2: a.addAttValue(AttValue("("+listOfExcludedValues[0]+","+listOfExcludedValues[1]+")", 0, error = aprox_prior_error)) elif len(listOfExcludedValues) > 2: a.addAttValue(AttValue("Other", 0, error = aprox_prior_error)) # if there are more than 1 value in the attribute, add it to the nomogram if len(a.attValues)>1: self.bnomogram.addAttribute(a) elif at.visited==0: name = at.name var = at a = AttrLineCont(name, self.bnomogram) if self.data: bas = getCached(self.data, orange.DomainBasicAttrStat, (self.data,)) maxAtValue = bas[var].max minAtValue = bas[var].min else: maxAtValue = 1. minAtValue = -1. numOfPartitions = 50. d = getDiff((maxAtValue-minAtValue)/numOfPartitions) # get curr_num = starting point for continuous att. sampling curr_num = getStartingPoint(d, minAtValue) rndFac = getRounding(d) while curr_num<maxAtValue+d: if abs(mult*curr_num*cl.beta[at])<aproxZero: a.addAttValue(AttValue("0.0", 0)) else: a.addAttValue(AttValue(str(curr_num), mult*curr_num*cl.beta[at])) curr_num += d a.continuous = True at.setattr("visited", 1) # if there are more than 1 value in the attribute, add it to the nomogram if len(a.attValues)>1: self.bnomogram.addAttribute(a) self.alignRadio.setDisabled(True) self.alignType = 0 self.graph.setScene(self.bnomogram) self.bnomogram.show() def lr2Classifier(self, cl): if len(cl.weights) == 1: weights = list(cl.weights[0]) if self.TargetClassIndex > 0: weights = [ -w for w in weights ] else: weights = list(cl.weights[self.TargetClassIndex]) domain = cl.domain for at in domain.attributes: at.setattr("visited",0) order = [] multiple = defaultdict(list) for iat,at in enumerate(domain.attributes): if at.getValueFrom and hasattr(at.getValueFrom, "variable") \ and isinstance(at.getValueFrom.variable, Orange.feature.Discrete): var = at.getValueFrom.variable if var not in multiple: order.append((var,-1)) multiple[var].append((at, iat)) else: order.append((at, iat)) #raw variable candidateats = [] for at,iat in order: if at not in multiple: span = 1. average = 0. basevar = at #check if normalized if isinstance(at.getValueFrom, Orange.classification.ClassifierFromVar) \ and isinstance(at.getValueFrom.variable, Orange.feature.Continuous) \ and isinstance(at.getValueFrom.transformer, Orange.core.NormalizeContinuous): span = at.getValueFrom.transformer.span average = at.getValueFrom.transformer.average basevar = at.getValueFrom.variable name = basevar.name if self.data: bas = getCached(self.data, orange.DomainBasicAttrStat, (self.data,)) maxAtValue = bas[basevar].max minAtValue = bas[basevar].min avgAtValue = bas[basevar].avg else: maxAtValue = 1. minAtValue = -1. avgAtValue = 0. # get curr_num = starting point for continuous att. sampling w = weights[iat] betas = [ ((minAtValue-average)/span)*w, ((maxAtValue-average)/span)*w ] rng = max(betas) - min(betas) freqbase = ((avgAtValue-average)/span)*w candidateats.append((at, name, "cont", minAtValue, maxAtValue, w, average, span, freqbase, rng)) else: found_values = set() values = [] for ati, iati in multiple[at]: val = ati.getValueFrom.variable.values[ati.getValueFrom.transformer.value] values.append((val, weights[iati])) found_values.add(val) excluded_values = list(set(at.values) - found_values) excluded_name = excluded_values[0] if len(excluded_values) == 1 else "other" values.append((excluded_name, 0)) betas = [ a[1] for a in values ] rng = max(betas) - min(betas) #the most frequent item has weight 0. candidateats.append((at, at.name, "disc", values, 0., rng)) self.warning(1011) biggest_influence = set([a[0] for a in sorted(candidateats, key=lambda x: -x[-1]) ][:SHOW_BIGGEST]) if len(biggest_influence) < len(candidateats): self.warning(1011, "Showing only %d features with highest influence (out of %d)." % (len(biggest_influence), len(candidateats) )) hiddenbeta = 0. for aaa in candidateats: if aaa[0] not in biggest_influence: hiddenbeta += aaa[-2] if self.bnomogram: self.bnomogram.destroy_and_init(self, AttValue('Constant', weights[-1]+hiddenbeta)) else: self.bnomogram = BasicNomogram(self, AttValue('Constant', weights[-1]+hiddenbeta)) for aaa in candidateats: if aaa[0] in biggest_influence: if aaa[2] == "cont": _, name, _, minAtValue, maxAtValue, w, average, span, _, _ = aaa numOfPartitions = 50. d = getDiff((maxAtValue-minAtValue)/numOfPartitions) a = AttrLineCont(name, self.bnomogram) curr_num = getStartingPoint(d, minAtValue) while curr_num<maxAtValue+d: if abs(curr_num*w)<aproxZero: a.addAttValue(AttValue("0.0", 0)) else: a.addAttValue(AttValue(str(curr_num), ((curr_num-average)/span)*w)) curr_num += d a.continuous = True elif aaa[2] == "disc": _, name, _, values, _, _ = aaa a = AttrLine(name, self.bnomogram) for n, val in values: a.addAttValue(AttValue(n, val)) if len(a.attValues)>1: self.bnomogram.addAttribute(a) self.alignRadio.setDisabled(True) self.alignType = 0 self.graph.setScene(self.bnomogram) self.bnomogram.show() def svmClassifier(self, cl): import orngLR_Jakulin import orngLinVis self.error(0) if self.TargetClassIndex == 0 or self.TargetClassIndex == cl.domain.classVar[0]: mult = -1 else: mult = 1 try: visualizer = orngLinVis.Visualizer(self.data, cl, buckets=1, dimensions=1) 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] beta_from_cl = mult*beta_from_cl except: self.error(0, "orngLinVis.Visualizer error"+ str(sys.exc_info()[0])+":"+str(sys.exc_info()[1])) # QMessageBox("orngLinVis.Visualizer error", str(sys.exc_info()[0])+":"+str(sys.exc_info()[1]), QMessageBox.Warning, # QMessageBox.NoButton, QMessageBox.NoButton, QMessageBox.NoButton, self).show() return if self.bnomogram: self.bnomogram.destroy_and_init(self, AttValue('Constant', -mult*math.log((1.0/min(max(visualizer.probfunc(0.0),aproxZero),0.9999))-1), 0)) else: self.bnomogram = BasicNomogram(self, AttValue('Constant', -mult*math.log((1.0/min(max(visualizer.probfunc(0.0),aproxZero),0.9999))-1), 0)) # get maximum and minimum values in visualizer.m maxMap = reduce(numpy.maximum, visualizer.m) minMap = reduce(numpy.minimum, visualizer.m) coeff = 0 # at_num = 1 correction = self.cl.coeff*self.cl.estimator.translator.trans[0].mult*self.cl.estimator.classifier.classifier.beta[1] for c in visualizer.coeff_names: if type(c[1])==str: for i in range(len(c)): if i == 0: if self.data.domain[c[0]].ordered: a = AttrLineOrdered(c[i], self.bnomogram) else: a = AttrLine(c[i], self.bnomogram) at_num = at_num + 1 else: if self.data: thickness = float(len(self.data.filter({self.data.domain[c[0]].name:str(c[i])})))/float(len(self.data)) a.addAttValue(AttValue(c[i], correction*mult*visualizer.coeffs[coeff], lineWidth=thickness)) coeff = coeff + 1 else: a = AttrLineCont(c[0], self.bnomogram) # get min and max from Data and transform coeff accordingly maxNew=maxMap[coeff] minNew=maxMap[coeff] if self.data: bas = getCached(self.data, orange.DomainBasicAttrStat, (self.data,)) maxNew = bas[c[0]].max minNew = bas[c[0]].min # transform SVM betas to betas siutable for nomogram if maxNew == minNew: beta = ((maxMap[coeff]-minMap[coeff])/aproxZero)*visualizer.coeffs[coeff] else: beta = ((maxMap[coeff]-minMap[coeff])/(maxNew-minNew))*visualizer.coeffs[coeff] n = -minNew+minMap[coeff] numOfPartitions = 50 d = getDiff((maxNew-minNew)/numOfPartitions) # get curr_num = starting point for continuous att. sampling curr_num = getStartingPoint(d, minNew) rndFac = getRounding(d) while curr_num<maxNew+d: a.addAttValue(AttValue(str(curr_num), correction*(mult*(curr_num-minNew)*beta-minMap[coeff]*visualizer.coeffs[coeff]))) curr_num += d at_num = at_num + 1 coeff = coeff + 1 a.continuous = True # if there are more than 1 value in the attribute, add it to the nomogram if len(a.attValues)>1: self.bnomogram.addAttribute(a) self.cl.domain = orange.Domain(self.data.domain.classVar) self.graph.setScene(self.bnomogram) self.bnomogram.show() # Input channel: the rule classifier (from CN2-EVC only) def ruleClassifier(self, cl): def selectSign(oper): if oper == orange.ValueFilter_continuous.Less: return "<" elif oper == orange.ValueFilter_continuous.LessEqual: return "<=" elif oper == orange.ValueFilter_continuous.Greater: return ">" elif oper == orange.ValueFilter_continuous.GreaterEqual: return ">=" else: return "=" def getConditions(rule): conds = rule.filter.conditions domain = rule.filter.domain ret = [] if len(conds)==0: ret = ret + ["TRUE"] for i,c in enumerate(conds): if i > 0: ret[-1] += " & " if type(c) == orange.ValueFilter_discrete: ret += [domain[c.position].name + "=" + str(domain[c.position].values[int(c.values[0])])] elif type(c) == orange.ValueFilter_continuous: ret += [domain[c.position].name + selectSign(c.oper) + "%.3f"%c.ref] return ret self.error(1) if not len(self.data.domain.classVar.values) == 2: self.error(1, "Rules require binary classes") classVal = cl.domain.classVar att = cl.domain.attributes if self.TargetClassIndex == 0 or self.TargetClassIndex == cl.domain.classVar[0]: mult = 1. else: mult = -1. # calculate prior probability (from self.TargetClassIndex) if self.bnomogram: self.bnomogram.destroy_and_init(self, AttValue("Constant", 0.0)) else: self.bnomogram = BasicNomogram(self, AttValue("Constant", 0.0)) self.cl.setattr("rulesOrdering", []) for r_i,r in enumerate(cl.rules): a = AttrLine(getConditions(r), self.bnomogram) self.cl.rulesOrdering.append(getConditions(r)) if r.classifier.defaultVal == 0: sign = mult else: sign = -mult a.addAttValue(AttValue("yes", sign*cl.ruleBetas[r_i], lineWidth=0, error = 0.0)) a.addAttValue(AttValue("no", 0.0, lineWidth=0, error = 0.0)) self.bnomogram.addAttribute(a) self.graph.setScene(self.bnomogram) self.bnomogram.show() def initClassValues(self, classValue): self.targetCombo.clear() self.targetCombo.addItems([str(v) for v in classValue]) def classifier(self, cl): self.closeContext() self.error(2) oldcl = self.cl self.cl = None if cl: for acceptable in (orange.BayesClassifier, orange.LogRegClassifier, orange.RuleClassifier_logit, Orange.classification.svm.LinearClassifier): if isinstance(cl, acceptable): self.cl = cl break else: self.error(2, "Nomograms can be drawn for only for Bayesian classifier and logistic regression") if not oldcl or not self.cl or not oldcl.domain == self.cl.domain: if self.cl: self.initClassValues(self.cl.domain.classVar) self.TargetClassIndex = 0 self.data = getattr(self.cl, "data", None) if self.data and self.data.domain and not self.data.domain.classVar: self.error(2, "Classless domain") # Here it said "return", but let us report an error and clean up the widget self.cl = self.data = None self.openContext("", self.data) if not self.data: self.histogramCheck.setChecked(False) self.histogramCheck.setDisabled(True) self.histogramLabel.setDisabled(True) self.CICheck.setChecked(False) self.CICheck.setDisabled(True) self.CILabel.setDisabled(True) else: self.histogramCheck.setEnabled(True) self.histogramCheck.makeConsistent() self.CICheck.setEnabled(True) self.CICheck.makeConsistent() self.updateNomogram() def setTarget(self): self.updateNomogram() def updateNomogram(self): ## import orngSVM def setNone(): for view in [self.footer, self.header, self.graph]: scene = view.scene() if scene: for item in scene.items(): scene.removeItem(item) if self.data and self.cl: # and not type(self.cl) == orngLR_Jakulin.MarginMetaClassifier: #check domains for at in self.cl.domain: if at.getValueFrom and hasattr(at.getValueFrom, "variable"): if (not at.getValueFrom.variable in self.data.domain) and (not at in self.data.domain): return else: if not at in self.data.domain: return if isinstance(self.cl, orange.BayesClassifier): # if len(self.cl.domain.classVar.values)>2: # QMessageBox("OWNomogram:", " Please use only Bayes classifiers that are induced on data with dichotomous class!", QMessageBox.Warning, # QMessageBox.NoButton, QMessageBox.NoButton, QMessageBox.NoButton, self).show() # else: self.nbClassifier(self.cl) ## elif isinstance(self.cl, orngLR_Jakulin.MarginMetaClassifier) and self.data: ## self.svmClassifier(self.cl) elif isinstance(self.cl, orange.RuleClassifier_logit): self.ruleClassifier(self.cl) elif isinstance(self.cl, orange.LogRegClassifier): # get if there are any continuous attributes in data -> then we need data to compute margins cont = False if self.cl.continuizedDomain: for at in self.cl.continuizedDomain.attributes: if not at.getValueFrom: cont = True if self.data or not cont: self.lrClassifier(self.cl) else: setNone() elif isinstance(self.cl, Orange.classification.svm.LinearClassifier): self.lr2Classifier(self.cl) else: setNone() if self.sort_type>0: self.sortNomogram() def sortNomogram(self): def sign(x): if x<0: return -1; return 1; def compare_to_ordering_in_rules(x,y): return self.cl.rulesOrdering.index(x.name) - self.cl.rulesOrdering.index(y.name) def compare_to_ordering_in_data(x,y): return self.data.domain.attributes.index(self.data.domain[x.name]) - self.data.domain.attributes.index(self.data.domain[y.name]) def compare_to_ordering_in_domain(x,y): return self.cl.domain.attributes.index(self.cl.domain[x.name]) - self.cl.domain.attributes.index(self.cl.domain[y.name]) def compate_beta_difference(x,y): return -sign(x.maxValue-x.minValue-y.maxValue+y.minValue) def compare_beta_positive(x, y): return -sign(x.maxValue-y.maxValue) def compare_beta_negative(x, y): return sign(x.minValue-y.minValue) if not self.bnomogram: return if self.sort_type == 0 and hasattr(self.cl, "rulesOrdering"): self.bnomogram.attributes.sort(compare_to_ordering_in_rules) elif self.sort_type == 0 and self.data: self.bnomogram.attributes.sort(compare_to_ordering_in_data) elif self.sort_type == 0 and self.cl and self.cl.domain: self.bnomogram.attributes.sort(compare_to_ordering_in_domain) if self.sort_type == 1: self.bnomogram.attributes.sort(compate_beta_difference) elif self.sort_type == 2: self.bnomogram.attributes.sort(compare_beta_positive) elif self.sort_type == 3: self.bnomogram.attributes.sort(compare_beta_negative) # update nomogram self.showNomogram() def setProbability(self): if self.probability and self.bnomogram: self.bnomogram.showAllMarkers() elif self.bnomogram: self.bnomogram.hideAllMarkers() def setBaseLine(self): if self.bnomogram: self.bnomogram.showBaseLine(True) def menuItemPrinter(self): import copy canvases = header, graph, footer = self.header.scene(), self.graph.scene(), self.footer.scene() # all scenes together scene_confed = QGraphicsScene(0, 0, max(c.width() for c in canvases), sum(c.height() for c in canvases)) # add items from header header_its = header.items() for it in header_its: scene_confed.addItem(it) # add items from graph graph_its = graph.items() for it in graph_its: scene_confed.addItem(it) it.moveBy(0., header.height()) # add from footer footer_its = footer.items() for it in footer_its: scene_confed.addItem(it) it.moveBy(0.,header.height() + graph.height()) try: import OWDlgs except: print "Missing file 'OWDlgs.py'. This file should be in OrangeWidgets folder. Unable to print/save image." sizeDlg = OWDlgs.OWChooseImageSizeDlg(scene_confed, parent=self) sizeDlg.exec_() # set all items back to original canvases for it in header_its: header.addItem(it) for it in graph_its: graph.addItem(it) it.moveBy(0., -header.height()) for it in footer_its: footer.addItem(it) it.moveBy(0, - header.height() - graph.height()) self.showNomogram() # Callbacks def showNomogram(self): if self.bnomogram and self.cl: #self.bnomogram.hide() self.bnomogram.show() self.bnomogram.update()