Skip to content

Commit 1717c4a

Browse files
authored
Merge pull request #2908 from BlazZupan/select-rows-annotated-data
[ENH] Select Rows: Add annotated data output
2 parents aedbf79 + 39bb3b4 commit 1717c4a

File tree

3 files changed

+80
-39
lines changed

3 files changed

+80
-39
lines changed

Orange/widgets/data/owselectrows.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import enum
2-
32
from collections import OrderedDict
43
from itertools import chain
54

5+
import numpy as np
6+
67
from AnyQt.QtWidgets import (
78
QWidget, QTableWidget, QHeaderView, QComboBox, QLineEdit, QToolButton,
89
QMessageBox, QMenu, QListView, QGridLayout, QPushButton, QSizePolicy,
@@ -26,6 +27,8 @@
2627
from Orange.widgets.utils import vartype
2728
from Orange.canvas import report
2829
from Orange.widgets.widget import Msg
30+
from Orange.widgets.utils.annotated_data import (create_annotated_table,
31+
ANNOTATED_DATA_SIGNAL_NAME)
2932

3033

3134
class SelectRowsContextHandler(DomainContextHandler):
@@ -79,6 +82,7 @@ class Inputs:
7982
class Outputs:
8083
matching_data = Output("Matching Data", Table, default=True)
8184
unmatched_data = Output("Unmatched Data", Table)
85+
annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)
8286

8387
want_main_area = False
8488

@@ -465,6 +469,8 @@ def _values_to_floats(self, attr, values):
465469
def commit(self):
466470
matching_output = self.data
467471
non_matching_output = None
472+
annotated_output = None
473+
468474
self.Error.clear()
469475
if self.data:
470476
domain = self.data.domain
@@ -512,6 +518,9 @@ def commit(self):
512518
self.filters.negate = True
513519
non_matching_output = self.filters(self.data)
514520

521+
row_sel = np.in1d(self.data.ids, matching_output.ids)
522+
annotated_output = create_annotated_table(self.data, row_sel)
523+
515524
# if hasattr(self.data, "name"):
516525
# matching_output.name = self.data.name
517526
# non_matching_output.name = self.data.name
@@ -529,14 +538,18 @@ def commit(self):
529538

530539
matching_output = remover(matching_output)
531540
non_matching_output = remover(non_matching_output)
541+
annotated_output = remover(annotated_output)
532542

533543
if matching_output is not None and not len(matching_output):
534544
matching_output = None
535545
if non_matching_output is not None and not len(non_matching_output):
536546
non_matching_output = None
547+
if annotated_output is not None and not len(annotated_output):
548+
annotated_output = None
537549

538550
self.Outputs.matching_data.send(matching_output)
539551
self.Outputs.unmatched_data.send(non_matching_output)
552+
self.Outputs.annotated_data.send(annotated_output)
540553

541554
self.match_desc = report.describe_data_brief(matching_output)
542555
self.nonmatch_desc = report.describe_data_brief(non_matching_output)

Orange/widgets/data/tests/test_owselectrows.py

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
# pylint: disable=missing-docstring
33
from AnyQt.QtCore import QLocale, Qt
44
from AnyQt.QtTest import QTest
5-
from AnyQt.QtWidgets import QLineEdit
5+
from AnyQt.QtWidgets import QLineEdit, QComboBox
6+
7+
import numpy as np
68

79
from Orange.data import (
810
Table, ContinuousVariable, StringVariable, DiscreteVariable)
@@ -12,6 +14,7 @@
1214

1315
from Orange.data.filter import FilterContinuous, FilterString
1416
from Orange.widgets.tests.utils import simulate, override_locale
17+
from Orange.widgets.utils.annotated_data import ANNOTATED_DATA_FEATURE_NAME
1518

1619
CFValues = {
1720
FilterContinuous.Equal: ["5.4"],
@@ -115,32 +118,6 @@ def test_continuous_filter_with_sl_SI_locale(self):
115118
self.enterFilter(iris.domain[2], "is below", "5.2")
116119
self.assertEqual(self.widget.conditions[0][2], ("52",))
117120

118-
def enterFilter(self, variable, filter, value=None, value2=None):
119-
row = self.widget.cond_list.model().rowCount()
120-
self.widget.add_button.click()
121-
122-
var_combo = self.widget.cond_list.cellWidget(row, 0)
123-
simulate.combobox_activate_item(var_combo, variable.name, delay=0)
124-
125-
oper_combo = self.widget.cond_list.cellWidget(row, 1)
126-
simulate.combobox_activate_item(oper_combo, filter, delay=0)
127-
128-
value_inputs = self._get_value_line_edits(row)
129-
for i, value in enumerate([value, value2]):
130-
if value is None:
131-
continue
132-
QTest.mouseClick(value_inputs[i], Qt.LeftButton)
133-
QTest.keyClicks(value_inputs[i], value, delay=0)
134-
QTest.keyClick(value_inputs[i], Qt.Key_Enter)
135-
136-
def _get_value_line_edits(self, row):
137-
value_inputs = self.widget.cond_list.cellWidget(row, 2)
138-
if value_inputs:
139-
value_inputs = [w for w in value_inputs.children()
140-
if isinstance(w, QLineEdit)]
141-
return value_inputs
142-
143-
144121
@override_locale(QLocale.Slovenian)
145122
def test_stores_settings_in_invariant_locale(self):
146123
iris = Table("iris")[:5]
@@ -156,8 +133,6 @@ def test_stores_settings_in_invariant_locale(self):
156133
saved_condition = context.values["conditions"][0]
157134
self.assertEqual(saved_condition[2][0], 5.2)
158135

159-
160-
161136
@override_locale(QLocale.C)
162137
def test_restores_continuous_filter_in_c_locale(self):
163138
iris = Table("iris")[:5]
@@ -226,23 +201,15 @@ def test_is_defined_on_continuous_variable(self):
226201
data = Table(datasets.path("testing_dataset_cls"))
227202
self.send_signal(self.widget.Inputs.data, data)
228203

229-
230204
self.enterFilter(data.domain["c2"], "is defined")
231205
self.assertFalse(self.widget.Error.parsing_error.is_shown())
232206
self.assertEqual(len(self.get_output("Matching Data")), 3)
233207
self.assertEqual(len(self.get_output("Unmatched Data")), 1)
208+
self.assertEqual(len(self.get_output("Data")), len(data))
234209

235210
# Test saving of settings
236211
self.widget.settingsHandler.pack_data(self.widget)
237212

238-
def widget_with_context(self, domain, conditions):
239-
ch = SelectRowsContextHandler()
240-
context = ch.new_context(domain, *ch.encode_domain(domain))
241-
context.values = dict(conditions=conditions)
242-
settings = dict(context_settings=[context])
243-
244-
return self.create_widget(OWSelectRows, settings)
245-
246213
def test_output_filter(self):
247214
"""
248215
None on output when there is no data.
@@ -255,7 +222,66 @@ def test_output_filter(self):
255222
self.enterFilter(data.domain[0], "is below", "-1")
256223
self.assertIsNone(self.get_output("Matching Data"))
257224
self.assertEqual(len(self.get_output("Unmatched Data")), len_data)
225+
self.assertEqual(len(self.get_output("Data")), len_data)
258226
self.widget.remove_all_button.click()
259227
self.enterFilter(data.domain[0], "is below", "10")
260228
self.assertIsNone(self.get_output("Unmatched Data"))
261229
self.assertEqual(len(self.get_output("Matching Data")), len_data)
230+
self.assertEqual(len(self.get_output("Data")), len_data)
231+
232+
def test_annotated_data(self):
233+
iris = Table("iris")
234+
self.send_signal(self.widget.Inputs.data, iris)
235+
236+
self.enterFilter(iris.domain["iris"], "is", "Iris-setosa")
237+
238+
annotated = self.get_output(self.widget.Outputs.annotated_data)
239+
self.assertEqual(len(annotated), 150)
240+
annotations = annotated.get_column_view(ANNOTATED_DATA_FEATURE_NAME)[0]
241+
np.testing.assert_equal(annotations[:50], True)
242+
np.testing.assert_equal(annotations[50:], False)
243+
244+
def widget_with_context(self, domain, conditions):
245+
ch = SelectRowsContextHandler()
246+
context = ch.new_context(domain, *ch.encode_domain(domain))
247+
context.values = dict(conditions=conditions)
248+
settings = dict(context_settings=[context])
249+
250+
return self.create_widget(OWSelectRows, settings)
251+
252+
def enterFilter(self, variable, filter, value1=None, value2=None):
253+
row = self.widget.cond_list.model().rowCount()
254+
self.widget.add_button.click()
255+
256+
var_combo = self.widget.cond_list.cellWidget(row, 0)
257+
simulate.combobox_activate_item(var_combo, variable.name, delay=0)
258+
259+
oper_combo = self.widget.cond_list.cellWidget(row, 1)
260+
simulate.combobox_activate_item(oper_combo, filter, delay=0)
261+
262+
value_inputs = self.__get_value_widgets(row)
263+
for i, value in enumerate([value1, value2]):
264+
if value is None:
265+
continue
266+
self.__set_value(value_inputs[i], value)
267+
268+
def __get_value_widgets(self, row):
269+
value_inputs = self.widget.cond_list.cellWidget(row, 2)
270+
if value_inputs:
271+
if isinstance(value_inputs, QComboBox):
272+
value_inputs = [value_inputs]
273+
else:
274+
value_inputs = [
275+
w for w in value_inputs.children()
276+
if isinstance(w, QLineEdit)]
277+
return value_inputs
278+
279+
def __set_value(self, widget, value):
280+
if isinstance(widget, QLineEdit):
281+
QTest.mouseClick(widget, Qt.LeftButton)
282+
QTest.keyClicks(widget, value, delay=0)
283+
QTest.keyClick(widget, Qt.Key_Enter)
284+
elif isinstance(widget, QComboBox):
285+
simulate.combobox_activate_item(widget, value)
286+
else:
287+
raise ValueError("Unsupported widget {}".format(widget))

doc/visual-programming/source/widgets/data/selectrows.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Outputs
1212
instances that match the conditions
1313
Non-Matching Data
1414
instances that do not match the conditions
15+
Data
16+
data with an additional column showing whether a instance is selected
1517

1618

1719
This widget selects a subset from an input dataset, based on user-defined

0 commit comments

Comments
 (0)