From 6e293915c56db2581145f6b1267ec15709647162 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 11 Oct 2016 13:37:14 +0200 Subject: [PATCH 01/15] misc: Add a module for 'Flagged Data' creation --- Orange/widgets/tests/test_annotated_data.py | 102 ++++++++++++++++++++ Orange/widgets/utils/annotated_data.py | 45 +++++++++ 2 files changed, 147 insertions(+) create mode 100644 Orange/widgets/tests/test_annotated_data.py create mode 100644 Orange/widgets/utils/annotated_data.py diff --git a/Orange/widgets/tests/test_annotated_data.py b/Orange/widgets/tests/test_annotated_data.py new file mode 100644 index 00000000000..db8750ee312 --- /dev/null +++ b/Orange/widgets/tests/test_annotated_data.py @@ -0,0 +1,102 @@ +import random +import unittest + +import numpy as np + +from Orange.data import Table, Variable +from Orange.widgets.utils.annotated_data import (create_annotated_table, + ANNOTATED_DATA_FEATURE_NAME) + + +class TestAnnotatedData(unittest.TestCase): + def setUp(self): + Variable._clear_all_caches() + random.seed(42) + self.zoo = Table("zoo") + + def test_create_annotated_table(self): + annotated = create_annotated_table(self.zoo, list(range(10))) + + # check annotated table domain + self.assertEqual(annotated.domain.variables, self.zoo.domain.variables) + self.assertEqual(2, len(annotated.domain.metas)) + self.assertIn(self.zoo.domain.metas[0], annotated.domain.metas) + self.assertIn(ANNOTATED_DATA_FEATURE_NAME, + [m.name for m in annotated.domain.metas]) + + # check annotated table data + np.testing.assert_array_equal(annotated.X, self.zoo.X) + np.testing.assert_array_equal(annotated.Y, self.zoo.Y) + np.testing.assert_array_equal(annotated.metas[:, 0].ravel(), + self.zoo.metas.ravel()) + self.assertEqual( + 10, np.sum([i[ANNOTATED_DATA_FEATURE_NAME] for i in annotated])) + + def test_create_annotated_table_selected(self): + # check annotated column for no selected indices + annotated = create_annotated_table(self.zoo, []) + self.assertEqual(len(annotated), len(self.zoo)) + self.assertEqual( + 0, np.sum([i[ANNOTATED_DATA_FEATURE_NAME] for i in annotated])) + + # check annotated column fol all selectes indices + annotated = create_annotated_table(self.zoo, list(range(len(self.zoo)))) + self.assertEqual(len(annotated), len(self.zoo)) + self.assertEqual( + len(self.zoo), + np.sum([i[ANNOTATED_DATA_FEATURE_NAME] for i in annotated])) + + def test_create_annotated_table_none_data(self): + self.assertIsNone(create_annotated_table(None, None)) + + def test_create_annotated_table_none_indices(self): + annotated = create_annotated_table(self.zoo, None) + self.assertEqual(len(annotated), len(self.zoo)) + self.assertEqual( + 0, np.sum([i[ANNOTATED_DATA_FEATURE_NAME] for i in annotated])) + + def test_cascade_annotated_tables(self): + # check cascade of annotated tables + data = self.zoo + data.domain.metas[0].name = ANNOTATED_DATA_FEATURE_NAME + for i in range(5): + data = create_annotated_table( + data, random.sample(range(0, len(self.zoo)), 20)) + self.assertEqual(2 + i, len(data.domain.metas)) + self.assertIn(self.zoo.domain.metas[0], data.domain.metas) + self.assertIn(ANNOTATED_DATA_FEATURE_NAME, + [m.name for m in data.domain.metas]) + for j in range(2, i + 3): + self.assertIn("{} ({})".format(ANNOTATED_DATA_FEATURE_NAME, j), + [m.name for m in data.domain.metas]) + + def test_cascade_annotated_tables_with_missing_middle_feature(self): + # check table for domain [..., "Feature", "Selected", "Selected (3)] -> + # [..., "Feature", "Selected", "Selected (3), "Selected (4)"] + data = self.zoo + data.domain.attributes[0].name = ANNOTATED_DATA_FEATURE_NAME + data.domain.metas[0].name = "{} ({})".format( + ANNOTATED_DATA_FEATURE_NAME, 3) + data = create_annotated_table( + data, random.sample(range(0, len(self.zoo)), 20)) + self.assertEqual(2, len(data.domain.metas)) + self.assertEqual(data.domain.attributes[0].name, + ANNOTATED_DATA_FEATURE_NAME) + self.assertEqual(data.domain.metas[0].name, + "{} ({})".format(ANNOTATED_DATA_FEATURE_NAME, 3)) + self.assertEqual(data.domain.metas[1].name, + "{} ({})".format(ANNOTATED_DATA_FEATURE_NAME, 4)) + + def test_cascade_annotated_tables_with_missing_annotated_feature(self): + # check table for domain [..., "Feature", "Selected (3)] -> + # [..., "Feature", "Selected (3), "Selected (4)"] + data = self.zoo + data.domain.metas[0].name = "{} ({})".format( + ANNOTATED_DATA_FEATURE_NAME, 3) + data = create_annotated_table( + data, random.sample(range(0, len(self.zoo)), 20)) + self.assertEqual(2, len(data.domain.metas)) + self.assertEqual(data.domain.metas[0].name, + "{} ({})".format(ANNOTATED_DATA_FEATURE_NAME, 3)) + self.assertEqual(data.domain.metas[1].name, + "{} ({})".format(ANNOTATED_DATA_FEATURE_NAME, 4)) diff --git a/Orange/widgets/utils/annotated_data.py b/Orange/widgets/utils/annotated_data.py new file mode 100644 index 00000000000..a5c6d1ae872 --- /dev/null +++ b/Orange/widgets/utils/annotated_data.py @@ -0,0 +1,45 @@ +import re +import numpy as np +from Orange.data import Table, Domain, DiscreteVariable + +ANNOTATED_DATA_SIGNAL_NAME = "Data" +ANNOTATED_DATA_FEATURE_NAME = "Selected" + + +def _get_next_name(names, name): + """ + Returns next 'possible' attribute name. The name should not be duplicated + and is generated using name parameter, appended by smallest possible index. + + :param names: list + :param name: str + :return: str + """ + indexes = [int(a.group(2)) for x in names + for a in re.finditer("(^{} \()(\d{{1,}})(\)$)".format(name), x)] + if name not in names and not indexes: + return name + return "{} ({})".format(name, max(indexes, default=1) + 1) + + +def create_annotated_table(data, selected_indices): + """ + Returns data with concatenated flag column. Flag column represents + whether data instance has been selected (Yes) or not (No), which is + determined in selected_indices parameter. + + :param data: Table + :param selected_indices: list or ndarray + :return: Table + """ + if data is None: + return None + names = [var.name for var in data.domain.variables + data.domain.metas] + name = _get_next_name(names, ANNOTATED_DATA_FEATURE_NAME) + metas = data.domain.metas + (DiscreteVariable(name, ("No", "Yes")),) + domain = Domain(data.domain.attributes, data.domain.class_vars, metas) + annotated = np.zeros((len(data), 1)) + if selected_indices is not None: + annotated[selected_indices] = 1 + return Table(domain, data.X, data.Y, + metas=np.hstack((data.metas, annotated))) From eafb96aa190fe8c9f1c3d496234e46f64074f539 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 11 Oct 2016 13:40:00 +0200 Subject: [PATCH 02/15] OWScatterPlot: Output Flagged Data --- Orange/widgets/visualize/owscatterplot.py | 6 ++++ .../visualize/tests/test_owscatterplot.py | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Orange/widgets/visualize/owscatterplot.py b/Orange/widgets/visualize/owscatterplot.py index 7759bc3e3f6..4c2df5f133d 100644 --- a/Orange/widgets/visualize/owscatterplot.py +++ b/Orange/widgets/visualize/owscatterplot.py @@ -10,6 +10,7 @@ DiscreteVariable from Orange.canvas import report from Orange.data.sql.table import SqlTable, AUTO_DL_LIMIT +from Orange.misc.flagged_data import create_flagged_table from Orange.preprocess.score import ReliefF, RReliefF from Orange.widgets import gui from Orange.widgets.settings import \ @@ -103,6 +104,7 @@ class OWScatterPlot(OWWidget): ("Features", AttributeList, "set_shown_attributes")] outputs = [("Selected Data", Table, Default), + ("Flagged Data", Table), ("Other Data", Table), ("Features", Table)] @@ -428,6 +430,7 @@ def selection_changed(self): def send_data(self): selected = unselected = None + selection = None # TODO: Implement selection for sql data if isinstance(self.data, SqlTable): selected = unselected = self.data @@ -435,6 +438,8 @@ def send_data(self): selection = self.graph.get_selection() if len(selection) == 0: self.send("Selected Data", None) + self.send("Flagged Data", + create_flagged_table(self.data, selection)) self.send("Other Data", self.data) return selected = self.data[selection] @@ -442,6 +447,7 @@ def send_data(self): unselection[selection] = False unselected = self.data[unselection] self.send("Selected Data", selected) + self.send("Flagged Data", create_flagged_table(self.data, selection)) if unselected is None or len(unselected) == 0: self.send("Other Data", None) else: diff --git a/Orange/widgets/visualize/tests/test_owscatterplot.py b/Orange/widgets/visualize/tests/test_owscatterplot.py index 26a6a21ae0f..3cf226d6a0f 100644 --- a/Orange/widgets/visualize/tests/test_owscatterplot.py +++ b/Orange/widgets/visualize/tests/test_owscatterplot.py @@ -2,6 +2,8 @@ # pylint: disable=missing-docstring import numpy as np +from PyQt4.QtCore import QRectF + from Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable from Orange.widgets.tests.base import WidgetTest from Orange.widgets.visualize.owscatterplot import \ @@ -68,3 +70,30 @@ def test_optional_combos(self): [domain.attributes[3]]) t2 = Table(d2, self.iris) self.send_signal("Data", t2) + + def test_outputs(self): + self.send_signal("Data", self.iris) + + # check selected data output + self.assertIsNone(self.get_output("Selected Data")) + + # check flagged data output + flagged = self.get_output("Flagged Data") + self.assertEqual(0, np.sum([i["Flag"] for i in flagged])) + + # select data points + self.widget.graph.select_by_rectangle(QRectF(4, 3, 3, 1)) + + # check selected data output + selected = self.get_output("Selected Data") + self.assertGreater(len(selected), 0) + self.assertEqual(selected.domain, self.iris.domain) + + # check flagged data output + flagged = self.get_output("Flagged Data") + self.assertEqual(len(selected), np.sum([i["Flag"] for i in flagged])) + + # check output when data is removed + self.send_signal("Data", None) + self.assertIsNone(self.get_output("Selected Data")) + self.assertIsNone(self.get_output("Flagged Data")) From 65b7525ea74c5e2739ad45bc449bad43283d4221 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 11 Oct 2016 13:45:53 +0200 Subject: [PATCH 03/15] OWScatterPlot: Remove Other Data output --- Orange/widgets/visualize/owscatterplot.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Orange/widgets/visualize/owscatterplot.py b/Orange/widgets/visualize/owscatterplot.py index 4c2df5f133d..d7661a85343 100644 --- a/Orange/widgets/visualize/owscatterplot.py +++ b/Orange/widgets/visualize/owscatterplot.py @@ -105,7 +105,6 @@ class OWScatterPlot(OWWidget): outputs = [("Selected Data", Table, Default), ("Flagged Data", Table), - ("Other Data", Table), ("Features", Table)] settingsHandler = DomainContextHandler() @@ -429,29 +428,21 @@ def selection_changed(self): self.send_data() def send_data(self): - selected = unselected = None + selected = None selection = None # TODO: Implement selection for sql data if isinstance(self.data, SqlTable): - selected = unselected = self.data + selected = self.data elif self.data is not None: selection = self.graph.get_selection() if len(selection) == 0: self.send("Selected Data", None) self.send("Flagged Data", create_flagged_table(self.data, selection)) - self.send("Other Data", self.data) return selected = self.data[selection] - unselection = np.full(len(self.data), True, dtype=bool) - unselection[selection] = False - unselected = self.data[unselection] self.send("Selected Data", selected) self.send("Flagged Data", create_flagged_table(self.data, selection)) - if unselected is None or len(unselected) == 0: - self.send("Other Data", None) - else: - self.send("Other Data", unselected) def send_features(self): features = None From ac9c42223a128050fd014f0283e83d09e58185bd Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 11 Oct 2016 13:51:19 +0200 Subject: [PATCH 04/15] OWScatterPlot: Refactor send_data() --- Orange/widgets/visualize/owscatterplot.py | 16 +++++++--------- .../visualize/tests/test_owscatterplot.py | 12 +++++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Orange/widgets/visualize/owscatterplot.py b/Orange/widgets/visualize/owscatterplot.py index d7661a85343..ce58c3a9ba4 100644 --- a/Orange/widgets/visualize/owscatterplot.py +++ b/Orange/widgets/visualize/owscatterplot.py @@ -10,7 +10,6 @@ DiscreteVariable from Orange.canvas import report from Orange.data.sql.table import SqlTable, AUTO_DL_LIMIT -from Orange.misc.flagged_data import create_flagged_table from Orange.preprocess.score import ReliefF, RReliefF from Orange.widgets import gui from Orange.widgets.settings import \ @@ -19,6 +18,8 @@ from Orange.widgets.visualize.owscatterplotgraph import OWScatterPlotGraph from Orange.widgets.visualize.utils import VizRankDialogAttrPair from Orange.widgets.widget import OWWidget, Default, AttributeList, Msg +from Orange.widgets.utils.annotated_data import (create_annotated_table, + ANNOTATED_DATA_SIGNAL_NAME) def font_resize(font, factor, minsize=None, maxsize=None): @@ -104,7 +105,7 @@ class OWScatterPlot(OWWidget): ("Features", AttributeList, "set_shown_attributes")] outputs = [("Selected Data", Table, Default), - ("Flagged Data", Table), + (ANNOTATED_DATA_SIGNAL_NAME, Table), ("Features", Table)] settingsHandler = DomainContextHandler() @@ -435,14 +436,11 @@ def send_data(self): selected = self.data elif self.data is not None: selection = self.graph.get_selection() - if len(selection) == 0: - self.send("Selected Data", None) - self.send("Flagged Data", - create_flagged_table(self.data, selection)) - return - selected = self.data[selection] + if len(selection) > 0: + selected = self.data[selection] self.send("Selected Data", selected) - self.send("Flagged Data", create_flagged_table(self.data, selection)) + self.send(ANNOTATED_DATA_SIGNAL_NAME, + create_annotated_table(self.data, selection)) def send_features(self): features = None diff --git a/Orange/widgets/visualize/tests/test_owscatterplot.py b/Orange/widgets/visualize/tests/test_owscatterplot.py index 3cf226d6a0f..3f43497391f 100644 --- a/Orange/widgets/visualize/tests/test_owscatterplot.py +++ b/Orange/widgets/visualize/tests/test_owscatterplot.py @@ -5,6 +5,7 @@ from PyQt4.QtCore import QRectF from Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable +from Orange.misc.flagged_data import FLAGGED_SIGNAL_NAME, FLAGGED_FEATURE_NAME from Orange.widgets.tests.base import WidgetTest from Orange.widgets.visualize.owscatterplot import \ OWScatterPlot, ScatterPlotVizRank @@ -78,8 +79,8 @@ def test_outputs(self): self.assertIsNone(self.get_output("Selected Data")) # check flagged data output - flagged = self.get_output("Flagged Data") - self.assertEqual(0, np.sum([i["Flag"] for i in flagged])) + flagged = self.get_output(FLAGGED_SIGNAL_NAME) + self.assertEqual(0, np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) # select data points self.widget.graph.select_by_rectangle(QRectF(4, 3, 3, 1)) @@ -90,10 +91,11 @@ def test_outputs(self): self.assertEqual(selected.domain, self.iris.domain) # check flagged data output - flagged = self.get_output("Flagged Data") - self.assertEqual(len(selected), np.sum([i["Flag"] for i in flagged])) + flagged = self.get_output(FLAGGED_SIGNAL_NAME) + self.assertEqual(len(selected), + np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) # check output when data is removed self.send_signal("Data", None) self.assertIsNone(self.get_output("Selected Data")) - self.assertIsNone(self.get_output("Flagged Data")) + self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) From f62e4e9cbfec84ba23904807f428bbffe282db69 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 11 Oct 2016 15:41:35 +0200 Subject: [PATCH 05/15] OWHierarchicalClustering: Output Flagged Data --- .../unsupervised/owhierarchicalclustering.py | 32 +++++++--- .../tests/test_owhierarchicalclustering.py | 61 +++++++++++++++++++ 2 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py diff --git a/Orange/widgets/unsupervised/owhierarchicalclustering.py b/Orange/widgets/unsupervised/owhierarchicalclustering.py index d1cf835baaf..a992c616e41 100644 --- a/Orange/widgets/unsupervised/owhierarchicalclustering.py +++ b/Orange/widgets/unsupervised/owhierarchicalclustering.py @@ -29,6 +29,8 @@ from Orange.widgets import widget, gui, settings from Orange.widgets.utils import colorpalette, itemmodels +from Orange.widgets.utils.annotated_data import (create_annotated_table, + ANNOTATED_DATA_SIGNAL_NAME) from Orange.widgets.io import FileFormat __all__ = ["OWHierarchicalClustering"] @@ -706,6 +708,7 @@ class OWHierarchicalClustering(widget.OWWidget): inputs = [("Distances", Orange.misc.DistMatrix, "set_distances")] outputs = [("Selected Data", Orange.data.Table, widget.Default), + (ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table), ("Other Data", Orange.data.Table)] #: Selected linkage @@ -779,37 +782,40 @@ def __init__(self): 1, 0) grid.addWidget(self.max_depth_spin, 1, 1) - box = gui.radioButtons( + self.selection_box = gui.radioButtons( self.controlArea, self, "selection_method", box="Selection", callback=self._selection_method_changed) grid = QGridLayout() - box.layout().addLayout(grid) + self.selection_box.layout().addLayout(grid) grid.addWidget( - gui.appendRadioButton(box, "Manual", addToLayout=False), + gui.appendRadioButton( + self.selection_box, "Manual", addToLayout=False), 0, 0 ) grid.addWidget( - gui.appendRadioButton(box, "Height ratio:", addToLayout=False), + gui.appendRadioButton( + self.selection_box, "Height ratio:", addToLayout=False), 1, 0 ) self.cut_ratio_spin = gui.spin( - box, self, "cut_ratio", 0, 100, step=1e-1, spinType=float, - callback=self._selection_method_changed + self.selection_box, self, "cut_ratio", 0, 100, step=1e-1, + spinType=float, callback=self._selection_method_changed ) self.cut_ratio_spin.setSuffix("%") grid.addWidget(self.cut_ratio_spin, 1, 1) grid.addWidget( - gui.appendRadioButton(box, "Top N:", addToLayout=False), + gui.appendRadioButton( + self.selection_box, "Top N:", addToLayout=False), 2, 0 ) - self.top_n_spin = gui.spin(box, self, "top_n", 1, 20, + self.top_n_spin = gui.spin(self.selection_box, self, "top_n", 1, 20, callback=self._selection_method_changed) grid.addWidget(self.top_n_spin, 2, 1) - box.layout().addLayout(grid) + self.selection_box.layout().addLayout(grid) self.zoom_slider = gui.hSlider( self.controlArea, self, "zoom_factor", box="Zoom", @@ -1092,6 +1098,9 @@ def commit(self): if not selected_indices: self.send("Selected Data", None) + annotated_data = create_annotated_table(items, selected_indices) \ + if self.selection_method == 0 and self.matrix.axis else None + self.send(ANNOTATED_DATA_SIGNAL_NAME, annotated_data) self.send("Other Data", None) return @@ -1148,8 +1157,12 @@ def commit(self): [items.domain[i] for i in unselected_indices], items.domain.class_vars, items.domain.metas) unselected_data = items.from_table(domain, items) + data = None self.send("Selected Data", selected_data) + annotated_data = create_annotated_table(data, selected_indices) if \ + self.selection_method == 0 else None + self.send(ANNOTATED_DATA_SIGNAL_NAME, annotated_data) self.send("Other Data", unselected_data) def sizeHint(self): @@ -1280,6 +1293,7 @@ def _selection_edited(self): # dendrogram view. self.selection_method = 0 self._selection_method_changed() + self._invalidate_output() def __zoom_in(self): def clip(minval, maxval, val): diff --git a/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py b/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py new file mode 100644 index 00000000000..20981b289b3 --- /dev/null +++ b/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py @@ -0,0 +1,61 @@ +# Test methods with long descriptive names can omit docstrings +# pylint: disable=missing-docstring +import numpy as np +from Orange.data import Table +from Orange.misc.flagged_data import FLAGGED_SIGNAL_NAME, FLAGGED_FEATURE_NAME +from Orange.distance import Euclidean +from Orange.widgets.unsupervised.owhierarchicalclustering import \ + OWHierarchicalClustering +from Orange.widgets.tests.base import WidgetTest + + +class TestOWHierarchicalClustering(WidgetTest): + def setUp(self): + self.widget = self.create_widget(OWHierarchicalClustering) + self.iris = Table("iris") + self.iris_distances = Euclidean(self.iris) + + def test_outputs(self): + self.send_signal("Distances", self.iris_distances) + + # check selected data output + self.assertIsNone(self.get_output("Selected Data")) + + # check flagged data output + flagged = self.get_output(FLAGGED_SIGNAL_NAME) + self.assertEqual(0, np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) + + # select a cluster + items = self.widget.dendrogram._items + cluster = items[next(iter(items))] + self.widget.dendrogram.set_selected_items([cluster]) + + # check selected data output + selected = self.get_output("Selected Data") + self.assertGreater(len(selected), 0) + + # check flagged data output + flagged = self.get_output(FLAGGED_SIGNAL_NAME) + self.assertEqual(len(selected), + np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) + + # check output when data is removed + self.send_signal("Distances", None) + self.assertIsNone(self.get_output("Selected Data")) + self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) + + def test_selection_box_output(self): + """Check output if Selection method changes""" + self.send_signal("Distances", self.iris_distances) + self.assertIsNone(self.get_output("Selected Data")) + self.assertIsNotNone(self.get_output(FLAGGED_SIGNAL_NAME)) + + # change selection to 'Height ratio' + self.widget.selection_box.buttons[1].click() + self.assertIsNotNone(self.get_output("Selected Data")) + self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) + + # change selection to 'Top N' + self.widget.selection_box.buttons[2].click() + self.assertIsNotNone(self.get_output("Selected Data")) + self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) From dd266ba492e140274bcd1c90a86729e459d73eed Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 11 Oct 2016 17:11:03 +0200 Subject: [PATCH 06/15] OWHierarchicalClustering: Remove Other Data output --- .../unsupervised/owhierarchicalclustering.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Orange/widgets/unsupervised/owhierarchicalclustering.py b/Orange/widgets/unsupervised/owhierarchicalclustering.py index a992c616e41..38949304c0a 100644 --- a/Orange/widgets/unsupervised/owhierarchicalclustering.py +++ b/Orange/widgets/unsupervised/owhierarchicalclustering.py @@ -708,8 +708,7 @@ class OWHierarchicalClustering(widget.OWWidget): inputs = [("Distances", Orange.misc.DistMatrix, "set_distances")] outputs = [("Selected Data", Orange.data.Table, widget.Default), - (ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table), - ("Other Data", Orange.data.Table)] + (ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)] #: Selected linkage linkage = settings.Setting(1) @@ -1101,10 +1100,9 @@ def commit(self): annotated_data = create_annotated_table(items, selected_indices) \ if self.selection_method == 0 and self.matrix.axis else None self.send(ANNOTATED_DATA_SIGNAL_NAME, annotated_data) - self.send("Other Data", None) return - selected_data = unselected_data = None + selected_data = None if isinstance(items, Orange.data.Table) and self.matrix.axis == 1: # Select rows @@ -1144,8 +1142,6 @@ def commit(self): if selected_indices: selected_data = data[mask] - if unselected_indices: - unselected_data = data[~mask] elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0: # Select columns @@ -1153,17 +1149,12 @@ def commit(self): [items.domain[i] for i in selected_indices], items.domain.class_vars, items.domain.metas) selected_data = items.from_table(domain, items) - domain = Orange.data.Domain( - [items.domain[i] for i in unselected_indices], - items.domain.class_vars, items.domain.metas) - unselected_data = items.from_table(domain, items) data = None self.send("Selected Data", selected_data) annotated_data = create_annotated_table(data, selected_indices) if \ self.selection_method == 0 else None self.send(ANNOTATED_DATA_SIGNAL_NAME, annotated_data) - self.send("Other Data", unselected_data) def sizeHint(self): return QSize(800, 500) From 762d1d4e55e7b045396e6591329064a83548dc6a Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 11 Oct 2016 17:13:40 +0200 Subject: [PATCH 07/15] OWHierarchicalClustering: Set Outputs to None when data is removed --- Orange/widgets/unsupervised/owhierarchicalclustering.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Orange/widgets/unsupervised/owhierarchicalclustering.py b/Orange/widgets/unsupervised/owhierarchicalclustering.py index 38949304c0a..a780dd9be79 100644 --- a/Orange/widgets/unsupervised/owhierarchicalclustering.py +++ b/Orange/widgets/unsupervised/owhierarchicalclustering.py @@ -1080,7 +1080,8 @@ def _invalidate_pruning(self): def commit(self): items = getattr(self.matrix, "items", self.items) if not items: - # nothing to commit + self.send("Selected Data", None) + self.send(ANNOTATED_DATA_SIGNAL_NAME, None) return selection = self.dendrogram.selected_nodes() From b2553abab704cbde06e566846521d0a5001f3d46 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Thu, 13 Oct 2016 10:26:46 +0200 Subject: [PATCH 08/15] OWHierarchicalClustering: Remove 'Other' value from Cluster variable Since there are only clusters in Selected Data, 'Other' should be removed from its domain. The value is still present in Flagged Data domain. --- .../unsupervised/owhierarchicalclustering.py | 15 +++++++++++++++ .../tests/test_owhierarchicalclustering.py | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/Orange/widgets/unsupervised/owhierarchicalclustering.py b/Orange/widgets/unsupervised/owhierarchicalclustering.py index a780dd9be79..8d8f2a7fd85 100644 --- a/Orange/widgets/unsupervised/owhierarchicalclustering.py +++ b/Orange/widgets/unsupervised/owhierarchicalclustering.py @@ -22,6 +22,7 @@ import Orange.data from Orange.data.domain import filter_visible +from Orange.data import Domain import Orange.misc from Orange.clustering.hierarchical import \ postorder, preorder, Tree, tree_from_linkage, dist_matrix_linkage, \ @@ -1143,6 +1144,20 @@ def commit(self): if selected_indices: selected_data = data[mask] + if self.append_clusters: + def remove_other_value(vars_): + vars_ = [var for var in vars_] + clust_var = vars_[-1].copy() + clust_var.values.pop() + vars_[-1] = clust_var + return vars_ + if self.cluster_role == self.AttributeRole: + attrs = remove_other_value(attrs) + elif self.cluster_role == self.ClassRole: + class_ = remove_other_value(class_) + elif self.cluster_role == self.MetaRole: + metas = remove_other_value(metas) + selected_data.domain = Domain(attrs, class_, metas) elif isinstance(items, Orange.data.Table) and self.matrix.axis == 0: # Select columns diff --git a/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py b/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py index 20981b289b3..f80ec683177 100644 --- a/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py +++ b/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py @@ -39,6 +39,15 @@ def test_outputs(self): self.assertEqual(len(selected), np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) + # compare selected and flagged data domains + self.assertTrue(all((var in flagged.domain.variables + for var in selected.domain.variables))) + self.assertNotIn("Other", selected.domain.metas[0].values) + self.assertIn("Other", flagged.domain.metas[0].values) + self.assertTrue( + all((var in [var.name for var in flagged.domain.metas] + for var in [var.name for var in selected.domain.metas]))) + # check output when data is removed self.send_signal("Distances", None) self.assertIsNone(self.get_output("Selected Data")) From 8e422a051496aeab4defd801a2a1bfb8934e79e0 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 11 Oct 2016 17:56:45 +0200 Subject: [PATCH 09/15] OWDistanceMap: Output Flagged Data --- Orange/widgets/unsupervised/owdistancemap.py | 8 ++- .../unsupervised/tests/test_owdistancemap.py | 50 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 Orange/widgets/unsupervised/tests/test_owdistancemap.py diff --git a/Orange/widgets/unsupervised/owdistancemap.py b/Orange/widgets/unsupervised/owdistancemap.py index f0e80d43b38..ab14b3c21ee 100644 --- a/Orange/widgets/unsupervised/owdistancemap.py +++ b/Orange/widgets/unsupervised/owdistancemap.py @@ -23,6 +23,8 @@ from Orange.widgets import widget, gui, settings from Orange.widgets.utils import itemmodels, colorbrewer +from Orange.widgets.utils.annotated_data import (create_annotated_table, + ANNOTATED_DATA_SIGNAL_NAME) from .owhierarchicalclustering import DendrogramWidget, GraphicsSimpleTextList def _remove_item(item): @@ -242,7 +244,9 @@ class OWDistanceMap(widget.OWWidget): priority = 1200 inputs = [("Distances", Orange.misc.DistMatrix, "set_distances")] - outputs = [("Data", Orange.data.Table), ("Features", widget.AttributeList)] + outputs = [("Data", Orange.data.Table, widget.Default), + (ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table), + ("Features", widget.AttributeList)] settingsHandler = settings.PerfectDomainContextHandler() @@ -630,6 +634,8 @@ def commit(self): featuresubset = widget.AttributeList(subset) self.send("Data", datasubset) + self.send(ANNOTATED_DATA_SIGNAL_NAME, + create_annotated_table(self.items, self._selection)) self.send("Features", featuresubset) def onDeleteWidget(self): diff --git a/Orange/widgets/unsupervised/tests/test_owdistancemap.py b/Orange/widgets/unsupervised/tests/test_owdistancemap.py new file mode 100644 index 00000000000..dff461930e9 --- /dev/null +++ b/Orange/widgets/unsupervised/tests/test_owdistancemap.py @@ -0,0 +1,50 @@ +# Test methods with long descriptive names can omit docstrings +# pylint: disable=missing-docstring +import random +import numpy as np +from Orange.data import Table +from Orange.misc.flagged_data import FLAGGED_SIGNAL_NAME, FLAGGED_FEATURE_NAME +from Orange.distance import Euclidean +from Orange.widgets.unsupervised.owdistancemap import OWDistanceMap +from Orange.widgets.tests.base import WidgetTest + + +class TestOWDistanceMap(WidgetTest): + def setUp(self): + self.widget = self.create_widget(OWDistanceMap) + self.iris = Table("iris") + self.iris_distances = Euclidean(self.iris) + + def test_outputs(self): + self.send_signal("Distances", self.iris_distances) + + # check selected data output + self.assertIsNone(self.get_output("Data")) + + # check flagged data output + flagged = self.get_output(FLAGGED_SIGNAL_NAME) + self.assertEqual(0, np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) + + # select data points + points = random.sample(range(0, len(self.iris)), 20) + self.widget._selection = points + self.widget.commit() + + # check selected data output + selected = self.get_output("Data") + self.assertEqual(len(selected), len(points)) + + # check flagged data output + flagged = self.get_output(FLAGGED_SIGNAL_NAME) + self.assertEqual(len(selected), + np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) + + # compare selected and flagged data domains + selected_vars = selected.domain.variables + selected.domain.metas + flagged_vars = flagged.domain.variables + flagged.domain.metas + self.assertTrue(all((var in flagged_vars for var in selected_vars))) + + # check output when data is removed + self.send_signal("Distances", None) + self.assertIsNone(self.get_output("Data")) + self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) From 793e81f776a5a1d725e62eff26a85e1d07e459ca Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 11 Oct 2016 18:01:12 +0200 Subject: [PATCH 10/15] OWDistanceMap: Rename Data to Selected Data --- Orange/widgets/unsupervised/owdistancemap.py | 4 ++-- Orange/widgets/unsupervised/tests/test_owdistancemap.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Orange/widgets/unsupervised/owdistancemap.py b/Orange/widgets/unsupervised/owdistancemap.py index ab14b3c21ee..45659122b58 100644 --- a/Orange/widgets/unsupervised/owdistancemap.py +++ b/Orange/widgets/unsupervised/owdistancemap.py @@ -244,7 +244,7 @@ class OWDistanceMap(widget.OWWidget): priority = 1200 inputs = [("Distances", Orange.misc.DistMatrix, "set_distances")] - outputs = [("Data", Orange.data.Table, widget.Default), + outputs = [("Selected Data", Orange.data.Table, widget.Default), (ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table), ("Features", widget.AttributeList)] @@ -633,7 +633,7 @@ def commit(self): subset = [self.items[i] for i in self._selection] featuresubset = widget.AttributeList(subset) - self.send("Data", datasubset) + self.send("Selected Data", datasubset) self.send(ANNOTATED_DATA_SIGNAL_NAME, create_annotated_table(self.items, self._selection)) self.send("Features", featuresubset) diff --git a/Orange/widgets/unsupervised/tests/test_owdistancemap.py b/Orange/widgets/unsupervised/tests/test_owdistancemap.py index dff461930e9..c2ca41760c1 100644 --- a/Orange/widgets/unsupervised/tests/test_owdistancemap.py +++ b/Orange/widgets/unsupervised/tests/test_owdistancemap.py @@ -19,7 +19,7 @@ def test_outputs(self): self.send_signal("Distances", self.iris_distances) # check selected data output - self.assertIsNone(self.get_output("Data")) + self.assertIsNone(self.get_output("Selected Data")) # check flagged data output flagged = self.get_output(FLAGGED_SIGNAL_NAME) @@ -31,7 +31,7 @@ def test_outputs(self): self.widget.commit() # check selected data output - selected = self.get_output("Data") + selected = self.get_output("Selected Data") self.assertEqual(len(selected), len(points)) # check flagged data output @@ -46,5 +46,5 @@ def test_outputs(self): # check output when data is removed self.send_signal("Distances", None) - self.assertIsNone(self.get_output("Data")) + self.assertIsNone(self.get_output("Selected Data")) self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) From 83b7bfdcd6ae9d227789d2628bc1e5f4de2c6f2d Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 11 Oct 2016 18:10:54 +0200 Subject: [PATCH 11/15] OWMDS: Output Flagged Data instead of Data --- Orange/widgets/unsupervised/owmds.py | 7 ++- .../widgets/unsupervised/tests/test_owmds.py | 53 +++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 Orange/widgets/unsupervised/tests/test_owmds.py diff --git a/Orange/widgets/unsupervised/owmds.py b/Orange/widgets/unsupervised/owmds.py index 7917b49ca58..43f886e4943 100644 --- a/Orange/widgets/unsupervised/owmds.py +++ b/Orange/widgets/unsupervised/owmds.py @@ -24,6 +24,8 @@ from Orange.widgets.utils.sql import check_sql_input from Orange.canvas import report from Orange.widgets.widget import Msg, OWWidget +from Orange.widgets.utils.annotated_data import (create_annotated_table, + ANNOTATED_DATA_SIGNAL_NAME) def torgerson(distances, n_components=2): @@ -101,7 +103,7 @@ class OWMDS(OWWidget): ("Data Subset", Orange.data.Table, "set_subset_data")] outputs = [("Selected Data", Orange.data.Table, widget.Default), - ("Data", Orange.data.Table)] + (ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)] #: Initialization type PCA, Random = 0, 1 @@ -1046,13 +1048,14 @@ def commit(self): elif self.output_embedding_role == OWMDS.MetaRole: output.metas[:, -2:] = embedding.X - self.send("Data", output) if output is not None and self._selection_mask is not None and \ numpy.any(self._selection_mask): subset = output[self._selection_mask] else: subset = None self.send("Selected Data", subset) + self.send(ANNOTATED_DATA_SIGNAL_NAME, + create_annotated_table(output, self._selection_mask)) def onDeleteWidget(self): super().onDeleteWidget() diff --git a/Orange/widgets/unsupervised/tests/test_owmds.py b/Orange/widgets/unsupervised/tests/test_owmds.py new file mode 100644 index 00000000000..2c4168a4778 --- /dev/null +++ b/Orange/widgets/unsupervised/tests/test_owmds.py @@ -0,0 +1,53 @@ +# Test methods with long descriptive names can omit docstrings +# pylint: disable=missing-docstring +import random +import numpy as np +from PyQt4.QtCore import QEvent +from Orange.data import Table +from Orange.misc.flagged_data import FLAGGED_SIGNAL_NAME, FLAGGED_FEATURE_NAME +from Orange.distance import Euclidean +from Orange.widgets.unsupervised.owmds import OWMDS +from Orange.widgets.tests.base import WidgetTest + + +class TestOWMDS(WidgetTest): + def setUp(self): + self.widget = self.create_widget(OWMDS) + self.iris = Table("iris") + self.iris_distances = Euclidean(self.iris) + + def test_outputs(self): + self.send_signal("Distances", self.iris_distances) + self.widget.customEvent(QEvent(QEvent.User)) + self.widget.commit() + + # check selected data output + self.assertIsNone(self.get_output("Selected Data")) + + # check flagged data output + flagged = self.get_output(FLAGGED_SIGNAL_NAME) + self.assertEqual(0, np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) + + # select data points + points = random.sample(range(0, len(self.iris)), 20) + self.widget.select_indices(points) + self.widget.commit() + + # check selected data output + selected = self.get_output("Selected Data") + self.assertEqual(len(selected), len(points)) + + # check flagged data output + flagged = self.get_output(FLAGGED_SIGNAL_NAME) + self.assertEqual(len(selected), + np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) + + # compare selected and flagged data domains + selected_vars = selected.domain.variables + selected.domain.metas + flagged_vars = flagged.domain.variables + flagged.domain.metas + self.assertTrue(all((var in flagged_vars for var in selected_vars))) + + # check output when data is removed + self.send_signal("Distances", None) + self.assertIsNone(self.get_output("Selected Data")) + self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) From 69ceb538919215ebc369a737bbb8215f6a7750ff Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Thu, 13 Oct 2016 16:43:31 +0200 Subject: [PATCH 12/15] Unittests: Refactoring --- Orange/widgets/tests/base.py | 76 +++++++++++++++++++ .../unsupervised/tests/test_owdistancemap.py | 53 ++++--------- .../tests/test_owhierarchicalclustering.py | 65 ++++++---------- .../widgets/unsupervised/tests/test_owmds.py | 57 ++++---------- .../visualize/tests/test_owscatterplot.py | 66 ++++++---------- 5 files changed, 154 insertions(+), 163 deletions(-) diff --git a/Orange/widgets/tests/base.py b/Orange/widgets/tests/base.py index 86438518a92..4fdb852f5c3 100644 --- a/Orange/widgets/tests/base.py +++ b/Orange/widgets/tests/base.py @@ -1,5 +1,7 @@ import unittest +import numpy as np + from Orange.base import SklLearner, SklModel from PyQt4.QtGui import (QApplication, QComboBox, QSpinBox, QDoubleSpinBox, QSlider) @@ -13,6 +15,8 @@ from Orange.regression.base_regression import LearnerRegression, ModelRegression from Orange.canvas.report.owreport import OWReport from Orange.widgets.utils.owlearnerwidget import OWBaseLearner +from Orange.widgets.utils.annotated_data import (ANNOTATED_DATA_FEATURE_NAME, + ANNOTATED_DATA_SIGNAL_NAME) app = None @@ -476,3 +480,75 @@ def get_value(learner, name): self.assertFalse(self.widget.Error.active) else: self.assertTrue(self.widget.Error.active) + + +class WidgetOutputsTestMixin: + """Class for widget's outputs testing. + + Contains init method to set up testing parameters and a test method, which + checks Selected Data and (Annotated) Data outputs. + + Since widgets have different ways of selecting data instances, _select_data + method should be implemented when subclassed. The method should assign + value to selected_indices parameter. + + If output's expected domain differs from input's domain, parameter + same_input_output_domain should be set to False. + + If Selected Data and Data domains differ, override method + _compare_selected_annotated_domains. + """ + + def init(self): + self.data = Table("iris") + self.same_input_output_domain = True + self.selected_indices = [] + + def test_outputs(self): + self.send_signal(self.signal_name, self.signal_data) + + # only needed in TestOWMDS + if type(self).__name__ == "TestOWMDS": + from PyQt4.QtCore import QEvent + self.widget.customEvent(QEvent(QEvent.User)) + self.widget.commit() + + # check selected data output + self.assertIsNone(self.get_output("Selected Data")) + + # check annotated data output + feature_name = ANNOTATED_DATA_FEATURE_NAME + annotated = self.get_output(ANNOTATED_DATA_SIGNAL_NAME) + self.assertEqual(0, np.sum([i[feature_name] for i in annotated])) + + # select data instances + self._select_data() + + # check selected data output + selected = self.get_output("Selected Data") + n_sel, n_attr = len(selected), len(self.data.domain.attributes) + self.assertGreater(n_sel, 0) + self.assertEqual(selected.domain == self.data.domain, + self.same_input_output_domain) + np.testing.assert_array_equal(selected.X[:, :n_attr], + self.data.X[self.selected_indices]) + + # check annotated data output + annotated = self.get_output(ANNOTATED_DATA_SIGNAL_NAME) + self.assertEqual(n_sel, np.sum([i[feature_name] for i in annotated])) + + # compare selected and annotated data domains + self._compare_selected_annotated_domains(selected, annotated) + + # check output when data is removed + self.send_signal(self.signal_name, None) + self.assertIsNone(self.get_output("Selected Data")) + self.assertIsNone(self.get_output(ANNOTATED_DATA_SIGNAL_NAME)) + + def _select_data(self): + raise NotImplementedError("Subclasses should implement select_data") + + def _compare_selected_annotated_domains(self, selected, annotated): + selected_vars = selected.domain.variables + selected.domain.metas + annotated_vars = annotated.domain.variables + annotated.domain.metas + self.assertTrue(all((var in annotated_vars for var in selected_vars))) diff --git a/Orange/widgets/unsupervised/tests/test_owdistancemap.py b/Orange/widgets/unsupervised/tests/test_owdistancemap.py index c2ca41760c1..e50a5454473 100644 --- a/Orange/widgets/unsupervised/tests/test_owdistancemap.py +++ b/Orange/widgets/unsupervised/tests/test_owdistancemap.py @@ -1,50 +1,25 @@ # Test methods with long descriptive names can omit docstrings # pylint: disable=missing-docstring import random -import numpy as np -from Orange.data import Table -from Orange.misc.flagged_data import FLAGGED_SIGNAL_NAME, FLAGGED_FEATURE_NAME from Orange.distance import Euclidean from Orange.widgets.unsupervised.owdistancemap import OWDistanceMap -from Orange.widgets.tests.base import WidgetTest +from Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin -class TestOWDistanceMap(WidgetTest): - def setUp(self): - self.widget = self.create_widget(OWDistanceMap) - self.iris = Table("iris") - self.iris_distances = Euclidean(self.iris) - - def test_outputs(self): - self.send_signal("Distances", self.iris_distances) +class TestOWDistanceMap(WidgetTest, WidgetOutputsTestMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + WidgetOutputsTestMixin.init(cls) - # check selected data output - self.assertIsNone(self.get_output("Selected Data")) + cls.signal_name = "Distances" + cls.signal_data = Euclidean(cls.data) - # check flagged data output - flagged = self.get_output(FLAGGED_SIGNAL_NAME) - self.assertEqual(0, np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) + def setUp(self): + self.widget = self.create_widget(OWDistanceMap) - # select data points - points = random.sample(range(0, len(self.iris)), 20) - self.widget._selection = points + def _select_data(self): + random.seed(42) + self.selected_indices = random.sample(range(0, len(self.data)), 20) + self.widget._selection = self.selected_indices self.widget.commit() - - # check selected data output - selected = self.get_output("Selected Data") - self.assertEqual(len(selected), len(points)) - - # check flagged data output - flagged = self.get_output(FLAGGED_SIGNAL_NAME) - self.assertEqual(len(selected), - np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) - - # compare selected and flagged data domains - selected_vars = selected.domain.variables + selected.domain.metas - flagged_vars = flagged.domain.variables + flagged.domain.metas - self.assertTrue(all((var in flagged_vars for var in selected_vars))) - - # check output when data is removed - self.send_signal("Distances", None) - self.assertIsNone(self.get_output("Selected Data")) - self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) diff --git a/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py b/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py index f80ec683177..b439b2c2295 100644 --- a/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py +++ b/Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py @@ -1,70 +1,53 @@ # Test methods with long descriptive names can omit docstrings # pylint: disable=missing-docstring -import numpy as np -from Orange.data import Table -from Orange.misc.flagged_data import FLAGGED_SIGNAL_NAME, FLAGGED_FEATURE_NAME from Orange.distance import Euclidean +from Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin from Orange.widgets.unsupervised.owhierarchicalclustering import \ OWHierarchicalClustering -from Orange.widgets.tests.base import WidgetTest +from Orange.widgets.utils.annotated_data import ANNOTATED_DATA_SIGNAL_NAME -class TestOWHierarchicalClustering(WidgetTest): - def setUp(self): - self.widget = self.create_widget(OWHierarchicalClustering) - self.iris = Table("iris") - self.iris_distances = Euclidean(self.iris) +class TestOWHierarchicalClustering(WidgetTest, WidgetOutputsTestMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + WidgetOutputsTestMixin.init(cls) - def test_outputs(self): - self.send_signal("Distances", self.iris_distances) + cls.distances = Euclidean(cls.data) + cls.signal_name = "Distances" + cls.signal_data = cls.distances + cls.same_input_output_domain = False - # check selected data output - self.assertIsNone(self.get_output("Selected Data")) - - # check flagged data output - flagged = self.get_output(FLAGGED_SIGNAL_NAME) - self.assertEqual(0, np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) + def setUp(self): + self.widget = self.create_widget(OWHierarchicalClustering) - # select a cluster + def _select_data(self): items = self.widget.dendrogram._items - cluster = items[next(iter(items))] + cluster = items[sorted(list(items.keys()))[4]] self.widget.dendrogram.set_selected_items([cluster]) + self.selected_indices = [14, 15, 32, 33] - # check selected data output - selected = self.get_output("Selected Data") - self.assertGreater(len(selected), 0) - - # check flagged data output - flagged = self.get_output(FLAGGED_SIGNAL_NAME) - self.assertEqual(len(selected), - np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) - - # compare selected and flagged data domains - self.assertTrue(all((var in flagged.domain.variables + def _compare_selected_annotated_domains(self, selected, annotated): + self.assertTrue(all((var in annotated.domain.variables for var in selected.domain.variables))) self.assertNotIn("Other", selected.domain.metas[0].values) - self.assertIn("Other", flagged.domain.metas[0].values) + self.assertIn("Other", annotated.domain.metas[0].values) self.assertTrue( - all((var in [var.name for var in flagged.domain.metas] + all((var in [var.name for var in annotated.domain.metas] for var in [var.name for var in selected.domain.metas]))) - # check output when data is removed - self.send_signal("Distances", None) - self.assertIsNone(self.get_output("Selected Data")) - self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) - def test_selection_box_output(self): """Check output if Selection method changes""" - self.send_signal("Distances", self.iris_distances) + self.send_signal("Distances", self.distances) self.assertIsNone(self.get_output("Selected Data")) - self.assertIsNotNone(self.get_output(FLAGGED_SIGNAL_NAME)) + self.assertIsNotNone(self.get_output(ANNOTATED_DATA_SIGNAL_NAME)) # change selection to 'Height ratio' self.widget.selection_box.buttons[1].click() self.assertIsNotNone(self.get_output("Selected Data")) - self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) + self.assertIsNone(self.get_output(ANNOTATED_DATA_SIGNAL_NAME)) # change selection to 'Top N' self.widget.selection_box.buttons[2].click() self.assertIsNotNone(self.get_output("Selected Data")) - self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) + self.assertIsNone(self.get_output(ANNOTATED_DATA_SIGNAL_NAME)) diff --git a/Orange/widgets/unsupervised/tests/test_owmds.py b/Orange/widgets/unsupervised/tests/test_owmds.py index 2c4168a4778..baa9d629ce2 100644 --- a/Orange/widgets/unsupervised/tests/test_owmds.py +++ b/Orange/widgets/unsupervised/tests/test_owmds.py @@ -1,53 +1,28 @@ # Test methods with long descriptive names can omit docstrings # pylint: disable=missing-docstring import random -import numpy as np -from PyQt4.QtCore import QEvent -from Orange.data import Table -from Orange.misc.flagged_data import FLAGGED_SIGNAL_NAME, FLAGGED_FEATURE_NAME + from Orange.distance import Euclidean from Orange.widgets.unsupervised.owmds import OWMDS -from Orange.widgets.tests.base import WidgetTest - +from Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin -class TestOWMDS(WidgetTest): - def setUp(self): - self.widget = self.create_widget(OWMDS) - self.iris = Table("iris") - self.iris_distances = Euclidean(self.iris) - def test_outputs(self): - self.send_signal("Distances", self.iris_distances) - self.widget.customEvent(QEvent(QEvent.User)) - self.widget.commit() +class TestOWMDS(WidgetTest, WidgetOutputsTestMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + WidgetOutputsTestMixin.init(cls) - # check selected data output - self.assertIsNone(self.get_output("Selected Data")) + cls.signal_name = "Distances" + cls.signal_data = Euclidean(cls.data) + cls.same_input_output_domain = False - # check flagged data output - flagged = self.get_output(FLAGGED_SIGNAL_NAME) - self.assertEqual(0, np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) + def setUp(self): + self.widget = self.create_widget(OWMDS) - # select data points - points = random.sample(range(0, len(self.iris)), 20) + def _select_data(self): + random.seed(42) + points = random.sample(range(0, len(self.data)), 20) self.widget.select_indices(points) self.widget.commit() - - # check selected data output - selected = self.get_output("Selected Data") - self.assertEqual(len(selected), len(points)) - - # check flagged data output - flagged = self.get_output(FLAGGED_SIGNAL_NAME) - self.assertEqual(len(selected), - np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) - - # compare selected and flagged data domains - selected_vars = selected.domain.variables + selected.domain.metas - flagged_vars = flagged.domain.variables + flagged.domain.metas - self.assertTrue(all((var in flagged_vars for var in selected_vars))) - - # check output when data is removed - self.send_signal("Distances", None) - self.assertIsNone(self.get_output("Selected Data")) - self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) + self.selected_indices = sorted(points) diff --git a/Orange/widgets/visualize/tests/test_owscatterplot.py b/Orange/widgets/visualize/tests/test_owscatterplot.py index 3f43497391f..0784713631c 100644 --- a/Orange/widgets/visualize/tests/test_owscatterplot.py +++ b/Orange/widgets/visualize/tests/test_owscatterplot.py @@ -5,31 +5,37 @@ from PyQt4.QtCore import QRectF from Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable -from Orange.misc.flagged_data import FLAGGED_SIGNAL_NAME, FLAGGED_FEATURE_NAME -from Orange.widgets.tests.base import WidgetTest +from Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin from Orange.widgets.visualize.owscatterplot import \ OWScatterPlot, ScatterPlotVizRank -class TestOWScatterPlot(WidgetTest): +class TestOWScatterPlot(WidgetTest, WidgetOutputsTestMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + WidgetOutputsTestMixin.init(cls) + + cls.signal_name = "Data" + cls.signal_data = cls.data + def setUp(self): self.widget = self.create_widget(OWScatterPlot) - self.iris = Table("iris") def test_set_data(self): # Connect iris to scatter plot - self.send_signal("Data", self.iris) + self.send_signal("Data", self.data) # First two attribute should be selected as x an y - self.assertEqual(self.widget.attr_x, self.iris.domain[0]) - self.assertEqual(self.widget.attr_y, self.iris.domain[1]) + self.assertEqual(self.widget.attr_x, self.data.domain[0]) + self.assertEqual(self.widget.attr_y, self.data.domain[1]) # Class var should be selected as color - self.assertIs(self.widget.graph.attr_color, self.iris.domain.class_var) + self.assertIs(self.widget.graph.attr_color, self.data.domain.class_var) # Change which attributes are displayed - self.widget.attr_x = self.iris.domain[2] - self.widget.attr_y = self.iris.domain[3] + self.widget.attr_x = self.data.domain[2] + self.widget.attr_y = self.data.domain[3] # Disconnect the data self.send_signal("Data", None) @@ -44,10 +50,10 @@ def test_set_data(self): # Connect iris again # same attributes that were used last time should be selected - self.send_signal("Data", self.iris) + self.send_signal("Data", self.data) - self.assertIs(self.widget.attr_x, self.iris.domain[2]) - self.assertIs(self.widget.attr_y, self.iris.domain[3]) + self.assertIs(self.widget.attr_x, self.data.domain[2]) + self.assertIs(self.widget.attr_y, self.data.domain[3]) def test_score_heuristics(self): domain = Domain([ContinuousVariable(c) for c in "abcd"], @@ -60,42 +66,18 @@ def test_score_heuristics(self): list("abcd")) def test_optional_combos(self): - domain = self.iris.domain + domain = self.data.domain d1 = Domain(domain.attributes[:2], domain.class_var, [domain.attributes[2]]) - t1 = Table(d1, self.iris) + t1 = Table(d1, self.data) self.send_signal("Data", t1) self.widget.graph.attr_size = domain.attributes[2] d2 = Domain(domain.attributes[:2], domain.class_var, [domain.attributes[3]]) - t2 = Table(d2, self.iris) + t2 = Table(d2, self.data) self.send_signal("Data", t2) - def test_outputs(self): - self.send_signal("Data", self.iris) - - # check selected data output - self.assertIsNone(self.get_output("Selected Data")) - - # check flagged data output - flagged = self.get_output(FLAGGED_SIGNAL_NAME) - self.assertEqual(0, np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) - - # select data points + def _select_data(self): self.widget.graph.select_by_rectangle(QRectF(4, 3, 3, 1)) - - # check selected data output - selected = self.get_output("Selected Data") - self.assertGreater(len(selected), 0) - self.assertEqual(selected.domain, self.iris.domain) - - # check flagged data output - flagged = self.get_output(FLAGGED_SIGNAL_NAME) - self.assertEqual(len(selected), - np.sum([i[FLAGGED_FEATURE_NAME] for i in flagged])) - - # check output when data is removed - self.send_signal("Data", None) - self.assertIsNone(self.get_output("Selected Data")) - self.assertIsNone(self.get_output(FLAGGED_SIGNAL_NAME)) + self.selected_indices = self.widget.graph.get_selection() From deb8585295eaed414c9ec2a09e511b7e4bb907fc Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 14 Oct 2016 15:08:35 +0200 Subject: [PATCH 13/15] OWConfusionMatrix: Output Flagged Data --- Orange/widgets/evaluate/owconfusionmatrix.py | 82 ++++++++++--------- .../evaluate/tests/test_owconfusionmatrix.py | 34 ++++---- 2 files changed, 61 insertions(+), 55 deletions(-) diff --git a/Orange/widgets/evaluate/owconfusionmatrix.py b/Orange/widgets/evaluate/owconfusionmatrix.py index 7f15a702571..13a2dea445b 100644 --- a/Orange/widgets/evaluate/owconfusionmatrix.py +++ b/Orange/widgets/evaluate/owconfusionmatrix.py @@ -14,6 +14,8 @@ import Orange from Orange.widgets import widget, settings, gui +from Orange.widgets.utils.annotated_data import (create_annotated_table, + ANNOTATED_DATA_SIGNAL_NAME) def confusion_matrix(res, index): @@ -78,7 +80,8 @@ class OWConfusionMatrix(widget.OWWidget): priority = 1001 inputs = [("Evaluation Results", Orange.evaluation.Results, "set_results")] - outputs = [("Selected Data", Orange.data.Table)] + outputs = [("Selected Data", Orange.data.Table, widget.Default), + (ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)] quantities = ["Number of instances", "Proportion of predicted", @@ -324,51 +327,56 @@ def commit(self): predicted = self.results.predicted[self.selected_learner[0]] selected = [i for i, t in enumerate(zip(actual, predicted)) if t in indices] + + extra = [] + class_var = self.data.domain.class_var + metas = self.data.domain.metas + + if self.append_predictions: + extra.append(predicted.reshape(-1, 1)) + var = Orange.data.DiscreteVariable( + "{}({})".format(class_var.name, learner_name), + class_var.values + ) + metas = metas + (var,) + + if self.append_probabilities and \ + self.results.probabilities is not None: + probs = self.results.probabilities[self.selected_learner[0]] + extra.append(numpy.array(probs, dtype=object)) + pvars = [Orange.data.ContinuousVariable("p({})".format(value)) + for value in class_var.values] + metas = metas + tuple(pvars) + + X = self.data.X + Y = self.data.Y + M = self.data.metas + row_ids = self.data.ids + + M = numpy.hstack((M,) + tuple(extra)) + domain = Orange.data.Domain( + self.data.domain.attributes, + self.data.domain.class_vars, + metas + ) + data = Orange.data.Table.from_numpy(domain, X, Y, M) + data.ids = row_ids + data.name = learner_name + if selected: row_indices = self.results.row_indices[selected] - extra = [] - class_var = self.data.domain.class_var - metas = self.data.domain.metas - - if self.append_predictions: - predicted = numpy.array(predicted[selected], dtype=object) - extra.append(predicted.reshape(-1, 1)) - var = Orange.data.DiscreteVariable( - "{}({})".format(class_var.name, learner_name), - class_var.values - ) - metas = metas + (var,) - - if self.append_probabilities and \ - self.results.probabilities is not None: - probs = self.results.probabilities[self.selected_learner[0], - selected] - extra.append(numpy.array(probs, dtype=object)) - pvars = [Orange.data.ContinuousVariable("p({})".format(value)) - for value in class_var.values] - metas = metas + tuple(pvars) - - X = self.data.X[row_indices] - Y = self.data.Y[row_indices] - M = self.data.metas[row_indices] - row_ids = self.data.ids[row_indices] - - M = numpy.hstack((M,) + tuple(extra)) - domain = Orange.data.Domain( - self.data.domain.attributes, - self.data.domain.class_vars, - metas - ) - data = Orange.data.Table.from_numpy(domain, X, Y, M) - data.ids = row_ids - data.name = learner_name + annotated_data = create_annotated_table(data, row_indices) + data = data[row_indices] else: + annotated_data = create_annotated_table(data, []) data = None else: data = None + annotated_data = None self.send("Selected Data", data) + self.send(ANNOTATED_DATA_SIGNAL_NAME, annotated_data) def _invalidate(self): indices = self.tableview.selectedIndexes() diff --git a/Orange/widgets/evaluate/tests/test_owconfusionmatrix.py b/Orange/widgets/evaluate/tests/test_owconfusionmatrix.py index ee03d0e9448..03573e90b1a 100644 --- a/Orange/widgets/evaluate/tests/test_owconfusionmatrix.py +++ b/Orange/widgets/evaluate/tests/test_owconfusionmatrix.py @@ -4,16 +4,18 @@ from Orange.classification import NaiveBayesLearner, TreeLearner from Orange.evaluation.testing import CrossValidation from Orange.widgets.evaluate.owconfusionmatrix import OWConfusionMatrix -from Orange.widgets.tests.base import WidgetTest +from Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin -class TestOWConfusionMatrix(WidgetTest): +class TestOWConfusionMatrix(WidgetTest, WidgetOutputsTestMixin): @classmethod def setUpClass(cls): super().setUpClass() + WidgetOutputsTestMixin.init(cls) + bayes = NaiveBayesLearner() tree = TreeLearner() - iris = Table("iris") + iris = cls.data titanic = Table("titanic") common = dict(k=3, store_data=True) cls.results_1_iris = CrossValidation(iris, [bayes], **common) @@ -21,6 +23,10 @@ def setUpClass(cls): cls.results_2_titanic = CrossValidation(titanic, [bayes, tree], **common) + cls.signal_name = "Evaluation Results" + cls.signal_data = cls.results_1_iris + cls.same_input_output_domain = False + def setUp(self): self.widget = self.create_widget(OWConfusionMatrix, stored_settings={"auto_apply": False}) @@ -40,19 +46,11 @@ def test_selected_learner(self): self.send_signal("Evaluation Results", self.results_1_iris) self.widget.selected_learner[:] = [0] - def test_outputs(self): - self.send_signal("Evaluation Results", self.results_1_iris) - - # check selected data output - self.assertIsNone(self.get_output("Selected Data")) - - # select data instances + def _select_data(self): self.widget.select_correct() - - # check selected data output - selected = self.get_output("Selected Data") - self.assertGreater(len(selected), 0) - - # check output when data is removed - self.send_signal("Evaluation Results", None) - self.assertIsNone(self.get_output("Selected Data")) + indices = self.widget.tableview.selectedIndexes() + indices = {(ind.row() - 2, ind.column() - 2) for ind in indices} + selected = [i for i, t in enumerate(zip( + self.widget.results.actual, self.widget.results.predicted[0])) + if t in indices] + self.selected_indices = self.widget.results.row_indices[selected] From 44358214348ce4e2c22e359b8fb256cc8b51c976 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 14 Oct 2016 16:00:51 +0200 Subject: [PATCH 14/15] OWTreeGraph: Output Flagged Data --- Orange/tree.py | 7 ++++- Orange/widgets/visualize/owtreeviewer.py | 16 ++++++++--- .../visualize/tests/test_owtreegraph.py | 28 +++++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 Orange/widgets/visualize/tests/test_owtreegraph.py diff --git a/Orange/tree.py b/Orange/tree.py index 7fe154b7a6c..30462f9f37c 100644 --- a/Orange/tree.py +++ b/Orange/tree.py @@ -224,9 +224,14 @@ def _count(node): return _count(self.root) def get_instances(self, nodes): + indices = self.get_indices(nodes) + if indices is not None: + return self.instances[indices] + + def get_indices(self, nodes): subsets = [node.subset for node in nodes] if subsets: - return self.instances[np.unique(np.hstack(subsets))] + return np.unique(np.hstack(subsets)) @staticmethod def climb(node): diff --git a/Orange/widgets/visualize/owtreeviewer.py b/Orange/widgets/visualize/owtreeviewer.py index 2f229339ded..1d4bc30859a 100644 --- a/Orange/widgets/visualize/owtreeviewer.py +++ b/Orange/widgets/visualize/owtreeviewer.py @@ -13,8 +13,10 @@ from Orange.widgets.settings import ContextSetting, ClassValuesContextHandler, \ Setting -from Orange.widgets import gui +from Orange.widgets import gui, widget from Orange.widgets.utils.colorpalette import ContinuousPaletteGenerator +from Orange.widgets.utils.annotated_data import (create_annotated_table, + ANNOTATED_DATA_SIGNAL_NAME) class PieChart(QGraphicsRectItem): @@ -151,7 +153,8 @@ class OWTreeGraph(OWTreeViewer2D): icon = "icons/TreeViewer.svg" priority = 35 inputs = [("Tree", TreeModel, "ctree")] - outputs = [("Data", Table)] + outputs = [("Selected Data", Table, widget.Default), + (ANNOTATED_DATA_SIGNAL_NAME, Table)] settingsHandler = ClassValuesContextHandler() target_class_index = ContextSetting(0) @@ -266,7 +269,9 @@ def ctree(self, model=None): self.info.setText('{} nodes, {} leaves'. format(model.node_count(), model.leaf_count())) self.setup_scene() - self.send("Data", None) + self.send("Selected Data", None) + self.send(ANNOTATED_DATA_SIGNAL_NAME, + create_annotated_table(self.dataset, None)) def walkcreate(self, node_inst, parent=None): """Create a structure of tree nodes from the given model""" @@ -291,7 +296,10 @@ def update_selection(self): nodes = [item.node_inst for item in self.scene.selectedItems() if isinstance(item, TreeNode)] data = self.model.get_instances(nodes) - self.send("Data", data) + self.send("Selected Data", data) + self.send(ANNOTATED_DATA_SIGNAL_NAME, + create_annotated_table(self.dataset, + self.model.get_indices(nodes))) def send_report(self): if not self.model: diff --git a/Orange/widgets/visualize/tests/test_owtreegraph.py b/Orange/widgets/visualize/tests/test_owtreegraph.py new file mode 100644 index 00000000000..2d7ee75c61f --- /dev/null +++ b/Orange/widgets/visualize/tests/test_owtreegraph.py @@ -0,0 +1,28 @@ +# Test methods with long descriptive names can omit docstrings +# pylint: disable=missing-docstring +from Orange.classification import TreeLearner +from Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin +from Orange.widgets.visualize.owtreeviewer import \ + OWTreeGraph + + +class TestOWTreeGraph(WidgetTest, WidgetOutputsTestMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + WidgetOutputsTestMixin.init(cls) + + tree = TreeLearner() + cls.model = tree(cls.data) + cls.model.instances = cls.data + + cls.signal_name = "Tree" + cls.signal_data = cls.model + + def setUp(self): + self.widget = self.create_widget(OWTreeGraph) + + def _select_data(self): + node = self.widget.scene.nodes()[0] + node.setSelected(True) + self.selected_indices = self.model.get_indices([node.node_inst]) From 56107506af2ebefe54505fabddd29dc64c2bb964 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 14 Oct 2016 16:20:48 +0200 Subject: [PATCH 15/15] OWHeatMap: Output Flagged Data --- Orange/widgets/visualize/owheatmap.py | 8 ++++- .../widgets/visualize/tests/test_owheatmap.py | 31 ++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/Orange/widgets/visualize/owheatmap.py b/Orange/widgets/visualize/owheatmap.py index e12649cb937..1d79e6d5ecf 100644 --- a/Orange/widgets/visualize/owheatmap.py +++ b/Orange/widgets/visualize/owheatmap.py @@ -23,6 +23,8 @@ from Orange.clustering import hierarchical from Orange.widgets.utils import colorbrewer +from Orange.widgets.utils.annotated_data import (create_annotated_table, + ANNOTATED_DATA_SIGNAL_NAME) from Orange.widgets import widget, gui, settings from Orange.widgets.io import FileFormat @@ -381,7 +383,8 @@ class OWHeatMap(widget.OWWidget): priority = 260 inputs = [("Data", Table, "set_dataset")] - outputs = [("Selected Data", Table, widget.Default)] + outputs = [("Selected Data", Table, widget.Default), + (ANNOTATED_DATA_SIGNAL_NAME, Table)] settingsHandler = settings.DomainContextHandler() @@ -1506,6 +1509,7 @@ def on_selection_finished(self): def commit(self): data = None + indices = None if self.merge_kmeans: assert self.merge_indices is not None merge_indices = self.merge_indices @@ -1524,6 +1528,8 @@ def commit(self): data = self.input_data[indices] self.send("Selected Data", data) + self.send(ANNOTATED_DATA_SIGNAL_NAME, + create_annotated_table(self.input_data, indices)) def onDeleteWidget(self): self.clear() diff --git a/Orange/widgets/visualize/tests/test_owheatmap.py b/Orange/widgets/visualize/tests/test_owheatmap.py index fda1a37bcaa..dbe96c5915c 100644 --- a/Orange/widgets/visualize/tests/test_owheatmap.py +++ b/Orange/widgets/visualize/tests/test_owheatmap.py @@ -1,22 +1,29 @@ # Test methods with long descriptive names can omit docstrings # pylint: disable=missing-docstring -import random from Orange.data import Table from Orange.preprocess import Continuize from Orange.widgets.visualize.owheatmap import OWHeatMap -from Orange.widgets.tests.base import WidgetTest +from Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin -class TestOWHeatMap(WidgetTest): +class TestOWHeatMap(WidgetTest, WidgetOutputsTestMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + WidgetOutputsTestMixin.init(cls) + + cls.housing = Table("housing") + cls.titanic = Table("titanic") + + cls.signal_name = "Data" + cls.signal_data = cls.data + def setUp(self): self.widget = self.create_widget(OWHeatMap) - self.iris = Table("iris") - self.housing = Table("housing") - self.titanic = Table("titanic") def test_input_data(self): """Check widget's data with data on the input""" - for data in (self.iris, self.housing): + for data in (self.data, self.housing): self.assertEqual(self.widget.data, None) self.send_signal("Data", data) self.assertEqual(self.widget.data, data) @@ -28,7 +35,7 @@ def test_input_data(self): def test_error_message(self): self.send_signal("Data", self.titanic) self.assertTrue(self.widget.Error.active) - self.send_signal("Data", self.iris) + self.send_signal("Data", self.data) self.assertFalse(self.widget.Error.active) def test_information_message(self): @@ -37,11 +44,11 @@ def test_information_message(self): cont_titanic = continuizer(self.titanic) self.send_signal("Data", cont_titanic) self.assertTrue(self.widget.Information.active) - self.send_signal("Data", self.iris) + self.send_signal("Data", self.data) self.assertFalse(self.widget.Information.active) def test_settings_changed(self): - self.send_signal("Data", self.iris) + self.send_signal("Data", self.data) # check output when "Sorting Column" setting changes self._select_data() self.assertIsNotNone(self.get_output("Selected Data")) @@ -61,6 +68,6 @@ def test_settings_changed(self): self.assertIsNone(self.get_output("Selected Data")) def _select_data(self): - rows = random.sample(range(0, len(self.iris)), 20) - self.widget.selection_manager.select_rows(rows) + self.selected_indices = list(range(10, 31)) + self.widget.selection_manager.select_rows(self.selected_indices) self.widget.on_selection_finished()