Repository: biolab/orange3

Issue:
Problems with Confusion matrix
I cannot put Confusion matrix on canvas it shows error: 

Could not create 'Confusion Matrix'

Traceback (most recent call last):
  File "C:\Python34\lib\site-packages\Orange\canvas\scheme\widgetsscheme.py", line 342, in create_widget_instance
    widget.**init**()
  File "C:\Python34\lib\site-packages\Orange\widgets\evaluate\owconfusionmatrix.py", line 116, in **init**
    callback=self._learner_changed
  File "C:\Python34\lib\site-packages\Orange\widgets\gui.py", line 1254, in listBox
    clist = ControlledList(clist, lb)
  File "C:\Python34\lib\site-packages\Orange\widgets\gui.py", line 2254, in __init__
    super().**init**(content)
TypeError: 'NoneType' object is not iterable

I used the Iris dataset, and yesterday everything worked properly. Today I can't use Confusion matrix at all.

Orange version 3.3



Discussion:
*Version 3.3.7

Can you share a minimum working example workflow? On 3.3.7 with below scheme, I can't reproduce.

[untitled.ows.zip](https://github.com/biolab/orange3/files/408843/untitled.ows.zip)

Yes of course.
![1](https://cloud.githubusercontent.com/assets/15958687/17515973/f68fe4b8-5e3b-11e6-8773-4a47f77528be.png)
![2](https://cloud.githubusercontent.com/assets/15958687/17515976/f9bbf92e-5e3b-11e6-9a80-b8c8989cc672.png)

Thank you for your response.

Even when the canvas is empty, I get an error.
I don't have errors when I import your code with Confusion matrix, but as soon as I add new one, I have.

I don't have errors anymore. I don't know why . I started Orange again a minute ago and it works. 😄 

Do you remember what scheme you had and what you did right before the problem started manifesting?

@JelenaKitanovic Is there any chance to reproduce this bug?

Hi,
Same here.

Steps to reproduce:
![image](https://cloud.githubusercontent.com/assets/767180/17844725/b52a1a6a-683c-11e6-9a2b-31524004e4b0.png)
- Add multiple learners and a confusion matrix.
- Open confusion matrix (not sure if needed)
- Remove connection to confusion matrix before saving
- Save
- Reopen project

Temporal fix I done to be able to continue working:
At guy.py (line 1253) check if clist (the expected selected learner list) is None

```
if value is not None:
        clist = getdeepattr(master, value)
        if clist is not None and not isinstance(clist, (int, ControlledList)):
        # if not isinstance(clist, (int, ControlledList)): # original code
            clist = ControlledList(clist, lb)
            master.__setattr__(value, clist)
        setattr(master, value, clist)
        connectControl(master, value, callback, lb.itemSelectionChanged,
                       CallFrontListBox(lb), CallBackListBox(lb, master))

```

Hope this helps.

Great job!

Hi again, 
Seems that: **ConfunsionMatrix** is reusing settings when creating new instances. 
So maybe _selected_learner_ property should be ignored when building a new instance.

![image](https://cloud.githubusercontent.com/assets/767180/17845313/4569d0b2-6841-11e6-8531-ae99149e6e88.png)

It breaks as soon as you create and remove a connection setting the _selected_learner_ as None.

right?
Thanks


Resolving patch:
diff --git a/Orange/widgets/evaluate/owconfusionmatrix.py b/Orange/widgets/evaluate/owconfusionmatrix.py
--- a/Orange/widgets/evaluate/owconfusionmatrix.py
+++ b/Orange/widgets/evaluate/owconfusionmatrix.py
@@ -86,7 +86,7 @@ class OWConfusionMatrix(widget.OWWidget):
 
     settingsHandler = settings.ClassValuesContextHandler()
 
-    selected_learner = settings.Setting(0)
+    selected_learner = settings.Setting([0], schema_only=True)
     selection = settings.ContextSetting(set())
     selected_quantity = settings.Setting(0)
     append_predictions = settings.Setting(True)
@@ -101,8 +101,6 @@ class OWConfusionMatrix(widget.OWWidget):
 
     def __init__(self):
         super().__init__()
-        if isinstance(self.selected_learner, list):
-            self.selected_learner = (self.selected_learner + [0])[0]
 
         self.data = None
         self.results = None
@@ -214,7 +212,7 @@ def _init_table(self, nclasses):
     def set_results(self, results):
         """Set the input results."""
 
-        prev_sel_learner = self.selected_learner
+        prev_sel_learner = self.selected_learner.copy()
         self.clear()
         self.warning()
         self.closeContext()
@@ -252,11 +250,10 @@ def set_results(self, results):
 
             self._init_table(len(class_values))
             self.openContext(data.domain.class_var)
-            if prev_sel_learner is None or \
-                    prev_sel_learner >= len(self.learners):
-                self.selected_learner = 0
+            if not prev_sel_learner or prev_sel_learner[0] >= len(self.learners):
+                self.selected_learner[:] = [0]
             else:
-                self.selected_learner = prev_sel_learner
+                self.selected_learner[:] = prev_sel_learner
             self._update()
             self._set_selection()
             self.unconditional_commit()
@@ -319,12 +316,12 @@ def cell_clicked(self, model_index):
     def commit(self):
         """Output data instances corresponding to selected cells"""
         if self.results is not None and self.data is not None \
-                and self.selected_learner is not None:
+                and self.selected_learner:
             indices = self.tableview.selectedIndexes()
             indices = {(ind.row() - 2, ind.column() - 2) for ind in indices}
             actual = self.results.actual
-            learner_name = self.learners[self.selected_learner]
-            predicted = self.results.predicted[self.selected_learner]
+            learner_name = self.learners[self.selected_learner[0]]
+            predicted = self.results.predicted[self.selected_learner[0]]
             selected = [i for i, t in enumerate(zip(actual, predicted))
                         if t in indices]
             row_indices = self.results.row_indices[selected]
@@ -344,7 +341,7 @@ def commit(self):
 
             if self.append_probabilities and \
                     self.results.probabilities is not None:
-                probs = self.results.probabilities[self.selected_learner,
+                probs = self.results.probabilities[self.selected_learner[0],
                                                    selected]
                 extra.append(numpy.array(probs, dtype=object))
                 pvars = [Orange.data.ContinuousVariable("p({})".format(value))
@@ -395,8 +392,8 @@ def _isinvalid(x):
             return isnan(x) or isinf(x)
 
         # Update the displayed confusion matrix
-        if self.results is not None and self.selected_learner is not None:
-            cmatrix = confusion_matrix(self.results, self.selected_learner)
+        if self.results is not None and self.selected_learner:
+            cmatrix = confusion_matrix(self.results, self.selected_learner[0])
             colsum = cmatrix.sum(axis=0)
             rowsum = cmatrix.sum(axis=1)
             n = len(cmatrix)
@@ -459,10 +456,10 @@ def _sum_item(value, border=""):
 
     def send_report(self):
         """Send report"""
-        if self.results is not None and self.selected_learner is not None:
+        if self.results is not None and self.selected_learner:
             self.report_table(
                 "Confusion matrix for {} (showing {})".
-                format(self.learners[self.selected_learner],
+                format(self.learners[self.selected_learner[0]],
                        self.quantities[self.selected_quantity].lower()),
                 self.tableview)
 
diff --git a/Orange/widgets/gui.py b/Orange/widgets/gui.py
--- a/Orange/widgets/gui.py
+++ b/Orange/widgets/gui.py
@@ -2251,7 +2251,7 @@ class ControlledList(list):
     selection in the list box.
     """
     def __init__(self, content, listBox=None):
-        super().__init__(content)
+        super().__init__(content if content is not None else [])
         self.listBox = listBox
 
     def __reduce__(self):


Test patch:
diff --git a/Orange/widgets/evaluate/tests/__init__.py b/Orange/widgets/evaluate/tests/__init__.py
new file mode 100644
diff --git a/Orange/widgets/evaluate/tests/test_owconfusionmatrix.py b/Orange/widgets/evaluate/tests/test_owconfusionmatrix.py
new file mode 100644
--- /dev/null
+++ b/Orange/widgets/evaluate/tests/test_owconfusionmatrix.py
@@ -0,0 +1,41 @@
+# pylint: disable=missing-docstring
+
+from Orange.data import Table
+from Orange.classification import NaiveBayesLearner, TreeLearner
+from Orange.evaluation.testing import CrossValidation
+from Orange.widgets.evaluate.owconfusionmatrix import OWConfusionMatrix
+from Orange.widgets.tests.base import WidgetTest
+
+class TestOWClassificationTree(WidgetTest):
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        bayes = NaiveBayesLearner()
+        tree = TreeLearner()
+        iris = Table("iris")
+        titanic = Table("titanic")
+        common = dict(k=3, store_data=True)
+        cls.results_1_iris = CrossValidation(iris, [bayes], **common)
+        cls.results_2_iris = CrossValidation(iris, [bayes, tree], **common)
+        cls.results_2_titanic = CrossValidation(titanic, [bayes, tree],
+                                                **common)
+
+    def setUp(self):
+        self.widget = self.create_widget(OWConfusionMatrix,
+                                         stored_settings={"auto_apply": False})
+
+    def test_selected_learner(self):
+        """Check learner and model for various values of all parameters
+        when pruning parameters are not checked
+        """
+        self.widget.set_results(self.results_2_iris)
+        self.assertEqual(self.widget.selected_learner, [0])
+        self.widget.selected_learner[:] = [1]
+        self.widget.set_results(self.results_2_titanic)
+        self.widget.selected_learner[:] = [1]
+        self.widget.set_results(self.results_1_iris)
+        self.widget.selected_learner[:] = [0]
+        self.widget.set_results(None)
+        self.widget.set_results(self.results_1_iris)
+        self.widget.selected_learner[:] = [0]
+