Repository: biolab/orange3

Issue:
Data Sampler: Fixed proportion with 0% crash
<!-- 
Thanks for taking the time to report a bug!
If you're raising an issue about an add-on (i.e., installed via Options > Add-ons), raise an issue in the relevant add-on's issue tracker instead. See: https://github.com/biolab?q=orange3
To fix the bug, we need to be able to reproduce it. Please answer the following questions to the best of your ability.
-->


**What's wrong?**

<!-- Be specific, clear, and concise. Include screenshots if relevant. -->
<!-- If you're getting an error message, copy it, and enclose it with three backticks (```). -->

```
Traceback (most recent call last):
  File "/Users/vesna/orange3/Orange/widgets/data/owdatasampler.py", line 228, in commit
    self.updateindices()
  File "/Users/vesna/orange3/Orange/widgets/data/owdatasampler.py", line 293, in updateindices
    self.indices = self.sample(data_length, size, stratified=False)
  File "/Users/vesna/orange3/Orange/widgets/data/owdatasampler.py", line 310, in sample
    return sampler(self.data)
  File "/Users/vesna/orange3/Orange/widgets/data/owdatasampler.py", line 427, in __call__
    return SampleRandomN(n, self.stratified,
  File "/Users/vesna/orange3/Orange/widgets/data/owdatasampler.py", line 416, in __call__
    return next(iter(ind))
  File "/Users/vesna/miniconda3/lib/python3.8/site-packages/sklearn/model_selection/_split.py", line 1622, in split
    for train, test in self._iter_indices(X, y, groups):
  File "/Users/vesna/miniconda3/lib/python3.8/site-packages/sklearn/model_selection/_split.py", line 1730, in _iter_indices
    n_train, n_test = _validate_shuffle_split(
  File "/Users/vesna/miniconda3/lib/python3.8/site-packages/sklearn/model_selection/_split.py", line 2071, in _validate_shuffle_split
    raise ValueError(
ValueError: test_size=0 should be either positive and smaller than the number of samples 150 or a float in the (0, 1) range
```

**How can we reproduce the problem?** 
<!-- Upload a zip with the .ows file and data. -->
<!-- Describe the steps (open this widget, click there, then add this...) -->
File (iris) -> Data Sampler (Fixed proportion of data, set to 0%, click Sample Data)


**What's your environment?**
<!-- To find your Orange version, see "Help → About → Version" or `Orange.version.full_version` in code -->
- Operating system: macos
- Orange version: master
- How you installed Orange: pip



Discussion:

Resolving patch:
diff --git a/Orange/widgets/data/owdatasampler.py b/Orange/widgets/data/owdatasampler.py
--- a/Orange/widgets/data/owdatasampler.py
+++ b/Orange/widgets/data/owdatasampler.py
@@ -107,7 +107,7 @@ def set_sampling_type_i():
         ibox = gui.indentedBox(sampling)
         self.sampleSizeSpin = gui.spin(
             ibox, self, "sampleSizeNumber", label="Instances: ",
-            minv=1, maxv=self._MAX_SAMPLE_SIZE,
+            minv=0, maxv=self._MAX_SAMPLE_SIZE,
             callback=set_sampling_type(self.FixedSize),
             controlWidth=90)
         gui.checkBox(
@@ -395,11 +395,15 @@ def __call__(self, table):
             o[sample] = 0
             others = np.nonzero(o)[0]
             return others, sample
-        if self.n == len(table):
+        if self.n in (0, len(table)):
             rgen = np.random.RandomState(self.random_state)
-            sample = np.arange(self.n)
-            rgen.shuffle(sample)
-            return np.array([], dtype=int), sample
+            shuffled = np.arange(len(table))
+            rgen.shuffle(shuffled)
+            empty = np.array([], dtype=int)
+            if self.n == 0:
+                return shuffled, empty
+            else:
+                return empty, shuffled
         elif self.stratified and table.domain.has_discrete_class:
             test_size = max(len(table.domain.class_var.values), self.n)
             splitter = skl.StratifiedShuffleSplit(


Test patch:
diff --git a/Orange/widgets/data/tests/test_owdatasampler.py b/Orange/widgets/data/tests/test_owdatasampler.py
--- a/Orange/widgets/data/tests/test_owdatasampler.py
+++ b/Orange/widgets/data/tests/test_owdatasampler.py
@@ -131,6 +131,13 @@ def set_fixed_sample_size(self, sample_size, with_replacement=False):
         self.widget.commit()
         return self.widget.sampleSizeSpin.value()
 
+    def set_fixed_proportion(self, proportion):
+        """Set fixed sample proportion.
+        """
+        self.select_sampling_type(self.widget.FixedProportion)
+        self.widget.sampleSizePercentageSlider.setValue(proportion)
+        self.widget.commit()
+
     def assertNoIntersection(self, sample, other):
         self.assertFalse(bool(set(sample.ids) & set(other.ids)))
 
@@ -170,6 +177,26 @@ def test_cv_output_migration(self):
         self.assertEqual(len(self.get_output(w.Outputs.data_sample)), 15)
         self.assertEqual(len(self.get_output(w.Outputs.remaining_data)), 135)
 
+    def test_empty_sample(self):
+        w = self.widget
+        self.send_signal(w.Inputs.data, self.iris)
+
+        self.set_fixed_sample_size(150)
+        self.assertEqual(len(self.get_output(w.Outputs.data_sample)), 150)
+        self.assertEqual(len(self.get_output(w.Outputs.remaining_data)), 0)
+
+        self.set_fixed_sample_size(0)
+        self.assertEqual(len(self.get_output(w.Outputs.data_sample)), 0)
+        self.assertEqual(len(self.get_output(w.Outputs.remaining_data)), 150)
+
+        self.set_fixed_proportion(100)
+        self.assertEqual(len(self.get_output(w.Outputs.data_sample)), 150)
+        self.assertEqual(len(self.get_output(w.Outputs.remaining_data)), 0)
+
+        self.set_fixed_proportion(0)
+        self.assertEqual(len(self.get_output(w.Outputs.data_sample)), 0)
+        self.assertEqual(len(self.get_output(w.Outputs.remaining_data)), 150)
+
     def test_send_report(self):
         w = self.widget
         self.send_signal(w.Inputs.data, self.iris)