Repository: biolab/orange3

Issue:
Continuize: Normalizing sparse data fails
**Describe the bug**
Continuize widget crashes when normalizing sparse data.

**To Reproduce**
Corpus (book-excerpts.tab) -> Bag Of Words -> Continuize -> In `Numeric Features` box select `Normalize by span`

**Orange version:**
master

**Expected behavior**
Disabled normalization for sparse datasets.




Discussion:

Resolving patch:
diff --git a/Orange/widgets/data/owcontinuize.py b/Orange/widgets/data/owcontinuize.py
--- a/Orange/widgets/data/owcontinuize.py
+++ b/Orange/widgets/data/owcontinuize.py
@@ -34,9 +34,12 @@ class Outputs:
     buttons_area_orientation = Qt.Vertical
     resizing_enabled = False
 
+    # continuous treats
+    Leave, NormalizeBySpan, NormalizeBySD = range(3)
+
     multinomial_treatment = Setting(0)
     zero_based = Setting(1)
-    continuous_treatment = Setting(0)
+    continuous_treatment = Setting(Leave)
     class_treatment = Setting(0)
 
     transform_class = Setting(False)
@@ -107,6 +110,7 @@ def settings_changed(self):
     @check_sql_input
     def setData(self, data):
         self.data = data
+        self.enable_normalization()
         if data is None:
             self.info.set_input_summary(self.info.NoInput)
             self.info.set_output_summary(self.info.NoOutput)
@@ -115,6 +119,15 @@ def setData(self, data):
             self.info.set_input_summary(len(data))
             self.unconditional_commit()
 
+    def enable_normalization(self):
+        enable = not (self.data and self.data.is_sparse())
+        if not enable and self.continuous_treatment in (self.NormalizeBySpan,
+                                                        self.NormalizeBySD):
+            self.continuous_treatment = self.Leave
+        buttons = self.controls.continuous_treatment.buttons
+        buttons[self.NormalizeBySpan].setEnabled(enable)
+        buttons[self.NormalizeBySD].setEnabled(enable)
+
     def constructContinuizer(self):
         conzer = DomainContinuizer(
             zero_based=self.zero_based,


Test patch:
diff --git a/Orange/widgets/data/tests/test_owcontinuize.py b/Orange/widgets/data/tests/test_owcontinuize.py
--- a/Orange/widgets/data/tests/test_owcontinuize.py
+++ b/Orange/widgets/data/tests/test_owcontinuize.py
@@ -110,6 +110,45 @@ def test_one_column_nan_values_normalize_span(self):
         self.send_signal(self.widget.Inputs.data, table)
         self.widget.unconditional_commit()
 
+    def test_disable_normalize_sparse(self):
+        def assert_enabled(enabled):
+            buttons[BySpan].click()
+            buttons[BySD].click()
+            self.assertTrue(buttons[Leave].isEnabled())
+            self.assertEqual(buttons[BySpan].isEnabled(), enabled)
+            self.assertEqual(buttons[BySD].isEnabled(), enabled)
+
+        w = self.widget
+        Leave, BySpan, BySD = w.Leave, w.NormalizeBySpan, w.NormalizeBySD
+        buttons = w.controls.continuous_treatment.buttons
+        iris = Table("iris")
+        sparse_iris = iris.to_sparse()
+
+        # input dense
+        self.send_signal(w.Inputs.data, iris)
+        assert_enabled(True)
+        self.assertEqual(w.continuous_treatment, BySD)
+
+        # input sparse
+        self.send_signal(w.Inputs.data, sparse_iris)
+        assert_enabled(False)
+        self.assertEqual(w.continuous_treatment, Leave)
+
+        self.widget.continuous_treatment = BySpan
+        self.assertRaises(ValueError, w.commit)
+
+        # remove data
+        self.send_signal(w.Inputs.data, None)
+        assert_enabled(True)
+
+        # input sparse
+        self.send_signal(w.Inputs.data, sparse_iris)
+        assert_enabled(False)
+
+        # input dense
+        self.send_signal(w.Inputs.data, iris)
+        assert_enabled(True)
+
 
 class TestOWContinuizeUtils(unittest.TestCase):
     def test_dummy_coding_zero_based(self):