Repository: biolab/orange3

Issue:
ValueError:  minlength must be positive
https://sentry.io/biolab/orange3/issues/206599954/

```
ValueError:  minlength must be positive
(5 additional frame(s) were not displayed)
...
  Module "Orange.statistics.contingency", line 41, in __new__

    return cls.from_data(dat, col_variable, row_variable)
  Module "Orange.statistics.contingency", line 80, in from_data

    [col_variable], row_variable)
  Module "Orange.data.table", line 1415, in _compute_contingency

    row_data, len(var.values) - 1, n_rows - 1, W)
  Module "Orange.statistics.util", line 152, in contingency

    minlength=ny * nx)[0].reshape(nx, ny).T)
  Module "Orange.statistics.util", line 52, in bincount

    minlength=minlength).astype(float),
```


Discussion:

Resolving patch:
diff --git a/Orange/widgets/visualize/owsieve.py b/Orange/widgets/visualize/owsieve.py
--- a/Orange/widgets/visualize/owsieve.py
+++ b/Orange/widgets/visualize/owsieve.py
@@ -29,6 +29,12 @@ class ChiSqStats:
     pair of attributes. The class is also used for ranking.
     """
     def __init__(self, data, attr1, attr2):
+        attr1 = data.domain[attr1]
+        attr2 = data.domain[attr2]
+        if attr1.is_discrete and not attr1.values or \
+                attr2.is_discrete and not attr2.values:
+            self.p = np.nan
+            return
         self.observed = get_contingency(data, attr1, attr2)
         self.n = np.sum(self.observed)
         self.probs_x = self.observed.sum(axis=0) / self.n
@@ -403,15 +409,22 @@ def _oper(attr, txt):
         view = self.canvasView
 
         chi = ChiSqStats(self.discrete_data, disc_x, disc_y)
-        n = chi.n
         max_ylabel_w = max((width(val) for val in disc_y.values), default=0)
         max_ylabel_w = min(max_ylabel_w, 200)
         x_off = width(attr_x.name) + max_ylabel_w
         y_off = 15
-        square_size = min(view.width() - x_off - 35, view.height() - y_off - 50)
+        square_size = min(view.width() - x_off - 35, view.height() - y_off - 80)
         square_size = max(square_size, 10)
         self.canvasView.setSceneRect(0, 0, view.width(), view.height())
-
+        if not disc_x.values or not disc_y.values:
+            text_ = "Features {} and {} have no values".format(disc_x, disc_y) \
+                if not disc_x.values and not disc_y.values and \
+                   disc_x != disc_y else "Feature {} has no values".format(
+                disc_x if not disc_x.values else disc_y)
+            text(text_, view.width() / 2 + 70, view.height() / 2,
+                 Qt.AlignRight | Qt.AlignVCenter)
+            return
+        n = chi.n
         curr_x = x_off
         max_xlabel_h = 0
         self.areas = []
@@ -452,6 +465,7 @@ def _oper(attr, txt):
              Qt.AlignLeft | Qt.AlignVCenter, bold=True, vertical=True)
         text(attr_x.name, x_off + square_size / 2, bottom,
              Qt.AlignHCenter | Qt.AlignTop, bold=True)
+        bottom += 30
         xl = text("χ²={:.2f}, p={:.3f}".format(chi.chisq, chi.p),
                   0, bottom)
         # Assume similar height for both lines


Test patch:
diff --git a/Orange/widgets/visualize/tests/test_owsieve.py b/Orange/widgets/visualize/tests/test_owsieve.py
--- a/Orange/widgets/visualize/tests/test_owsieve.py
+++ b/Orange/widgets/visualize/tests/test_owsieve.py
@@ -1,8 +1,11 @@
 # Test methods with long descriptive names can omit docstrings
 # pylint: disable=missing-docstring
+import numpy as np
+
 from AnyQt.QtCore import QEvent, QPoint, Qt
 from AnyQt.QtGui import QMouseEvent
 
+from Orange.data import DiscreteVariable, Domain, Table
 from Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin
 from Orange.widgets.visualize.owsieve import OWSieveDiagram
 
@@ -26,3 +29,11 @@ def _select_data(self):
             QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,
             Qt.LeftButton, Qt.KeyboardModifiers()))
         return [0, 4, 6, 7, 11, 17, 19, 21, 22, 24, 26, 39, 40, 43, 44, 46]
+
+    def test_missing_values(self):
+        """Check widget for dataset with missing values"""
+        attrs = [DiscreteVariable("c1", ["a", "b", "c"])]
+        class_var = DiscreteVariable("cls", [])
+        X = np.array([1, 2, 0, 1, 0, 2])[:, None]
+        data = Table(Domain(attrs, class_var), X, np.array([np.nan] * 6))
+        self.send_signal("Data", data)