Repository: biolab/orange3

Issue:
Calibration Plot: TypeError after connecting to predictions
<!-- 
Thanks for taking the time to report a bug!
If you're raising an issue about an add-on (i.e., installed via Options > Add-ons), raise an issue in the relevant add-on's issue tracker instead. See: https://github.com/biolab?q=orange3
To fix the bug, we need to be able to reproduce it. Please answer the following questions to the best of your ability.
-->


**What's wrong?**
<!-- Be specific, clear, and concise. Include screenshots if relevant. -->
<!-- If you're getting an error message, copy it, and enclose it with three backticks (```). -->
The Calibration Plot crashes with a TypeError after getting a signal from Predictions.


**How can we reproduce the problem?** 
<!-- Upload a zip with the .ows file and data. -->
<!-- Describe the steps (open this widget, click there, then add this...) -->
File (Iris) -> Logistic Regression -> Predictions -> Calibration Plot


**What's your environment?**
<!-- To find your Orange version, see "Help → About → Version" or `Orange.version.full_version` in code -->
- Operating system: OSX
- Orange version: Master
- How you installed Orange: pip



Discussion:

Resolving patch:
diff --git a/Orange/widgets/evaluate/owcalibrationplot.py b/Orange/widgets/evaluate/owcalibrationplot.py
--- a/Orange/widgets/evaluate/owcalibrationplot.py
+++ b/Orange/widgets/evaluate/owcalibrationplot.py
@@ -487,7 +487,7 @@ def commit(self):
         if results is not None:
             problems = [
                 msg for condition, msg in (
-                    (len(results.folds) > 1,
+                    (results.folds is not None and len(results.folds) > 1,
                      "each training data sample produces a different model"),
                     (results.models is None,
                      "test results do not contain stored models - try testing "
diff --git a/Orange/widgets/evaluate/owpredictions.py b/Orange/widgets/evaluate/owpredictions.py
--- a/Orange/widgets/evaluate/owpredictions.py
+++ b/Orange/widgets/evaluate/owpredictions.py
@@ -687,7 +687,8 @@ def _commit_evaluation_results(self):
         nanmask = numpy.isnan(self.data.get_column_view(self.class_var)[0])
         data = self.data[~nanmask]
         results = Results(data, store_data=True)
-        results.folds = None
+        results.folds = [...]
+        results.models = numpy.array([[p.predictor for p in self.predictors]])
         results.row_indices = numpy.arange(len(data))
         results.actual = data.Y.ravel()
         results.predicted = numpy.vstack(


Test patch:
diff --git a/Orange/widgets/evaluate/tests/test_owcalibrationplot.py b/Orange/widgets/evaluate/tests/test_owcalibrationplot.py
--- a/Orange/widgets/evaluate/tests/test_owcalibrationplot.py
+++ b/Orange/widgets/evaluate/tests/test_owcalibrationplot.py
@@ -1,5 +1,6 @@
 import copy
 import warnings
+import unittest
 from unittest.mock import Mock, patch
 
 import numpy as np
@@ -637,3 +638,19 @@ def test_warn_nan_probabilities(self, *_):
         self.assertTrue(widget.Warning.omitted_nan_prob_points.is_shown())
         self._set_list_selection(widget.controls.selected_classifiers, [0, 2])
         self.assertFalse(widget.Warning.omitted_folds.is_shown())
+
+    @patch("Orange.widgets.evaluate.owcalibrationplot.ThresholdClassifier")
+    @patch("Orange.widgets.evaluate.owcalibrationplot.CalibratedLearner")
+    def test_no_folds(self, *_):
+        """Don't crash on malformed Results with folds=None"""
+        widget = self.widget
+
+        self.results.folds = None
+        self.send_signal(widget.Inputs.evaluation_results, self.results)
+        widget.selected_classifiers = [0]
+        widget.commit.now()
+        self.assertIsNotNone(self.get_output(widget.Outputs.calibrated_model))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Orange/widgets/evaluate/tests/test_owpredictions.py b/Orange/widgets/evaluate/tests/test_owpredictions.py
--- a/Orange/widgets/evaluate/tests/test_owpredictions.py
+++ b/Orange/widgets/evaluate/tests/test_owpredictions.py
@@ -10,6 +10,7 @@
 
 from Orange.base import Model
 from Orange.classification import LogisticRegressionLearner, NaiveBayesLearner
+from Orange.classification.majority import ConstantModel
 from Orange.data.io import TabReader
 from Orange.evaluation.scoring import TargetScore
 from Orange.preprocess import Remove
@@ -507,6 +508,9 @@ def test_multi_inputs(self):
         def check_evres(expected):
             out = self.get_output(w.Outputs.evaluation_results)
             self.assertSequenceEqual(out.learner_names, expected)
+            self.assertEqual(out.folds, [...])
+            self.assertEqual(out.models.shape, (1, len(out.learner_names)))
+            self.assertIsInstance(out.models[0, 0], ConstantModel)
 
         check_evres(["P1", "P2", "P3"])