Repository: biolab/orange3

Issue:
"Select Rows" loses class labels with "Remove unused classes"
**Describe the bug**
Selecting a subset of discretized class values make them all unknown, if I use "Remove unused classes".

In contrast, it works well with a "raw" variable such as the class for iris.tab.

**To Reproduce**
- File (housing)
- Discretize (discretize to three intervals)
- Select Rows (MEDV, select 2/3 values, choose "Remove unused classes")
- Data Table (observe unknown class values)

**Orange version:**
master



Discussion:

Resolving patch:
diff --git a/Orange/preprocess/remove.py b/Orange/preprocess/remove.py
--- a/Orange/preprocess/remove.py
+++ b/Orange/preprocess/remove.py
@@ -2,7 +2,7 @@
 
 import numpy as np
 
-from Orange.data import Domain, DiscreteVariable, Table
+from Orange.data import Domain, DiscreteVariable
 from Orange.preprocess.transformation import Lookup
 from Orange.statistics.util import nanunique
 from .preprocess import Preprocess
@@ -238,7 +238,10 @@ def remove_unused_values(var, data):
     if len(unique) == len(var.values):
         return var
     used_values = [var.values[i] for i in unique]
-    return DiscreteVariable(var.name, values=used_values, sparse=var.sparse)
+    translation_table = np.array([np.NaN] * len(var.values))
+    translation_table[unique] = range(len(used_values))
+    return DiscreteVariable(var.name, values=used_values, sparse=var.sparse,
+                            compute_value=Lookup(var, translation_table))
 
 
 def sort_var_values(var):


Test patch:
diff --git a/Orange/tests/test_remove.py b/Orange/tests/test_remove.py
--- a/Orange/tests/test_remove.py
+++ b/Orange/tests/test_remove.py
@@ -6,7 +6,7 @@
 import numpy as np
 
 from Orange.data import Table
-from Orange.preprocess import Remove
+from Orange.preprocess import Remove, discretize
 from Orange.tests import test_filename
 
 
@@ -160,3 +160,16 @@ def test_remove_mapping(self):
         cleaned = remover(data)
         np.testing.assert_array_equal(cleaned.Y[:50], 0)
         np.testing.assert_array_equal(cleaned.Y[50:], 1)
+
+    def test_remove_mapping_after_compute_value(self):
+        housing = Table("housing")
+        method = discretize.EqualFreq(n=3)
+        discretizer = discretize.DomainDiscretizer(
+            discretize_class=True, method=method)
+        domain = discretizer(housing)
+        data = housing.transform(domain)
+        val12 = np.nonzero(data.Y > 0)[0]
+        data = data[val12]
+        remover = Remove(class_flags=Remove.RemoveUnusedValues)
+        cleaned = remover(data)
+        np.testing.assert_equal(cleaned.Y, data.Y - 1)
diff --git a/Orange/widgets/data/tests/test_owselectrows.py b/Orange/widgets/data/tests/test_owselectrows.py
--- a/Orange/widgets/data/tests/test_owselectrows.py
+++ b/Orange/widgets/data/tests/test_owselectrows.py
@@ -10,6 +10,7 @@
 
 from Orange.data import (
     Table, ContinuousVariable, StringVariable, DiscreteVariable, Domain)
+from Orange.preprocess import discretize
 from Orange.widgets.data.owselectrows import (
     OWSelectRows, FilterDiscreteType, SelectRowsContextHandler)
 from Orange.widgets.tests.base import WidgetTest, datasets
@@ -312,6 +313,21 @@ def test_end_support_for_version_1(self):
 Basically, revert this commit.
 """)
 
+    def test_purge_discretized(self):
+        housing = Table("housing")
+        method = discretize.EqualFreq(n=3)
+        discretizer = discretize.DomainDiscretizer(
+            discretize_class=True, method=method)
+        domain = discretizer(housing)
+        data = housing.transform(domain)
+        widget = self.widget_with_context(domain, [["MEDV", 2, (2, 3)]])
+        widget.purge_classes = True
+        self.send_signal(widget.Inputs.data, data)
+        out = self.get_output(widget.Outputs.matching_data)
+        expected = data.Y[(data.Y == 1) + (data.Y == 2)]
+        expected = (expected == 2).astype(float)
+        np.testing.assert_equal(out.Y, expected)
+
     def widget_with_context(self, domain, conditions):
         ch = SelectRowsContextHandler()
         context = ch.new_context(domain, *ch.encode_domain(domain))