source: orange/Orange/OrangeWidgets/Data/OWDiscretize.py @ 11287:92efd54a02fd

Revision 11287:92efd54a02fd, 45.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

New style meta descriptions for some widgets.

Needed for intersphinx documentation discovery.

Line 
1"""
2<name>Discretize</name>
3<description>Discretization of continuous attributes.</description>
4<icon>icons/Discretize.svg</icon>
5<contact>Ales Erjavec (ales.erjavec(@at@)fri.uni-lj.si)</contact>
6<priority>2100</priority>
7"""
8
9import orange
10from OWWidget import *
11from OWGraph import *
12import OWGUI
13
14
15NAME = "Discretize"
16DESCRIPTION = "Discretization of continuous attributes."
17
18MAINTAINER = "Ales Erjavec"
19MAINTAINER_EMAIL = "ales.erjavec < at > fri.uni-lj.si"
20
21CATEGORY = "Data"
22PRIORITY = 2100
23ICON = "icons/Discretize.svg"
24
25INPUTS = [("Data", orange.ExampleTable, "setData")]
26OUTPUTS = [("Data", orange.ExampleTable,)]
27
28WIDGET_CLASS = "OWDiscretize"
29
30
31def frange(low, up, steps):
32    inc=(up-low)/steps
33    return [low+i*inc for i in range(steps)]
34
35class DiscGraph(OWGraph):
36    def __init__(self, master, *args):
37        OWGraph.__init__(self, *args)
38        self.master=master
39
40        self.rugKeys = []
41        self.cutLineKeys = []
42        self.cutMarkerKeys = []
43        self.probCurveKey = None
44        self.baseCurveKey = None
45        self.lookaheadCurveKey = None
46
47        self.setAxisScale(QwtPlot.yRight, 0.0, 1.0, 0.0)
48        self.setYLaxisTitle("Split gain")
49        self.setXaxisTitle("Attribute value")
50        self.setYRaxisTitle("Class probability")
51        self.setShowYRaxisTitle(1)
52        self.setShowYLaxisTitle(1)
53        self.setShowXaxisTitle(1)
54        self.enableYLaxis(1)
55        self.enableYRaxis(1)
56
57        self.resolution=50
58        #self.setCursor(Qt.ArrowCursor)
59        #self.canvas().setCursor(Qt.ArrowCursor)
60
61        self.data = self.attr = self.contingency = None
62        self.minVal = self.maxVal = 0
63        self.curCutPoints=[]
64
65
66    def computeAddedScore(self, spoints):
67        candidateSplits = [x for x in frange(self.minVal, self.maxVal, self.resolution) if x not in spoints]
68        idisc = orange.IntervalDiscretizer(points = [-99999] + spoints)
69        var = idisc.constructVariable(self.data.domain[self.master.continuousIndices[self.master.selectedAttr]])
70        measure = self.master.measures[self.master.measure][1]
71        score=[]
72        chisq = self.master.measure == 2
73        for cut in candidateSplits:
74            idisc.points = spoints + [cut]
75            idisc.points.sort()
76            score.append(measure(var, self.data))
77
78        return candidateSplits, score
79
80
81    def invalidateBaseScore(self):
82        self.baseCurveX = self.baseCurveY = None
83
84
85    def computeLookaheadScore(self, split):
86        if self.data and self.data.domain.classVar:
87            self.lookaheadCurveX, self.lookaheadCurveY = self.computeAddedScore(list(self.curCutPoints) + [split])
88        else:
89            self.lookaheadCurveX = self.lookaheadCurveY = None
90
91
92    def clearAll(self):
93        for rug in self.rugKeys:
94            rug.detach()
95        self.rugKeys = []
96
97        if self.baseCurveKey:
98            self.baseCurveKey.detach()
99            self.baseCurveKey = None
100
101        if self.lookaheadCurveKey:
102            self.lookaheadCurveKey.detach()
103            self.lookaheadCurveKey = None
104
105        if self.probCurveKey:
106            self.probCurveKey.detach()
107            self.probCurveKey = None
108
109        for c in self.cutLineKeys:
110            c.detach()
111        self.cutLineKeys = []
112
113        for m in self.cutMarkerKeys:
114            m.detach()
115        self.cutMarkerKeys = []
116
117        self.replot()
118
119
120    def setData(self, attr, data):
121        self.clearAll()
122        self.attr, self.data = attr, data
123        self.curCutPoints = []
124
125        if not data or not attr:
126            self.snapDecimals = 1
127            self.probDist = None
128            return
129
130        if data.domain.classVar:
131            self.contingency = orange.ContingencyAttrClass(attr, data)
132            try:
133                self.condProb = orange.ConditionalProbabilityEstimatorConstructor_loess(
134                   self.contingency,
135                   nPoints=50)
136            except:
137                self.condProb = None
138            self.probDist = None
139            attrValues = self.contingency.keys()
140        else:
141            self.condProb = self.contingency = None
142            self.probDist = orange.Distribution(attr, data)
143            attrValues = self.probDist.keys()
144
145        if attrValues:
146            self.minVal, self.maxVal = min(attrValues), max(attrValues)
147        else:
148            self.minVal, self.maxVal = 0, 1
149        mdist = self.maxVal - self.minVal
150        if mdist > 1e-30:
151            self.snapDecimals = -int(math.ceil(math.log(mdist, 10)) -2)
152        else:
153            self.snapDecimals = 1
154
155        self.baseCurveX = None
156
157        self.plotRug(True)
158        self.plotProbCurve(True)
159        self.plotCutLines(True)
160
161        self.updateLayout()
162        self.replot()
163
164
165    def plotRug(self, noUpdate = False):
166        for rug in self.rugKeys:
167            rug.detach()
168        self.rugKeys = []
169
170        if self.master.showRug:
171            targetClass = self.master.targetClass
172
173            if self.contingency:
174                freqhigh = [(val, freq[targetClass]) for val, freq in self.contingency.items() if freq[targetClass] > 1e-6]
175                freqlow = [(val, freq.abs - freq[targetClass]) for val, freq in self.contingency.items()]
176                freqlow = [f for f in freqlow if f[1] > 1e-6]
177            elif self.probDist:
178                freqhigh = []
179                freqlow = self.probDist.items()
180            else:
181                return
182
183            if freqhigh:
184                maxf = max([f[1] for f in freqhigh])
185                if freqlow:
186                    maxf = max(maxf, max([f[1] for f in freqlow]))
187            elif freqlow:
188                maxf = max([f[1] for f in freqlow])
189            else:
190                return
191
192            freqfac = maxf > 1e-6 and .1 / maxf or 1
193
194            for val, freq in freqhigh:
195                c = self.addCurve("", Qt.gray, Qt.gray, 1, style = QwtPlotCurve.Lines, symbol = QwtSymbol.NoSymbol, xData = [val, val], yData = [1.0, 1.0 - max(.02, freqfac * freq)], autoScale = 1)
196                c.setYAxis(QwtPlot.yRight)
197                self.rugKeys.append(c)
198
199            for val, freq in freqlow:
200                c = self.addCurve("", Qt.gray, Qt.gray, 1, style = QwtPlotCurve.Lines, symbol = QwtSymbol.NoSymbol, xData = [val, val], yData = [0.04, 0.04 + max(.02, freqfac * freq)], autoScale = 1)
201                c.setYAxis(QwtPlot.yRight)
202                self.rugKeys.append(c)
203
204        if not noUpdate:
205            self.replot()
206
207
208    def plotBaseCurve(self, noUpdate = False):
209        if self.baseCurveKey:
210            self.baseCurveKey.detach()
211            self.baseCurveKey = None
212
213        if self.master.showBaseLine and self.master.resetIndividuals and self.data and self.data.domain.classVar and self.attr:
214            if not self.baseCurveX:
215                self.baseCurveX, self.baseCurveY = self.computeAddedScore(list(self.curCutPoints))
216
217            #self.setAxisOptions(QwtPlot.yLeft, self.master.measure == 3 and QwtAutoScale.Inverted or QwtAutoScale.None)
218            self.axisScaleEngine(QwtPlot.yLeft).setAttributes(self.master.measure == 3 and QwtScaleEngine.Inverted or QwtScaleEngine.NoAttribute)
219            #self.axisScaleEngine(QwtPlot.yLeft).setAttribute(QwtScaleEngine.Inverted, self.master.measure == 3)
220            self.baseCurveKey = self.addCurve("", Qt.black, Qt.black, 1, style = QwtPlotCurve.Lines, symbol = QwtSymbol.NoSymbol, xData = self.baseCurveX, yData = self.baseCurveY, lineWidth = 2, autoScale = 1)
221            self.baseCurveKey.setYAxis(QwtPlot.yLeft)
222
223        if not noUpdate:
224            self.replot()
225
226
227    def plotLookaheadCurve(self, noUpdate = False):
228        if self.lookaheadCurveKey:
229            self.lookaheadCurveKey.detach()
230            self.lookaheadCurveKey = None
231
232        if self.lookaheadCurveX and self.master.showLookaheadLine:
233            #self.setAxisOptions(QwtPlot.yLeft, self.master.measure == 3 and QwtAutoScale.Inverted or QwtAutoScale.None)
234            self.axisScaleEngine(QwtPlot.yLeft).setAttributes(self.master.measure == 3 and QwtScaleEngine.Inverted or QwtScaleEngine.NoAttribute)
235            self.lookaheadCurveKey = self.addCurve("", Qt.black, Qt.black, 1, style = QwtPlotCurve.Lines, symbol = QwtSymbol.NoSymbol, xData = self.lookaheadCurveX, yData = self.lookaheadCurveY, lineWidth = 1, autoScale = 1)
236            self.lookaheadCurveKey.setYAxis(QwtPlot.yLeft)
237            #self.lookaheadCurveKey.setVisible(1)
238
239        if not noUpdate:
240            self.replot()
241
242
243    def plotProbCurve(self, noUpdate = False):
244        if self.probCurveKey:
245            self.probCurveKey.detach()
246            self.probCurveKey = None
247
248        if self.contingency and self.condProb and self.master.showTargetClassProb:
249            xData = self.contingency.keys()[1:-1]
250            self.probCurveKey = self.addCurve("", Qt.gray, Qt.gray, 1, style = QwtPlotCurve.Lines, symbol = QwtSymbol.NoSymbol, xData = xData, yData = [self.condProb(x)[self.master.targetClass] for x in xData], lineWidth = 2, autoScale = 1)
251            self.probCurveKey.setYAxis(QwtPlot.yRight)
252
253        if not noUpdate:
254            self.replot()
255
256
257    def plotCutLines(self, noUpdate = False):
258        attr = self.data.domain[self.master.continuousIndices[self.master.selectedAttr]]
259        for c in self.cutLineKeys:
260            c.detach()
261        self.cutLineKeys = []
262
263        for m in self.cutMarkerKeys:
264            m.detach()
265        self.cutMarkerKeys = []
266
267        for cut in self.curCutPoints:
268            c = self.addCurve("", Qt.blue, Qt.blue, 1, style = QwtPlotCurve.Steps, symbol = QwtSymbol.NoSymbol, xData = [cut, cut], yData = [.9, 0.1], autoScale = 1)
269            c.setYAxis(QwtPlot.yRight)
270            self.cutLineKeys.append(c)
271
272            m = self.addMarker(str(attr(cut)), cut, .9, Qt.AlignCenter | Qt.AlignTop, bold=1)
273            m.setYAxis(QwtPlot.yRight)
274            self.cutMarkerKeys.append(m)
275        if not noUpdate:
276            self.replot()
277
278    def getCutCurve(self, cut):
279        ccc = self.transform(QwtPlot.xBottom, cut)
280        for i, c in enumerate(self.curCutPoints):
281            cc = self.transform(QwtPlot.xBottom, c)
282            if abs(cc-ccc)<3:
283                self.cutLineKeys[i].curveInd = i
284                return self.cutLineKeys[i]
285        return None
286
287
288    def setSplits(self, splits):
289        if self.data:
290            self.curCutPoints = splits
291
292            self.baseCurveX = None
293            self.plotBaseCurve()
294            self.plotCutLines()
295
296
297    def addCutPoint(self, cut):
298        self.curCutPoints.append(cut)
299        c = self.addCurve("", Qt.blue, Qt.blue, 1, style = QwtPlotCurve.Steps, symbol = QwtSymbol.NoSymbol, xData = [cut, cut], yData = [1.0, 0.015], autoScale = 1)
300        c.setYAxis(QwtPlot.yRight)
301        self.cutLineKeys.append(c)
302        c.curveInd = len(self.cutLineKeys) - 1
303        return c
304
305
306    def mousePressEvent(self, e):
307        if not self.data:
308            return
309
310        self.mouseCurrentlyPressed = 1
311
312        canvasPos = self.canvas().mapFrom(self, e.pos())
313        cut = self.invTransform(QwtPlot.xBottom, canvasPos.x())
314        curve = self.getCutCurve(cut)
315        if not curve and self.master.snap:
316            curve = self.getCutCurve(round(cut, self.snapDecimals))
317
318        if curve:
319            if e.button() == Qt.RightButton:
320                self.curCutPoints.pop(curve.curveInd)
321                self.plotCutLines(True)
322            else:
323                cut = self.curCutPoints.pop(curve.curveInd)
324                self.plotCutLines(True)
325                self.selectedCutPoint=self.addCutPoint(cut)
326        else:
327            self.selectedCutPoint=self.addCutPoint(cut)
328            self.plotCutLines(True)
329
330        self.baseCurveX = None
331        self.plotBaseCurve()
332        self.master.synchronizeIf()
333
334
335    def mouseMoveEvent(self, e):
336        if not self.data:
337            return
338
339        canvasPos = self.canvas().mapFrom(self, e.pos())
340
341        if self.mouseCurrentlyPressed:
342            if self.selectedCutPoint:
343                canvasPos = self.canvas().mapFrom(self, e.pos())
344                pos = self.invTransform(QwtPlot.xBottom, canvasPos.x())
345                if self.master.snap:
346                    pos = round(pos, self.snapDecimals)
347
348                if self.curCutPoints[self.selectedCutPoint.curveInd]==pos:
349                    return
350                if pos > self.maxVal or pos < self.minVal:
351                    self.curCutPoints.pop(self.selectedCutPoint.curveInd)
352                    self.baseCurveX = None
353                    self.plotCutLines(True)
354                    self.mouseCurrentlyPressed = 0
355                    return
356
357                self.curCutPoints[self.selectedCutPoint.curveInd] = pos
358                self.selectedCutPoint.setData([pos, pos], [.9, 0.1])
359
360                self.computeLookaheadScore(pos)
361                self.plotLookaheadCurve()
362                self.replot()
363
364                self.master.synchronizeIf()
365
366
367        elif self.getCutCurve(self.invTransform(QwtPlot.xBottom, canvasPos.x())):
368            self.canvas().setCursor(Qt.SizeHorCursor)
369        else:
370            self.canvas().setCursor(Qt.ArrowCursor)
371
372
373    def mouseReleaseEvent(self, e):
374        if not self.data:
375            return
376
377        self.mouseCurrentlyPressed = 0
378        self.selectedCutPoint = None
379        self.baseCurveX = None
380        self.plotBaseCurve()
381        self.plotCutLines(True)
382        self.master.synchronizeIf()
383        if self.lookaheadCurveKey and self.lookaheadCurveKey:
384            self.lookaheadCurveKey.setVisible(0)
385        self.replot()
386
387
388    def targetClassChanged(self):
389        self.plotRug()
390        self.plotProbCurve()
391
392
393class CustomListItemDelegate(QItemDelegate):
394    def paint(self, painter, option, index):
395        item = self.parent().itemFromIndex(index)
396        item.setText(item.name + item.master.indiLabels[item.labelIdx])
397        QItemDelegate.paint(self, painter, option, index)
398
399
400class ListItemWithLabel(QListWidgetItem):
401    def __init__(self, icon, name, labelIdx, master):
402        QListWidgetItem.__init__(self, icon, name)
403        self.name = name
404        self.master = master
405        self.labelIdx = labelIdx
406
407#    def paint(self, painter):
408#        btext = str(self.text())
409#        self.setText(btext + self.master.indiLabels[self.labelIdx])
410#        QListWidgetItem.paint(self, painter)
411#        self.setText(btext)
412
413
414class OWDiscretize(OWWidget):
415    settingsList=["autoApply", "measure", "showBaseLine", "showLookaheadLine", "showTargetClassProb", "showRug", "snap", "autoSynchronize", "resetIndividuals"]
416    contextHandlers = {"": PerfectDomainContextHandler("", ["targetClass", "discretization", "classDiscretization",
417                                                     "indiDiscretization", "intervals", "classIntervals", "indiIntervals",
418                                                     "outputOriginalClass", "indiData", "indiLabels", "resetIndividuals",
419                                                     "selectedAttr", "customSplits", "customClassSplits"])}
420
421    callbackDeposit=[]
422
423    D_N_METHODS = 5
424    D_LEAVE, D_ENTROPY, D_FREQUENCY, D_WIDTH, D_REMOVE = range(5)
425    D_NEED_N_INTERVALS = [2, 3]
426
427    def __init__(self, parent=None, signalManager=None, name="Interactive Discretization"):
428        OWWidget.__init__(self, parent, signalManager, name)
429        self.showBaseLine=1
430        self.showLookaheadLine=1
431        self.showTargetClassProb=1
432        self.showRug=0
433        self.snap=1
434        self.measure=0
435        self.targetClass=0
436        self.discretization = self.classDiscretization = self.indiDiscretization = 1
437        self.intervals = self.classIntervals = self.indiIntervals = 3
438        self.outputOriginalClass = True
439        self.indiData = []
440        self.indiLabels = []
441        self.resetIndividuals = 0
442        self.customClassSplits = ""
443
444        self.selectedAttr = 0
445        self.customSplits = ["", "", ""]
446        self.autoApply = True
447        self.dataChanged = False
448        self.autoSynchronize = True
449        self.pointsChanged = False
450
451        self.customLineEdits = []
452        self.needsDiscrete = []
453
454        self.data = self.originalData = None
455
456        self.loadSettings()
457
458        self.inputs=[("Data", ExampleTable, self.setData)]
459        self.outputs=[("Data", ExampleTable)]
460        self.measures=[("Information gain", orange.MeasureAttribute_info()),
461                       #("Gain ratio", orange.MeasureAttribute_gainRatio),
462                       ("Gini", orange.MeasureAttribute_gini()),
463                       ("chi-square", orange.MeasureAttribute_chiSquare()),
464                       ("chi-square prob.", orange.MeasureAttribute_chiSquare(computeProbabilities=1)),
465                       ("Relevance", orange.MeasureAttribute_relevance()),
466                       ("ReliefF", orange.MeasureAttribute_relief())]
467        self.discretizationMethods=["Leave continuous", "Entropy-MDL discretization", "Equal-frequency discretization", "Equal-width discretization", "Remove continuous attributes"]
468        self.classDiscretizationMethods=["Equal-frequency discretization", "Equal-width discretization"]
469        self.indiDiscretizationMethods=["Default", "Leave continuous", "Entropy-MDL discretization", "Equal-frequency discretization", "Equal-width discretization", "Remove attribute"]
470
471        self.mainHBox =  OWGUI.widgetBox(self.mainArea, orientation=0)
472
473        vbox = self.controlArea
474        box = OWGUI.radioButtonsInBox(vbox, self, "discretization", self.discretizationMethods[:-1], "Default discretization", callback=[self.clearLineEditFocus, self.defaultMethodChanged])
475        self.needsDiscrete.append(box.buttons[1])
476        box.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
477        indent = OWGUI.checkButtonOffsetHint(self.needsDiscrete[-1])
478        self.interBox = OWGUI.widgetBox(OWGUI.indentedBox(box, sep=indent))
479        OWGUI.widgetLabel(self.interBox, "Number of intervals (for equal width/frequency)")
480        OWGUI.separator(self.interBox, height=4)
481        self.intervalSlider=OWGUI.hSlider(OWGUI.indentedBox(self.interBox), self, "intervals", None, 2, 10, callback=[self.clearLineEditFocus, self.defaultMethodChanged])
482        OWGUI.appendRadioButton(box, self, "discretization", self.discretizationMethods[-1])
483        OWGUI.separator(vbox)
484
485        ribg = OWGUI.radioButtonsInBox(vbox, self, "resetIndividuals", ["Use default discretization for all attributes", "Explore and set individual discretizations"], "Individual attribute treatment", callback = self.setAllIndividuals)
486        ll = QWidget(ribg)
487        ll.setFixedHeight(1)
488        OWGUI.widgetLabel(ribg, "Set discretization of all attributes to")
489        hcustbox = OWGUI.widgetBox(OWGUI.indentedBox(ribg), 0, 0)
490        for c in range(1, 4):
491            OWGUI.appendRadioButton(ribg, self, "resetIndividuals", "Custom %i" % c, insertInto = hcustbox)
492
493        OWGUI.separator(vbox)
494
495        box = self.classDiscBox = OWGUI.radioButtonsInBox(vbox, self, "classDiscretization", self.classDiscretizationMethods, "Class discretization", callback=[self.clearLineEditFocus, self.classMethodChanged])
496        cinterBox = OWGUI.widgetBox(box)
497        self.intervalSlider=OWGUI.hSlider(OWGUI.indentedBox(cinterBox, sep=indent), self, "classIntervals", None, 2, 10, callback=[self.clearLineEditFocus, self.classMethodChanged], label="Number of intervals")
498        hbox = OWGUI.widgetBox(box, orientation = 0)
499        OWGUI.appendRadioButton(box, self, "discretization", "Custom" + "  ", insertInto = hbox)
500        self.classCustomLineEdit = OWGUI.lineEdit(hbox, self, "customClassSplits", callback = self.classCustomChanged, focusInCallback = self.classCustomSelected)
501#        Can't validate - need to allow spaces
502        box.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
503        OWGUI.separator(box)
504        self.classIntervalsLabel = OWGUI.widgetLabel(box, "Current splits: ")
505        OWGUI.separator(box)
506        OWGUI.checkBox(box, self, "outputOriginalClass", "Output original class", callback = self.commitIf)
507        OWGUI.widgetLabel(box, "("+"Widget always uses discretized class internally."+")")
508
509        OWGUI.separator(vbox)
510        #OWGUI.rubber(vbox)
511
512        box = OWGUI.widgetBox(vbox, "Commit")
513        applyButton = OWGUI.button(box, self, "Commit", callback = self.commit, default=True)
514        autoApplyCB = OWGUI.checkBox(box, self, "autoApply", "Commit automatically", callback=[self.clearLineEditFocus])
515        OWGUI.setStopper(self, applyButton, autoApplyCB, "dataChanged", self.commit)
516        OWGUI.rubber(vbox)
517
518        #self.mainSeparator = OWGUI.separator(self.mainHBox, width=25)        # space between control and main area
519        self.mainIABox =  OWGUI.widgetBox(self.mainHBox, "Individual attribute settings")
520        self.mainBox = OWGUI.widgetBox(self.mainIABox, orientation=0)
521        OWGUI.separator(self.mainIABox)#, height=30)
522        graphBox = OWGUI.widgetBox(self.mainIABox, "", orientation=0)
523       
524       
525#        self.needsDiscrete.append(graphBox)
526        graphOptBox = OWGUI.widgetBox(graphBox)
527        OWGUI.separator(graphBox, width=10)
528       
529        graphGraphBox = OWGUI.widgetBox(graphBox)
530        self.graph = DiscGraph(self, graphGraphBox)
531        graphGraphBox.layout().addWidget(self.graph)
532        reportButton2 = OWGUI.button(graphGraphBox, self, "Report Graph", callback = self.reportGraph, debuggingEnabled=0)
533
534        #graphOptBox.layout().setSpacing(4)
535        box = OWGUI.widgetBox(graphOptBox, "Split gain measure", addSpace=True)
536        self.measureCombo=OWGUI.comboBox(box, self, "measure", orientation=0, items=[e[0] for e in self.measures], callback=[self.clearLineEditFocus, self.graph.invalidateBaseScore, self.graph.plotBaseCurve])
537        OWGUI.checkBox(box, self, "showBaseLine", "Show discretization gain", callback=[self.clearLineEditFocus, self.graph.plotBaseCurve])
538        OWGUI.checkBox(box, self, "showLookaheadLine", "Show lookahead gain", callback=self.clearLineEditFocus)
539        self.needsDiscrete.append(box)
540
541        box = OWGUI.widgetBox(graphOptBox, "Target class", addSpace=True)
542        self.targetCombo=OWGUI.comboBox(box, self, "targetClass", orientation=0, callback=[self.clearLineEditFocus, self.graph.targetClassChanged])
543        stc = OWGUI.checkBox(box, self, "showTargetClassProb", "Show target class probability", callback=[self.clearLineEditFocus, self.graph.plotProbCurve])
544        OWGUI.checkBox(box, self, "showRug", "Show rug (may be slow)", callback=[self.clearLineEditFocus, self.graph.plotRug])
545        self.needsDiscrete.extend([self.targetCombo, stc])
546
547        box = OWGUI.widgetBox(graphOptBox, "Editing", addSpace=True)
548        OWGUI.checkBox(box, self, "snap", "Snap to grid", callback=[self.clearLineEditFocus])
549        syncCB = OWGUI.checkBox(box, self, "autoSynchronize", "Apply on the fly", callback=self.clearLineEditFocus)
550        syncButton = OWGUI.button(box, self, "Apply", callback = self.synchronizePressed)
551        OWGUI.setStopper(self, syncButton, syncCB, "pointsChanged", self.synchronize)
552        OWGUI.rubber(graphOptBox)
553
554        self.attrList = OWGUI.listBox(self.mainBox, self, callback = self.individualSelected)
555        self.attrList.setItemDelegate(CustomListItemDelegate(self.attrList))
556        self.attrList.setFixedWidth(300)
557
558        self.defaultMethodChanged()
559
560        OWGUI.separator(self.mainBox, width=10)
561        box = OWGUI.radioButtonsInBox(OWGUI.widgetBox(self.mainBox), self, "indiDiscretization", [], callback=[self.clearLineEditFocus, self.indiMethodChanged])
562        #hbbox = OWGUI.widgetBox(box)
563        #hbbox.layout().setSpacing(4)
564        for meth in self.indiDiscretizationMethods[:-1]:
565            OWGUI.appendRadioButton(box, self, "indiDiscretization", meth)
566        self.needsDiscrete.append(box.buttons[2])
567        self.indiInterBox = OWGUI.indentedBox(box, sep=indent, orientation = "horizontal")
568        OWGUI.widgetLabel(self.indiInterBox, "Num. of intervals: ")
569        self.indiIntervalSlider = OWGUI.hSlider(self.indiInterBox, self, "indiIntervals", None, 2, 10, callback=[self.clearLineEditFocus, self.indiMethodChanged], width = 100)
570        OWGUI.rubber(self.indiInterBox) 
571        OWGUI.appendRadioButton(box, self, "indiDiscretization", self.indiDiscretizationMethods[-1])
572        #OWGUI.rubber(hbbox)
573        #OWGUI.separator(box)
574        #hbbox = OWGUI.widgetBox(box)
575        for i in range(3):
576            hbox = OWGUI.widgetBox(box, orientation = "horizontal")
577            OWGUI.appendRadioButton(box, self, "indiDiscretization", "Custom %i" % (i+1) + " ", insertInto = hbox)
578            le = OWGUI.lineEdit(hbox, self, "", callback = lambda w=i: self.customChanged(w), focusInCallback = lambda w=i: self.customSelected(w))
579            le.setFixedWidth(110)
580            self.customLineEdits.append(le)
581            OWGUI.toolButton(hbox, self, "CC", width=30, callback = lambda w=i: self.copyToCustom(w))
582            OWGUI.rubber(hbox)
583        OWGUI.rubber(box)
584
585        #self.controlArea.setFixedWidth(0)
586
587        self.contAttrIcon =  self.createAttributeIconDict()[orange.VarTypes.Continuous]
588       
589        self.setAllIndividuals()
590
591
592
593    def setData(self, data=None):
594        self.closeContext()
595
596        self.indiData = []
597        self.attrList.clear()
598        for le in self.customLineEdits:
599            le.clear()
600        self.indiDiscretization = 0
601
602        self.originalData = data
603        haveClass = bool(data and data.domain.classVar)
604        continuousClass = haveClass and data.domain.classVar.varType == orange.VarTypes.Continuous
605
606        self.data = self.originalData
607        if continuousClass:
608            if not self.discretizeClass():
609                self.data = self.discClassData = None
610                self.warning(0)
611                self.error(0, "Cannot discretize the class")
612        else:
613            self.data = self.originalData
614            self.discClassData = None
615
616        for c in self.needsDiscrete:
617            c.setVisible(haveClass)
618
619        if self.data:
620            domain = self.data.domain
621            self.continuousIndices = [i for i, attr in enumerate(domain.attributes) if attr.varType == orange.VarTypes.Continuous]
622            if not self.continuousIndices:
623                self.data = None
624
625        self.classDiscBox.setEnabled(not data or continuousClass)
626        if self.data:
627            for i, attr in enumerate(domain.attributes):
628                if attr.varType == orange.VarTypes.Continuous:
629                    self.attrList.addItem(ListItemWithLabel(self.contAttrIcon, attr.name, self.attrList.count(), self))
630                    self.indiData.append([0, 4, "", "", ""])
631                else:
632                    self.indiData.append(None)
633
634            self.fillClassCombo()
635            self.indiLabels = [""] * self.attrList.count()
636
637            self.graph.setData(None, self.data)
638            self.selectedAttr = 0
639            self.openContext("", data)
640#            if self.classDiscretization == 2:
641#                self.discretizeClass()
642
643            # Prevent entropy discretization with non-discrete class
644            if not haveClass:
645                if self.discretization == self.D_ENTROPY:
646                    self.discretization = self.D_FREQUENCY
647                # Say I'm overcautious if you will, but you haven't seen as much as I did :)
648                if not haveClass:
649                    if self.indiDiscretization-1 == self.D_ENTROPY:
650                        self.indiDiscretization = 0
651                    for indiData in self.indiData:
652                        if indiData and indiData[0] == self.D_ENTROPY:
653                            indiData[0] = 0
654
655            self.computeDiscretizers()
656            self.attrList.setCurrentItem(self.attrList.item(self.selectedAttr))
657        else:
658            self.targetCombo.clear()
659            self.graph.setData(None, None)
660
661#        self.graph.setData(self.data)
662
663        self.makeConsistent()
664
665        # this should be here because 'resetIndividuals' is a context setting
666        self.showHideIndividual()
667
668        self.commit()
669
670
671    def fillClassCombo(self):
672        self.targetCombo.clear()
673
674        if not self.data or not self.data.domain.classVar:
675            return
676
677        domain = self.data.domain
678        for v in domain.classVar.values:
679            self.targetCombo.addItem(str(v))
680        if self.targetClass<len(domain.classVar.values):
681            self.targetCombo.setCurrentIndex(self.targetClass)
682        else:
683            self.targetCombo.setCurrentIndex(0)
684            self.targetClass=0
685
686    def classChanged(self):
687        self.fillClassCombo()
688        self.computeDiscretizers()
689
690
691    def clearLineEditFocus(self):
692        if self.data:
693            df = self.indiDiscretization
694            for le in self.customLineEdits:
695                if le.hasFocus():
696                    le.clearFocus()
697            self.indiDiscretization = self.indiData[self.continuousIndices[self.selectedAttr]][0] = df
698            if self.classCustomLineEdit.hasFocus():
699                self.classCustomLineEdit.clearFocus()
700
701
702
703    def individualSelected(self):
704        if not self.data:
705            return
706
707        if self.attrList.selectedItems() == []: return
708        self.selectedAttr = self.attrList.row(self.attrList.selectedItems()[0])
709        attrIndex = self.continuousIndices[self.selectedAttr]
710        attr = self.data.domain[attrIndex]
711        indiData = self.indiData[attrIndex]
712
713        self.customSplits = indiData[2:]
714        for le, cs in zip(self.customLineEdits, self.customSplits):
715            le.setText(" ".join(cs))
716
717        self.indiDiscretization, self.indiIntervals = indiData[:2]
718        self.indiInterBox.setEnabled(self.indiDiscretization-1 in self.D_NEED_N_INTERVALS)
719
720        self.graph.setData(attr, self.data)
721        if hasattr(self, "discretizers"):
722            self.graph.setSplits(self.discretizers[attrIndex] and self.discretizers[attrIndex].getValueFrom.transformer.points or [])
723        else:
724            self.graph.plotBaseCurve(False)
725
726
727    def computeDiscretizers(self):
728        self.discretizers = []
729
730        if not self.data:
731            return
732
733        self.discretizers = [None] * len(self.data.domain)
734        for i, idx in enumerate(self.continuousIndices):
735            self.computeDiscretizer(i, idx)
736
737        self.commitIf()
738
739
740    def makeConsistent(self):
741        self.interBox.setEnabled(self.discretization in self.D_NEED_N_INTERVALS)
742        self.indiInterBox.setEnabled(self.indiDiscretization-1 in self.D_NEED_N_INTERVALS)
743
744
745    def defaultMethodChanged(self):
746        self.interBox.setEnabled(self.discretization in self.D_NEED_N_INTERVALS)
747
748        if not self.data:
749            return
750
751        for i, idx in enumerate(self.continuousIndices):
752            self.computeDiscretizer(i, idx, True)
753
754        self.commitIf()
755
756    def classMethodChanged(self):
757        if not self.data:
758            return
759
760        self.discretizeClass()
761        self.classChanged()
762        attrIndex = self.continuousIndices[self.selectedAttr]
763        self.graph.setData(self.data.domain[attrIndex], self.data)
764        self.graph.setSplits(self.discretizers[attrIndex] and self.discretizers[attrIndex].getValueFrom.transformer.points or [])
765        if self.targetClass > len(self.data.domain.classVar.values):
766            self.targetClass = len(self.data.domain.classVar.values)-1
767
768
769    def indiMethodChanged(self, dontSetACustom=False):
770        if self.data:
771            i, idx = self.selectedAttr, self.continuousIndices[self.selectedAttr]
772            self.indiData[idx][0] = self.indiDiscretization
773            self.indiData[idx][1] = self.indiIntervals
774
775            self.indiInterBox.setEnabled(self.indiDiscretization-1 in self.D_NEED_N_INTERVALS)
776            if self.indiDiscretization and self.indiDiscretization - self.D_N_METHODS != self.resetIndividuals - 1:
777                self.resetIndividuals = 1
778
779            if not self.data:
780                return
781
782            which = self.indiDiscretization - self.D_N_METHODS - 1
783            if not dontSetACustom and which >= 0 and not self.customSplits[which]:
784                attr = self.data.domain[idx]
785                splitsTxt = self.indiData[idx][2+which] = [str(attr(x)) for x in self.graph.curCutPoints]
786                self.customSplits[which] = splitsTxt # " ".join(splitsTxt)
787                self.customLineEdits[which].setText(" ".join(splitsTxt))
788                self.computeDiscretizer(i, idx)
789            else:
790                self.computeDiscretizer(i, idx)
791
792            self.commitIf()
793
794
795    def customSelected(self, which):
796        if self.data and self.indiDiscretization != self.D_N_METHODS + which + 1: # added 1 - we need it, right?
797            self.indiDiscretization = self.D_N_METHODS + which + 1
798            idx = self.continuousIndices[self.selectedAttr]
799            attr = self.data.domain[idx]
800            self.indiMethodChanged()
801
802
803    def showHideIndividual(self):
804        if not self.resetIndividuals:
805                self.mainArea.hide()
806        elif self.mainArea.isHidden():
807            self.graph.plotBaseCurve()
808            self.mainArea.show()
809        qApp.processEvents()
810        QTimer.singleShot(0, self.adjustSize)
811
812    def setAllIndividuals(self):
813        self.showHideIndividual()
814
815        if not self.data:
816            return
817
818        self.clearLineEditFocus()
819        method = self.resetIndividuals
820        if method == 1:
821            return
822        if method:
823            method += self.D_N_METHODS - 1
824        for i, idx in enumerate(self.continuousIndices):
825            if self.indiData[idx][0] != method:
826                self.indiData[idx][0] = method
827                if i == self.selectedAttr:
828                    self.indiDiscretization = method
829                    self.indiMethodChanged(True) # don't set a custom
830                    if method:
831                        self.computeDiscretizer(i, idx)
832                else:
833                    self.computeDiscretizer(i, idx)
834
835        self.attrList.reset()
836        self.commitIf()
837
838
839    def customChanged(self, which):
840        if not self.data:
841            return
842
843        idx = self.continuousIndices[self.selectedAttr]
844        le = self.customLineEdits[which]
845
846        content = str(le.text()).replace(":", " ").replace(",", " ").split()
847        content = dict.fromkeys(content).keys()  # remove duplicates (except 8.0, 8.000 ...)
848        try:
849            content.sort(lambda x, y:cmp(float(x), float(y)))
850        except:
851            content = str(le.text())
852
853        le.setText(" ".join(content))
854        self.customSplits[which] = content
855        self.indiData[idx][which+2] = content
856
857        self.indiData[idx][0] = self.indiDiscretization = which + self.D_N_METHODS + 1
858
859        self.computeDiscretizer(self.selectedAttr, self.continuousIndices[self.selectedAttr])
860        self.commitIf()
861
862
863    def copyToCustom(self, which):
864        self.clearLineEditFocus()
865        if not self.data:
866            return
867
868        idx = self.continuousIndices[self.selectedAttr]
869
870        if self.indiDiscretization >= self.D_N_METHODS + 1:
871            splits = self.customSplits[self.indiDiscretization - self.D_N_METHODS - 1]
872            try:
873                valid = bool([float(i) for i in self.customSplits[which]].split())
874            except:
875                valid = False
876        else:
877            valid = False
878
879        if not valid:
880            attr = self.data.domain[idx]
881            splits = list(self.discretizers[idx] and self.discretizers[idx].getValueFrom.transformer.points or [])
882            splits = [str(attr(i)) for i in splits]
883
884        self.indiData[idx][2+which] = self.customSplits[which] = splits
885        self.customLineEdits[which].setText(" ".join(splits))
886#        self.customSelected(which)
887
888
889    # This weird construction of the list is needed for easier translation into other languages
890    shortDiscNames = [""] + [" (%s)" % x for x in ("leave continuous", "entropy", "equal frequency", "equal width", "removed")] + [(" ("+"custom %i"+")") % x for x in range(1, 4)]
891    # This one is used for reports
892    shortDiscNamesUnpar = ("", "leave continuous", "entropy", "equal frequency", "equal width", "removed", "custom", "custom", "custom")
893
894    def computeDiscretizer(self, i, idx, onlyDefaults=False):
895        attr = self.data.domain[idx]
896        indiData = self.indiData[idx]
897
898        discType, intervals = indiData[:2]
899        discName = self.shortDiscNames[discType]
900
901        defaultUsed = not discType
902
903        if defaultUsed:
904            discType = self.discretization+1
905            intervals = self.intervals
906
907        if discType >= self.D_N_METHODS + 1:
908
909            try:
910                customs = [float(r) for r in indiData[discType-self.D_N_METHODS+1]]
911            except:
912                customs = []
913
914            if not customs:
915                discType = self.discretization+1
916                intervals = self.intervals
917                discName = "%s ->%s)" % (self.shortDiscNames[indiData[0]][:-1], self.shortDiscNames[discType][2:-1])
918                defaultUsed = True
919
920        if onlyDefaults and not defaultUsed:
921            return
922
923        discType -= 1
924        try:
925            if discType == self.D_LEAVE: # leave continuous
926                discretizer = None
927            elif discType == self.D_ENTROPY:
928                discretizer = orange.EntropyDiscretization(attr, self.data)
929            elif discType == self.D_FREQUENCY:
930                discretizer = orange.EquiNDiscretization(attr, self.data, numberOfIntervals = intervals)
931            elif discType == self.D_WIDTH:
932                discretizer = orange.EquiDistDiscretization(attr, self.data, numberOfIntervals = intervals)
933            elif discType == self.D_REMOVE:
934                discretizer = False
935            else:
936                discretizer = orange.IntervalDiscretizer(points = customs).constructVariable(attr)
937        except:
938            discretizer = False
939
940
941        self.discretizers[idx] = discretizer
942
943        if discType == self.D_LEAVE:
944            discInts = ""
945        elif discType == self.D_REMOVE:
946            discInts = ""
947        elif not discretizer:
948            discInts = ": "+"<can't discretize>"
949        else:
950            points = discretizer.getValueFrom.transformer.points
951            discInts = points and (": " + ", ".join([str(attr(x)) for x in points])) or ": "+"<removed>"
952        self.indiLabels[i] = discInts + discName
953        self.attrList.reset()
954
955        if i == self.selectedAttr:
956            self.graph.setSplits(discretizer and discretizer.getValueFrom.transformer.points or [])
957
958
959
960    def discretizeClass(self):
961        if self.originalData:
962            discType = self.classDiscretization
963            classVar = self.originalData.domain.classVar
964
965            if discType == 2:
966                try:
967                    content = self.customClassSplits.replace(":", " ").replace(",", " ").replace("-", " ").split()
968                    customs = dict.fromkeys([float(x) for x in content]).keys()  # remove duplicates (except 8.0, 8.000 ...)
969                    customs.sort()
970                except:
971                    customs = []
972
973                if not customs:
974                    discType = 0
975
976            try:
977                if discType == 0:
978                    discretizer = orange.EquiNDiscretization(classVar, self.originalData, numberOfIntervals = self.classIntervals)
979                elif discType == 1:
980                    discretizer = orange.EquiDistDiscretization(classVar, self.originalData, numberOfIntervals = self.classIntervals)
981                else:
982                    discretizer = orange.IntervalDiscretizer(points = customs).constructVariable(classVar)
983
984                self.discClassData = orange.ExampleTable(orange.Domain(self.originalData.domain.attributes, discretizer), self.originalData)
985                if self.data:
986                    self.data = self.discClassData
987                # else, the data has no continuous attributes other then the class
988
989                self.classIntervalsLabel.setText("Current splits: " + ", ".join([str(classVar(x)) for x in discretizer.getValueFrom.transformer.points]))
990                self.error(0)
991                self.warning(0)
992                return True
993            except:
994                if self.data:
995                    self.warning(0, "Cannot discretize the class; using previous class")
996                else:
997                    self.error(0, "Cannot discretize the class")
998                self.classIntervalsLabel.setText("")
999                return False
1000
1001
1002    def classCustomChanged(self):
1003        self.classMethodChanged()
1004
1005    def classCustomSelected(self):
1006        if self.classDiscretization != 2: # prevent a cycle (this function called by setFocus at its end)
1007            self.classDiscretization = 2
1008            self.classMethodChanged()
1009            self.classCustomLineEdit.setFocus()
1010
1011    def discretize(self):
1012        if not self.data:
1013            return
1014
1015
1016    def synchronizeIf(self):
1017        if self.autoSynchronize:
1018            self.synchronize()
1019        else:
1020            self.pointsChanged = True
1021
1022    def synchronizePressed(self):
1023        self.clearLineEditFocus()
1024        self.synchronize()
1025
1026    def synchronize(self):
1027        if not self.data:
1028            return
1029
1030        slot = self.indiDiscretization - self.D_N_METHODS - 1
1031        if slot < 0:
1032            for slot in range(3):
1033                if not self.customLineEdits[slot].text():
1034                    break
1035            else:
1036                slot = 0
1037            self.indiDiscretization = slot + self.D_N_METHODS + 1
1038
1039        idx = self.continuousIndices[self.selectedAttr]
1040        attr = self.data.domain[idx]
1041        cp = list(self.graph.curCutPoints)
1042        cp.sort()
1043        splits = [str(attr(i)) for i in cp]
1044        splitsTxt = " ".join(splits)
1045        self.indiData[idx][0] = self.indiDiscretization
1046        self.indiData[idx][2+slot] = self.customSplits[slot] = splits
1047        self.customLineEdits[slot].setText(splitsTxt)
1048
1049        discretizer = orange.IntervalDiscretizer(points = cp).constructVariable(attr)
1050        self.discretizers[idx] = discretizer
1051
1052        self.indiLabels[self.selectedAttr] = ": " + splitsTxt + self.shortDiscNames[self.indiDiscretization]
1053        self.attrList.reset()
1054
1055        self.pointsChanged = False
1056        self.commitIf()
1057
1058
1059    def commitIf(self):
1060        if self.autoApply:
1061            self.commit()
1062        else:
1063            self.dataChanged = True
1064
1065    def commit(self):
1066        self.clearLineEditFocus()
1067
1068        if self.data:
1069            newattrs=[]
1070            for attr, disc in zip(self.data.domain.attributes, self.discretizers):
1071                if disc:
1072                    if disc.getValueFrom.transformer.points:
1073                        newattrs.append(disc)
1074                elif disc == None:  # can also be False -> remove
1075                    newattrs.append(attr)
1076
1077            if self.data.domain.classVar:
1078                if self.outputOriginalClass:
1079                    newdomain = orange.Domain(newattrs, self.originalData.domain.classVar)
1080                else:
1081                    newdomain = orange.Domain(newattrs, self.data.domain.classVar)
1082            else:
1083                newdomain = orange.Domain(newattrs, None)
1084
1085            newdata = orange.ExampleTable(newdomain, self.originalData)
1086
1087        elif self.discClassData and self.outputOriginalClass:
1088            newdata = self.discClassData
1089
1090        elif self.originalData and not (self.originalData.domain.classVar and self.originalData.domain.classVar.varType == orange.VarTypes.Continuous and not self.discClassData):  # no continuous attributes...
1091            newdata = self.originalData
1092        else:
1093            newdata = None
1094
1095        self.send("Data", newdata)
1096        dataChanged = False
1097
1098
1099    def sendReport(self):
1100        self.reportData(self.data, "Input data")
1101       
1102        settings = [("Default method", self.shortDiscNamesUnpar[self.discretization+1])]
1103        if 3 <= self.discretization <= 4:
1104            settings.append(("Number of intervals", str(self.intervals)))
1105        self.reportSettings("Settings", settings)
1106       
1107        attrs = []
1108        if self.data:
1109            for i, (attr, disc) in enumerate(zip(self.data.domain.attributes, self.discretizers)):
1110                if disc:
1111                    discType, intervals = self.indiData[i][:2]
1112                    cutpoints = ", ".join(str(attr(x)) for x in disc.getValueFrom.transformer.points)
1113                    if not cutpoints:
1114                        attrs.append((attr.name, "removed"))
1115                    elif not discType:
1116                        attrs.append((attr.name, cutpoints))
1117                    else:
1118                        attrs.append((attr.name, "%s (%s)" % (cutpoints, self.shortDiscNamesUnpar[discType])))
1119                elif disc == None:
1120                    if attr.varType == orange.VarTypes.Continuous:
1121                        attrs.append((attr.name, "left continuous"))
1122                    else:
1123                        attrs.append((attr.name, "already discrete"))
1124            classVar = self.data.domain.classVar
1125            if classVar:
1126                if classVar.varType == orange.VarTypes.Continuous:
1127                    attrs.append(("Class ('%s')" % classVar.name, "%s (%s)" % (self.classIntervalsLabel,
1128                                  ["equal frequency", "equal width", "custom"][self.classDiscretization])))
1129                    attrs.append(("Output discretized class", OWGUI.YesNo[self.outputOriginalClass]))
1130        self.reportSettings("Attributes", attrs)
1131
1132
1133    def reportGraph(self):
1134        try:
1135            attrName = self.data.domain[self.continuousIndices[self.selectedAttr]].name
1136        except:
1137            return
1138        self.reportSettings("Discretization Graph", 
1139                            [("Attribute", attrName),
1140                             ("Gain measure", self.measures[self.measure][0]),
1141                             ("Target class", self.data.domain.classVar.values[self.targetClass])])
1142        self.reportRaw("<br/>")
1143        self.reportImage(self.graph.saveToFileDirect, QSize(400, 300))
1144        self.finishReport()
1145
1146               
1147import sys
1148if __name__=="__main__":
1149    app=QApplication(sys.argv)
1150    w=OWDiscretize()
1151    w.show()
1152#    d=orange.ExampleTable("../../doc/datasets/bridges.tab")
1153#    d=orange.ExampleTable("../../doc/datasets/auto-mpg.tab")
1154    d = orange.ExampleTable("../../doc/datasets/iris.tab")
1155#    d = orange.ExampleTable(r"E:\Development\Orange Datasets\UCI\iris.tab")
1156    w.setData(d)
1157    #w.setData(None)
1158    #w.setData(d)
1159    app.exec_()
1160    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.