예제 #1
0
class OWLearningCurveB(OWBaseWidget):
    name = "Learning Curve (B)"
    description = ("Takes a dataset and a set of learners and shows a "
                   "learning curve in a table")
    icon = "icons/LearningCurve.svg"
    priority = 1010

    # [start-snippet-1]
    class Inputs:
        data = Input("Data", Table, default=True)
        test_data = Input("Test Data", Table)
        learner = MultiInput("Learner", Learner)


# [end-snippet-1]

#: cross validation folds

    folds = settings.Setting(5)
    #: points in the learning curve
    steps = settings.Setting(10)
    #: index of the selected scoring function
    scoringF = settings.Setting(0)
    #: compute curve on any change of parameters
    commitOnChange = settings.Setting(True)

    def __init__(self):
        super().__init__()

        # sets self.curvePoints, self.steps equidistant points from
        # 1/self.steps to 1
        self.updateCurvePoints()

        self.scoring = [("Classification Accuracy",
                         Orange.evaluation.scoring.CA),
                        ("AUC", Orange.evaluation.scoring.AUC),
                        ("Precision", Orange.evaluation.scoring.Precision),
                        ("Recall", Orange.evaluation.scoring.Recall)]
        #: Input data on which to construct the learning curve
        self.data = None
        #: Optional test data
        self.testdata = None
        #: LearnerData for each learner input
        self.learners: List[LearnerData] = []

        # GUI
        box = gui.widgetBox(self.controlArea, "Info")
        self.infoa = gui.widgetLabel(box, 'No data on input.')
        self.infob = gui.widgetLabel(box, 'No learners.')

        gui.separator(self.controlArea)

        box = gui.widgetBox(self.controlArea, "Evaluation Scores")
        gui.comboBox(box,
                     self,
                     "scoringF",
                     items=[x[0] for x in self.scoring],
                     callback=self._invalidate_curves)

        gui.separator(self.controlArea)

        box = gui.widgetBox(self.controlArea, "Options")
        gui.spin(box,
                 self,
                 'folds',
                 2,
                 100,
                 step=1,
                 label='Cross validation folds:  ',
                 keyboardTracking=False,
                 callback=lambda: self._invalidate_results()
                 if self.commitOnChange else None)
        gui.spin(box,
                 self,
                 'steps',
                 2,
                 100,
                 step=1,
                 label='Learning curve points:  ',
                 keyboardTracking=False,
                 callback=[
                     self.updateCurvePoints,
                     lambda: self._invalidate_results()
                     if self.commitOnChange else None
                 ])
        gui.checkBox(box, self, 'commitOnChange',
                     'Apply setting on any change')
        self.commitBtn = gui.button(box,
                                    self,
                                    "Apply Setting",
                                    callback=self._invalidate_results,
                                    disabled=True)

        gui.rubber(self.controlArea)

        # table widget
        self.table = gui.table(self.mainArea,
                               selectionMode=QTableWidget.NoSelection)

    ##########################################################################
    # slots: handle input signals

    @Inputs.data
    def set_dataset(self, data):
        """Set the input train dataset."""
        # Clear all results/scores for all learner inputs
        for item in self.learners:
            item.results = None
            item.curve = None

        self.data = data

        if data is not None:
            self.infoa.setText('%d instances in input dataset' % len(data))
        else:
            self.infoa.setText('No data on input.')

        self.commitBtn.setEnabled(self.data is not None)

    @Inputs.test_data
    def set_testdataset(self, testdata):
        """Set a separate test dataset."""
        # Clear all results/scores for all learner inputs
        for item in self.learners:
            item.results = None
            item.curve = None

        self.testdata = testdata

    @Inputs.learner
    def set_learner(self, index: int, learner):
        """Set the input learner at index"""
        # update/replace a learner on a previously connected link
        item = self.learners[index]
        item.learner = learner
        item.results = None
        item.curve = None

    @Inputs.learner.insert
    def insert_learner(self, index, learner):
        """Insert a learner at index"""
        self.learners.insert(index, LearnerData(learner, None, None))

    @Inputs.learner.remove
    def remove_learner(self, index):
        """"Remove a learner at index"""
        # remove a learner and corresponding results
        del self.learners[index]

    def handleNewSignals(self):
        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 is not None:
            self._update()
            self._update_curve_points()
        self._update_table()

    def _invalidate_curves(self):
        if self.data is not None:
            self._update_curve_points()
        self._update_table()

    def _invalidate_results(self):
        for item in self.learners:
            item.results = None
            item.curve = None

        if self.data is not None:
            self._update()
            self._update_curve_points()
        self._update_table()

    def _update(self):
        assert self.data is not None
        # collect all learners for which results have not yet been computed
        need_update = [(i, item) for (i, item) in enumerate(self.learners)
                       if item.results is None]
        if not need_update:
            return

        learners = [item.learner for _, item in need_update]

        if self.testdata is None:
            # compute the learning curve result for all learners in one go
            results = learning_curve(
                learners,
                self.data,
                folds=self.folds,
                proportions=self.curvePoints,
            )
        else:
            results = learning_curve_with_test_data(
                learners,
                self.data,
                self.testdata,
                times=self.folds,
                proportions=self.curvePoints,
            )
        # split the combined result into per learner/model results
        results = [
            list(Results.split_by_model(p_results)) for p_results in results
        ]

        for i, (_, item) in enumerate(need_update):
            item.results = [p_results[i] for p_results in results]

    def _update_curve_points(self):
        scoref = self.scoring[self.scoringF][1]
        for item in self.learners:
            item.curve = [scoref(x)[0] for x in item.results]

    def _update_table(self):
        self.table.setRowCount(0)
        self.table.setRowCount(len(self.curvePoints))
        self.table.setColumnCount(len(self.learners))

        self.table.setHorizontalHeaderLabels(
            [item.learner.name for item in self.learners])
        self.table.setVerticalHeaderLabels(
            ["{:.2f}".format(p) for p in self.curvePoints])

        if self.data is None:
            return

        for column, item in enumerate(self.learners):
            for row, point in enumerate(item.curve):
                self.table.setItem(row, column,
                                   QTableWidgetItem("{:.5f}".format(point)))

        for i in range(len(self.learners)):
            sh = self.table.sizeHintForColumn(i)
            cwidth = self.table.columnWidth(i)
            self.table.setColumnWidth(i, max(sh, cwidth))

    def updateCurvePoints(self):
        self.curvePoints = [(x + 1.) / self.steps for x in range(self.steps)]
예제 #2
0
class OWLearningCurveC(OWBaseWidget):
    name = "Learning Curve (C)"
    description = ("Takes a dataset and a set of learners and shows a "
                   "learning curve in a table")
    icon = "icons/LearningCurve.svg"
    priority = 1010

    class Inputs:
        data = Input("Data", Table, default=True)
        test_data = Input("Test Data", Table)
        learner = MultiInput("Learner", Learner)

    #: cross validation folds
    folds = settings.Setting(5)
    #: points in the learning curve
    steps = settings.Setting(10)
    #: index of the selected scoring function
    scoringF = settings.Setting(0)
    #: compute curve on any change of parameters
    commitOnChange = settings.Setting(True)

    def __init__(self):
        super().__init__()

        # sets self.curvePoints, self.steps equidistant points from
        # 1/self.steps to 1
        self.updateCurvePoints()

        self.scoring = [("Classification Accuracy",
                         Orange.evaluation.scoring.CA),
                        ("AUC", Orange.evaluation.scoring.AUC),
                        ("Precision", Orange.evaluation.scoring.Precision),
                        ("Recall", Orange.evaluation.scoring.Recall)]
        #: Input data on which to construct the learning curve
        self.data = None
        #: Optional test data
        self.testdata = None
        #: LearnerData for each learner input
        self.learners: List[LearnerData] = []

        # [start-snippet-3]
        #: The current evaluating task (if any)
        self._task = None  # type: Optional[Task]
        #: An executor we use to submit learner evaluations into a thread pool
        self._executor = concurrent.futures.ThreadPoolExecutor()
        # [end-snippet-3]

        # GUI
        box = gui.widgetBox(self.controlArea, "Info")
        self.infoa = gui.widgetLabel(box, 'No data on input.')
        self.infob = gui.widgetLabel(box, 'No learners.')

        gui.separator(self.controlArea)

        box = gui.widgetBox(self.controlArea, "Evaluation Scores")
        gui.comboBox(box,
                     self,
                     "scoringF",
                     items=[x[0] for x in self.scoring],
                     callback=self._invalidate_curves)

        gui.separator(self.controlArea)

        box = gui.widgetBox(self.controlArea, "Options")
        gui.spin(box,
                 self,
                 'folds',
                 2,
                 100,
                 step=1,
                 label='Cross validation folds:  ',
                 keyboardTracking=False,
                 callback=lambda: self._invalidate_results()
                 if self.commitOnChange else None)
        gui.spin(box,
                 self,
                 'steps',
                 2,
                 100,
                 step=1,
                 label='Learning curve points:  ',
                 keyboardTracking=False,
                 callback=[
                     self.updateCurvePoints,
                     lambda: self._invalidate_results()
                     if self.commitOnChange else None
                 ])
        gui.checkBox(box, self, 'commitOnChange',
                     'Apply setting on any change')
        self.commitBtn = gui.button(box,
                                    self,
                                    "Apply Setting",
                                    callback=self._invalidate_results,
                                    disabled=True)

        gui.rubber(self.controlArea)

        # table widget
        self.table = gui.table(self.mainArea,
                               selectionMode=QTableWidget.NoSelection)

    ##########################################################################
    # slots: handle input signals

    @Inputs.data
    def set_dataset(self, data):
        """Set the input train dataset."""
        # Clear all results/scores
        for item in self.learners:
            item.results = None
            item.curve = None

        self.data = data

        if data is not None:
            self.infoa.setText('%d instances in input dataset' % len(data))
        else:
            self.infoa.setText('No data on input.')

        self.commitBtn.setEnabled(self.data is not None)

    @Inputs.test_data
    def set_testdataset(self, testdata):
        """Set a separate test dataset."""
        # Clear all results/scores for all learner inputs
        for item in self.learners:
            item.results = None
            item.curve = None

        self.testdata = testdata

    @Inputs.learner
    def set_learner(self, index: int, learner):
        """Set the input learner at index"""
        # update/replace a learner on a previously connected link
        item = self.learners[index]
        item.learner = learner
        item.results = None
        item.curve = None

    @Inputs.learner.insert
    def insert_learner(self, index, learner):
        """Insert a learner at index"""
        self.learners.insert(index, LearnerData(learner, None, None))

    @Inputs.learner.remove
    def remove_learner(self, index):
        """"Remove a learner at index"""
        # remove a learner and corresponding results
        del self.learners[index]

# [start-snippet-4]

    def handleNewSignals(self):
        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))
        self._update()
# [end-snippet-4]

    def _invalidate_curves(self):
        if self.data is not None:
            self._update_curve_points()
        self._update_table()

    def _invalidate_results(self):
        for item in self.learners:
            item.results = None
            item.curve = None
        self._update()

# [start-snippet-5]

    def _update(self):
        if self._task is not None:
            # First make sure any pending tasks are cancelled.
            self.cancel()
        assert self._task is None

        if self.data is None:
            return
        # collect all learners for which results have not yet been computed
        need_update = [(i, item) for (i, item) in enumerate(self.learners)
                       if item.results is None]
        if not need_update:
            self._update_curve_points()
            self._update_table()
            return
# [end-snippet-5]
# [start-snippet-6]
        learners = [item.learner for _, item in need_update]
        # setup the learner evaluations as partial function capturing
        # the necessary arguments.
        if self.testdata is None:
            learning_curve_func = partial(
                learning_curve,
                learners,
                self.data,
                folds=self.folds,
                proportions=self.curvePoints,
            )
        else:
            learning_curve_func = partial(
                learning_curve_with_test_data,
                learners,
                self.data,
                self.testdata,
                times=self.folds,
                proportions=self.curvePoints,
            )
# [end-snippet-6]
# [start-snippet-7]
# setup the task state
        self._task = task = Task()
        # The learning_curve[_with_test_data] also takes a callback function
        # to report the progress. We instrument this callback to both invoke
        # the appropriate slots on this widget for reporting the progress
        # (in a thread safe manner) and to implement cooperative cancellation.
        set_progress = methodinvoke(self, "setProgressValue", (float, ))

        def callback(finished):
            # check if the task has been cancelled and raise an exception
            # from within. This 'strategy' can only be used with code that
            # properly cleans up after itself in the case of an exception
            # (does not leave any global locks, opened file descriptors, ...)
            if task.cancelled:
                raise KeyboardInterrupt()
            set_progress(finished * 100)

        # capture the callback in the partial function
        learning_curve_func = partial(learning_curve_func, callback=callback)
        # [end-snippet-7]
        # [start-snippet-8]
        self.progressBarInit()
        # Submit the evaluation function to the executor and fill in the
        # task with the resultant Future.
        task.future = self._executor.submit(learning_curve_func)
        # Setup the FutureWatcher to notify us of completion
        task.watcher = FutureWatcher(task.future)
        # by using FutureWatcher we ensure `_task_finished` slot will be
        # called from the main GUI thread by the Qt's event loop
        task.watcher.done.connect(self._task_finished)
# [end-snippet-8]

# [start-snippet-progress]

    @pyqtSlot(float)
    def setProgressValue(self, value):
        assert self.thread() is QThread.currentThread()
        self.progressBarSet(value)
# [end-snippet-progress]

# [start-snippet-9]

    @pyqtSlot(concurrent.futures.Future)
    def _task_finished(self, f):
        """
        Parameters
        ----------
        f : Future
            The future instance holding the result of learner evaluation.
        """
        assert self.thread() is QThread.currentThread()
        assert self._task is not None
        assert self._task.future is f
        assert f.done()

        self._task = None
        self.progressBarFinished()

        try:
            results = f.result()  # type: List[Results]
        except Exception as ex:
            # Log the exception with a traceback
            log = logging.getLogger()
            log.exception(__name__, exc_info=True)
            self.error("Exception occurred during evaluation: {!r}".format(ex))
            # clear all results
            for item in self.learners:
                item.results = None
        else:
            # split the combined result into per learner/model results ...
            results = [
                list(Results.split_by_model(p_results))
                for p_results in results
            ]  # type: List[List[Results]]
            assert all(len(r.learners) == 1 for r1 in results for r in r1)
            assert len(results) == len(self.curvePoints)

            learners = [r.learners[0] for r in results[0]]
            # map learner back to LearnerData instance
            data_by_learner = {item.learner: item for item in self.learners}
            # ... and update self.results
            for i, learner in enumerate(learners):
                item = data_by_learner[learner]
                item.results = [p_results[i] for p_results in results]
        # update the display
        self._update_curve_points()
        self._update_table()
# [end-snippet-9]

# [start-snippet-10]

    def cancel(self):
        """
        Cancel the current task (if any).
        """
        if self._task is not None:
            self._task.cancel()
            assert self._task.future.done()
            # disconnect the `_task_finished` slot
            self._task.watcher.done.disconnect(self._task_finished)
            self._task = None
            self.progressBarFinished()
# [end-snippet-10]

# [start-snippet-11]

    def onDeleteWidget(self):
        self.cancel()
        super().onDeleteWidget()


# [end-snippet-11]

    def _update_curve_points(self):
        scoref = self.scoring[self.scoringF][1]
        for item in self.learners:
            item.curve = [scoref(x)[0] for x in item.results]

    def _update_table(self):
        self.table.setRowCount(0)
        self.table.setRowCount(len(self.curvePoints))
        self.table.setColumnCount(len(self.learners))

        self.table.setHorizontalHeaderLabels(
            [item.learner.name for item in self.learners])
        self.table.setVerticalHeaderLabels(
            ["{:.2f}".format(p) for p in self.curvePoints])

        if self.data is None:
            return

        for column, item in enumerate(self.learners):
            for row, point in enumerate(item.curve):
                self.table.setItem(row, column,
                                   QTableWidgetItem("{:.5f}".format(point)))

        for i in range(len(self.learners)):
            sh = self.table.sizeHintForColumn(i)
            cwidth = self.table.columnWidth(i)
            self.table.setColumnWidth(i, max(sh, cwidth))

    def updateCurvePoints(self):
        self.curvePoints = [(x + 1.) / self.steps for x in range(self.steps)]
예제 #3
0
class OWNxEmbedding(OWWidget):
    name = "Network Embeddings"
    description = "Embed network elements"
    icon = "icons/NetworkEmbedding.svg"
    priority = 6450

    class Inputs:
        network = Input("Network", Network, default=True)

    class Outputs:
        items = Output("Items", Table)

    resizing_enabled = False
    want_main_area = False

    p = settings.Setting(1.0)
    q = settings.Setting(1.0)
    walk_len = settings.Setting(50)
    num_walks = settings.Setting(10)
    emb_size = settings.Setting(128)
    window_size = settings.Setting(5)
    num_epochs = settings.Setting(1)

    auto_commit = settings.Setting(True)

    def __init__(self):
        super().__init__()
        self.network = None
        self.embedder = None
        self._worker_thread = None
        self._progress_updater = None

        def commit():
            return self.commit()

        box = gui.widgetBox(self.controlArea, box=True)
        kwargs = dict(controlWidth=75,
                      alignment=Qt.AlignRight,
                      callback=commit)
        gui.spin(box,
                 self,
                 "p",
                 0.0,
                 10.0,
                 0.1,
                 label="Return parameter (p): ",
                 spinType=float,
                 **kwargs)
        gui.spin(box,
                 self,
                 "q",
                 0.0,
                 10.0,
                 0.1,
                 label="In-out parameter (q): ",
                 spinType=float,
                 **kwargs)
        gui.spin(box,
                 self,
                 "walk_len",
                 1,
                 100_000,
                 1,
                 label="Walk length: ",
                 **kwargs)
        gui.spin(box,
                 self,
                 "num_walks",
                 1,
                 10_000,
                 1,
                 label="Walks per node: ",
                 **kwargs)
        gui.spin(box,
                 self,
                 "emb_size",
                 1,
                 10_000,
                 1,
                 label="Embedding size: ",
                 **kwargs)
        gui.spin(box,
                 self,
                 "window_size",
                 1,
                 20,
                 1,
                 label="Context size: ",
                 **kwargs)
        gui.spin(box,
                 self,
                 "num_epochs",
                 1,
                 100,
                 1,
                 label="Number of epochs: ",
                 **kwargs)

        gui.auto_commit(self.controlArea,
                        self,
                        "auto_commit",
                        "Commit",
                        checkbox_label="Auto-commit",
                        orientation=Qt.Horizontal)
        commit()

    @Inputs.network
    def set_network(self, net):
        self.network = net
        self.commit()

    def commit(self):
        self.Warning.clear()

        # cancel existing computation if running
        if self._worker_thread is not None:
            self._worker_thread.finished.disconnect()
            self._worker_thread.quit()
            self._worker_thread = None

        if self.network is None:
            self.Outputs.items.send(None)
            return

        self._progress_updater = ProgressBarUpdater(self, self.num_epochs)
        self.embedder = embeddings.Node2Vec(self.p,
                                            self.q,
                                            self.walk_len,
                                            self.num_walks,
                                            self.emb_size,
                                            self.window_size,
                                            self.num_epochs,
                                            callbacks=[self._progress_updater])

        self._worker_thread = EmbedderThread(
            lambda: self.embedder(self.network))
        self._worker_thread.finished.connect(self.on_finished)
        self.progressBarInit()
        self.progressBarSet(1e-5)
        self._worker_thread.start()

    def on_finished(self):
        output = self._worker_thread.result
        self._worker_thread = None
        self._progress_updater = None
        self.progressBarFinished()
        self.Outputs.items.send(output)

    def onDeleteWidget(self):
        if self._worker_thread is not None:
            # prevent the callback from trying to access deleted widget object
            self._progress_updater.widget = None
            self._worker_thread.finished.disconnect()
            self._worker_thread.quit()
        super().onDeleteWidget()
class OWLearningCurveB(OWBaseWidget):
    name = "Learning Curve (B)"
    description = ("Takes a dataset and a set of learners and shows a "
                   "learning curve in a table")
    icon = "icons/LearningCurve.svg"
    priority = 1010

    # [start-snippet-1]
    class Inputs:
        data = Input("Data", Orange.data.Table, default=True)
        test_data = Input("Test Data", Orange.data.Table)
        learner = Input("Learner",
                        Orange.classification.Learner,
                        multiple=True)
# [end-snippet-1]

#: cross validation folds

    folds = settings.Setting(5)
    #: points in the learning curve
    steps = settings.Setting(10)
    #: index of the selected scoring function
    scoringF = settings.Setting(0)
    #: compute curve on any change of parameters
    commitOnChange = settings.Setting(True)

    def __init__(self):
        super().__init__()

        # sets self.curvePoints, self.steps equidistant points from
        # 1/self.steps to 1
        self.updateCurvePoints()

        self.scoring = [("Classification Accuracy",
                         Orange.evaluation.scoring.CA),
                        ("AUC", Orange.evaluation.scoring.AUC),
                        ("Precision", Orange.evaluation.scoring.Precision),
                        ("Recall", Orange.evaluation.scoring.Recall)]
        #: input data on which to construct the learning curve
        self.data = None
        #: optional test data
        self.testdata = None
        #: A {input_id: Learner} mapping of current learners from input channel
        self.learners = OrderedDict()
        #: A {input_id: List[Results]} mapping of input id to evaluation
        #: results list, one for each curve point
        self.results = OrderedDict()
        #: A {input_id: List[float]} mapping of input id to learning curve
        #: point scores
        self.curves = OrderedDict()

        # GUI
        box = gui.widgetBox(self.controlArea, "Info")
        self.infoa = gui.widgetLabel(box, 'No data on input.')
        self.infob = gui.widgetLabel(box, 'No learners.')

        gui.separator(self.controlArea)

        box = gui.widgetBox(self.controlArea, "Evaluation Scores")
        gui.comboBox(box,
                     self,
                     "scoringF",
                     items=[x[0] for x in self.scoring],
                     callback=self._invalidate_curves)

        gui.separator(self.controlArea)

        box = gui.widgetBox(self.controlArea, "Options")
        gui.spin(box,
                 self,
                 'folds',
                 2,
                 100,
                 step=1,
                 label='Cross validation folds:  ',
                 keyboardTracking=False,
                 callback=lambda: self._invalidate_results()
                 if self.commitOnChange else None)
        gui.spin(box,
                 self,
                 'steps',
                 2,
                 100,
                 step=1,
                 label='Learning curve points:  ',
                 keyboardTracking=False,
                 callback=[
                     self.updateCurvePoints,
                     lambda: self._invalidate_results()
                     if self.commitOnChange else None
                 ])
        gui.checkBox(box, self, 'commitOnChange',
                     'Apply setting on any change')
        self.commitBtn = gui.button(box,
                                    self,
                                    "Apply Setting",
                                    callback=self._invalidate_results,
                                    disabled=True)

        gui.rubber(self.controlArea)

        # table widget
        self.table = gui.table(self.mainArea,
                               selectionMode=QTableWidget.NoSelection)

    ##########################################################################
    # slots: handle input signals

    @Inputs.data
    def set_dataset(self, data):
        """Set the input train dataset."""
        # Clear all results/scores
        for id in list(self.results):
            self.results[id] = None
        for id in list(self.curves):
            self.curves[id] = None

        self.data = data

        if data is not None:
            self.infoa.setText('%d instances in input dataset' % len(data))
        else:
            self.infoa.setText('No data on input.')

        self.commitBtn.setEnabled(self.data is not None)

    @Inputs.test_data
    def set_testdataset(self, testdata):
        """Set a separate test dataset."""
        # Clear all results/scores
        for id in list(self.results):
            self.results[id] = None
        for id in list(self.curves):
            self.curves[id] = None

        self.testdata = testdata

    @Inputs.learner
    def set_learner(self, learner, id):
        """Set the input learner for channel id."""
        if id in self.learners:
            if learner is None:
                # remove a learner and corresponding results
                del self.learners[id]
                del self.results[id]
                del self.curves[id]
            else:
                # update/replace a learner on a previously connected link
                self.learners[id] = learner
                # invalidate the cross-validation results and curve scores
                # (will be computed/updated in `_update`)
                self.results[id] = None
                self.curves[id] = None
        else:
            if learner is not None:
                self.learners[id] = learner
                # initialize the cross-validation results and curve scores
                # (will be computed/updated in `_update`)
                self.results[id] = None
                self.curves[id] = None

        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))

    def handleNewSignals(self):
        if self.data is not None:
            self._update()
            self._update_curve_points()
        self._update_table()

    def _invalidate_curves(self):
        if self.data is not None:
            self._update_curve_points()
        self._update_table()

    def _invalidate_results(self):
        for id in self.learners:
            self.curves[id] = None
            self.results[id] = None

        if self.data is not None:
            self._update()
            self._update_curve_points()
        self._update_table()

    def _update(self):
        assert self.data is not None
        # collect all learners for which results have not yet been computed
        need_update = [(id, learner) for id, learner in self.learners.items()
                       if self.results[id] is None]
        if not need_update:
            return
        learners = [learner for _, learner in need_update]

        if self.testdata is None:
            # compute the learning curve result for all learners in one go
            results = learning_curve(
                learners,
                self.data,
                folds=self.folds,
                proportions=self.curvePoints,
            )
        else:
            results = learning_curve_with_test_data(
                learners,
                self.data,
                self.testdata,
                times=self.folds,
                proportions=self.curvePoints,
            )
        # split the combined result into per learner/model results
        results = [
            list(Results.split_by_model(p_results)) for p_results in results
        ]

        for i, (id, learner) in enumerate(need_update):
            self.results[id] = [p_results[i] for p_results in results]

    def _update_curve_points(self):
        for id in self.learners:
            curve = [
                self.scoring[self.scoringF][1](x)[0] for x in self.results[id]
            ]
            self.curves[id] = curve

    def _update_table(self):
        self.table.setRowCount(0)
        self.table.setRowCount(len(self.curvePoints))
        self.table.setColumnCount(len(self.learners))

        self.table.setHorizontalHeaderLabels(
            [learner.name for _, learner in self.learners.items()])
        self.table.setVerticalHeaderLabels(
            ["{:.2f}".format(p) for p in self.curvePoints])

        if self.data is None:
            return

        for column, curve in enumerate(self.curves.values()):
            for row, point in enumerate(curve):
                self.table.setItem(row, column,
                                   QTableWidgetItem("{:.5f}".format(point)))

        for i in range(len(self.learners)):
            sh = self.table.sizeHintForColumn(i)
            cwidth = self.table.columnWidth(i)
            self.table.setColumnWidth(i, max(sh, cwidth))

    def updateCurvePoints(self):
        self.curvePoints = [(x + 1.) / self.steps for x in range(self.steps)]

# [start-snippet-3]

    def test_run_signals(self):
        data = Orange.data.Table("iris")
        indices = numpy.random.permutation(len(data))

        traindata = data[indices[:-20]]
        testdata = data[indices[-20:]]

        self.set_dataset(traindata)
        self.set_testdataset(testdata)

        l1 = Orange.classification.NaiveBayesLearner()
        l1.name = 'Naive Bayes'
        self.set_learner(l1, 1)

        l2 = Orange.classification.LogisticRegressionLearner()
        l2.name = 'Logistic Regression'
        self.set_learner(l2, 2)

        l4 = Orange.classification.SklTreeLearner()
        l4.name = "Decision Tree"
        self.set_learner(l4, 3)