Repository: biolab/orange3

Issue:
ValueError:  Found array with 0 feature(s) (shape=(150, 0)) while a minimum of 1 is required.
https://sentry.io/biolab/orange3/issues/196744099/

```
ValueError:  Found array with 0 feature(s) (shape=(150, 0)) while a minimum of 1 is required.
(5 additional frame(s) were not displayed)
...
  Module "Orange.projection.base", line 35, in preprocess

    data = pp(data)
  Module "Orange.preprocess.preprocess", line 168, in __call__

    X = self.imputer.fit_transform(data.X)
  Module "sklearn.base", line 494, in fit_transform

    return self.fit(X, **fit_params).transform(X)
  Module "sklearn.preprocessing.imputation", line 156, in fit

    force_all_finite=False)
  Module "sklearn.utils.validation", line 424, in check_array

    context))
```


Discussion:

Resolving patch:
diff --git a/Orange/widgets/unsupervised/owpca.py b/Orange/widgets/unsupervised/owpca.py
--- a/Orange/widgets/unsupervised/owpca.py
+++ b/Orange/widgets/unsupervised/owpca.py
@@ -48,6 +48,10 @@ class Warning(widget.OWWidget.Warning):
             "All components of the PCA are trivial (explain 0 variance). "
             "Input data is constant (or near constant).")
 
+    class Error(widget.OWWidget.Error):
+        no_features = widget.Msg("At least 1 feature is required")
+        no_instances = widget.Msg("At least 1 data instance is required")
+
     def __init__(self):
         super().__init__()
         self.data = None
@@ -178,6 +182,7 @@ def set_data(self, data):
         self.fit()
 
     def fit(self):
+        self.clear_messages()
         self.clear()
         self.start_button.setEnabled(False)
         if self.data is None:
@@ -190,6 +195,19 @@ def fit(self):
             self.start_button.setEnabled(True)
         else:
             self.sampling_box.setVisible(False)
+            if len(data.domain.attributes) == 0:
+                self.Error.no_features()
+                self.send("Transformed data", None)
+                self.send("Components", None)
+                self.send("PCA", None)
+                return
+            if len(data) == 0:
+                self.Error.no_instances()
+                self.send("Transformed data", None)
+                self.send("Components", None)
+                self.send("PCA", None)
+                return
+
             pca = self._pca_projector(data)
             variance_ratio = pca.explained_variance_ratio_
             cumulative = numpy.cumsum(variance_ratio)


Test patch:
diff --git a/Orange/widgets/unsupervised/tests/test_owpca.py b/Orange/widgets/unsupervised/tests/test_owpca.py
--- a/Orange/widgets/unsupervised/tests/test_owpca.py
+++ b/Orange/widgets/unsupervised/tests/test_owpca.py
@@ -1,10 +1,11 @@
-from Orange.data import Table
+# Test methods with long descriptive names can omit docstrings
+# pylint: disable=missing-docstring
+from Orange.data import Table, Domain
 from Orange.widgets.tests.base import WidgetTest
 from Orange.widgets.unsupervised.owpca import OWPCA
 
 
-class TestOWDistanceMatrix(WidgetTest):
-
+class TestOWPCA(WidgetTest):
     def setUp(self):
         self.widget = self.create_widget(OWPCA)  # type: OWPCA
 
@@ -21,3 +22,18 @@ def test_constant_data(self):
         self.assertTrue(self.widget.Warning.trivial_components.is_shown())
         self.assertIsNone(self.get_output("Transformed Data"))
         self.assertIsNone(self.get_output("Components"))
+
+    def test_empty_data(self):
+        """ Check widget for dataset with no rows and for dataset with no attributes """
+        data = Table("iris")
+        self.send_signal("Data", data[:0])
+        self.assertTrue(self.widget.Error.no_instances.is_shown())
+
+        domain = Domain([], None, data.domain.variables)
+        new_data = Table.from_table(domain, data)
+        self.send_signal("Data", new_data)
+        self.assertTrue(self.widget.Error.no_features.is_shown())
+        self.assertFalse(self.widget.Error.no_instances.is_shown())
+
+        self.send_signal("Data", None)
+        self.assertFalse(self.widget.Error.no_features.is_shown())