Skip to content

Commit

Permalink
Merge pull request #2908 from BlazZupan/select-rows-annotated-data
Browse files Browse the repository at this point in the history
[ENH] Select Rows: Add annotated data output
  • Loading branch information
lanzagar authored Feb 19, 2018
2 parents aedbf79 + 39bb3b4 commit 1717c4a
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 39 deletions.
15 changes: 14 additions & 1 deletion Orange/widgets/data/owselectrows.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import enum

from collections import OrderedDict
from itertools import chain

import numpy as np

from AnyQt.QtWidgets import (
QWidget, QTableWidget, QHeaderView, QComboBox, QLineEdit, QToolButton,
QMessageBox, QMenu, QListView, QGridLayout, QPushButton, QSizePolicy,
Expand All @@ -26,6 +27,8 @@
from Orange.widgets.utils import vartype
from Orange.canvas import report
from Orange.widgets.widget import Msg
from Orange.widgets.utils.annotated_data import (create_annotated_table,
ANNOTATED_DATA_SIGNAL_NAME)


class SelectRowsContextHandler(DomainContextHandler):
Expand Down Expand Up @@ -79,6 +82,7 @@ class Inputs:
class Outputs:
matching_data = Output("Matching Data", Table, default=True)
unmatched_data = Output("Unmatched Data", Table)
annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)

want_main_area = False

Expand Down Expand Up @@ -465,6 +469,8 @@ def _values_to_floats(self, attr, values):
def commit(self):
matching_output = self.data
non_matching_output = None
annotated_output = None

self.Error.clear()
if self.data:
domain = self.data.domain
Expand Down Expand Up @@ -512,6 +518,9 @@ def commit(self):
self.filters.negate = True
non_matching_output = self.filters(self.data)

row_sel = np.in1d(self.data.ids, matching_output.ids)
annotated_output = create_annotated_table(self.data, row_sel)

# if hasattr(self.data, "name"):
# matching_output.name = self.data.name
# non_matching_output.name = self.data.name
Expand All @@ -529,14 +538,18 @@ def commit(self):

matching_output = remover(matching_output)
non_matching_output = remover(non_matching_output)
annotated_output = remover(annotated_output)

if matching_output is not None and not len(matching_output):
matching_output = None
if non_matching_output is not None and not len(non_matching_output):
non_matching_output = None
if annotated_output is not None and not len(annotated_output):
annotated_output = None

self.Outputs.matching_data.send(matching_output)
self.Outputs.unmatched_data.send(non_matching_output)
self.Outputs.annotated_data.send(annotated_output)

self.match_desc = report.describe_data_brief(matching_output)
self.nonmatch_desc = report.describe_data_brief(non_matching_output)
Expand Down
102 changes: 64 additions & 38 deletions Orange/widgets/data/tests/test_owselectrows.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
# pylint: disable=missing-docstring
from AnyQt.QtCore import QLocale, Qt
from AnyQt.QtTest import QTest
from AnyQt.QtWidgets import QLineEdit
from AnyQt.QtWidgets import QLineEdit, QComboBox

import numpy as np

from Orange.data import (
Table, ContinuousVariable, StringVariable, DiscreteVariable)
Expand All @@ -12,6 +14,7 @@

from Orange.data.filter import FilterContinuous, FilterString
from Orange.widgets.tests.utils import simulate, override_locale
from Orange.widgets.utils.annotated_data import ANNOTATED_DATA_FEATURE_NAME

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

def enterFilter(self, variable, filter, value=None, value2=None):
row = self.widget.cond_list.model().rowCount()
self.widget.add_button.click()

var_combo = self.widget.cond_list.cellWidget(row, 0)
simulate.combobox_activate_item(var_combo, variable.name, delay=0)

oper_combo = self.widget.cond_list.cellWidget(row, 1)
simulate.combobox_activate_item(oper_combo, filter, delay=0)

value_inputs = self._get_value_line_edits(row)
for i, value in enumerate([value, value2]):
if value is None:
continue
QTest.mouseClick(value_inputs[i], Qt.LeftButton)
QTest.keyClicks(value_inputs[i], value, delay=0)
QTest.keyClick(value_inputs[i], Qt.Key_Enter)

def _get_value_line_edits(self, row):
value_inputs = self.widget.cond_list.cellWidget(row, 2)
if value_inputs:
value_inputs = [w for w in value_inputs.children()
if isinstance(w, QLineEdit)]
return value_inputs


@override_locale(QLocale.Slovenian)
def test_stores_settings_in_invariant_locale(self):
iris = Table("iris")[:5]
Expand All @@ -156,8 +133,6 @@ def test_stores_settings_in_invariant_locale(self):
saved_condition = context.values["conditions"][0]
self.assertEqual(saved_condition[2][0], 5.2)



@override_locale(QLocale.C)
def test_restores_continuous_filter_in_c_locale(self):
iris = Table("iris")[:5]
Expand Down Expand Up @@ -226,23 +201,15 @@ def test_is_defined_on_continuous_variable(self):
data = Table(datasets.path("testing_dataset_cls"))
self.send_signal(self.widget.Inputs.data, data)


self.enterFilter(data.domain["c2"], "is defined")
self.assertFalse(self.widget.Error.parsing_error.is_shown())
self.assertEqual(len(self.get_output("Matching Data")), 3)
self.assertEqual(len(self.get_output("Unmatched Data")), 1)
self.assertEqual(len(self.get_output("Data")), len(data))

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

def widget_with_context(self, domain, conditions):
ch = SelectRowsContextHandler()
context = ch.new_context(domain, *ch.encode_domain(domain))
context.values = dict(conditions=conditions)
settings = dict(context_settings=[context])

return self.create_widget(OWSelectRows, settings)

def test_output_filter(self):
"""
None on output when there is no data.
Expand All @@ -255,7 +222,66 @@ def test_output_filter(self):
self.enterFilter(data.domain[0], "is below", "-1")
self.assertIsNone(self.get_output("Matching Data"))
self.assertEqual(len(self.get_output("Unmatched Data")), len_data)
self.assertEqual(len(self.get_output("Data")), len_data)
self.widget.remove_all_button.click()
self.enterFilter(data.domain[0], "is below", "10")
self.assertIsNone(self.get_output("Unmatched Data"))
self.assertEqual(len(self.get_output("Matching Data")), len_data)
self.assertEqual(len(self.get_output("Data")), len_data)

def test_annotated_data(self):
iris = Table("iris")
self.send_signal(self.widget.Inputs.data, iris)

self.enterFilter(iris.domain["iris"], "is", "Iris-setosa")

annotated = self.get_output(self.widget.Outputs.annotated_data)
self.assertEqual(len(annotated), 150)
annotations = annotated.get_column_view(ANNOTATED_DATA_FEATURE_NAME)[0]
np.testing.assert_equal(annotations[:50], True)
np.testing.assert_equal(annotations[50:], False)

def widget_with_context(self, domain, conditions):
ch = SelectRowsContextHandler()
context = ch.new_context(domain, *ch.encode_domain(domain))
context.values = dict(conditions=conditions)
settings = dict(context_settings=[context])

return self.create_widget(OWSelectRows, settings)

def enterFilter(self, variable, filter, value1=None, value2=None):
row = self.widget.cond_list.model().rowCount()
self.widget.add_button.click()

var_combo = self.widget.cond_list.cellWidget(row, 0)
simulate.combobox_activate_item(var_combo, variable.name, delay=0)

oper_combo = self.widget.cond_list.cellWidget(row, 1)
simulate.combobox_activate_item(oper_combo, filter, delay=0)

value_inputs = self.__get_value_widgets(row)
for i, value in enumerate([value1, value2]):
if value is None:
continue
self.__set_value(value_inputs[i], value)

def __get_value_widgets(self, row):
value_inputs = self.widget.cond_list.cellWidget(row, 2)
if value_inputs:
if isinstance(value_inputs, QComboBox):
value_inputs = [value_inputs]
else:
value_inputs = [
w for w in value_inputs.children()
if isinstance(w, QLineEdit)]
return value_inputs

def __set_value(self, widget, value):
if isinstance(widget, QLineEdit):
QTest.mouseClick(widget, Qt.LeftButton)
QTest.keyClicks(widget, value, delay=0)
QTest.keyClick(widget, Qt.Key_Enter)
elif isinstance(widget, QComboBox):
simulate.combobox_activate_item(widget, value)
else:
raise ValueError("Unsupported widget {}".format(widget))
2 changes: 2 additions & 0 deletions doc/visual-programming/source/widgets/data/selectrows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Outputs
instances that match the conditions
Non-Matching Data
instances that do not match the conditions
Data
data with an additional column showing whether a instance is selected


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

0 comments on commit 1717c4a

Please sign in to comment.