mitar avatar mitar committed a6b3d9c

Renaming documentation for widgets developers.

Comments (0)

Files changed (162)

Add a comment to this file

orange/doc/extend-widgets/DataSamplerA.png

Added
New image
Add a comment to this file

orange/doc/extend-widgets/DataSamplerB.png

Added
New image
Add a comment to this file

orange/doc/extend-widgets/DataSamplerC.png

Added
New image

orange/doc/extend-widgets/OWAttributeSampler.py

+"""
+<name>Attribute Sampler</name>
+<description>Lets the user select a list of attributes and the class attribute</description>
+<icon>icons/AttributeSampler.png</icon>
+<priority>1020</priority>
+"""
+
+from OWWidget import *
+import OWGUI
+
+class OWAttributeSampler(OWWidget):
+    settingsList = []
+    contextHandlers = {"": DomainContextHandler("", [
+            ContextField("classAttribute", DomainContextHandler.Required),
+            ContextField("attributeList", DomainContextHandler.List + DomainContextHandler.SelectedRequired,
+                         selected="selectedAttributes")])}
+
+    def __init__(self, parent=None, signalManager=None):
+        OWWidget.__init__(self, parent, signalManager, 'AttributeSampler')
+
+        self.inputs = [("Examples", ExampleTable, self.dataset)]
+        self.outputs = [("Examples", ExampleTable)]
+
+        self.icons = self.createAttributeIconDict()
+
+        self.attributeList = []
+        self.selectedAttributes = []
+        self.classAttribute = None
+        self.loadSettings()
+
+        OWGUI.listBox(self.controlArea, self, "selectedAttributes", "attributeList", box="Selected attributes", selectionMode = QListWidget.ExtendedSelection)
+        OWGUI.separator(self.controlArea)
+        self.classAttrCombo = OWGUI.comboBox(self.controlArea, self, "classAttribute", box="Class attribute")
+        OWGUI.separator(self.controlArea)
+        OWGUI.button(self.controlArea, self, "Commit", callback = self.outputData)
+
+        self.resize(150,400)
+
+
+    def dataset(self, data):
+        self.closeContext()
+
+        self.classAttrCombo.clear()
+        if data:
+            self.attributeList = [(attr.name, attr.varType) for attr in data.domain]
+            self.selectedAttributes = []
+            for attrName, attrType in self.attributeList:
+                self.classAttrCombo.addItem(self.icons[attrType], attrName)
+            self.classAttribute = 0
+        else:
+            self.attributeList = []
+            self.selectedAttributes = []
+            self.classAttrCombo.addItem("")
+
+        self.openContext("", data)
+
+        self.data = data
+        self.outputData()
+
+
+    def outputData(self):
+        if not self.data:
+            self.send("Examples", None)
+        else:
+            newDomain = orange.Domain([self.data.domain[i] for i in self.selectedAttributes], self.data.domain[self.classAttribute])
+            newData = orange.ExampleTable(newDomain, self.data)
+            self.send("Examples", newData)
+
+
+##############################################################################
+# Test the widget, run from prompt
+
+if __name__=="__main__":
+    appl = QApplication(sys.argv)
+    ow = OWAttributeSampler()
+    ow.show()
+
+    data = orange.ExampleTable('../datasets/iris.tab')
+    ow.dataset(data)
+
+    appl.exec_()

orange/doc/extend-widgets/OWDataSamplerA.py

+"""
+<name>Data Sampler (A)</name>
+<description>Randomly selects a subset of instances from the data set</description>
+<icon>icons/DataSamplerA.png</icon>
+<priority>10</priority>
+"""
+from OWWidget import *
+import OWGUI
+
+class OWDataSamplerA(OWWidget):
+    
+    def __init__(self, parent=None, signalManager=None):
+        OWWidget.__init__(self, parent, signalManager, 'SampleDataA')
+        
+        self.inputs = [("Data", ExampleTable, self.data)]
+        self.outputs = [("Sampled Data", ExampleTable)]
+
+        # GUI
+        box = OWGUI.widgetBox(self.controlArea, "Info")
+        self.infoa = OWGUI.widgetLabel(box, 'No data on input yet, waiting to get something.')
+        self.infob = OWGUI.widgetLabel(box, '')
+        self.resize(100,50)
+
+    def data(self, dataset):
+        if dataset:
+            self.infoa.setText('%d instances in input data set' % len(dataset))
+            indices = orange.MakeRandomIndices2(p0=0.1)
+            ind = indices(dataset)
+            sample = dataset.select(ind, 0)
+            self.infob.setText('%d sampled instances' % len(sample))
+            self.send("Sampled Data", sample)
+        else:
+            self.infoa.setText('No data on input yet, waiting to get something.')
+            self.infob.setText('')
+            self.send("Sampled Data", None)
+            
+
+##############################################################################
+# Test the widget, run from prompt
+
+if __name__=="__main__":
+    appl = QApplication(sys.argv)
+    ow = OWDataSamplerA()
+    ow.show()
+    dataset = orange.ExampleTable('../datasets/iris.tab')
+    ow.data(dataset)
+    appl.exec_()

orange/doc/extend-widgets/OWDataSamplerB.py

+"""
+<name>Data Sampler (B)</name>
+<description>Randomly selects a subset of instances from the data set</description>
+<icon>icons/DataSamplerB.png</icon>
+<priority>20</priority>
+"""
+from OWWidget import *
+import OWGUI
+
+class OWDataSamplerB(OWWidget):
+    settingsList = ['proportion', 'commitOnChange']
+    def __init__(self, parent=None, signalManager=None):
+        OWWidget.__init__(self, parent, signalManager, 'SampleDataB')
+
+        self.inputs = [("Data", ExampleTable, self.data)]
+        self.outputs = [("Sampled Data", ExampleTable)]
+
+        self.proportion = 50
+        self.commitOnChange = 0
+        self.loadSettings()
+
+        # GUI
+        box = OWGUI.widgetBox(self.controlArea, "Info")
+        self.infoa = OWGUI.widgetLabel(box, 'No data on input yet, waiting to get something.')
+        self.infob = OWGUI.widgetLabel(box, '')
+
+        OWGUI.separator(self.controlArea)
+        self.optionsBox = OWGUI.widgetBox(self.controlArea, "Options")
+        OWGUI.spin(self.optionsBox, self, 'proportion', min=10, max=90, step=10,
+                   label='Sample Size [%]:', callback=[self.selection, self.checkCommit])
+        OWGUI.checkBox(self.optionsBox, self, 'commitOnChange', 'Commit data on selection change')
+        OWGUI.button(self.optionsBox, self, "Commit", callback=self.commit)
+        self.optionsBox.setDisabled(1)
+
+        self.resize(100,50)
+
+    def data(self, dataset):
+        if dataset:
+            self.dataset = dataset
+            self.infoa.setText('%d instances in input data set' % len(dataset))
+            self.optionsBox.setDisabled(0)
+            self.selection()
+            self.commit()
+        else:
+            self.send("Sampled Data", None)
+            self.optionsBox.setDisabled(1)
+            self.infoa.setText('No data on input yet, waiting to get something.')
+            self.infob.setText('')
+
+    def selection(self):
+        indices = orange.MakeRandomIndices2(p0=self.proportion / 100.)
+        ind = indices(self.dataset)
+        self.sample = self.dataset.select(ind, 0)
+        self.infob.setText('%d sampled instances' % len(self.sample))
+
+    def commit(self):
+        self.send("Sampled Data", self.sample)
+
+    def checkCommit(self):
+        if self.commitOnChange:
+            self.commit()
+
+##############################################################################
+# Test the widget, run from prompt
+
+if __name__=="__main__":
+    appl = QApplication(sys.argv)
+    ow = OWDataSamplerB()
+    ow.show()
+    dataset = orange.ExampleTable('../datasets/iris.tab')
+    ow.data(dataset)
+    appl.exec_()

orange/doc/extend-widgets/OWDataSamplerC.py

+"""
+<name>Data Sampler (C)</name>
+<description>Randomly selects a subset of instances from the data set</description>
+<icon>icons/DataSamplerC.png</icon>
+<priority>30</priority>
+"""
+from OWWidget import *
+import OWGUI
+
+class OWDataSamplerC(OWWidget):
+    settingsList = ['proportion', 'commitOnChange']
+    def __init__(self, parent=None, signalManager=None):
+        OWWidget.__init__(self, parent, signalManager, 'SampleDataC')
+        
+        self.inputs = [("Data", ExampleTable, self.data)]
+        self.outputs = [("Sampled Data", ExampleTable), ("Other Data", ExampleTable)]
+
+        self.proportion = 50
+        self.commitOnChange = 0
+        self.loadSettings()
+
+        # GUI
+        box = OWGUI.widgetBox(self.controlArea, "Info")
+        self.infoa = OWGUI.widgetLabel(box, 'No data on input yet, waiting to get something.')
+        self.infob = OWGUI.widgetLabel(box, '')
+
+        OWGUI.separator(self.controlArea)
+        self.optionsBox = OWGUI.widgetBox(self.controlArea, "Options")
+        OWGUI.spin(self.optionsBox, self, 'proportion', min=10, max=90, step=10,
+                   label='Sample Size [%]:', callback=[self.selection, self.checkCommit])
+        OWGUI.checkBox(self.optionsBox, self, 'commitOnChange', 'Commit data on selection change')
+        OWGUI.button(self.optionsBox, self, "Commit", callback=self.commit)
+        self.optionsBox.setDisabled(1)
+
+        self.resize(100,50)
+
+    def data(self, dataset):
+        if dataset:
+            self.dataset = dataset
+            self.infoa.setText('%d instances in input data set' % len(dataset))
+            self.optionsBox.setDisabled(0)
+            self.selection()
+            self.commit()
+        else:
+            self.send("Sampled Data", None)
+            self.optionsBox.setDisabled(1)
+            self.infoa.setText('No data on input yet, waiting to get something.')
+            self.infob.setText('')
+
+    def selection(self):
+        indices = orange.MakeRandomIndices2(p0=self.proportion / 100.)
+        ind = indices(self.dataset)
+        self.sample = self.dataset.select(ind, 0)
+        self.otherdata = self.dataset.select(ind, 1)
+        self.infob.setText('%d sampled instances' % len(self.sample))
+
+    def commit(self):
+        self.send("Sampled Data", self.sample)
+        self.send("Other Data", self.otherdata)
+
+    def checkCommit(self):
+        if self.commitOnChange:
+            self.commit()
+
+##############################################################################
+# Test the widget, run from prompt
+
+if __name__=="__main__":
+    appl = QApplication(sys.argv)
+    ow = OWDataSamplerC()
+    ow.show()
+    dataset = orange.ExampleTable('../datasets/iris.tab')
+    ow.data(dataset)
+    appl.exec_()

orange/doc/extend-widgets/OWLearningCurveA.py

+"""
+<name>Learning Curve (A)</name>
+<description>Takes a data set and a set of learners and shows a learning curve in a table</description>
+<icon>icons/LearningCurveA.png</icon>
+<priority>1000</priority>
+"""
+
+from OWWidget import *
+import OWGUI, orngTest, orngStat
+
+class OWLearningCurveA(OWWidget):
+    settingsList = ["folds", "steps", "scoringF", "commitOnChange"]
+    
+    def __init__(self, parent=None, signalManager=None):
+        OWWidget.__init__(self, parent, signalManager, 'LearningCurveA')
+
+        self.inputs = [("Data", ExampleTable, self.dataset), ("Learner", orange.Learner, self.learner, 0)]
+        
+        self.folds = 5     # cross validation folds
+        self.steps = 10    # points in the learning curve
+        self.scoringF = 0  # scoring function
+        self.commitOnChange = 1 # compute curve on any change of parameters
+        self.loadSettings()
+        self.setCurvePoints() # sets self.curvePoints, self.steps equidistantpoints from 1/self.steps to 1
+        self.scoring = [("Classification Accuracy", orngStat.CA), ("AUC", orngStat.AUC), ("BrierScore", orngStat.BrierScore), ("Information Score", orngStat.IS), ("Sensitivity", orngStat.sens), ("Specificity", orngStat.spec)]
+        self.learners = [] # list of current learners from input channel, tuples (id, learner)
+        self.data = None   # data on which to construct the learning curve
+        self.curves = []   # list of evaluation results (one per learning curve point)
+        self.scores = []   # list of current scores, learnerID:[learner scores]
+
+        # GUI
+        box = OWGUI.widgetBox(self.controlArea, "Info")
+        self.infoa = OWGUI.widgetLabel(box, 'No data on input.')
+        self.infob = OWGUI.widgetLabel(box, 'No learners.')
+
+        OWGUI.separator(self.controlArea)
+        box = OWGUI.widgetBox(self.controlArea, "Evaluation Scores")
+        scoringNames = [x[0] for x in self.scoring]
+        OWGUI.comboBox(box, self, "scoringF", items=scoringNames, callback=self.computeScores)
+
+        OWGUI.separator(self.controlArea)
+        box = OWGUI.widgetBox(self.controlArea, "Options")
+        OWGUI.spin(box, self, 'folds', 2, 100, step=1, label='Cross validation folds:  ',
+                   callback=lambda: self.computeCurve(self.commitOnChange))
+        OWGUI.spin(box, self, 'steps', 2, 100, step=1, label='Learning curve points:  ',
+                   callback=[self.setCurvePoints, lambda: self.computeCurve(self.commitOnChange)])
+
+        OWGUI.checkBox(box, self, 'commitOnChange', 'Apply setting on any change')
+        self.commitBtn = OWGUI.button(box, self, "Apply Setting", callback=self.computeCurve, disabled=1)
+
+        # table widget
+        self.table = OWGUI.table(self.mainArea, selectionMode=QTableWidget.NoSelection)
+                
+        self.resize(500,200)
+
+    ##############################################################################    
+    # slots: handle input signals        
+
+    def dataset(self, data):
+        if data:
+            self.infoa.setText('%d instances in input data set' % len(data))
+            self.data = data
+            if (len(self.learners)):
+                self.computeCurve()
+        else:
+            self.infoa.setText('No data on input.')
+            self.curves = []
+            self.scores = []
+        self.commitBtn.setEnabled(self.data<>None)
+
+    def learner(self, learner, id=None):
+        ids = [x[0] for x in self.learners]
+        if not learner: # remove a learner and corresponding results
+            if not ids.count(id):
+                return # no such learner, removed before
+            indx = ids.index(id)
+            for i in range(self.steps):
+                self.curves[i].remove(indx)
+            del self.scores[indx]
+            del self.learners[indx]
+            self.setTable()
+        else:
+            if ids.count(id): # update (already seen a learner from this source)
+                indx = ids.index(id)
+                self.learners[indx] = (id, learner)
+                if self.data:
+                    curve = self.getLearningCurve([learner])
+                    score = [self.scoring[self.scoringF][1](x)[0] for x in curve]
+                    self.scores[indx] = score
+                    for i in range(self.steps):
+                        self.curves[i].add(curve[i], 0, replace=indx)
+            else: # add new learner
+                self.learners.append((id, learner))
+                if self.data:
+                    curve = self.getLearningCurve([learner])
+                    score = [self.scoring[self.scoringF][1](x)[0] for x in curve]
+                    self.scores.append(score)
+                    if len(self.curves):
+                        for i in range(self.steps):
+                            self.curves[i].add(curve[i], 0)
+                    else:
+                        self.curves = curve
+        if len(self.learners):
+            self.infob.setText("%d learners on input." % len(self.learners))
+        else:
+            self.infob.setText("No learners.")
+        self.commitBtn.setEnabled(len(self.learners))
+##        if len(self.scores):
+        if self.data:
+            self.setTable()
+
+    ##############################################################################    
+    # learning curve table, callbacks
+
+    # recomputes the learning curve
+    def computeCurve(self, condition=1):
+        if condition:
+            learners = [x[1] for x in self.learners]
+            self.curves = self.getLearningCurve(learners)
+            self.computeScores()
+
+    def computeScores(self):            
+        self.scores = [[] for i in range(len(self.learners))]
+        for x in self.curves:
+            for (i,s) in enumerate(self.scoring[self.scoringF][1](x)):
+                self.scores[i].append(s)
+        self.setTable()
+
+    def getLearningCurve(self, learners):   
+        pb = OWGUI.ProgressBar(self, iterations=self.steps*self.folds)
+        curve = orngTest.learningCurveN(learners, self.data, folds=self.folds, proportions=self.curvePoints, callback=pb.advance)
+        pb.finish()
+        return curve
+
+    def setCurvePoints(self):
+        self.curvePoints = [(x+1.)/self.steps for x in range(self.steps)]
+
+    def setTable(self):
+        self.table.setColumnCount(0)
+        self.table.setColumnCount(len(self.learners))
+        self.table.setRowCount(self.steps)
+
+        # set the headers
+        self.table.setHorizontalHeaderLabels([l.name for i,l in self.learners])
+        self.table.setVerticalHeaderLabels(["%4.2f" % p for p in self.curvePoints])
+
+        # set the table contents
+        for l in range(len(self.learners)):
+            for p in range(self.steps):
+                OWGUI.tableItem(self.table, p, l, "%7.5f" % self.scores[l][p])
+
+        for i in range(len(self.learners)):
+            self.table.setColumnWidth(i, 80)
+
+##############################################################################
+# Test the widget, run from prompt
+
+if __name__=="__main__":
+    appl = QApplication(sys.argv)
+    ow = OWLearningCurveA()
+    ow.show()
+    
+    l1 = orange.BayesLearner()
+    l1.name = 'Naive Bayes'
+    ow.learner(l1, 1)
+
+    data = orange.ExampleTable('../datasets/iris.tab')
+    ow.dataset(data)
+
+    l2 = orange.BayesLearner()
+    l2.name = 'Naive Bayes (m=10)'
+    l2.estimatorConstructor = orange.ProbabilityEstimatorConstructor_m(m=10)
+    l2.conditionalEstimatorConstructor = orange.ConditionalProbabilityEstimatorConstructor_ByRows(estimatorConstructor = orange.ProbabilityEstimatorConstructor_m(m=10))
+    ow.learner(l2, 2)
+
+    import orngTree
+    l4 = orngTree.TreeLearner(minSubset=2)
+    l4.name = "Decision Tree"
+    ow.learner(l4, 4)
+
+#    ow.learner(None, 1)
+#    ow.learner(None, 2)
+#    ow.learner(None, 4)
+
+    appl.exec_()

orange/doc/extend-widgets/OWLearningCurveB.py

+"""
+<name>Learning Curve (B)</name>
+<description>Takes a data set and a set of learners and shows a learning curve in a table</description>
+<icon>icons/LearningCurveB.png</icon>
+<priority>1010</priority>
+"""
+
+from OWWidget import *
+import OWGUI, orngTest, orngStat
+
+class OWLearningCurveB(OWWidget):
+    settingsList = ["folds", "steps", "scoringF", "commitOnChange"]
+    
+    def __init__(self, parent=None, signalManager=None):
+        OWWidget.__init__(self, parent, signalManager, 'LearningCurveA')
+
+        self.inputs = [("Train Data", ExampleTable, self.trainset), ("Test Data", ExampleTable, self.testset, 1, 1), ("Learner", orange.Learner, self.learner, 0)]
+        
+        self.folds = 5     # cross validation folds
+        self.steps = 10    # points in the learning curve
+        self.scoringF = 0  # scoring function
+        self.commitOnChange = 1 # compute curve on any change of parameters
+        self.loadSettings()
+        self.setCurvePoints() # sets self.curvePoints, self.steps equidistantpoints from 1/self.steps to 1
+        self.scoring = [("Classification Accuracy", orngStat.CA), ("AUC", orngStat.AUC), ("BrierScore", orngStat.BrierScore), ("Information Score", orngStat.IS), ("Sensitivity", orngStat.sens), ("Specificity", orngStat.spec)]
+        self.learners = [] # list of current learners from input channel, tuples (id, learner)
+        self.data = None   # data on which to construct the learning curve
+        self.testdata = None # optional test data
+        self.curves = []   # list of evaluation results (one per learning curve point)
+        self.scores = []   # list of current scores, learnerID:[learner scores]
+
+        # GUI
+        box = OWGUI.widgetBox(self.controlArea, "Info")
+        self.infoa = OWGUI.widgetLabel(box, 'No data on input.')
+        self.infob = OWGUI.widgetLabel(box, 'No learners.')
+
+        OWGUI.separator(self.controlArea)
+        box = OWGUI.widgetBox(self.controlArea, "Evaluation Scores")
+        scoringNames = [x[0] for x in self.scoring]
+        OWGUI.comboBox(box, self, "scoringF", items=scoringNames, callback=self.computeScores)
+
+        OWGUI.separator(self.controlArea)
+        box = OWGUI.widgetBox(self.controlArea, "Options")
+        OWGUI.spin(box, self, 'folds', 2, 100, step=1, label='Cross validation folds:  ',
+                   callback=lambda: self.computeCurve(self.commitOnChange))
+        OWGUI.spin(box, self, 'steps', 2, 100, step=1, label='Learning curve points:  ',
+                   callback=[self.setCurvePoints, lambda: self.computeCurve(self.commitOnChange)])
+
+        OWGUI.checkBox(box, self, 'commitOnChange', 'Apply setting on any change')
+        self.commitBtn = OWGUI.button(box, self, "Apply Setting", callback=self.computeCurve, disabled=1)
+
+        # table widget
+        self.table = OWGUI.table(self.mainArea, selectionMode=QTableWidget.NoSelection)
+                
+        self.resize(500,200)
+
+    ##############################################################################    
+    # slots: handle input signals        
+
+    def trainset(self, data):
+        if data:
+            self.infoa.setText('%d instances in input data set' % len(data))
+            self.data = data
+            if (len(self.learners)):
+                self.computeCurve()
+        else:
+            self.infoa.setText('No data on input.')
+            self.curves = []
+            self.scores = []
+        self.commitBtn.setEnabled(self.data<>None)
+
+    def testset(self, testdata):
+        if not testdata and not self.testdata:
+            return # avoid any unnecessary computation
+        self.testdata = testdata
+        if self.data and len(self.learners):
+            self.computeCurve()
+
+    def learner(self, learner, id=None):
+        ids = [x[0] for x in self.learners]
+        if not learner: # remove a learner and corresponding results
+            if not ids.count(id):
+                return # no such learner, removed before
+            indx = ids.index(id)
+            for i in range(self.steps):
+                self.curves[i].remove(indx)
+            del self.scores[indx]
+            del self.learners[indx]
+            self.setTable()
+        else:
+            if ids.count(id): # update (already seen a learner from this source)
+                indx = ids.index(id)
+                self.learners[indx] = (id, learner)
+                if self.data:
+                    curve = self.getLearningCurve([learner])
+                    score = [self.scoring[self.scoringF][1](x)[0] for x in curve]
+                    self.scores[indx] = score
+                    for i in range(self.steps):
+                        self.curves[i].add(curve[i], 0, replace=indx)
+            else: # add new learner
+                self.learners.append((id, learner))
+                if self.data:
+                    curve = self.getLearningCurve([learner])
+                    score = [self.scoring[self.scoringF][1](x)[0] for x in curve]
+                    self.scores.append(score)
+                    if len(self.curves):
+                        for i in range(self.steps):
+                            self.curves[i].add(curve[i], 0)
+                    else:
+                        self.curves = curve
+        if len(self.learners):
+            self.infob.setText("%d learners on input." % len(self.learners))
+        else:
+            self.infob.setText("No learners.")
+        self.commitBtn.setEnabled(len(self.learners))
+##        if len(self.scores):
+        if self.data:
+            self.setTable()
+
+    ##############################################################################    
+    # learning curve table, callbacks
+
+    # recomputes the learning curve
+    def computeCurve(self, condition=1):
+        if condition:
+            learners = [x[1] for x in self.learners]
+            self.curves = self.getLearningCurve(learners)
+            self.computeScores()
+
+    def computeScores(self):            
+        self.scores = [[] for i in range(len(self.learners))]
+        for x in self.curves:
+            for (i,s) in enumerate(self.scoring[self.scoringF][1](x)):
+                self.scores[i].append(s)
+        self.setTable()
+
+    def getLearningCurve(self, learners):   
+        pb = OWGUI.ProgressBar(self, iterations=self.steps*self.folds)
+        if not self.testdata:
+            curve = orngTest.learningCurveN(learners, self.data, folds=self.folds, proportions=self.curvePoints, callback=pb.advance)
+        else:
+            curve = orngTest.learningCurveWithTestData(learners,
+              self.data, self.testdata, times=self.folds, proportions=self.curvePoints, callback=pb.advance)            
+        pb.finish()
+        return curve
+
+    def setCurvePoints(self):
+        self.curvePoints = [(x+1.)/self.steps for x in range(self.steps)]
+
+    def setTable(self):
+        self.table.setColumnCount(0)
+        self.table.setColumnCount(len(self.learners))
+        self.table.setRowCount(self.steps)
+
+        # set the headers
+        self.table.setHorizontalHeaderLabels([l.name for i,l in self.learners])
+        self.table.setVerticalHeaderLabels(["%4.2f" % p for p in self.curvePoints])
+
+        # set the table contents
+        for l in range(len(self.learners)):
+            for p in range(self.steps):
+                OWGUI.tableItem(self.table, p, l, "%7.5f" % self.scores[l][p])
+
+        for i in range(len(self.learners)):
+            self.table.setColumnWidth(i, 80)
+
+##############################################################################
+# Test the widget, run from prompt
+
+if __name__=="__main__":
+    appl = QApplication(sys.argv)
+    ow = OWLearningCurveB()
+    ow.show()
+    
+    l1 = orange.BayesLearner()
+    l1.name = 'Naive Bayes'
+    ow.learner(l1, 1)
+
+    data = orange.ExampleTable('../datasets/iris.tab')
+    indices = orange.MakeRandomIndices2(data, p0 = 0.7)
+    train = data.select(indices, 0)
+    test = data.select(indices, 1)
+
+    ow.trainset(train)
+    ow.testset(test)
+
+    l2 = orange.BayesLearner()
+    l2.name = 'Naive Bayes (m=10)'
+    l2.estimatorConstructor = orange.ProbabilityEstimatorConstructor_m(m=10)
+    l2.conditionalEstimatorConstructor = orange.ConditionalProbabilityEstimatorConstructor_ByRows(estimatorConstructor = orange.ProbabilityEstimatorConstructor_m(m=10))
+    ow.learner(l2, 2)
+
+    import orngTree
+    l4 = orngTree.TreeLearner(minSubset=2)
+    l4.name = "Decision Tree"
+    ow.learner(l4, 4)
+
+#    ow.learner(None, 1)
+#    ow.learner(None, 2)
+#    ow.learner(None, 4)
+
+    appl.exec_()

orange/doc/extend-widgets/OWLearningCurveC.py

+"""
+<name>Learning Curve (C)</name>
+<description>Takes a data set and a set of learners and plots a learning curve in a table</description>
+<icon>icons/LearningCurveC.png</icon>
+<priority>1020</priority>
+"""
+
+from OWWidget import *
+from OWColorPalette import ColorPixmap
+import OWGUI, orngTest, orngStat
+from OWGraph import *
+
+import warnings
+
+class OWLearningCurveC(OWWidget):
+    settingsList = ["folds", "steps", "scoringF", "commitOnChange",
+                    "graphPointSize", "graphDrawLines", "graphShowGrid"]
+
+    def __init__(self, parent=None, signalManager=None):
+        OWWidget.__init__(self, parent, signalManager, 'LearningCurveC')
+
+        self.inputs = [("Data", ExampleTable, self.dataset), ("Learner", orange.Learner, self.learner, 0)]
+
+        self.folds = 5     # cross validation folds
+        self.steps = 10    # points in the learning curve
+        self.scoringF = 0  # scoring function
+        self.commitOnChange = 1 # compute curve on any change of parameters
+        self.graphPointSize = 5 # size of points in the graphs
+        self.graphDrawLines = 1 # draw lines between points in the graph
+        self.graphShowGrid = 1  # show gridlines in the graph
+        self.selectedLearners = [] 
+        self.loadSettings()
+
+        warnings.filterwarnings("ignore", ".*builtin attribute.*", orange.AttributeWarning)
+
+        self.setCurvePoints() # sets self.curvePoints, self.steps equidistantpoints from 1/self.steps to 1
+        self.scoring = [("Classification Accuracy", orngStat.CA), ("AUC", orngStat.AUC), ("BrierScore", orngStat.BrierScore), ("Information Score", orngStat.IS), ("Sensitivity", orngStat.sens), ("Specificity", orngStat.spec)]
+        self.learners = [] # list of current learners from input channel, tuples (id, learner)
+        self.data = None   # data on which to construct the learning curve
+        self.curves = []   # list of evaluation results (one per learning curve point)
+        self.scores = []   # list of current scores, learnerID:[learner scores]
+
+        # GUI
+        box = OWGUI.widgetBox(self.controlArea, "Info")
+        self.infoa = OWGUI.widgetLabel(box, 'No data on input.')
+        self.infob = OWGUI.widgetLabel(box, 'No learners.')
+
+        ## class selection (classQLB)
+        OWGUI.separator(self.controlArea)
+        self.cbox = OWGUI.widgetBox(self.controlArea, "Learners")
+        self.llb = OWGUI.listBox(self.cbox, self, "selectedLearners", selectionMode=QListWidget.MultiSelection, callback=self.learnerSelectionChanged)
+        
+        self.llb.setMinimumHeight(50)
+        self.blockSelectionChanges = 0
+
+        OWGUI.separator(self.controlArea)
+        box = OWGUI.widgetBox(self.controlArea, "Evaluation Scores")
+        scoringNames = [x[0] for x in self.scoring]
+        OWGUI.comboBox(box, self, "scoringF", items=scoringNames,
+                       callback=self.computeScores)
+
+        OWGUI.separator(self.controlArea)
+        box = OWGUI.widgetBox(self.controlArea, "Options")
+        OWGUI.spin(box, self, 'folds', 2, 100, step=1,
+                   label='Cross validation folds:  ',
+                   callback=lambda: self.computeCurve(self.commitOnChange))
+        OWGUI.spin(box, self, 'steps', 2, 100, step=1,
+                   label='Learning curve points:  ',
+                   callback=[self.setCurvePoints, lambda: self.computeCurve(self.commitOnChange)])
+
+        OWGUI.checkBox(box, self, 'commitOnChange', 'Apply setting on any change')
+        self.commitBtn = OWGUI.button(box, self, "Apply Setting", callback=self.computeCurve, disabled=1)
+
+        # start of content (right) area
+        tabs = OWGUI.tabWidget(self.mainArea)
+
+        # graph widget
+        tab = OWGUI.createTabPage(tabs, "Graph")
+        self.graph = OWGraph(tab)
+        self.graph.setAxisAutoScale(QwtPlot.xBottom)
+        self.graph.setAxisAutoScale(QwtPlot.yLeft)
+        tab.layout().addWidget(self.graph)
+        self.setGraphGrid()
+
+        # table widget
+        tab = OWGUI.createTabPage(tabs, "Table")
+        self.table = OWGUI.table(tab, selectionMode=QTableWidget.NoSelection)
+
+        self.resize(550,200)
+
+    ##############################################################################
+    # slots: handle input signals
+
+    def dataset(self, data):
+        if data:
+            self.infoa.setText('%d instances in input data set' % len(data))
+            self.data = data
+            if (len(self.learners)):
+                self.computeCurve()
+            self.replotGraph()
+        else:
+            self.infoa.setText('No data on input.')
+            self.curves = []
+            self.scores = []
+            self.graph.removeDrawingCurves()
+            self.graph.replot()
+        self.commitBtn.setEnabled(self.data<>None)
+
+    # manage learner signal
+    # we use following additional attributes for learner:
+    # - isSelected, learner is selected (display the learning curve)
+    # - curve, learning curve for the learner
+    # - score, evaluation score for the learning
+    def learner(self, learner, id=None):
+        ids = [x[0] for x in self.learners]
+        if not learner: # remove a learner and corresponding results
+            if not ids.count(id):
+                return # no such learner, removed before
+            indx = ids.index(id)
+            for i in range(self.steps):
+                self.curves[i].remove(indx)
+            del self.scores[indx]
+            self.learners[indx][1].curve.detach()
+            del self.learners[indx]
+            self.setTable()
+            self.updatellb()
+        else:
+            if ids.count(id): # update (already seen a learner from this source)
+                indx = ids.index(id)
+                prevLearner = self.learners[indx][1]
+                learner.isSelected = prevLearner.isSelected
+                self.learners[indx] = (id, learner)
+                if self.data:
+                    curve = self.getLearningCurve([learner])
+                    score = [self.scoring[self.scoringF][1](x)[0] for x in curve]
+                    self.scores[indx] = score
+                    for i in range(self.steps):
+                        self.curves[i].add(curve[i], 0, replace=indx)
+                    learner.score = score
+                    prevLearner.curve.detach()
+                    self.drawLearningCurve(learner)
+                self.updatellb()
+            else: # add new learner
+                learner.isSelected = 1
+                self.learners.append((id, learner))
+                if self.data:
+                    curve = self.getLearningCurve([learner])
+                    score = [self.scoring[self.scoringF][1](x)[0] for x in curve]
+                    self.scores.append(score)
+                    if len(self.curves):
+                        for i in range(self.steps):
+                            self.curves[i].add(curve[i], 0)
+                    else:
+                        self.curves = curve
+                    learner.score = score
+                self.updatellb()
+                self.drawLearningCurve(learner)
+        if len(self.learners):
+            self.infob.setText("%d learners on input." % len(self.learners))
+        else:
+            self.infob.setText("No learners.")
+        self.commitBtn.setEnabled(len(self.learners))
+        if self.data:
+            self.setTable()
+
+    ##############################################################################
+    # learning curve table, callbacks
+
+    # recomputes the learning curve
+    def computeCurve(self, condition=1):
+        if condition:
+            learners = [x[1] for x in self.learners]
+            self.curves = self.getLearningCurve(learners)
+            self.computeScores()
+
+    def computeScores(self):
+        self.scores = [[] for i in range(len(self.learners))]
+        for x in self.curves:
+            for (i,s) in enumerate(self.scoring[self.scoringF][1](x)):
+                self.scores[i].append(s)
+        for (i,l) in enumerate(self.learners):
+            l[1].score = self.scores[i]
+        self.setTable()
+        self.replotGraph()
+
+    def getLearningCurve(self, learners):
+        pb = OWGUI.ProgressBar(self, iterations=self.steps*self.folds)
+        curve = orngTest.learningCurveN(learners, self.data, folds=self.folds, proportions=self.curvePoints, callback=pb.advance)
+        pb.finish()
+        return curve
+
+    def setCurvePoints(self):
+        self.curvePoints = [(x+1.)/self.steps for x in range(self.steps)]
+
+    def setTable(self):
+        self.table.setColumnCount(0)
+        self.table.setColumnCount(len(self.learners))
+        self.table.setRowCount(self.steps)
+
+        # set the headers
+        self.table.setHorizontalHeaderLabels([l.name for i,l in self.learners])
+        self.table.setVerticalHeaderLabels(["%4.2f" % p for p in self.curvePoints])
+
+        # set the table contents
+        for l in range(len(self.learners)):
+            for p in range(self.steps):
+                OWGUI.tableItem(self.table, p, l, "%7.5f" % self.scores[l][p])
+
+        for i in range(len(self.learners)):
+            self.table.setColumnWidth(i, 80)
+
+
+    # management of learner selection
+
+    def updatellb(self):
+        self.blockSelectionChanges = 1
+        self.llb.clear()
+        colors = ColorPaletteHSV(len(self.learners))
+        for (i,lt) in enumerate(self.learners):
+            l = lt[1]
+            item = QListWidgetItem(ColorPixmap(colors[i]), l.name)
+            self.llb.addItem(item)
+            item.setSelected(l.isSelected)
+            l.color = colors[i]
+        self.blockSelectionChanges = 0
+
+    def learnerSelectionChanged(self):
+        if self.blockSelectionChanges: return
+        for (i,lt) in enumerate(self.learners):
+            l = lt[1]
+            if l.isSelected != (i in self.selectedLearners):
+                if l.isSelected: # learner was deselected
+                    l.curve.detach()
+                else: # learner was selected
+                    self.drawLearningCurve(l)
+                self.graph.replot()
+            l.isSelected = i in self.selectedLearners
+
+    # Graph specific methods
+
+    def setGraphGrid(self):
+        self.graph.enableGridYL(self.graphShowGrid)
+        self.graph.enableGridXB(self.graphShowGrid)
+
+    def setGraphStyle(self, learner):
+        curve = learner.curve
+        if self.graphDrawLines:
+            curve.setStyle(QwtPlotCurve.Lines)
+        else:
+            curve.setStyle(QwtPlotCurve.NoCurve)
+        curve.setSymbol(QwtSymbol(QwtSymbol.Ellipse, \
+          QBrush(QColor(0,0,0)), QPen(QColor(0,0,0)),
+          QSize(self.graphPointSize, self.graphPointSize)))
+        curve.setPen(QPen(learner.color, 5))
+
+    def drawLearningCurve(self, learner):
+        if not self.data: return
+        curve = self.graph.addCurve(learner.name, xData=self.curvePoints, yData=learner.score, autoScale=True)
+        
+        learner.curve = curve
+        self.setGraphStyle(learner)
+        self.graph.replot()
+
+    def replotGraph(self):
+        self.graph.removeDrawingCurves()
+        for l in self.learners:
+            self.drawLearningCurve(l[1])
+
+##############################################################################
+# Test the widget, run from prompt
+
+if __name__=="__main__":
+    appl = QApplication(sys.argv)
+    ow = OWLearningCurveC()
+    ow.show()
+
+    l1 = orange.BayesLearner()
+    l1.name = 'Naive Bayes'
+    ow.learner(l1, 1)
+
+    data = orange.ExampleTable('../datasets/iris.tab')
+    ow.dataset(data)
+
+    l2 = orange.BayesLearner()
+    l2.name = 'Naive Bayes (m=10)'
+    l2.estimatorConstructor = orange.ProbabilityEstimatorConstructor_m(m=10)
+    l2.conditionalEstimatorConstructor = orange.ConditionalProbabilityEstimatorConstructor_ByRows(estimatorConstructor = orange.ProbabilityEstimatorConstructor_m(m=10))
+
+    l3 = orange.kNNLearner(name="k-NN")
+    ow.learner(l3, 3)
+
+    import orngTree
+    l4 = orngTree.TreeLearner(minSubset=2)
+    l4.name = "Decision Tree"
+    ow.learner(l4, 4)
+
+#    ow.learner(None, 1)
+#    ow.learner(None, 2)
+#    ow.learner(None, 4)
+    
+
+
+    appl.exec_()

orange/doc/extend-widgets/OWLearningCurve_plot.py

+"""
+<name>Learning Curve (C)</name>
+<description>Takes a data set and a set of learners and plots a learning curve in a table</description>
+<icon>icons/LearningCurveC.png</icon>
+<priority>1020</priority>
+"""
+
+from OWWidget import *
+from OWColorPalette import ColorPixmap
+import OWGUI, orngTest, orngStat
+from owplot import *
+
+import warnings
+
+class OWLearningCurveC(OWWidget):
+    settingsList = ["folds", "steps", "scoringF", "commitOnChange",
+                    "graphPointSize", "graphDrawLines", "graphShowGrid"]
+
+    def __init__(self, parent=None, signalManager=None):
+        OWWidget.__init__(self, parent, signalManager, 'LearningCurveC')
+
+        self.inputs = [("Data", ExampleTable, self.dataset), ("Learner", orange.Learner, self.learner, 0)]
+
+        self.folds = 5     # cross validation folds
+        self.steps = 10    # points in the learning curve
+        self.scoringF = 0  # scoring function
+        self.commitOnChange = 1 # compute curve on any change of parameters
+        self.graphPointSize = 5 # size of points in the graphs
+        self.graphDrawLines = 1 # draw lines between points in the graph
+        self.graphShowGrid = 1  # show gridlines in the graph
+        self.selectedLearners = [] 
+        self.loadSettings()
+
+        warnings.filterwarnings("ignore", ".*builtin attribute.*", orange.AttributeWarning)
+
+        self.setCurvePoints() # sets self.curvePoints, self.steps equidistantpoints from 1/self.steps to 1
+        self.scoring = [("Classification Accuracy", orngStat.CA), ("AUC", orngStat.AUC), ("BrierScore", orngStat.BrierScore), ("Information Score", orngStat.IS), ("Sensitivity", orngStat.sens), ("Specificity", orngStat.spec)]
+        self.learners = [] # list of current learners from input channel, tuples (id, learner)
+        self.data = None   # data on which to construct the learning curve
+        self.curves = []   # list of evaluation results (one per learning curve point)
+        self.scores = []   # list of current scores, learnerID:[learner scores]
+
+        # GUI
+        box = OWGUI.widgetBox(self.controlArea, "Info")
+        self.infoa = OWGUI.widgetLabel(box, 'No data on input.')
+        self.infob = OWGUI.widgetLabel(box, 'No learners.')
+
+        ## class selection (classQLB)
+        OWGUI.separator(self.controlArea)
+        self.cbox = OWGUI.widgetBox(self.controlArea, "Learners")
+        self.llb = OWGUI.listBox(self.cbox, self, "selectedLearners", selectionMode=QListWidget.MultiSelection, callback=self.learnerSelectionChanged)
+        
+        self.llb.setMinimumHeight(50)
+        self.blockSelectionChanges = 0
+
+        OWGUI.separator(self.controlArea)
+        box = OWGUI.widgetBox(self.controlArea, "Evaluation Scores")
+        scoringNames = [x[0] for x in self.scoring]
+        OWGUI.comboBox(box, self, "scoringF", items=scoringNames,
+                       callback=self.computeScores)
+
+        OWGUI.separator(self.controlArea)
+        box = OWGUI.widgetBox(self.controlArea, "Options")
+        OWGUI.spin(box, self, 'folds', 2, 100, step=1,
+                   label='Cross validation folds:  ',
+                   callback=lambda: self.computeCurve(self.commitOnChange))
+        OWGUI.spin(box, self, 'steps', 2, 100, step=1,
+                   label='Learning curve points:  ',
+                   callback=[self.setCurvePoints, lambda: self.computeCurve(self.commitOnChange)])
+
+        OWGUI.checkBox(box, self, 'commitOnChange', 'Apply setting on any change')
+        self.commitBtn = OWGUI.button(box, self, "Apply Setting", callback=self.computeCurve, disabled=1)
+
+        # start of content (right) area
+        tabs = OWGUI.tabWidget(self.mainArea)
+
+        # graph widget
+        tab = OWGUI.createTabPage(tabs, "Graph")
+        self.graph = OWPlot(tab)
+        self.graph.set_axis_autoscale(xBottom)
+        self.graph.set_axis_autoscale(yLeft)
+        tab.layout().addWidget(self.graph)
+        self.setGraphGrid()
+
+        # table widget
+        tab = OWGUI.createTabPage(tabs, "Table")
+        self.table = OWGUI.table(tab, selectionMode=QTableWidget.NoSelection)
+
+        self.resize(550,200)
+
+    ##############################################################################
+    # slots: handle input signals
+
+    def dataset(self, data):
+        if data:
+            self.infoa.setText('%d instances in input data set' % len(data))
+            self.data = data
+            if (len(self.learners)):
+                self.computeCurve()
+            self.replotGraph()
+        else:
+            self.infoa.setText('No data on input.')
+            self.curves = []
+            self.scores = []
+            self.graph.clear()
+            self.graph.replot()
+        self.commitBtn.setEnabled(self.data<>None)
+
+    # manage learner signal
+    # we use following additional attributes for learner:
+    # - isSelected, learner is selected (display the learning curve)
+    # - curve, learning curve for the learner
+    # - score, evaluation score for the learning
+    def learner(self, learner, id=None):
+        ids = [x[0] for x in self.learners]
+        if not learner: # remove a learner and corresponding results
+            if not ids.count(id):
+                return # no such learner, removed before
+            indx = ids.index(id)
+            for i in range(self.steps):
+                self.curves[i].remove(indx)
+            del self.scores[indx]
+            self.learners[indx][1].curve.detach()
+            del self.learners[indx]
+            self.setTable()
+            self.updatellb()
+        else:
+            if ids.count(id): # update (already seen a learner from this source)
+                indx = ids.index(id)
+                prevLearner = self.learners[indx][1]
+                learner.isSelected = prevLearner.isSelected
+                self.learners[indx] = (id, learner)
+                if self.data:
+                    curve = self.getLearningCurve([learner])
+                    score = [self.scoring[self.scoringF][1](x)[0] for x in curve]
+                    self.scores[indx] = score
+                    for i in range(self.steps):
+                        self.curves[i].add(curve[i], 0, replace=indx)
+                    learner.score = score
+                    prevLearner.curve.detach()
+                    self.drawLearningCurve(learner)
+                self.updatellb()
+            else: # add new learner
+                learner.isSelected = 1
+                self.learners.append((id, learner))
+                if self.data:
+                    curve = self.getLearningCurve([learner])
+                    score = [self.scoring[self.scoringF][1](x)[0] for x in curve]
+                    self.scores.append(score)
+                    if len(self.curves):
+                        for i in range(self.steps):
+                            self.curves[i].add(curve[i], 0)
+                    else:
+                        self.curves = curve
+                    learner.score = score
+                self.updatellb()
+                self.drawLearningCurve(learner)
+        if len(self.learners):
+            self.infob.setText("%d learners on input." % len(self.learners))
+        else:
+            self.infob.setText("No learners.")
+        self.commitBtn.setEnabled(len(self.learners))
+        if self.data:
+            self.setTable()
+
+    ##############################################################################
+    # learning curve table, callbacks
+
+    # recomputes the learning curve
+    def computeCurve(self, condition=1):
+        if condition:
+            learners = [x[1] for x in self.learners]
+            self.curves = self.getLearningCurve(learners)
+            self.computeScores()
+
+    def computeScores(self):
+        self.scores = [[] for i in range(len(self.learners))]
+        for x in self.curves:
+            for (i,s) in enumerate(self.scoring[self.scoringF][1](x)):
+                self.scores[i].append(s)
+        for (i,l) in enumerate(self.learners):
+            l[1].score = self.scores[i]
+        self.setTable()
+        self.replotGraph()
+
+    def getLearningCurve(self, learners):
+        pb = OWGUI.ProgressBar(self, iterations=self.steps*self.folds)
+        curve = orngTest.learningCurveN(learners, self.data, folds=self.folds, proportions=self.curvePoints, callback=pb.advance)
+        pb.finish()
+        return curve
+
+    def setCurvePoints(self):
+        self.curvePoints = [(x+1.)/self.steps for x in range(self.steps)]
+
+    def setTable(self):
+        self.table.setColumnCount(0)
+        self.table.setColumnCount(len(self.learners))
+        self.table.setRowCount(self.steps)
+
+        # set the headers
+        self.table.setHorizontalHeaderLabels([l.name for i,l in self.learners])
+        self.table.setVerticalHeaderLabels(["%4.2f" % p for p in self.curvePoints])
+
+        # set the table contents
+        for l in range(len(self.learners)):
+            for p in range(self.steps):
+                OWGUI.tableItem(self.table, p, l, "%7.5f" % self.scores[l][p])
+
+        for i in range(len(self.learners)):
+            self.table.setColumnWidth(i, 80)
+
+
+    # management of learner selection
+
+    def updatellb(self):
+        self.blockSelectionChanges = 1
+        self.llb.clear()
+        colors = ColorPaletteHSV(len(self.learners))
+        for (i,lt) in enumerate(self.learners):
+            l = lt[1]
+            item = QListWidgetItem(ColorPixmap(colors[i]), l.name)
+            self.llb.addItem(item)
+            item.setSelected(l.isSelected)
+            l.color = colors[i]
+        self.blockSelectionChanges = 0
+
+    def learnerSelectionChanged(self):
+        if self.blockSelectionChanges: return
+        for (i,lt) in enumerate(self.learners):
+            l = lt[1]
+            if l.isSelected != (i in self.selectedLearners):
+                if l.isSelected: # learner was deselected
+                    l.curve.detach()
+                else: # learner was selected
+                    self.drawLearningCurve(l)
+                self.graph.replot()
+            l.isSelected = i in self.selectedLearners
+
+    # Graph specific methods
+
+    def setGraphGrid(self):
+        self.graph.enableGridYL(self.graphShowGrid)
+        self.graph.enableGridXB(self.graphShowGrid)
+
+    def setGraphStyle(self, learner):
+        curve = learner.curve
+        if self.graphDrawLines:
+            curve.set_style(OWCurve.LinesPoints)
+        else:
+            curve.set_style(OWCurve.Points)
+        curve.set_symbol(OWPoint.Ellipse)
+        curve.set_point_size(self.graphPointSize)
+        curve.set_color(self.graph.color(OWPalette.Data))
+        curve.set_pen(QPen(learner.color, 5))
+
+    def drawLearningCurve(self, learner):
+        if not self.data: return
+        curve = self.graph.add_curve(learner.name, xData=self.curvePoints, yData=learner.score, autoScale=True)
+        
+        learner.curve = curve
+        self.setGraphStyle(learner)
+        self.graph.replot()
+
+    def replotGraph(self):
+        self.graph.clear()
+        for l in self.learners:
+            self.drawLearningCurve(l[1])
+
+##############################################################################
+# Test the widget, run from prompt
+
+if __name__=="__main__":
+    appl = QApplication(sys.argv)
+    ow = OWLearningCurveC()
+    ow.show()
+
+    l1 = orange.BayesLearner()
+    l1.name = 'Naive Bayes'
+    ow.learner(l1, 1)
+
+    data = orange.ExampleTable('../datasets/iris.tab')
+    ow.dataset(data)
+
+    l2 = orange.BayesLearner()
+    l2.name = 'Naive Bayes (m=10)'
+    l2.estimatorConstructor = orange.ProbabilityEstimatorConstructor_m(m=10)
+    l2.conditionalEstimatorConstructor = orange.ConditionalProbabilityEstimatorConstructor_ByRows(estimatorConstructor = orange.ProbabilityEstimatorConstructor_m(m=10))
+
+    l3 = orange.kNNLearner(name="k-NN")
+    ow.learner(l3, 3)
+
+    import orngTree
+    l4 = orngTree.TreeLearner(minSubset=2)
+    l4.name = "Decision Tree"
+    ow.learner(l4, 4)
+
+#    ow.learner(None, 1)
+#    ow.learner(None, 2)
+#    ow.learner(None, 4)
+    
+
+
+    appl.exec_()

orange/doc/extend-widgets/api.htm

+<html>
+<head>
+<title>Orange Widgets Reference Guide for Developers</title>
+<link rel=stylesheet HREF="../style.css" type="text/css">
+<link rel=stylesheet href="style-print.css" type="text/css" media=print>
+</head>
+<body>
+<h1>Orange Widgets Reference Guide for Developers</h1>
+
+<h2>Channels Definitions, Data Exchange</h2>
+
+<p>Input and output channels are defined anywhere within the
+<code>__init__</code> function of a main widget class. The definition
+is used when running a widget, but also when registering your widget
+within Orange Canvas. Channel definitions are optional, depending on
+what your widget does.
+
+<h3>Output Channels</h3>
+
+Following is an example that defines two output channels:</p>
+
+<xmp class="code">self.outputs = [("Sampled Data", orange.ExampleTable), ("Learner", orange.Learner)]
+</xmp>
+
+<p><code>self.outputs</code> should thus be a list of tuples, within
+each the first element is a name of the channel, and the second the
+type of the tokens that will be passed through. Token types are class
+names; most often these are some Orange classes, but they can also be
+anything you may define as class in Python.</p>
+
+<p>Widgets send the data by using <code>self.send</code> call,
+like:</p>
+
+<xmp class="code">self.send("Sampled Data", mydata)
+</xmp>
+
+<p>Parameters of <code>send</code> are channel name and a token to be
+send (e.g., a variable that holds the data to be send through the
+channel).</p>
+
+<p>When tokens are send around, the signaling mechanism annotates
+them with a pointer to an object that sent the toke (<em>e.g.</em>, a widget
+id). Additionally, this annotation can be coupled with some name
+passed to <code>send</code>, in case you have a widget that can send
+few tokens one after the other and you would like to enable a receiving widget
+recognize these are different tokens (and not updates of the same
+one):</p>
+
+<xmp class="code">id = 10
+self.send("Sampled Data", mydata, id)
+</xmp>
+
+<h3>Input Channels</h3>
+
+<p>An example of the simplest definition of an input channel is:</p>
+
+<xmp class="code">self.inputs = [("Data", orange.ExampleTable, self.receiveData)]
+</xmp>
+
+<p>Again, <code>self.inputs</code> is a list of tuples, where the
+elements are the name of the channel, followed by a channel type and a
+Python function that will be called with any token received. For the
+channel defined above, a corresponding receiving function would be of
+the type (we would most often define it within the widget class
+defintion, hence <code>self</code> for the first attribute):</p>
+
+<xmp class="code">def receiveData(self, data):
+   # handle data in some way
+</xmp>
+
+<p>Any time our widget would receive a token, <code>receiveData</code>
+would be called. Notice there would be no way of knowing anything
+about the sender of the token, hence widget would most often replace
+the previously received token with the new one, and forget about the
+old one.</p>
+
+<p>Widgets can often clear their output by sending a <code>None</code>
+as a token. Also, upon deletion of some widget, this is the way that
+Orange Canvas would inform all directly connected downstream widgets
+about deletion. Similar, when channels connecting two widgets are
+deleted, Orange Canvas would automatically send <code>None</code> to
+the receiving widget. Make sure your widget handles <code>None</code>
+tokens appropriately!</code>
+
+<p>There are cases when widget would like to know about the origin of
+a token. Say, you would like to input several learners to the
+evaluation widget, how would this distinguish between the learners of
+different origins? Remember (from above) that tokens are actually
+passed around with IDs (pointers to widgets that sent them). To
+declare a widget is interested about these IDs, one needs to define an
+input channel in the following way:</p>
+
+<xmp class="code">self.inputs = [("Learners", orange.Learner, self.learner, 0)]
+</xmp>
+
+<p>where the last argument refers if we have a "single token channel"
+(last argument should be 1, default) or a "multitoken" one (0 as a
+fourth element of the list holding channel definition). For the above
+declared channel, the receiving function should include an extra
+argument for the ID, like:</p>
+
+<xmp class="code">def learner(self, learnertoken, tokenid):
+   # handle learnertoken and tokeid in some way
+</xmp>
+
+<p>Widgets such as <code>OWTestLearners</code> and alike use such
+schema.</p>
+
+<p>Finally, we may have input channels of the same type. If a widget
+would declare input channels like:</p>
+
+<xmp class="code">self.inputs = [("Data", orange.ExampleTable, self.maindata), ("Additional Data", orange.ExampleTable, self.otherdata)]
+</xmp>
+
+<p>and we connect this widget in Orange Canvas to a sending widget
+that has a single orange.ExampleTable output channel, Canvas would
+bring up Set Channels dialog. There, a sending widget's channel would
+be connected to both receiving channels. As we would often prefer to
+connect to a single (default) channel instead (still allowing user of
+Orange Canvas to set up a different schema manually), we set all the
+channels to which we do not want an automatic connection into a "minor
+mode". We do this by the using the fifth element in the channel
+definition list, like:</p>
+
+<xmp class="code">self.inputs = [("Data", orange.ExampleTable, self.maindata),
+("Additional Data", orange.ExampleTable, self.otherdata, 1, 1)]
+</xmp>
+
+<hr>
+
+<p>Note: This documentation is not complete, we are now providing it as
+is, and are working on it to update it. Until then, you should find
+most information about widget APIs in the <a href="/doc/widgets">Tutorial</a>.
+
+</body>
+</html> 
Add a comment to this file

orange/doc/extend-widgets/attributesampler.png

Added
New image

orange/doc/extend-widgets/basics.htm

+<HTML>
+<HEAD>
+<LINK REL=StyleSheet HREF="../style.css" TYPE="text/css">
+</HEAD>
+<BODY>
+
+<H1>Getting Started</H1>
+ 
+<p>The tutorial on these pages is meant for those who are interested in
+developing widgets in Orange. Orange Widgets are components in
+Orange's visual programming environment. They are wrappers around some
+data analysis code that provide graphical user interface
+(GUI). Widgets communicate, and pass tokens through communication
+channels to interact with other widgets. While simplest widgets
+consist of even less than 100 lines of code, those more complex that
+often implement some fancy graphical display of data and allow for
+some really nice interaction may be over 1000 lines long.</p>
+
+<p>When we have started to write this tutorial, we have been working
+on widgets for quite a while. There are now (now being in the very
+time this page has been crafted) about 50 widgets available, and we
+have pretty much defined how widgets and their interfaces should look
+like. We have also made some libraries that help set up GUI with only
+a few lines of code, and some mechanisms that one may found useful and
+user friendly, like progress bars and alike.</p>
+
+<p>On this page, we will start with some simple essentials, and then
+show how to build a simple widget that will be ready to run within
+Orange Canvas, our visual programming environment.</p>
+
+<H2>Prerequisites</H2>
+
+<p>Each Orange widget belongs to a category and within a
+category has an associated priority. Opening Orange Canvas, a visual
+programming environment that comes with Orange, widgets are listed in
+toolbox on the top of the window:</p>
+
+<img src="widgettoolbox.png">
+
+<p>By default, Orange is installed in site-packages directory of
+Python libraries. Widgets are all put in the subdirectories of
+OrangeWidget directory; these subdirectories define widget
+categories. For instance, under windows and default settings, a
+directory that stores all the widgets displayed in the Evaluate pane is
+C:\Python23\Lib\site-packages\orange\OrangeWidgets\Evaluate. Figure
+above shows that at the time of writing of this text there were five
+widgets for evaluation of classifiers, and this is how my Evaluate
+directory looked like:</P>
+
+<img src="explorer.png">
+
+<p>Notice that there are a number of files in Evaluate directory, so
+how does Orange Canvas distinguish those that define widgets? Well,
+widgets are Python script files that start with a header. Here is a
+header for OWTestLearners.py:</p>
+
+<xmp class="code">"""
+<name>Test Learners</name>
+<description>Estimates the predictive performance of learners on a data set.</description>
+<icon>icons/TestLearners.png</icon>
+<priority>200</priority>
+"""
+</xmp>
+
+<P>OWTestLearners is a Python script, so the header information we
+show about lies within the comment block, with triple quote opening
+and closing the comment. Header defines the name of the widget, its
+description, the name of the picture file the widget will use for an
+icon, and a number expressing the priority of the widget. The name of
+the widget as given in the header will be the one that will be used
+throughout in Orange Canvas. As for naming, the actual file name of
+the widget is not important. The description of the widget is shown
+once mouse rests on an toolbar icon representing the widget. And for
+the priority: this determines the order in which widgets appear in the
+toolbox. The one shown above for Evaluate groups has widget named Test
+Learners with priority 200, Classifications with 300, ROC Analysis
+with 1010, Lift Curve with 1020 and Calibration Plot with 1030. Notice
+that every time the priority number crosses a multiplier of a 1000,
+there is a gap in the toolbox between the widgets; in this way, a
+subgroups of the widgets within the same group can be imposed.</P>
+
+<p>Widgets communicate. They use typed channels, and exchange
+tokens. Each widget would define its input and output channels in
+something like:</P>
+
+<xmp class="code">self.inputs = [("Test Data Set", ExampleTable, self.cdata), ("Learner", orange.Learner, self.learner, 0)]
+self.outputs = [("Evaluation Results", orngTest.ExperimentResults)]
+</xmp>
+
+<P>Above two lines are for Test Learners widget, so hovering with your
+mouse over its icon in the widget toolbox would yield:</p>
+
+<img src="mouseoverwidgetintoolbox.png">
+
+<p>We will go over the syntax of channel definitions later, but for
+now the following is important:</P>
+<ol>
+<li>Widgets are defined in a Python files.</li>
+<li>For Orange and Orange canvas to find them, they reside in
+subdirectories in OrangeWidgets directory of Orange installation. The
+name of the subdirectory matters, as this is the name of the widget
+category. Widgets in the same directory will be grouped in the same
+pane of widget toolbox in Orange Canvas.</li>
+<li>A file describing a widget starts with a header. This, given in
+sort of XMLish style, tells about the name, short description,
+location of an icon and priority of the widget. </li>
+<li>The sole role of
+priority is to specify the placement (order) of widgets in the Orange
+Canvas toolbox.</li>
+<li>Somewhere in the code (where we will learn later) there are two
+lines which tell which channels the widgets uses for
+communication. These, together with the header information, completely
+specify the widget as it is seen from the outside.</li>
+</ol>
+
+<p>Oh, by the way. Orange caches widget descriptions to achieve a faster
+startup, but this cache is automatically refreshed at startup if any change
+is detected in widgets' files.</p>
+
+<H2>Let's Start</H2>
+
+<P>Now that we went through some of the more boring stuff, let us now
+have some fun and write a widget. We will start with a very simple
+one, that will receive a data set on the input and will output a data
+set with 10% of the data instances. Not to mess with other widgets, we
+will create a Test directory within OrangeWidgets directory, and write
+the widget in a file called <a
+href="OWDataSamplerA.py">OWDataSamplerA</a>: OW for Orange Widget,
+DataSampler since this is what widget will be doing, and A since we
+prototype a number of this widgets in our tutorial.</P>
+
+<P>The script defining the OWDataSamplerA widget starts with a follwing header:</P>
+
+<xmp class="code"><name>Data Sampler</name>
+<description>Randomly selects a subset of instances from the data set</description>
+<icon>icons/DataSamplerA.png</icon>
+<priority>10</priority>
+</xmp>
+
+<P>This should all be clear now, perhaps just a remark on an icon. We
+can put any name here, and if Orange Canvas won't find the
+corresponding file, it will use a file called Unknown.png (an icon
+with a question mark).</P>
+
+<p>Orange Widgets are all derived from the class OWWidget. The name of
+the class should be match the file name, so the lines following the
+header in our Data Sampler widget should look something like:</P>
+
+<xmp class="code">from OWWidget import *
+import OWGUI
+
+class OWDataSamplerA(OWWidget):
+    
+    def __init__(self, parent=None, signalManager=None):
+        OWWidget.__init__(self, parent, signalManager, 'SampleDataA')
+        
+        self.inputs = [("Data", ExampleTable, self.data)]
+        self.outputs = [("Sampled Data", ExampleTable)]
+
+        # GUI
+        box = OWGUI.widgetBox(self.controlArea, "Info")
+        self.infoa = OWGUI.widgetLabel(box, 'No data on input yet, waiting to get something.')
+        self.infob = OWGUI.widgetLabel(box, '')
+        self.resize(100,50)
+</xmp>
+
+<P>In initialization, the widget calls the <code>init</code> function
+of a base class, passing the name 'SampleData' which will,
+essentially, be used for nothing else than a stem of a file for saving
+the parameters of the widgets (we will regress on these somehow
+latter in tutorial). Widget then defines inputs and outputs. For
+input, widget defines a "Data" channel, accepting tokens of the type
+orange.ExampleTable and specifying that <code>data</code> function will
+be used to handle them. For now, we will use a single output channel
+called "Sampled Data", which will be of the same type
+(orange.ExampleTable).</p>
+
+<p>Notice that the types of the channels are
+specified by a class name; you can use any classes here, but if your
+widgets need to talk with other widgets in Orange, you will need to
+check which classes are used there. Luckily, and as one of the main
+design principles, there are just a few channel types that current
+Orange widgets are using.<P>
+
+<p>The next four lines specify the GUI of our widget. This will be
+simple, and will include only two lines of text of which, if nothing
+will happen, the first line will report on "no data yet", and second
+line will be empty. By (another) design principles, in an interface
+Orange widgets are most often split to control and main area. Control
+area appears on the left and should include any controls for settings
+or options that your widget will use. Main are would most often
+include a graph, table or some drawing that will be based on the
+inputs to the widget and current options/setting in the control
+area. OWWidget make these two areas available through its attributes
+<code>self.controlArea</code> and <code>self.mainArea</code>. Notice
+that while it would be nice for all widgets to have this common visual
+look, you can use these areas in any way you want to, even disregarding one
+and composing your widget completely unlike the others in Orange.</p>
+
+<p>As our widget won't display anything apart from some info, we will
+place the two labels in the control area and surround it with the box
+"Info".</p>
+
+<p>In order to complete our widget, we now need to define how will it
+handle the input data. This is done in a function called
+<code>data</code> (remember, we did introduce this name in the
+specification of the input channel):</p>
+
+<xmp class="code">    def data(self, dataset):
+        if dataset:
+            self.infoa.setText('%d instances in input data set' % len(dataset))
+            indices = orange.MakeRandomIndices2(p0=0.1)
+            ind = indices(dataset)
+            sample = dataset.select(ind, 0)
+            self.infob.setText('%d sampled instances' % len(sample))
+            self.send("Sampled Data", sample)
+        else:
+            self.infoa.setText('No data on input yet, waiting to get something.')
+            self.infob.setText('')
+            self.send("Sampled Data", None)
+</xmp>
+
+<p>The function is defined within a class definition, so its first
+argument has to be <code>self</code>. The second argument called
+<code>dataset</code> is the token sent through the input channel which
+our function needs to handle.</p>
+
+<p>To handle the non-empty token, the widget updates the interface
+reporting on number of data items on the input, then does the data
+sampling using Orange's routines for these (see <a
+href="/doc/reference/RandomIndices.htm">chapter on Random
+Sampling in Orange Reference Guide</a> for more), and updates the
+interface reporting on the number of sampled instances. Finally, the
+sampled data is sent as a token to the output channel with a name
+"Sampled Data".</p>
+
+<p>Notice that the token can be empty (<code>dataset==None</code>),
+resulting from either the sending widget to which we have connected
+intentionally emptying the channel, or when the link between the two
+widgets is removed. In any case, it is important that we always write
+token handlers that appropriately handle the empty tokens. In our
+implementation, we took care of empty input data set by appropriately
+setting the GUI of a widget and sending an empty token to the
+output channel.</p>
+
+<p>Although our widget is now ready to test, for a final touch, let's
+design an icon for our widget. As specified in the widget header, we
+will call it <a href="DataSamplerA.png">DataSamplerA.png</a> and will
+put it in icons subdirectory of OrangeWidgets directory (together with
+all other icons of other widgets).</p>
+
+<p>For a test, we now open Orange Canvas. There should be a new pane in a
+widget toolbox called Test (this is the name of the directory we have
+used to put in our widget). If we click on this pane, it displays an
+icon of our widget. Try to hoover on it to see if the header and
+channel info was processed correctly:</P>
+
+<img src="samplewidgetontoolbox.png">
+
+<P>Now for the real test. We put the File widget on the schema (from
+Data pane), read iris.tab data set (or any that comes handy, if you
+can find none, download iris from <a href="/datasets.psp">Orange's data set
+repository</a>). We also put our Data Sampler widget on the pane and
+open it (double click on the icon, or right-click and choose
+Open):</p>
+
+<img src="datasamplerAempty.png">
+
+<p>Drag this window off the window with the widget schema of Orange
+Canvas, and connect File and Data Sampler widget (click on an ouput
+connector - green box - of the File widget, and drag the line to the
+input connector of the Data Sampler). If everything is ok, as soon as
+you release the mouse the connection is established and, the token
+that was waiting on the output of the file widget was sent to the Data
+Sampler widget, which in turn updated its window:</p>
+
+<img src="datasamplerAupdated.png">
+
+<p>To see if the Data Sampler indeed sent some data to the output,
+connect it to the Data Table widget:</p>
+
+<img src="schemawithdatatable.png">
+
+<p>Try opening different data files (the change should propagate
+through your widgets and with Data Table window open, you should
+immediately see the result of sampling). Try also removing the
+connection between File and Data Sampler (right click on the
+connection, choose Remove). What happens to the data displayed in the
+Data Table?</p>
+
+<h2>Testing Your Widget Outside Orange Canvas</h2>
+
+</p>When prototyping a single widget, for a fast test I often get
+bored of running Orange Canvas, setting the schema and clicking on
+icons to get widget windows. There are two options to bypass this. The
+first one is to add a testing script at the end of your widget. To do
+this, we finished Data Sampler with:</p>
+
+<xmp class="code">if __name__=="__main__":
+    appl = QApplication(sys.argv)
+    ow = OWDataSamplerA()
+    ow.show()
+    dataset = orange.ExampleTable('iris.tab')
+    ow.data(dataset)
+    appl.exec_()
+</xmp>
+
+<p>These are essentially some calls to Qt routines that run GUI for our
+widgets. At the core, however, notice that instead of sending the
+token to the input channel, we directly called the routine for token
+handling (<cofde>data</code>).</p>
+
+<p>To test your widget in more complex environment, that for instance
+requires to set a complex schema in which your widget collaborates,
+use Orange Canvas to set the schema and then either 1) save the schema
+to be opened every time you run Orange Canvas, or 2) save this schema
+(File menu) as an application within a single file you will need to
+run each time you will test your widget.</p>
+
+</body>
+</html>
+

orange/doc/extend-widgets/channels.htm

+<HTML>
+<HEAD>
+<LINK REL=StyleSheet HREF="../style.css" TYPE="text/css">
+<title>Orange Widgets: Channels and Tokens</title>
+</HEAD>
+<BODY>
+
+<H1>Channels and Tokens</H1>
+ 
+<p>Our data sampler widget was, regarding the channels, rather simple
+and linear: the widget was designed to receive the token from one
+widget, and send an output token to another widget. Just like in an
+example schema below:</p>
+
+<img src="schemawithdatasamplerB.png">
+
+<p>There's quite a bit more to channels and management of tokens, and
+we will overview most of the stuff you need to know to make your more
+complex widgets in this section.</p>
+
+<h2>Multi-Input Channels</h2>
+
+<p>First, I do not like the name, but can't make up anything better. In
+essence, the basic idea about "multi-input" channels is that they can
+be used to connect them with several output channels. That is, if a
+widget supports such a channel, several widgets can feed their input
+to that widget simultaneously.</p>
+
+<p>Say we want to build a widget that takes a data set and test
+various predictive modelling techniques on it. A widget has to have an
+input data channel, and this we know how to deal with from our <a
+href="settings.htm">previous</a> lesson. But, somehow differently, we
+want to connect any number of widgets which define learners to our
+testing widget. Just like in a schema below, where three different
+learners are used:</p>
+
+<img src="learningcurve.png">
+
+<p>We will here take a look at how we define the channels for a learning
+curve widget, and how we manage its input tokens. But before we do it,
+just in brief: learning curve is something that you can use to test
+some machine learning algorithm in trying to see how its performance
+depends on the size of the training set size. For this, one can draw a
+smaller subset of data, learn the classifier, and test it on remaining
+data set. To do this in a just way (by Salzberg, 1997), we perform
+k-fold cross validation but use only a proportion of the data for
+training. The output of the widget should then look something
+like:</p>
+
+<img src="learningcurve-output.png">
+
+<p>Now back to channels and tokens. Input and output channels for our
+widget are defined by:</p>
+
+<xmp class="code">self.inputs = [("Data", ExampleTable, self.dataset), ("Learner", orange.Learner, self.learner, 0)]
+</xmp>
+