Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,655 changes: 1,179 additions & 476 deletions Orange/widgets/data/oweditdomain.py

Large diffs are not rendered by default.

270 changes: 162 additions & 108 deletions Orange/widgets/data/tests/test_oweditdomain.py
Original file line number Diff line number Diff line change
@@ -1,82 +1,57 @@
# Test methods with long descriptive names can omit docstrings
# pylint: disable=missing-docstring

# pylint: disable=all
from unittest import TestCase
import numpy as np
from numpy.testing import assert_array_equal

from AnyQt.QtCore import QModelIndex, QItemSelectionModel, Qt

from Orange.data import ContinuousVariable, DiscreteVariable, \
StringVariable, TimeVariable, Table, Domain
from Orange.widgets.data.oweditdomain import EditDomainReport, OWEditDomain, \
ContinuousVariableEditor, DiscreteVariableEditor, VariableEditor, \
TimeVariableEditor
from AnyQt.QtWidgets import QAction
from AnyQt.QtTest import QTest

from Orange.data import (
ContinuousVariable, DiscreteVariable, StringVariable, TimeVariable,
Table, Domain
)
from Orange.preprocess.transformation import Identity, Lookup
from Orange.widgets.data.oweditdomain import (
OWEditDomain,
ContinuousVariableEditor, DiscreteVariableEditor, VariableEditor,
TimeVariableEditor, Categorical, Real, Time, String,
Rename, Annotate, CategoriesMapping, report_transform,
apply_transform
)
from Orange.widgets.data.owcolor import OWColor, ColorRole
from Orange.widgets.tests.base import WidgetTest, GuiTest

SECTION_NAME = "NAME"


class TestEditDomainReport(TestCase):
# This tests test private methods
# pylint: disable=protected-access

def setUp(self):
self.report = EditDomainReport([], [])

def test_section_yields_nothing_for_no_changes(self):
result = self.report._section(SECTION_NAME, [])
self.assertEmpty(result)

def test_section_yields_header_for_changes(self):
result = self.report._section(SECTION_NAME, ["a"])
self.assertTrue(any(SECTION_NAME in item for item in result))

def test_value_changes_yields_nothing_for_no_change(self):
a = DiscreteVariable("a", values="abc")
self.assertEmpty(self.report._value_changes(a, a))

def test_value_changes_yields_nothing_for_continuous_variables(self):
v1, v2 = ContinuousVariable("a"), ContinuousVariable("b")
self.assertEmpty(self.report._value_changes(v1, v2))

def test_value_changes_yields_changed_values(self):
v1, v2 = DiscreteVariable("a", "ab"), DiscreteVariable("b", "ac")
self.assertNotEmpty(self.report._value_changes(v1, v2))

def test_label_changes_yields_nothing_for_no_change(self):
v1 = ContinuousVariable("a")
v1.attributes["a"] = "b"
self.assertEmpty(self.report._value_changes(v1, v1))

def test_label_changes_yields_added_labels(self):
v1 = ContinuousVariable("a")
v2 = v1.copy(None)
v2.attributes["a"] = "b"
self.assertNotEmpty(self.report._label_changes(v1, v2))

def test_label_changes_yields_removed_labels(self):
v1 = ContinuousVariable("a")
v1.attributes["a"] = "b"
v2 = v1.copy(None)
del v2.attributes["a"]
self.assertNotEmpty(self.report._label_changes(v1, v2))

def test_label_changes_yields_modified_labels(self):
v1 = ContinuousVariable("a")
v1.attributes["a"] = "b"
v2 = v1.copy(None)
v2.attributes["a"] = "c"
self.assertNotEmpty(self.report._label_changes(v1, v2))

def assertEmpty(self, iterable):
self.assertRaises(StopIteration, lambda: next(iter(iterable)))

def assertNotEmpty(self, iterable):
try:
next(iter(iterable))
except StopIteration:
self.fail("Iterator did not produce any lines")
class TestReport(TestCase):
def test_rename(self):
var = Real("X", (-1, ""), ())
tr = Rename("Y")
val = report_transform(var, [tr])
self.assertIn("X", val)
self.assertIn("Y", val)

def test_annotate(self):
var = Real("X", (-1, ""), (("a", "1"), ("b", "z")))
tr = Annotate((("a", "2"), ("j", "z")))
r = report_transform(var, [tr])
self.assertIn("a", r)
self.assertIn("b", r)

def test_categories_mapping(self):
var = Categorical("C", ("a", "b", "c"), None, ())
tr = CategoriesMapping(
(("a", "aa"),
("b", None),
("c", "cc"),
(None, "ee")),
)
r = report_transform(var, [tr])
self.assertIn("a", r)
self.assertIn("aa", r)
self.assertIn("b", r)
self.assertIn("<s>", r)


class TestOWEditDomain(WidgetTest):
Expand Down Expand Up @@ -137,7 +112,7 @@ def test_list_attributes_remain_lists(self):
idx = editor.labels_model.index(0, 1)
editor.labels_model.setData(idx, "[1, 2, 4]", Qt.EditRole)

self.widget.unconditional_commit()
self.widget.commit()
t2 = self.get_output(self.widget.Outputs.data)
self.assertEqual(t2.domain["a"].attributes["list"], [1, 2, 4])

Expand All @@ -156,15 +131,20 @@ def test_duplicate_names(self):
self.widget.domain_view.setCurrentIndex(idx)
editor = self.widget.editor_stack.findChild(ContinuousVariableEditor)

editor.name_edit.setText("iris")
editor.commit()
def enter_text(widget, text):
# type: (QLineEdit, str) -> None
widget.selectAll()
QTest.keyClick(widget, Qt.Key_Delete)
QTest.keyClicks(widget, text)
QTest.keyClick(widget, Qt.Key_Return)

enter_text(editor.name_edit, "iris")
self.widget.commit()
self.assertTrue(self.widget.Error.duplicate_var_name.is_shown())
output = self.get_output(self.widget.Outputs.data)
self.assertIsNone(output)

editor.name_edit.setText("sepal height")
editor.commit()
enter_text(editor.name_edit, "sepal height")
self.widget.commit()
self.assertFalse(self.widget.Error.duplicate_var_name.is_shown())
output = self.get_output(self.widget.Outputs.data)
Expand All @@ -176,10 +156,12 @@ def test_time_variable_preservation(self):
self.send_signal(self.widget.Inputs.data, table)
output = self.get_output(self.widget.Outputs.data)
self.assertEqual(str(table[0, 4]), str(output[0, 4]))
view = self.widget.variables_view
view.setCurrentIndex(view.model().index(4))

editor = self.widget.editor_stack.findChild(TimeVariableEditor)
editor.name_edit.setText("Date")
editor.commit()
editor.variable_changed.emit()
self.widget.commit()
output = self.get_output(self.widget.Outputs.data)
self.assertEqual(str(table[0, 4]), str(output[0, 4]))
Expand All @@ -188,54 +170,68 @@ def test_time_variable_preservation(self):
class TestEditors(GuiTest):
def test_variable_editor(self):
w = VariableEditor()
self.assertIs(w.get_data(), None)
self.assertEqual(w.get_data(), (None, []))

v = StringVariable(name="S")
v.attributes.update({"A": 1, "B": "b"},)
w.set_data(v)
v = String("S", (("A", "1"), ("B", "b")))
w.set_data(v, [])

self.assertEqual(w.name_edit.text(), v.name)
self.assertEqual(w.labels_model.get_dict(), v.attributes)
self.assertTrue(w.is_same())
self.assertEqual(w.labels_model.get_dict(),
{"A": "1", "B": "b"})
self.assertEqual(w.get_data(), (v, []))

w.set_data(None)
self.assertEqual(w.name_edit.text(), "")
self.assertEqual(w.labels_model.get_dict(), {})
self.assertIs(w.get_data(), None)
self.assertEqual(w.get_data(), (None, []))

w.set_data(v, [Rename("T"), Annotate((("a", "1"), ("b", "2")))])
self.assertEqual(w.name_edit.text(), "T")
self.assertEqual(w.labels_model.rowCount(), 2)
add = w.findChild(QAction, "action-add-label")
add.trigger()
remove = w.findChild(QAction, "action-delete-label")
remove.trigger()

def test_continuous_editor(self):
w = ContinuousVariableEditor()
self.assertIs(w.get_data(), None)
self.assertEqual(w.get_data(), (None, []))

v = ContinuousVariable("X", number_of_decimals=5)
v.attributes.update({"A": 1, "B": "b"})
w.set_data(v)
v = Real("X", (-1, ""), (("A", "1"), ("B", "b")))
w.set_data(v, [])

self.assertEqual(w.name_edit.text(), v.name)
self.assertEqual(w.labels_model.get_dict(), v.attributes)
self.assertTrue(w.is_same())
self.assertEqual(w.labels_model.get_dict(), dict(v.annotations))

w.set_data(None)
self.assertEqual(w.name_edit.text(), "")
self.assertEqual(w.labels_model.get_dict(), {})
self.assertIs(w.get_data(), None)
self.assertEqual(w.get_data(), (None, []))

def test_discrete_editor(self):
w = DiscreteVariableEditor()
self.assertIs(w.get_data(), None)
self.assertEqual(w.get_data(), (None, []))

v = DiscreteVariable("C", values=["a", "b", "c"])
v.attributes.update({"A": 1, "B": "b"})
v = Categorical("C", ("a", "b", "c"), None,
(("A", "1"), ("B", "b")))
w.set_data(v)

self.assertEqual(w.name_edit.text(), v.name)
self.assertEqual(w.labels_model.get_dict(), v.attributes)
self.assertTrue(w.is_same())

self.assertEqual(w.labels_model.get_dict(), dict(v.annotations))
self.assertEqual(w.get_data(), (v, []))
w.set_data(None)
self.assertEqual(w.name_edit.text(), "")
self.assertEqual(w.labels_model.get_dict(), {})
self.assertIs(w.get_data(), None)
self.assertEqual(w.get_data(), (None, []))
mapping = [
("c", "C"),
("a", "A"),
("b", None),
(None, "b")
]
w.set_data(v, [CategoriesMapping(mapping)])
w.grab() # run delegate paint method
self.assertEqual(w.get_data(), (v, [CategoriesMapping(mapping)]))

# test selection/deselection in the view
w.set_data(v)
Expand All @@ -249,21 +245,79 @@ def test_discrete_editor(self):

def test_time_editor(self):
w = TimeVariableEditor()
self.assertIs(w.get_data(), None)
self.assertEqual(w.get_data(), (None, []))

v = TimeVariable("T", have_date=1)
v.attributes.update({"A": 1, "B": "b"})
w.set_data(v)
v = Time("T", (("A", "1"), ("B", "b")))
w.set_data(v,)

self.assertEqual(w.name_edit.text(), v.name)
self.assertEqual(w.labels_model.get_dict(), v.attributes)
self.assertTrue(w.is_same())

var = w.get_data()
self.assertTrue(var.have_date)
self.assertFalse(var.have_time)
self.assertEqual(w.labels_model.get_dict(), dict(v.annotations))

w.set_data(None)
self.assertEqual(w.name_edit.text(), "")
self.assertEqual(w.labels_model.get_dict(), {})
self.assertIs(w.get_data(), None)
self.assertEqual(w.get_data(), (None, []))


class TestTransforms(TestCase):
def _test_common(self, var):
tr = [Rename(var.name + "_copy"), Annotate((("A", "1"),))]
XX = apply_transform(var, tr)
self.assertEqual(XX.name, var.name + "_copy")
self.assertEqual(XX.attributes, {"A": 1})
self.assertIsInstance(XX.compute_value, Identity)
self.assertIs(XX.compute_value.variable, var)

def test_continous(self):
X = ContinuousVariable("X")
self._test_common(X)

def test_string(self):
X = StringVariable("S")
self._test_common(X)

def test_time(self):
X = TimeVariable("X")
self._test_common(X)

def test_discrete(self):
D = DiscreteVariable("D", values=("a", "b"))
self._test_common(D)

def test_discrete_rename(self):
D = DiscreteVariable("D", values=("a", "b"))
DD = apply_transform(D, [CategoriesMapping((("a", "A"), ("b", "B")))])
self.assertSequenceEqual(DD.values, ["A", "B"])
self.assertIs(DD.compute_value.variable, D)

def test_discrete_reorder(self):
D = DiscreteVariable("D", values=("2", "3", "1", "0"))
DD = apply_transform(D, [CategoriesMapping((("0", "0"), ("1", "1"),
("2", "2"), ("3", "3")))])
self.assertSequenceEqual(DD.values, ["0", "1", "2", "3"])
self._assertLookupEquals(
DD.compute_value, Lookup(D, np.array([2, 3, 1, 0]))
)

def test_discrete_add_drop(self):
D = DiscreteVariable("D", values=("2", "3", "1", "0"), base_value=1)
mapping = (
("0", None),
("1", "1"),
("2", "2"),
("3", None),
(None, "A"),
)
tr = [CategoriesMapping(mapping)]
DD = apply_transform(D, tr)
self.assertSequenceEqual(DD.values, ["1", "2", "A"])
self._assertLookupEquals(
DD.compute_value, Lookup(D, np.array([1, np.nan, 0, np.nan]))
)
self.assertEqual(DD.base_value, -1)

def _assertLookupEquals(self, first, second):
self.assertIsInstance(first, Lookup)
self.assertIsInstance(second, Lookup)
self.assertIs(first.variable, second.variable)
assert_array_equal(first.lookup_table, second.lookup_table)
Loading