Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] Visualize widgets: Output Annotated data and Fixups #1677

Merged
merged 11 commits into from
Oct 21, 2016
18 changes: 8 additions & 10 deletions Orange/widgets/data/owtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
from Orange.widgets.settings import (Setting, ContextSetting,
DomainContextHandler)
from Orange.widgets.utils import datacaching
from Orange.widgets.utils.annotated_data import (create_annotated_table,
ANNOTATED_DATA_SIGNAL_NAME)
from Orange.widgets.utils.itemmodels import TableModel


Expand Down Expand Up @@ -367,7 +369,7 @@ class OWDataTable(widget.OWWidget):

inputs = [("Data", Table, "set_dataset", widget.Multiple)]
outputs = [("Selected Data", Table, widget.Default),
("Other Data", Table)]
(ANNOTATED_DATA_SIGNAL_NAME, Table)]

show_distributions = Setting(False)
dist_color_RGB = Setting((220, 220, 220, 255))
Expand Down Expand Up @@ -506,6 +508,7 @@ def update(f):
self.selected_cols = []
self.openContext(data)
self.set_selection()
self.commit()

def _setup_table_view(self, view, data):
"""Setup the `view` (QTableView) with `data` (Orange.data.Table)
Expand Down Expand Up @@ -785,7 +788,7 @@ def commit(self):
"""
Commit/send the current selected row/column selection.
"""
selected_data = other_data = None
selected_data = table = rowsel = None
view = self.tabs.currentWidget()
if view and view.model() is not None:
model = self._get_model(view)
Expand All @@ -795,7 +798,7 @@ def commit(self):
# for SqlTables
if isinstance(table, SqlTable):
self.send("Selected Data", selected_data)
self.send("Other Data", other_data)
self.send(ANNOTATED_DATA_SIGNAL_NAME, None)
return

rowsel, colsel = self.get_selection(view)
Expand Down Expand Up @@ -841,19 +844,14 @@ def select_vars(role):
# Avoid a copy if all/none rows are selected.
if not rowsel:
selected_data = None
other_data = select(table, None, domain)
elif len(rowsel) == len(table):
selected_data = select(table, None, domain)
other_data = None
else:
selected_data = select(table, rowsel, domain)
selmask = numpy.ones((len(table),), dtype=bool)
selmask[rowsel] = False

other_data = select(table, numpy.flatnonzero(selmask), domain)

self.send("Selected Data", selected_data)
self.send("Other Data", other_data)
self.send(ANNOTATED_DATA_SIGNAL_NAME,
create_annotated_table(table, rowsel))

def copy(self):
"""
Expand Down
31 changes: 23 additions & 8 deletions Orange/widgets/data/tests/test_owtable.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
from Orange.data import Table
# Test methods with long descriptive names can omit docstrings
# pylint: disable=missing-docstring
from Orange.widgets.data.owtable import OWDataTable
from Orange.widgets.tests.base import WidgetTest
from Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin


class TestOWDataTable(WidgetTest):
class TestOWDataTable(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(OWDataTable)
self.iris = Table("iris")

def test_input_data(self):
"""Check number of tabs with data on the input"""
self.send_signal("Data", self.iris, 1)
self.send_signal("Data", self.data, 1)
self.assertEqual(self.widget.tabs.count(), 1)
self.send_signal("Data", self.iris, 2)
self.send_signal("Data", self.data, 2)
self.assertEqual(self.widget.tabs.count(), 2)
self.send_signal("Data", None, 1)
self.assertEqual(self.widget.tabs.count(), 1)

def test_data_model(self):
self.send_signal("Data", self.iris, 1)
self.assertEqual(self.widget.tabs.widget(0).model().rowCount(), len(self.iris))
self.send_signal("Data", self.data, 1)
self.assertEqual(self.widget.tabs.widget(0).model().rowCount(),
len(self.data))

def _select_data(self):
self.widget.selected_cols = list(range(len(self.data.domain)))
self.widget.selected_rows = list(range(0, len(self.data.domain), 10))
self.widget.set_selection()
return self.widget.selected_rows
2 changes: 1 addition & 1 deletion Orange/widgets/evaluate/tests/test_owconfusionmatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ def _select_data(self):
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]
return self.widget.results.row_indices[selected]
7 changes: 3 additions & 4 deletions Orange/widgets/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,6 @@ class WidgetOutputsTestMixin:
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)
Expand All @@ -523,7 +522,7 @@ def test_outputs(self):
self.assertEqual(0, np.sum([i[feature_name] for i in annotated]))

# select data instances
self._select_data()
selected_indices = self._select_data()

# check selected data output
selected = self.get_output("Selected Data")
Expand All @@ -532,7 +531,7 @@ def test_outputs(self):
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])
self.data.X[selected_indices])

# check annotated data output
annotated = self.get_output(ANNOTATED_DATA_SIGNAL_NAME)
Expand All @@ -552,4 +551,4 @@ def _select_data(self):
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)))
self.assertLess(set(selected_vars), set(annotated_vars))
4 changes: 2 additions & 2 deletions Orange/widgets/unsupervised/owhierarchicalclustering.py
Original file line number Diff line number Diff line change
Expand Up @@ -1101,7 +1101,7 @@ def commit(self):

if not selected_indices:
self.send("Selected Data", None)
annotated_data = create_annotated_table(items, selected_indices) \
annotated_data = create_annotated_table(items, []) \
if self.selection_method == 0 and self.matrix.axis else None
self.send(ANNOTATED_DATA_SIGNAL_NAME, annotated_data)
return
Expand Down Expand Up @@ -1148,7 +1148,7 @@ def commit(self):
selected_data = data[mask]
if self.append_clusters:
def remove_other_value(vars_):
vars_ = [var for var in vars_]
vars_ = list(vars_)
clust_var = vars_[-1].copy()
clust_var.values.pop()
vars_[-1] = clust_var
Expand Down
5 changes: 3 additions & 2 deletions Orange/widgets/unsupervised/tests/test_owdistancemap.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def setUp(self):

def _select_data(self):
random.seed(42)
self.selected_indices = random.sample(range(0, len(self.data)), 20)
self.widget._selection = self.selected_indices
selected_indices = random.sample(range(0, len(self.data)), 20)
self.widget._selection = selected_indices
self.widget.commit()
return selected_indices
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,15 @@ def _select_data(self):
items = self.widget.dendrogram._items
cluster = items[sorted(list(items.keys()))[4]]
self.widget.dendrogram.set_selected_items([cluster])
self.selected_indices = [14, 15, 32, 33]
return [14, 15, 32, 33]

def _compare_selected_annotated_domains(self, selected, annotated):
self.assertTrue(all((var in annotated.domain.variables
for var in selected.domain.variables)))
self.assertEqual(annotated.domain.variables,
selected.domain.variables)
self.assertNotIn("Other", selected.domain.metas[0].values)
self.assertIn("Other", annotated.domain.metas[0].values)
self.assertTrue(
all((var in [var.name for var in annotated.domain.metas]
for var in [var.name for var in selected.domain.metas])))
self.assertLess(set(var.name for var in selected.domain.metas),
set(var.name for var in annotated.domain.metas))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change anything here, but: you can also use set comprehensions in such cases. (I keep forgetting this, too.)


def test_selection_box_output(self):
"""Check output if Selection method changes"""
Expand Down
2 changes: 1 addition & 1 deletion Orange/widgets/unsupervised/tests/test_owmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ def _select_data(self):
points = random.sample(range(0, len(self.data)), 20)
self.widget.select_indices(points)
self.widget.commit()
self.selected_indices = sorted(points)
return sorted(points)
9 changes: 8 additions & 1 deletion Orange/widgets/visualize/owlinearprojection.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
from Orange.data.sql.table import SqlTable
from Orange.widgets import widget, gui, settings
from Orange.widgets.utils import itemmodels, colorpalette
from Orange.widgets.utils.annotated_data import (
create_annotated_table, ANNOTATED_DATA_SIGNAL_NAME
)
from .owscatterplotgraph import LegendItem, legend_anchor_pos
from Orange.widgets.utils import classdensity
from Orange.canvas import report
Expand Down Expand Up @@ -238,7 +241,8 @@ class OWLinearProjection(widget.OWWidget):
("Data Subset", Table, "set_subset_data")]
# #TODO: Allow for axes to be supplied from an external source.
# ("Projection", numpy.ndarray, "set_axes"),]
outputs = [("Selected Data", Table)]
outputs = [("Selected Data", Table, widget.Default),
(ANNOTATED_DATA_SIGNAL_NAME, Table)]

settingsHandler = settings.DomainContextHandler()

Expand Down Expand Up @@ -1064,12 +1068,15 @@ def select_indices(self, indices, modifiers=Qt.NoModifier):

def commit(self):
subset = None
indices = None
if self.data is not None and self._selection_mask is not None:
indices = numpy.flatnonzero(self._selection_mask)
if len(indices) > 0:
subset = self.data[indices]

self.send("Selected Data", subset)
self.send(ANNOTATED_DATA_SIGNAL_NAME,
create_annotated_table(self.data, indices))

def send_report(self):
self.report_plot(name="", plot=self.viewbox.getViewBox())
Expand Down
16 changes: 11 additions & 5 deletions Orange/widgets/visualize/owmosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
from Orange.preprocess import Discretize
from Orange.preprocess.discretize import EqualFreq
from Orange.statistics.distribution import get_distribution
from Orange.widgets import gui
from Orange.widgets import gui, widget
from Orange.widgets.settings import (
Setting, DomainContextHandler, ContextSetting)
from Orange.widgets.utils import to_html, get_variable_values_sorted
from Orange.widgets.utils.annotated_data import (create_annotated_table,
ANNOTATED_DATA_SIGNAL_NAME)
from Orange.widgets.visualize.utils import (
CanvasText, CanvasRectangle, ViewWithPress)
from Orange.widgets.widget import OWWidget, Default, Msg
Expand All @@ -30,7 +32,8 @@ class OWMosaicDisplay(OWWidget):

inputs = [("Data", Table, "set_data", Default),
("Data Subset", Table, "set_subset_data")]
outputs = [("Selected Data", Table)]
outputs = [("Selected Data", Table, widget.Default),
(ANNOTATED_DATA_SIGNAL_NAME, Table)]

settingsHandler = DomainContextHandler()
use_boxes = Setting(True)
Expand Down Expand Up @@ -218,6 +221,8 @@ def select_area(self, index, ev):
def send_selection(self):
if not self.selection or self.data is None:
self.send("Selected Data", None)
self.send(ANNOTATED_DATA_SIGNAL_NAME,
create_annotated_table(self.data, []))
return
filters = []
self.Warning.no_cont_selection_sql.clear()
Expand All @@ -235,12 +240,13 @@ def send_selection(self):
else:
filters = filters[0]
selection = filters(self.discrete_data)
idset = set(selection.ids)
sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]
if self.discrete_data is not self.data:
idset = set(selection.ids)
sel_idx = [i for i, id in enumerate(self.data.ids) if id in idset]
selection = self.data[sel_idx]
self.send("Selected Data", selection)

self.send(ANNOTATED_DATA_SIGNAL_NAME,
create_annotated_table(self.data, sel_idx))

def send_report(self):
self.report_plot(self.canvas)
Expand Down
23 changes: 16 additions & 7 deletions Orange/widgets/visualize/owpythagorastree.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@
)

from Orange.data.table import Table
from Orange.widgets import gui, settings
from Orange.widgets import gui, settings, widget
from Orange.widgets.utils import to_html
from Orange.widgets.utils.annotated_data import (
create_annotated_table,
ANNOTATED_DATA_SIGNAL_NAME
)
from Orange.widgets.utils.colorpalette import ContinuousPaletteGenerator
from Orange.widgets.visualize.pythagorastreeviewer import (
PythagorasTreeViewer,
Expand All @@ -60,7 +64,8 @@ class OWPythagorasTree(OWWidget):
priority = 1000

inputs = [('Tree', TreeModel, 'set_tree')]
outputs = [('Selected Data', Table)]
outputs = [('Selected Data', Table, widget.Default),
(ANNOTATED_DATA_SIGNAL_NAME, Table)]

# Enable the save as feature
graph_name = 'scene'
Expand Down Expand Up @@ -228,6 +233,8 @@ def set_tree(self, model=None):
# if hasattr(model, 'meta_depth_limit'):
# self.depth_limit = model.meta_depth_limit
# self.update_depth()
self.send(ANNOTATED_DATA_SIGNAL_NAME,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? The "Selected data" signal is not sent here. I commented it out and it seems it still works.

create_annotated_table(self.instances, None))

def clear(self):
"""Clear all relevant data from the widget."""
Expand Down Expand Up @@ -343,14 +350,16 @@ def commit(self):
"""Commit the selected data to output."""
if self.instances is None:
self.send('Selected Data', None)
self.send(ANNOTATED_DATA_SIGNAL_NAME, None)
return
# this is taken almost directly from the owclassificationtreegraph.py
items = filter(lambda x: isinstance(x, SquareGraphicsItem),
self.scene.selectedItems())

nodes = [i.tree_node.label for i in self.scene.selectedItems()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this already computed above, as the second argument in the call of get_instances_in_nodes?

if isinstance(i, SquareGraphicsItem)]
data = self.tree_adapter.get_instances_in_nodes(
self.clf_dataset, [item.tree_node.label for item in items])
self.clf_dataset, nodes)
self.send('Selected Data', data)
selected_indices = self.model.get_indices(nodes)
self.send(ANNOTATED_DATA_SIGNAL_NAME,
create_annotated_table(self.instances, selected_indices))

def send_report(self):
"""Send report."""
Expand Down
Loading