Miha Stajdohar avatar Miha Stajdohar committed f4dd8db

From HTML to Sphinx.

Comments (0)

Files changed (48)

 import numpydoc
 sphinx.ext.autodoc.inspect = myinspect
 numpydoc.docscrape.inspect = myinspect
-
 module_setup = imp.load_source('module_setup', os.path.join(PATH, '..', 'setup.py'))
 VERSION = module_setup.VERSION
 AUTHOR = module_setup.AUTHOR
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 sys.path.append(os.path.abspath(os.path.join(PATH, "..")))
+sys.path.append(os.path.abspath(os.path.join(PATH, "..", "Orange")))
 import Orange
 
 # -- General configuration -----------------------------------------------------
Add a comment to this file

docs/extend-widgets/rst/DataSamplerB.png

Added
New image

docs/extend-widgets/rst/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_()

docs/extend-widgets/rst/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_()

docs/extend-widgets/rst/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_()

docs/extend-widgets/rst/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, Multiple)]
+        
+        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_()

docs/extend-widgets/rst/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, Default),
+                       ("Test Data", ExampleTable, self.testset),
+                       ("Learner", orange.Learner, self.learner, Multiple)]
+        
+        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_()

docs/extend-widgets/rst/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, Multiple)]
+
+        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_()

docs/extend-widgets/rst/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, Multiple)]
+
+        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_()

docs/extend-widgets/rst/OrangeWidgets.plot.owcurve.rst

-.. automodule :: OrangeWidgets.plot.owcurve
+.. automodule :: Orange.OrangeWidgets.plot.owcurve

docs/extend-widgets/rst/OrangeWidgets.plot.owlegend.rst

-.. automodule :: OrangeWidgets.plot.owlegend
+.. automodule :: OrangeWidgets.plot.owlegend

docs/extend-widgets/rst/OrangeWidgets.plot.owplot.rst

-.. automodule :: OrangeWidgets.plot.owplot
+.. automodule :: OrangeWidgets.plot.owplot

docs/extend-widgets/rst/OrangeWidgets.plot.owplotgui.rst

-.. automodule :: OrangeWidgets.plot.owplotgui
+.. automodule :: OrangeWidgets.plot.owplotgui

docs/extend-widgets/rst/OrangeWidgets.plot.owpoint.rst

-.. automodule :: OrangeWidgets.plot.owpoint
+.. automodule :: OrangeWidgets.plot.owpoint

docs/extend-widgets/rst/OrangeWidgets.plot.owtools.rst

-.. automodule :: OrangeWidgets.plot.owtools
+.. automodule :: OrangeWidgets.plot.owtools

docs/extend-widgets/rst/api.rst

+#############################################
+Orange Widgets Reference Guide for Developers
+#############################################
+
+***********************************
+Channels Definitions, Data Exchange
+***********************************
+
+Input and output channels are defined anywhere within the
+:obj:`__init__` 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.
+
+Output Channels
+***************
+
+Following is an example that defines two output channels::
+
+    self.outputs = [("Sampled Data", orange.ExampleTable), ("Learner", orange.Learner)]
+
+:obj:`self.outputs` 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.
+
+Widgets send the data by using :obj:`self.send` call,
+like::
+
+    self.send("Sampled Data", mydata)
+
+Parameters of :obj:`send` are channel name and a token to be
+send (e.g., a variable that holds the data to be send through the
+channel).
+
+When tokens are send around, the signaling mechanism annotates
+them with a pointer to an object that sent the toke (e.g., a widget
+id). Additionally, this annotation can be coupled with some name
+passed to :obj:`send`, 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)::
+
+    id = 10
+    self.send("Sampled Data", mydata, id)
+
+**************
+Input Channels
+**************
+
+An example of the simplest definition of an input channel is::
+
+    self.inputs = [("Data", orange.ExampleTable, self.receiveData)]
+
+Again, :obj:`self.inputs` 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 :obj:`self` for the first attribute)::
+
+    def receiveData(self, data):
+    # handle data in some way
+
+Any time our widget would receive a token, :obj:`receiveData`
+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.
+
+Widgets can often clear their output by sending a :obj:`None`
+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 :obj:`None` to
+the receiving widget. Make sure your widget handles :obj:`None`
+tokens appropriately!`
+
+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::
+
+    self.inputs = [("Learners", orange.Learner, self.learner, Multiple)]
+
+where the last argument refers if we have a "Single" (default if not
+specified) or a "Multiple" channel. For the above declared channel, the
+receiving function should include an extra argument for the ID, like::
+
+   def learner(self, learnertoken, tokenid):
+   # handle learnertoken and tokeid in some way
+
+Widgets such as :obj:`OWTestLearners` and alike use such
+schema.
+
+Finally, we may have input channels of the same type. If a widget
+would declare input channels like::
+
+    self.inputs = [("Data", orange.ExampleTable, self.maindata),
+               ("Additional Data", orange.ExampleTable, self.otherdata)]
+
+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 could
+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 that channel
+as the default. We do this by the using the fourth element in the channel
+definition list, like::
+
+    self.inputs = [("Data", orange.ExampleTable, self.maindata, Default),
+               ("Additional Data", orange.ExampleTable, self.otherdata)]
Add a comment to this file

docs/extend-widgets/rst/attributesampler.png

Added
New image

docs/extend-widgets/rst/basics.rst

+###############
+Getting Started
+###############
+
+
+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.
+
+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.
+
+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.
+
+*************
+Prerequisites
+*************
+
+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:
+
+.. image:: widgettoolbox.png
+
+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:
+
+.. image:: explorer.png
+
+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::
+
+    <name>Test Learners</name>
+    <description>Estimates the predictive performance of learners on a data set.</description>
+    <icon>icons/TestLearners.png</icon>
+    <priority>200</priority>
+
+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.
+
+Widgets communicate. They use typed channels, and exchange
+tokens. Each widget would define its input and output channels in
+something like::
+
+    self.inputs = [("Test Data Set", ExampleTable, self.cdata), ("Learner", orange.Learner, self.learner, 0)]
+    self.outputs = [("Evaluation Results", orngTest.ExperimentResults)]
+
+Above two lines are for Test Learners widget, so hovering with your
+mouse over its icon in the widget toolbox would yield:
+
+.. image:: mouseoverwidgetintoolbox.png
+
+We will go over the syntax of channel definitions later, but for
+now the following is important:
+
+* Widgets are defined in a Python files.
+* 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.
+*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.
+*The sole role of
+priority is to specify the placement (order) of widgets in the Orange
+Canvas toolbox.
+*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.
+
+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.
+
+***********
+Let's Start
+***********
+
+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 `OWDataSamplerA <OWDataSamplerA.py>`: 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.
+
+The script defining the OWDataSamplerA widget starts with a follwing header::
+
+    <name>Data Sampler</name>
+    <description>Randomly selects a subset of instances from the data set</description>
+    <icon>icons/DataSamplerA.png</icon>
+    <priority>10</priority>
+
+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).
+
+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::
+
+    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)
+
+In initialization, the widget calls the :obj:`init` 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 :obj:`data` 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).
+
+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.
+
+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
+:obj:`self.controlArea` and :obj:`self.mainArea`. 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.
+
+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".
+
+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
+:obj:`data` (remember, we did introduce this name in the
+specification of the input channel)::
+
+    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)
+
+The function is defined within a class definition, so its first
+argument has to be :obj:`self`. The second argument called
+:obj:`dataset` is the token sent through the input channel which
+our function needs to handle.
+
+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, 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".
+
+Notice that the token can be empty (:obj:`dataset==None`),
+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.
+
+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 `DataSamplerA.png <DataSamplerA.png>`_ and will
+put it in icons subdirectory of OrangeWidgets directory (together with
+all other icons of other widgets).
+
+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:
+
+.. image:: samplewidgetontoolbox.png
+
+Now for the real test. We put the File widget on the schema (from
+Data pane), read iris.tab data set. We also put our Data Sampler widget on the pane and
+open it (double click on the icon, or right-click and choose
+Open):
+
+.. image:: datasamplerAempty.png
+
+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:
+
+.. image:: datasamplerAupdated.png
+
+To see if the Data Sampler indeed sent some data to the output,
+connect it to the Data Table widget:
+
+.. image:: schemawithdatatable.png
+
+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?
+
+*****************************************
+Testing Your Widget Outside Orange Canvas
+*****************************************
+
+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::
+
+    if __name__=="__main__":
+        appl = QApplication(sys.argv)
+        ow = OWDataSamplerA()
+        ow.show()
+        dataset = orange.ExampleTable('iris.tab')
+        ow.data(dataset)
+        appl.exec_()
+
+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 (:obj:`data`).
+
+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.

docs/extend-widgets/rst/channels.rst

+###################
+Channels and Tokens
+###################
+ 
+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:
+
+.. image:: schemawithdatasamplerB.png
+
+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.
+
+********************
+Multi-Input Channels
+********************
+
+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.
+
+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 :doc:`previous`
+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:
+
+.. image:: learningcurve.png
+
+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:
+
+.. image:: learningcurve-output.png
+
+Now back to channels and tokens. Input and output channels for our
+widget are defined by::
+
+    self.inputs = [("Data", ExampleTable, self.dataset),
+               ("Learner", orange.Learner, self.learner, Multiple)]
+
+Notice that everything is pretty much the same as it was with
+widgets from previous lessons, the only difference being
+:obj:`Multiple + Default` as the last value in the list that defines
+the :obj:`Learner` channel. This :obj:`Multiple + Default` says
+that this is a multi-input channel and is the default input for its type.
+If it would be unspecified then by default value of
+:obj:`Single + NonDefault` would be used. That would mean that the
+widget can receive the input only from one widget and is not the default input
+channel for its type (more on default channels later).
+
+How does the widget know from which widget did the token come from?
+In Orange, tokens are sent around with an id of a widget that is
+sending the token (essentially, with a pointer to the corresponding
+widget object), and having a multi-input channel only tells Orange to
+send a token together with sending widget id, the two arguments with
+which the receiving function is called. For our :obj:`Learner`
+channel the receiving function is :obj:`learner`, and this looks
+like the following::
+
+    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 self.data:
+            self.setTable()
+
+OK, this looks like one long and complicated function. But be
+patient! Learning curve is not the simplest widget there is, so
+there's some extra code in the function above to manage the
+information it handles in the appropriate way. To understand the
+signals, though, you should only understand the following. We store
+the learners (objects that learn from data) in the list
+:obj:`self.learners`. The list contains tuples with an id of the
+widget that has sent the learner, and the learner itself. We could
+store such information in a dictionary as well, but for this
+particular widget the order of learners is important, and we thought
+that list is a more appropriate structure.
+
+The function above first checks if the learner sent is empty
+(:obj:`None`). Remember that sending an empty learner
+essentially means that the link with the sending widget was removed,
+hance we need to remove such learner from our list. If a non-empty
+learner was sent, then it is either a new learner (say, from a widget
+we have just linked to our learning curve widget), or an update
+version of the previously sent learner. If the later is the case, then
+there is an ID which we already have in the learners list, and we
+need to replace previous information on that learner. If a new learner
+was sent, the case is somehow simpler, and we just add this learner
+and its learning curve to the corresponding variables that hold this
+information.
+
+The function that handles :obj:`learners` as shown above is
+the most complicated function in our learning curve widget. In fact,
+the rest of the widget does some simple GUI management, and calls
+learning curve routines from testing and performance
+scoring functions from stats. I rather like
+the easy by which new scoring functions are added to the widget, since
+all that is needed is the augmenting the list ::
+
+    self.scoring = [("Classification Accuracy", orngStat.CA),\
+                ("AUC", orngStat.AUC), \
+                ("BrierScore", orngStat.BrierScore),\
+                ("Information Score", orngStat.IS),\
+                ("Sensitivity", orngStat.sens), \
+                ("Specificity", orngStat.spec)]
+
+which is defined in the initialization part of the widget. The
+other useful trick in this widget is that evaluation (k-fold cross
+validation) is carried out just once given the learner, data set and
+evaluation parameters, and scores are then derived from class
+probability estimates as obtained from the evaluation procedure. Which
+essentially means that switching from one to another scoring function
+(and displaying the result in the table) takes only a split of a
+second. To see the rest of the widget, check out `its code <OWLearningCurveA.py>`_.
+
+*****************************
+Using Several Output Channels
+*****************************
+
+There's nothing new here, only that we need a widget that has
+several output channels of the same type to illustrate the idea of the
+default channels in the next section. For this purpose, we will modify
+our sampling widget as defined in previous lessons such that it will
+send out the sampled data to one channel, and all other data to
+another channel. The corresponding channel definition of this widget
+is::
+
+    self.outputs = [("Sampled Data", ExampleTable), ("Other Data", ExampleTable)]
+
+We used this in the third incarnation of `data sampler widget <OWDataSamplerC.py>`_,
+with essentially the only other change in the code in the :obj:`selection` and
+:obj:`commit` functions::
+
+    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)
+
+If a widget that has multiple channels of the same type is
+connected to a widget that accepts such tokens, Orange Canvas opens a
+window asking the user to confirm which channels to connect. The
+channel mentioned in :obj:`self.outputs` is connected by
+default. Hence, if we have just connected Data Sampler
+(C) widget to a Data Table widget in a schema below:
+
+.. image:: datasampler-totable.png
+
+we would get a following window querying users for information on
+which channels to connect:
+
+.. image:: datasampler-channelquerry.png
+
+*************************************************************
+Default Channels (When Using Input Channels of the Same Type)
+*************************************************************
+
+Now, let's say we want to extend our learning curve widget such
+that it does the learning the same way as it used to, but can -
+provided that such data set is defined - test the
+learners (always) on the same, external data set. That is, besides the
+training data set, we need another channel of the same type but used
+for training data set. Notice, however, that most often we will only
+provide the training data set, so we would not like to be bothered (in
+Orange Canvas) with the dialog which channel to connect to, as the
+training data set channel will be the default one.
+
+When enlisting the input channel of the same type, the non-default
+channels have a special flag in the channel specification list. So for
+our new `learning curve <OWLearningCurveB.py>`_ widget, the
+channel specification is::
+
+    self.inputs = [("Train Data", ExampleTable, self.trainset, Default),
+               ("Test Data", ExampleTable, self.testset),
+               ("Learner", orange.Learner, self.learner, Multiple)]
+
+That is, the :obj:`Train Data` channel is a single-token
+channel which is a default one (third parameter). Note that the flags can
+be added (or OR-d) together so :obj:`Default + Multi` is a valid flag.
+To test how this works, connect a file widget to a learning curve widget and
+- nothing will really happen:
+
+.. image:: file-to-learningcurveb.png
+
+That is, no window with a query on which channels
+to connect to will open. To find out which channels got connected,
+double click on the green link between the two widgets:
+
+.. image:: file-to-learningcurveb-channels.png

docs/extend-widgets/rst/contextsettings.rst

+##########################
+Context-Dependent Settings
+##########################
+
+You have already learned about :obj:`storing widget settings <settings>`.
+But there's more: some settings are context
+dependent. Open Orange Canvas and observe the scatter plot - feed it
+some data, select two attributes for x- and y-axis, select some
+examples... and then give it some other data. Your settings get
+lost. Or do they? Well, change back to the original data and you will
+see the same two attributes on the axes and even the same examples
+selected.
+
+What happens is that Orange remembers the settings (chosen
+attributes etc.) and ties them with the data domain. The next time it
+gets the data from the same (or similar enough) domain, the settings
+will be reused. The history of an arbitrary number of domains can be
+stored in this manner.