We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `Orange/preprocess/remove.py`
Content:
```
1 from collections import namedtuple
2 
3 import numpy as np
4 
5 from Orange.data import Domain, DiscreteVariable, Table
6 from Orange.preprocess.transformation import Lookup
7 from Orange.statistics.util import nanunique
8 from .preprocess import Preprocess
9 
10 __all__ = ["Remove"]
11 
12 
13 class Remove(Preprocess):
14     """
15     Construct a preprocessor for removing constant features/classes
16     and unused values.
17     Given a data table, preprocessor returns a new table and a list of
18     results. In the new table, the constant features/classes and unused
19     values are removed. The list of results consists of two dictionaries.
20     The first one contains numbers of 'removed', 'reduced' and 'sorted'
21     features. The second one contains numbers of 'removed', 'reduced'
22     and 'sorted' features.
23 
24     Parameters
25     ----------
26     attr_flags : int (default: 0)
27         If SortValues, values of discrete attributes are sorted.
28         If RemoveConstant, unused attributes are removed.
29         If RemoveUnusedValues, unused values are removed from discrete
30         attributes.
31         It is possible to merge operations in one by summing several types.
32 
33     class_flags: int (default: 0)
34         If SortValues, values of discrete class attributes are sorted.
35         If RemoveConstant, unused class attributes are removed.
36         If RemoveUnusedValues, unused values are removed from discrete
37         class attributes.
38         It is possible to merge operations in one by summing several types.
39 
40     Examples
41     --------
42     >>> from Orange.data import Table
43     >>> from Orange.preprocess import Remove
44     >>> data = Table("zoo")[:10]
45     >>> flags = sum([Remove.SortValues, Remove.RemoveConstant, Remove.RemoveUnusedValues])
46     >>> remover = Remove(attr_flags=flags, class_flags=flags)
47     >>> new_data = remover(data)
48     >>> attr_results, class_results = remover.attr_results, remover.class_results
49     """
50 
51     SortValues, RemoveConstant, RemoveUnusedValues = 1, 2, 4
52 
53     def __init__(self, attr_flags=0, class_flags=0, meta_flags=0):
54         self.attr_flags = attr_flags
55         self.class_flags = class_flags
56         self.meta_flags = meta_flags
57         self.attr_results = None
58         self.class_results = None
59         self.meta_results = None
60 
61     def __call__(self, data):
62         """
63         Removes unused features or classes from the given data. Returns a new
64         data table.
65 
66         Parameters
67         ----------
68         data : Orange.data.Table
69             A data table to remove features or classes from.
70 
71         Returns
72         -------
73         data : Orange.data.Table
74             New data table.
75         """
76         if data is None:
77             return None
78 
79         domain = data.domain
80         attrs_state = [purge_var_M(var, data, self.attr_flags)
81                        for var in domain.attributes]
82         class_state = [purge_var_M(var, data, self.class_flags)
83                        for var in domain.class_vars]
84         metas_state = [purge_var_M(var, data, self.meta_flags)
85                        for var in domain.metas]
86 
87         att_vars, self.attr_results = self.get_vars_and_results(attrs_state)
88         cls_vars, self.class_results = self.get_vars_and_results(class_state)
89         meta_vars, self.meta_results = self.get_vars_and_results(metas_state)
90 
91         domain = Domain(att_vars, cls_vars, meta_vars)
92         return data.transform(domain)
93 
94     def get_vars_and_results(self, state):
95         removed, reduced, sorted = 0, 0, 0
96         vars = []
97         for st in state:
98             removed += is_removed(st)
99             reduced += not is_removed(st) and is_reduced(st)
100             sorted += not is_removed(st) and is_sorted(st)
101             if not is_removed(st):
102                 vars.append(merge_transforms(st).var)
103         res = {'removed': removed, 'reduced': reduced, 'sorted': sorted}
104         return vars, res
105 
106 
107 # Define a simple Purge expression 'language'.
108 #: A input variable (leaf expression).
109 Var = namedtuple("Var", ["var"])
110 #: Removed variable (can only ever be present as a root node).
111 Removed = namedtuple("Removed", ["sub", "var"])
112 #: A reduced variable
113 Reduced = namedtuple("Reduced", ["sub", "var"])
114 #: A sorted variable
115 Sorted = namedtuple("Sorted", ["sub", "var"])
116 #: A general (lookup) transformed variable.
117 #: (this node is returned as a result of `merge` which joins consecutive
118 #: Removed/Reduced nodes into a single Transformed node)
119 Transformed = namedtuple("Transformed", ["sub", "var"])
120 
121 
122 def is_var(exp):
123     """Is `exp` a `Var` node."""
124     return isinstance(exp, Var)
125 
126 
127 def is_removed(exp):
128     """Is `exp` a `Removed` node."""
129     return isinstance(exp, Removed)
130 
131 
132 def _contains(exp, cls):
133     """Does `node` contain a sub node of type `cls`"""
134     if isinstance(exp, cls):
135         return True
136     elif isinstance(exp, Var):
137         return False
138     else:
139         return _contains(exp.sub, cls)
140 
141 
142 def is_reduced(exp):
143     """Does `exp` contain a `Reduced` node."""
144     return _contains(exp, Reduced)
145 
146 
147 def is_sorted(exp):
148     """Does `exp` contain a `Reduced` node."""
149     return _contains(exp, Sorted)
150 
151 
152 def merge_transforms(exp):
153     """
154     Merge consecutive Removed, Reduced or Transformed nodes.
155 
156     .. note:: Removed nodes are returned unchanged.
157 
158     """
159     if isinstance(exp, (Var, Removed)):
160         return exp
161     elif isinstance(exp, (Reduced, Sorted, Transformed)):
162         prev = merge_transforms(exp.sub)
163         if isinstance(prev, (Reduced, Sorted, Transformed)):
164             B = exp.var.compute_value
165             assert isinstance(B, Lookup)
166             A = B.variable.compute_value
167             assert isinstance(A, Lookup)
168 
169             new_var = DiscreteVariable(
170                 exp.var.name,
171                 values=exp.var.values,
172                 ordered=exp.var.ordered,
173                 compute_value=merge_lookup(A, B),
174                 sparse=exp.var.sparse,
175             )
176             assert isinstance(prev.sub, Var)
177             return Transformed(prev.sub, new_var)
178         else:
179             assert prev is exp.sub
180             return exp
181     else:
182         raise TypeError
183 
184 
185 def purge_var_M(var, data, flags):
186     state = Var(var)
187     if flags & Remove.RemoveConstant:
188         var = remove_constant(state.var, data)
189         if var is None:
190             return Removed(state, state.var)
191 
192     if state.var.is_discrete:
193         if flags & Remove.RemoveUnusedValues:
194             newattr = remove_unused_values(state.var, data)
195 
196             if newattr is not state.var:
197                 state = Reduced(state, newattr)
198 
199             if flags & Remove.RemoveConstant and len(state.var.values) < 2:
200                 return Removed(state, state.var)
201 
202         if flags & Remove.SortValues:
203             newattr = sort_var_values(state.var)
204             if newattr is not state.var:
205                 state = Sorted(state, newattr)
206 
207     return state
208 
209 
210 def has_at_least_two_values(data, var):
211     ((dist, unknowns),) = data._compute_distributions([var])
212     # TODO this check is suboptimal for sparse since get_column_view
213     # densifies the data. Should be irrelevant after Pandas.
214     _, sparse = data.get_column_view(var)
215     if var.is_continuous:
216         dist = dist[1, :]
217     min_size = 0 if sparse and unknowns else 1
218     return np.sum(dist > 0.0) > min_size
219 
220 
221 def remove_constant(var, data):
222     if var.is_continuous:
223         if not has_at_least_two_values(data, var):
224             return None
225         else:
226             return var
227     elif var.is_discrete:
228         if len(var.values) < 2:
229             return None
230         else:
231             return var
232     else:
233         return var
234 
235 
236 def remove_unused_values(var, data):
237     unique = nanunique(data.get_column_view(var)[0].astype(float)).astype(int)
238     if len(unique) == len(var.values):
239         return var
240     used_values = [var.values[i] for i in unique]
241     return DiscreteVariable(var.name, values=used_values, sparse=var.sparse)
242 
243 
244 def sort_var_values(var):
245     newvalues = list(sorted(var.values))
246 
247     if newvalues == list(var.values):
248         return var
249 
250     translation_table = np.array(
251         [float(newvalues.index(value)) for value in var.values]
252     )
253 
254     return DiscreteVariable(var.name, values=newvalues,
255                             compute_value=Lookup(var, translation_table),
256                             sparse=var.sparse)
257 
258 
259 def merge_lookup(A, B):
260     """
261     Merge two consecutive Lookup transforms into one.
262     """
263     lookup_table = np.array(A.lookup_table)
264     mask = np.isfinite(lookup_table)
265     indices = np.array(lookup_table[mask], dtype=int)
266     lookup_table[mask] = B.lookup_table[indices]
267     return Lookup(A.variable, lookup_table)
268
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
     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):

{"golden_diff": "diff --git a/Orange/preprocess/remove.py b/Orange/preprocess/remove.py\n--- a/Orange/preprocess/remove.py\n+++ b/Orange/preprocess/remove.py\n@@ -2,7 +2,7 @@\n \n import numpy as np\n \n-from Orange.data import Domain, DiscreteVariable, Table\n+from Orange.data import Domain, DiscreteVariable\n from Orange.preprocess.transformation import Lookup\n from Orange.statistics.util import nanunique\n from .preprocess import Preprocess\n@@ -238,7 +238,10 @@\n     if len(unique) == len(var.values):\n         return var\n     used_values = [var.values[i] for i in unique]\n-    return DiscreteVariable(var.name, values=used_values, sparse=var.sparse)\n+    translation_table = np.array([np.NaN] * len(var.values))\n+    translation_table[unique] = range(len(used_values))\n+    return DiscreteVariable(var.name, values=used_values, sparse=var.sparse,\n+                            compute_value=Lookup(var, translation_table))\n \n \n def sort_var_values(var):\n", "issue": "\"Select Rows\" loses class labels with \"Remove unused classes\"\n**Describe the bug**\r\nSelecting a subset of discretized class values make them all unknown, if I use \"Remove unused classes\".\r\n\r\nIn contrast, it works well with a \"raw\" variable such as the class for iris.tab.\r\n\r\n**To Reproduce**\r\n- File (housing)\r\n- Discretize (discretize to three intervals)\r\n- Select Rows (MEDV, select 2/3 values, choose \"Remove unused classes\")\r\n- Data Table (observe unknown class values)\r\n\r\n**Orange version:**\r\nmaster\r\n\n", "before_files": [{"content": "from collections import namedtuple\n\nimport numpy as np\n\nfrom Orange.data import Domain, DiscreteVariable, Table\nfrom Orange.preprocess.transformation import Lookup\nfrom Orange.statistics.util import nanunique\nfrom .preprocess import Preprocess\n\n__all__ = [\"Remove\"]\n\n\nclass Remove(Preprocess):\n    \"\"\"\n    Construct a preprocessor for removing constant features/classes\n    and unused values.\n    Given a data table, preprocessor returns a new table and a list of\n    results. In the new table, the constant features/classes and unused\n    values are removed. The list of results consists of two dictionaries.\n    The first one contains numbers of 'removed', 'reduced' and 'sorted'\n    features. The second one contains numbers of 'removed', 'reduced'\n    and 'sorted' features.\n\n    Parameters\n    ----------\n    attr_flags : int (default: 0)\n        If SortValues, values of discrete attributes are sorted.\n        If RemoveConstant, unused attributes are removed.\n        If RemoveUnusedValues, unused values are removed from discrete\n        attributes.\n        It is possible to merge operations in one by summing several types.\n\n    class_flags: int (default: 0)\n        If SortValues, values of discrete class attributes are sorted.\n        If RemoveConstant, unused class attributes are removed.\n        If RemoveUnusedValues, unused values are removed from discrete\n        class attributes.\n        It is possible to merge operations in one by summing several types.\n\n    Examples\n    --------\n    >>> from Orange.data import Table\n    >>> from Orange.preprocess import Remove\n    >>> data = Table(\"zoo\")[:10]\n    >>> flags = sum([Remove.SortValues, Remove.RemoveConstant, Remove.RemoveUnusedValues])\n    >>> remover = Remove(attr_flags=flags, class_flags=flags)\n    >>> new_data = remover(data)\n    >>> attr_results, class_results = remover.attr_results, remover.class_results\n    \"\"\"\n\n    SortValues, RemoveConstant, RemoveUnusedValues = 1, 2, 4\n\n    def __init__(self, attr_flags=0, class_flags=0, meta_flags=0):\n        self.attr_flags = attr_flags\n        self.class_flags = class_flags\n        self.meta_flags = meta_flags\n        self.attr_results = None\n        self.class_results = None\n        self.meta_results = None\n\n    def __call__(self, data):\n        \"\"\"\n        Removes unused features or classes from the given data. Returns a new\n        data table.\n\n        Parameters\n        ----------\n        data : Orange.data.Table\n            A data table to remove features or classes from.\n\n        Returns\n        -------\n        data : Orange.data.Table\n            New data table.\n        \"\"\"\n        if data is None:\n            return None\n\n        domain = data.domain\n        attrs_state = [purge_var_M(var, data, self.attr_flags)\n                       for var in domain.attributes]\n        class_state = [purge_var_M(var, data, self.class_flags)\n                       for var in domain.class_vars]\n        metas_state = [purge_var_M(var, data, self.meta_flags)\n                       for var in domain.metas]\n\n        att_vars, self.attr_results = self.get_vars_and_results(attrs_state)\n        cls_vars, self.class_results = self.get_vars_and_results(class_state)\n        meta_vars, self.meta_results = self.get_vars_and_results(metas_state)\n\n        domain = Domain(att_vars, cls_vars, meta_vars)\n        return data.transform(domain)\n\n    def get_vars_and_results(self, state):\n        removed, reduced, sorted = 0, 0, 0\n        vars = []\n        for st in state:\n            removed += is_removed(st)\n            reduced += not is_removed(st) and is_reduced(st)\n            sorted += not is_removed(st) and is_sorted(st)\n            if not is_removed(st):\n                vars.append(merge_transforms(st).var)\n        res = {'removed': removed, 'reduced': reduced, 'sorted': sorted}\n        return vars, res\n\n\n# Define a simple Purge expression 'language'.\n#: A input variable (leaf expression).\nVar = namedtuple(\"Var\", [\"var\"])\n#: Removed variable (can only ever be present as a root node).\nRemoved = namedtuple(\"Removed\", [\"sub\", \"var\"])\n#: A reduced variable\nReduced = namedtuple(\"Reduced\", [\"sub\", \"var\"])\n#: A sorted variable\nSorted = namedtuple(\"Sorted\", [\"sub\", \"var\"])\n#: A general (lookup) transformed variable.\n#: (this node is returned as a result of `merge` which joins consecutive\n#: Removed/Reduced nodes into a single Transformed node)\nTransformed = namedtuple(\"Transformed\", [\"sub\", \"var\"])\n\n\ndef is_var(exp):\n    \"\"\"Is `exp` a `Var` node.\"\"\"\n    return isinstance(exp, Var)\n\n\ndef is_removed(exp):\n    \"\"\"Is `exp` a `Removed` node.\"\"\"\n    return isinstance(exp, Removed)\n\n\ndef _contains(exp, cls):\n    \"\"\"Does `node` contain a sub node of type `cls`\"\"\"\n    if isinstance(exp, cls):\n        return True\n    elif isinstance(exp, Var):\n        return False\n    else:\n        return _contains(exp.sub, cls)\n\n\ndef is_reduced(exp):\n    \"\"\"Does `exp` contain a `Reduced` node.\"\"\"\n    return _contains(exp, Reduced)\n\n\ndef is_sorted(exp):\n    \"\"\"Does `exp` contain a `Reduced` node.\"\"\"\n    return _contains(exp, Sorted)\n\n\ndef merge_transforms(exp):\n    \"\"\"\n    Merge consecutive Removed, Reduced or Transformed nodes.\n\n    .. note:: Removed nodes are returned unchanged.\n\n    \"\"\"\n    if isinstance(exp, (Var, Removed)):\n        return exp\n    elif isinstance(exp, (Reduced, Sorted, Transformed)):\n        prev = merge_transforms(exp.sub)\n        if isinstance(prev, (Reduced, Sorted, Transformed)):\n            B = exp.var.compute_value\n            assert isinstance(B, Lookup)\n            A = B.variable.compute_value\n            assert isinstance(A, Lookup)\n\n            new_var = DiscreteVariable(\n                exp.var.name,\n                values=exp.var.values,\n                ordered=exp.var.ordered,\n                compute_value=merge_lookup(A, B),\n                sparse=exp.var.sparse,\n            )\n            assert isinstance(prev.sub, Var)\n            return Transformed(prev.sub, new_var)\n        else:\n            assert prev is exp.sub\n            return exp\n    else:\n        raise TypeError\n\n\ndef purge_var_M(var, data, flags):\n    state = Var(var)\n    if flags & Remove.RemoveConstant:\n        var = remove_constant(state.var, data)\n        if var is None:\n            return Removed(state, state.var)\n\n    if state.var.is_discrete:\n        if flags & Remove.RemoveUnusedValues:\n            newattr = remove_unused_values(state.var, data)\n\n            if newattr is not state.var:\n                state = Reduced(state, newattr)\n\n            if flags & Remove.RemoveConstant and len(state.var.values) < 2:\n                return Removed(state, state.var)\n\n        if flags & Remove.SortValues:\n            newattr = sort_var_values(state.var)\n            if newattr is not state.var:\n                state = Sorted(state, newattr)\n\n    return state\n\n\ndef has_at_least_two_values(data, var):\n    ((dist, unknowns),) = data._compute_distributions([var])\n    # TODO this check is suboptimal for sparse since get_column_view\n    # densifies the data. Should be irrelevant after Pandas.\n    _, sparse = data.get_column_view(var)\n    if var.is_continuous:\n        dist = dist[1, :]\n    min_size = 0 if sparse and unknowns else 1\n    return np.sum(dist > 0.0) > min_size\n\n\ndef remove_constant(var, data):\n    if var.is_continuous:\n        if not has_at_least_two_values(data, var):\n            return None\n        else:\n            return var\n    elif var.is_discrete:\n        if len(var.values) < 2:\n            return None\n        else:\n            return var\n    else:\n        return var\n\n\ndef remove_unused_values(var, data):\n    unique = nanunique(data.get_column_view(var)[0].astype(float)).astype(int)\n    if len(unique) == len(var.values):\n        return var\n    used_values = [var.values[i] for i in unique]\n    return DiscreteVariable(var.name, values=used_values, sparse=var.sparse)\n\n\ndef sort_var_values(var):\n    newvalues = list(sorted(var.values))\n\n    if newvalues == list(var.values):\n        return var\n\n    translation_table = np.array(\n        [float(newvalues.index(value)) for value in var.values]\n    )\n\n    return DiscreteVariable(var.name, values=newvalues,\n                            compute_value=Lookup(var, translation_table),\n                            sparse=var.sparse)\n\n\ndef merge_lookup(A, B):\n    \"\"\"\n    Merge two consecutive Lookup transforms into one.\n    \"\"\"\n    lookup_table = np.array(A.lookup_table)\n    mask = np.isfinite(lookup_table)\n    indices = np.array(lookup_table[mask], dtype=int)\n    lookup_table[mask] = B.lookup_table[indices]\n    return Lookup(A.variable, lookup_table)\n", "path": "Orange/preprocess/remove.py"}], "after_files": [{"content": "from collections import namedtuple\n\nimport numpy as np\n\nfrom Orange.data import Domain, DiscreteVariable\nfrom Orange.preprocess.transformation import Lookup\nfrom Orange.statistics.util import nanunique\nfrom .preprocess import Preprocess\n\n__all__ = [\"Remove\"]\n\n\nclass Remove(Preprocess):\n    \"\"\"\n    Construct a preprocessor for removing constant features/classes\n    and unused values.\n    Given a data table, preprocessor returns a new table and a list of\n    results. In the new table, the constant features/classes and unused\n    values are removed. The list of results consists of two dictionaries.\n    The first one contains numbers of 'removed', 'reduced' and 'sorted'\n    features. The second one contains numbers of 'removed', 'reduced'\n    and 'sorted' features.\n\n    Parameters\n    ----------\n    attr_flags : int (default: 0)\n        If SortValues, values of discrete attributes are sorted.\n        If RemoveConstant, unused attributes are removed.\n        If RemoveUnusedValues, unused values are removed from discrete\n        attributes.\n        It is possible to merge operations in one by summing several types.\n\n    class_flags: int (default: 0)\n        If SortValues, values of discrete class attributes are sorted.\n        If RemoveConstant, unused class attributes are removed.\n        If RemoveUnusedValues, unused values are removed from discrete\n        class attributes.\n        It is possible to merge operations in one by summing several types.\n\n    Examples\n    --------\n    >>> from Orange.data import Table\n    >>> from Orange.preprocess import Remove\n    >>> data = Table(\"zoo\")[:10]\n    >>> flags = sum([Remove.SortValues, Remove.RemoveConstant, Remove.RemoveUnusedValues])\n    >>> remover = Remove(attr_flags=flags, class_flags=flags)\n    >>> new_data = remover(data)\n    >>> attr_results, class_results = remover.attr_results, remover.class_results\n    \"\"\"\n\n    SortValues, RemoveConstant, RemoveUnusedValues = 1, 2, 4\n\n    def __init__(self, attr_flags=0, class_flags=0, meta_flags=0):\n        self.attr_flags = attr_flags\n        self.class_flags = class_flags\n        self.meta_flags = meta_flags\n        self.attr_results = None\n        self.class_results = None\n        self.meta_results = None\n\n    def __call__(self, data):\n        \"\"\"\n        Removes unused features or classes from the given data. Returns a new\n        data table.\n\n        Parameters\n        ----------\n        data : Orange.data.Table\n            A data table to remove features or classes from.\n\n        Returns\n        -------\n        data : Orange.data.Table\n            New data table.\n        \"\"\"\n        if data is None:\n            return None\n\n        domain = data.domain\n        attrs_state = [purge_var_M(var, data, self.attr_flags)\n                       for var in domain.attributes]\n        class_state = [purge_var_M(var, data, self.class_flags)\n                       for var in domain.class_vars]\n        metas_state = [purge_var_M(var, data, self.meta_flags)\n                       for var in domain.metas]\n\n        att_vars, self.attr_results = self.get_vars_and_results(attrs_state)\n        cls_vars, self.class_results = self.get_vars_and_results(class_state)\n        meta_vars, self.meta_results = self.get_vars_and_results(metas_state)\n\n        domain = Domain(att_vars, cls_vars, meta_vars)\n        return data.transform(domain)\n\n    def get_vars_and_results(self, state):\n        removed, reduced, sorted = 0, 0, 0\n        vars = []\n        for st in state:\n            removed += is_removed(st)\n            reduced += not is_removed(st) and is_reduced(st)\n            sorted += not is_removed(st) and is_sorted(st)\n            if not is_removed(st):\n                vars.append(merge_transforms(st).var)\n        res = {'removed': removed, 'reduced': reduced, 'sorted': sorted}\n        return vars, res\n\n\n# Define a simple Purge expression 'language'.\n#: A input variable (leaf expression).\nVar = namedtuple(\"Var\", [\"var\"])\n#: Removed variable (can only ever be present as a root node).\nRemoved = namedtuple(\"Removed\", [\"sub\", \"var\"])\n#: A reduced variable\nReduced = namedtuple(\"Reduced\", [\"sub\", \"var\"])\n#: A sorted variable\nSorted = namedtuple(\"Sorted\", [\"sub\", \"var\"])\n#: A general (lookup) transformed variable.\n#: (this node is returned as a result of `merge` which joins consecutive\n#: Removed/Reduced nodes into a single Transformed node)\nTransformed = namedtuple(\"Transformed\", [\"sub\", \"var\"])\n\n\ndef is_var(exp):\n    \"\"\"Is `exp` a `Var` node.\"\"\"\n    return isinstance(exp, Var)\n\n\ndef is_removed(exp):\n    \"\"\"Is `exp` a `Removed` node.\"\"\"\n    return isinstance(exp, Removed)\n\n\ndef _contains(exp, cls):\n    \"\"\"Does `node` contain a sub node of type `cls`\"\"\"\n    if isinstance(exp, cls):\n        return True\n    elif isinstance(exp, Var):\n        return False\n    else:\n        return _contains(exp.sub, cls)\n\n\ndef is_reduced(exp):\n    \"\"\"Does `exp` contain a `Reduced` node.\"\"\"\n    return _contains(exp, Reduced)\n\n\ndef is_sorted(exp):\n    \"\"\"Does `exp` contain a `Reduced` node.\"\"\"\n    return _contains(exp, Sorted)\n\n\ndef merge_transforms(exp):\n    \"\"\"\n    Merge consecutive Removed, Reduced or Transformed nodes.\n\n    .. note:: Removed nodes are returned unchanged.\n\n    \"\"\"\n    if isinstance(exp, (Var, Removed)):\n        return exp\n    elif isinstance(exp, (Reduced, Sorted, Transformed)):\n        prev = merge_transforms(exp.sub)\n        if isinstance(prev, (Reduced, Sorted, Transformed)):\n            B = exp.var.compute_value\n            assert isinstance(B, Lookup)\n            A = B.variable.compute_value\n            assert isinstance(A, Lookup)\n\n            new_var = DiscreteVariable(\n                exp.var.name,\n                values=exp.var.values,\n                ordered=exp.var.ordered,\n                compute_value=merge_lookup(A, B),\n                sparse=exp.var.sparse,\n            )\n            assert isinstance(prev.sub, Var)\n            return Transformed(prev.sub, new_var)\n        else:\n            assert prev is exp.sub\n            return exp\n    else:\n        raise TypeError\n\n\ndef purge_var_M(var, data, flags):\n    state = Var(var)\n    if flags & Remove.RemoveConstant:\n        var = remove_constant(state.var, data)\n        if var is None:\n            return Removed(state, state.var)\n\n    if state.var.is_discrete:\n        if flags & Remove.RemoveUnusedValues:\n            newattr = remove_unused_values(state.var, data)\n\n            if newattr is not state.var:\n                state = Reduced(state, newattr)\n\n            if flags & Remove.RemoveConstant and len(state.var.values) < 2:\n                return Removed(state, state.var)\n\n        if flags & Remove.SortValues:\n            newattr = sort_var_values(state.var)\n            if newattr is not state.var:\n                state = Sorted(state, newattr)\n\n    return state\n\n\ndef has_at_least_two_values(data, var):\n    ((dist, unknowns),) = data._compute_distributions([var])\n    # TODO this check is suboptimal for sparse since get_column_view\n    # densifies the data. Should be irrelevant after Pandas.\n    _, sparse = data.get_column_view(var)\n    if var.is_continuous:\n        dist = dist[1, :]\n    min_size = 0 if sparse and unknowns else 1\n    return np.sum(dist > 0.0) > min_size\n\n\ndef remove_constant(var, data):\n    if var.is_continuous:\n        if not has_at_least_two_values(data, var):\n            return None\n        else:\n            return var\n    elif var.is_discrete:\n        if len(var.values) < 2:\n            return None\n        else:\n            return var\n    else:\n        return var\n\n\ndef remove_unused_values(var, data):\n    unique = nanunique(data.get_column_view(var)[0].astype(float)).astype(int)\n    if len(unique) == len(var.values):\n        return var\n    used_values = [var.values[i] for i in unique]\n    translation_table = np.array([np.NaN] * len(var.values))\n    translation_table[unique] = range(len(used_values))\n    return DiscreteVariable(var.name, values=used_values, sparse=var.sparse,\n                            compute_value=Lookup(var, translation_table))\n\n\ndef sort_var_values(var):\n    newvalues = list(sorted(var.values))\n\n    if newvalues == list(var.values):\n        return var\n\n    translation_table = np.array(\n        [float(newvalues.index(value)) for value in var.values]\n    )\n\n    return DiscreteVariable(var.name, values=newvalues,\n                            compute_value=Lookup(var, translation_table),\n                            sparse=var.sparse)\n\n\ndef merge_lookup(A, B):\n    \"\"\"\n    Merge two consecutive Lookup transforms into one.\n    \"\"\"\n    lookup_table = np.array(A.lookup_table)\n    mask = np.isfinite(lookup_table)\n    indices = np.array(lookup_table[mask], dtype=int)\n    lookup_table[mask] = B.lookup_table[indices]\n    return Lookup(A.variable, lookup_table)\n", "path": "Orange/preprocess/remove.py"}]}