Repository: biolab/orange3

Issue:
OWDistributions: crash on binning sparse variables
<!--
This is an issue template. Please fill in the relevant details in the
sections below.

Wrap code and verbatim terminal window output into triple backticks, see:
https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code

If you're raising an issue about an add-on (e.g. installed via
Options > Add-ons), raise an issue on the relevant add-on's issue
tracker instead. See: https://github.com/biolab?q=orange3
-->

##### Orange version
<!-- From menu _Help→About→Version_ or code `Orange.version.full_version` -->
3.21.dev

##### Expected behavior
Distributions doesn't crash when binning sparse data.


##### Actual behavior
Distributions crashes when binning sparse.


##### Steps to reproduce the behavior
Corpus - Bag of Words - Distributions (Bin numeric variables)


##### Additional info (worksheets, data, screenshots, ...)
```
----------------------------- TypeError Exception -----------------------------
Traceback (most recent call last):
  File "/Users/ajda/orange/orange3/Orange/widgets/gui.py", line 2316, in __call__
    self.func(**kwds)
  File "/Users/ajda/orange/orange3/Orange/widgets/visualize/owdistributions.py", line 644, in _on_groupvar_idx_changed
    self._setup()
  File "/Users/ajda/orange/orange3/Orange/widgets/visualize/owdistributions.py", line 372, in _setup
    data = Discretize(method=disc, remove_const=False)(data)
  File "/Users/ajda/orange/orange3/Orange/preprocess/preprocess.py", line 113, in __call__
    discretized(data.domain.attributes, True),
  File "/Users/ajda/orange/orange3/Orange/preprocess/preprocess.py", line 108, in discretized
    vars = [var for var in vars if var is not None]
  File "/Users/ajda/orange/orange3/Orange/preprocess/preprocess.py", line 108, in <listcomp>
    vars = [var for var in vars if var is not None]
  File "/Users/ajda/orange/orange3/Orange/preprocess/preprocess.py", line 107, in <genexpr>
    vars = (transform(var) for var in vars)
  File "/Users/ajda/orange/orange3/Orange/preprocess/preprocess.py", line 96, in transform
    new_var = method(data, var)
  File "/Users/ajda/orange/orange3/Orange/preprocess/discretize.py", line 166, in __call__
    points = self._split_eq_width(min, max)
  File "/Users/ajda/orange/orange3/Orange/preprocess/discretize.py", line 171, in _split_eq_width
    if np.isnan(min) or np.isnan(max) or min == max:
TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
-------------------------------------------------------------------------------
```


OWDistributions: crash on binning sparse variables
<!--
This is an issue template. Please fill in the relevant details in the
sections below.

Wrap code and verbatim terminal window output into triple backticks, see:
https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code

If you're raising an issue about an add-on (e.g. installed via
Options > Add-ons), raise an issue on the relevant add-on's issue
tracker instead. See: https://github.com/biolab?q=orange3
-->

##### Orange version
<!-- From menu _Help→About→Version_ or code `Orange.version.full_version` -->
3.21.dev

##### Expected behavior
Distributions doesn't crash when binning sparse data.


##### Actual behavior
Distributions crashes when binning sparse.


##### Steps to reproduce the behavior
Corpus - Bag of Words - Distributions (Bin numeric variables)


##### Additional info (worksheets, data, screenshots, ...)
```
----------------------------- TypeError Exception -----------------------------
Traceback (most recent call last):
  File "/Users/ajda/orange/orange3/Orange/widgets/gui.py", line 2316, in __call__
    self.func(**kwds)
  File "/Users/ajda/orange/orange3/Orange/widgets/visualize/owdistributions.py", line 644, in _on_groupvar_idx_changed
    self._setup()
  File "/Users/ajda/orange/orange3/Orange/widgets/visualize/owdistributions.py", line 372, in _setup
    data = Discretize(method=disc, remove_const=False)(data)
  File "/Users/ajda/orange/orange3/Orange/preprocess/preprocess.py", line 113, in __call__
    discretized(data.domain.attributes, True),
  File "/Users/ajda/orange/orange3/Orange/preprocess/preprocess.py", line 108, in discretized
    vars = [var for var in vars if var is not None]
  File "/Users/ajda/orange/orange3/Orange/preprocess/preprocess.py", line 108, in <listcomp>
    vars = [var for var in vars if var is not None]
  File "/Users/ajda/orange/orange3/Orange/preprocess/preprocess.py", line 107, in <genexpr>
    vars = (transform(var) for var in vars)
  File "/Users/ajda/orange/orange3/Orange/preprocess/preprocess.py", line 96, in transform
    new_var = method(data, var)
  File "/Users/ajda/orange/orange3/Orange/preprocess/discretize.py", line 166, in __call__
    points = self._split_eq_width(min, max)
  File "/Users/ajda/orange/orange3/Orange/preprocess/discretize.py", line 171, in _split_eq_width
    if np.isnan(min) or np.isnan(max) or min == max:
TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
-------------------------------------------------------------------------------
```




Discussion:

Resolving patch:
diff --git a/Orange/preprocess/discretize.py b/Orange/preprocess/discretize.py
--- a/Orange/preprocess/discretize.py
+++ b/Orange/preprocess/discretize.py
@@ -6,7 +6,7 @@
 from Orange.data import DiscreteVariable, Domain
 from Orange.data.sql.table import SqlTable
 from Orange.preprocess.util import _RefuseDataInConstructor
-from Orange.statistics import distribution, contingency
+from Orange.statistics import distribution, contingency, util as ut
 from Orange.statistics.basic_stats import BasicStats
 from Orange.util import Reprable
 from .transformation import Transformation
@@ -160,7 +160,7 @@ def __call__(self, data, attribute, fixed=None):
             else:
                 values = data[:, attribute]
                 values = values.X if values.X.size else values.Y
-                min, max = np.nanmin(values), np.nanmax(values)
+                min, max = ut.nanmin(values), ut.nanmax(values)
                 points = self._split_eq_width(min, max)
         return Discretizer.create_discretized_var(
             data.domain[attribute], points)


Test patch:
diff --git a/Orange/tests/test_discretize.py b/Orange/tests/test_discretize.py
--- a/Orange/tests/test_discretize.py
+++ b/Orange/tests/test_discretize.py
@@ -13,6 +13,9 @@
 
 
 # noinspection PyPep8Naming
+from Orange.widgets.tests.utils import table_dense_sparse
+
+
 class TestEqualFreq(TestCase):
     def test_equifreq_with_too_few_values(self):
         s = [0] * 50 + [1] * 50
@@ -53,9 +56,10 @@ def test_equalwidth_on_two_values(self):
         self.assertEqual(len(dvar.values), 4)
         self.assertEqual(dvar.compute_value.points, [0.25, 0.5, 0.75])
 
-    def test_equalwidth_100_to_4(self):
+    @table_dense_sparse
+    def test_equalwidth_100_to_4(self, prepare_table):
         X = np.arange(101).reshape((101, 1))
-        table = data.Table(X)
+        table = prepare_table(data.Table(X))
         disc = discretize.EqualWidth(n=4)
         dvar = disc(table, table.domain[0])
         self.assertEqual(len(dvar.values), 4)
diff --git a/Orange/widgets/visualize/tests/test_owdistributions.py b/Orange/widgets/visualize/tests/test_owdistributions.py
--- a/Orange/widgets/visualize/tests/test_owdistributions.py
+++ b/Orange/widgets/visualize/tests/test_owdistributions.py
@@ -5,6 +5,7 @@
 from Orange.data.table import dataset_dirs
 from Orange.tests import test_dirname
 from Orange.widgets.tests.base import WidgetTest, datasets
+from Orange.widgets.tests.utils import table_dense_sparse
 from Orange.widgets.visualize.owdistributions import OWDistributions
 
 
@@ -85,3 +86,15 @@ def test_no_distributions(self):
         self.send_signal(self.widget.Inputs.data, None)
         cb.setChecked(True)
         cb.click()
+
+    @table_dense_sparse
+    def test_binning_numeric_variables(self, prepare_table):
+        """The widget should not crash when checking `Bin numeric variables`"""
+        data = prepare_table(self.iris)
+        self.send_signal(self.widget.Inputs.data, data)
+
+        self.widget.controls.disc_cont.setChecked(True)
+        self.wait_until_stop_blocking()
+
+        self.widget.controls.disc_cont.setChecked(False)
+        self.wait_until_stop_blocking()