diff --git a/Orange/widgets/visualize/owsilhouetteplot.py b/Orange/widgets/visualize/owsilhouetteplot.py index b4489303864..5c5b26d1a39 100644 --- a/Orange/widgets/visualize/owsilhouetteplot.py +++ b/Orange/widgets/visualize/owsilhouetteplot.py @@ -8,7 +8,7 @@ if sys.version_info > (3, 5): from typing import Optional -import numpy +import numpy as np import sklearn.metrics from AnyQt.QtWidgets import ( @@ -99,13 +99,13 @@ def __init__(self): self._matrix = None # type: Optional[Orange.misc.DistMatrix] #: An bool mask (size == len(data)) indicating missing group/cluster #: assignments - self._mask = None # type: Optional[numpy.ndarray] + self._mask = None # type: Optional[np.ndarray] #: An array of cluster/group labels for instances with valid group #: assignment - self._labels = None # type: Optional[numpy.ndarray] + self._labels = None # type: Optional[np.ndarray] #: An array of silhouette scores for instances with valid group #: assignment - self._silhouette = None # type: Optional[numpy.ndarray] + self._silhouette = None # type: Optional[np.ndarray] self._silplot = None # type: Optional[SilhouettePlot] gui.comboBox( @@ -263,7 +263,7 @@ def _update(self): if self._matrix is None and self._effective_data is not None: _, metric = self.Distances[self.distance_idx] try: - self._matrix = numpy.asarray(metric(self._effective_data)) + self._matrix = np.asarray(metric(self._effective_data)) except MemoryError: self.Error.memory_error() return @@ -286,12 +286,12 @@ def _clear_messages(self): def _update_labels(self): labelvar = self.cluster_var_model[self.cluster_var_idx] labels, _ = self.data.get_column_view(labelvar) - labels = numpy.asarray(labels, dtype=float) - mask = numpy.isnan(labels) + labels = np.asarray(labels, dtype=float) + mask = np.isnan(labels) labels = labels.astype(int) labels = labels[~mask] - labels_unq, _ = numpy.unique(labels, return_counts=True) + labels_unq, _ = np.unique(labels, return_counts=True) if len(labels_unq) < 2: self.Error.need_two_clusters() @@ -307,7 +307,7 @@ def _update_labels(self): self._silhouette = silhouette if labels is not None: - count_missing = numpy.count_nonzero(mask) + count_missing = np.count_nonzero(mask) if count_missing: self.Warning.missing_cluster_assignment( count_missing, s="s" if count_missing > 1 else "") @@ -333,8 +333,8 @@ def _replot(self): else: silplot.setScores( self._silhouette, - numpy.zeros(len(self._silhouette), dtype=int), - [""], numpy.array([[63, 207, 207]]) + np.zeros(len(self._silhouette), dtype=int), + [""], np.array([[63, 207, 207]]) ) self.scene.addItem(silplot) @@ -379,17 +379,17 @@ def commit(self): """ selected = indices = data = None if self.data is not None: - selectedmask = numpy.full(len(self.data), False, dtype=bool) + selectedmask = np.full(len(self.data), False, dtype=bool) if self._silplot is not None: indices = self._silplot.selection() - assert (numpy.diff(indices) > 0).all(), "strictly increasing" + assert (np.diff(indices) > 0).all(), "strictly increasing" if self._mask is not None: - indices = numpy.flatnonzero(~self._mask)[indices] + indices = np.flatnonzero(~self._mask)[indices] selectedmask[indices] = True if self._mask is not None: - scores = numpy.full(shape=selectedmask.shape, - fill_value=numpy.nan) + scores = np.full(shape=selectedmask.shape, + fill_value=np.nan) scores[~self._mask] = self._silhouette else: scores = self._silhouette @@ -408,14 +408,14 @@ def commit(self): domain = self.data.domain data = self.data - if numpy.count_nonzero(selectedmask): + if np.count_nonzero(selectedmask): selected = self.data.from_table( - domain, self.data, numpy.flatnonzero(selectedmask)) + domain, self.data, np.flatnonzero(selectedmask)) if self.add_scores: if selected is not None: - selected[:, silhouette_var] = numpy.c_[scores[selectedmask]] - data[:, silhouette_var] = numpy.c_[scores] + selected[:, silhouette_var] = np.c_[scores[selectedmask]] + data[:, silhouette_var] = np.c_[scores] self.Outputs.selected_data.send(selected) self.Outputs.annotated_data.send(create_annotated_table(data, indices)) @@ -456,7 +456,7 @@ def __init__(self, parent=None, **kwargs): self.__rowNamesVisible = True self.__barHeight = 3 self.__selectionRect = None - self.__selection = numpy.asarray([], dtype=int) + self.__selection = np.asarray([], dtype=int) self.__selstate = None self.__pen = QPen(Qt.NoPen) self.__layout = QGraphicsGridLayout() @@ -482,10 +482,10 @@ def setScores(self, scores, labels, values, colors, rownames=None): rownames : list of str, optional A list (len == N) of row names. """ - scores = numpy.asarray(scores, dtype=float) - labels = numpy.asarray(labels, dtype=int) + scores = np.asarray(scores, dtype=float) + labels = np.asarray(labels, dtype=int) if rownames is not None: - rownames = numpy.asarray(rownames, dtype=object) + rownames = np.asarray(rownames, dtype=object) if not scores.ndim == labels.ndim == 1: raise ValueError("scores and labels must be 1 dimensional") @@ -494,13 +494,13 @@ def setScores(self, scores, labels, values, colors, rownames=None): if rownames is not None and rownames.shape != scores.shape: raise ValueError("rownames must have the same size as scores") - Ck = numpy.unique(labels) + Ck = np.unique(labels) if not Ck[0] >= 0 and Ck[-1] < len(values): raise ValueError( "All indices in `labels` must be in `range(len(values))`") - cluster_indices = [numpy.flatnonzero(labels == i) + cluster_indices = [np.flatnonzero(labels == i) for i in range(len(values))] - cluster_indices = [indices[numpy.argsort(scores[indices])[::-1]] + cluster_indices = [indices[np.argsort(scores[indices])[::-1]] for indices in cluster_indices] groups = [ namespace(scores=scores[indices], indices=indices, label=label, @@ -515,7 +515,7 @@ def setScores(self, scores, labels, values, colors, rownames=None): def setRowNames(self, names): if names is not None: - names = numpy.asarray(names, dtype=object) + names = np.asarray(names, dtype=object) layout = self.layout() @@ -589,13 +589,15 @@ def clear(self): def __setup(self): # Setup the subwidgets/groups/layout - smax = max((numpy.max(g.scores) for g in self.__groups + smax = max((np.nanmax(g.scores) for g in self.__groups if g.scores.size), default=1) + smax = 1 if np.isnan(smax) else smax - smin = min((numpy.min(g.scores) for g in self.__groups + smin = min((np.nanmin(g.scores) for g in self.__groups if g.scores.size), default=-1) + smin = -1 if np.isnan(smin) else smin smin = min(smin, 0) font = self.font() @@ -704,7 +706,7 @@ def mousePressEvent(self, event): rect=None, ) if saction & SelectAction.Clear: - self.__selstate.selection = numpy.array([], dtype=int) + self.__selstate.selection = np.array([], dtype=int) self.setSelection(self.__selstate.selection) event.accept() @@ -761,9 +763,9 @@ def mouseReleaseEvent(self, event): self.__selstate = None def __move_selection(self, selection, offset): - ids = numpy.asarray([pi.data(0) for pi in self.__plotItems()]).ravel() - indices = [numpy.where(ids == i)[0] for i in selection] - indices = numpy.asarray(indices) + offset + ids = np.asarray([pi.data(0) for pi in self.__plotItems()]).ravel() + indices = [np.where(ids == i)[0] for i in selection] + indices = np.asarray(indices) + offset if min(indices) >= 0 and max(indices) < len(ids): self.setSelection(ids[indices]) @@ -786,18 +788,18 @@ def __setSelectionRect(self, rect, action): selection = self.__selection if action & SelectAction.Toogle: - selection = numpy.setxor1d(selection, indices) + selection = np.setxor1d(selection, indices) elif action & SelectAction.Deselect: - selection = numpy.setdiff1d(selection, indices) + selection = np.setdiff1d(selection, indices) elif action & SelectAction.Select: - selection = numpy.union1d(selection, indices) + selection = np.union1d(selection, indices) self.setSelection(selection) def __selectionIndices(self, rect): items = [item for item in self.__plotItems() if item.geometry().intersects(rect)] - selection = [numpy.array([], dtype=int)] + selection = [np.array([], dtype=int)] for item in items: indices = item.data(0) itemrect = item.geometry().intersected(rect) @@ -806,10 +808,10 @@ def __selectionIndices(self, rect): .intersected(crect)) assert itemrect.top() >= 0 rowh = crect.height() / item.count() - indextop = numpy.floor(itemrect.top() / rowh) - indexbottom = numpy.ceil(itemrect.bottom() / rowh) + indextop = np.floor(itemrect.top() / rowh) + indexbottom = np.ceil(itemrect.bottom() / rowh) selection.append(indices[int(indextop): int(indexbottom)]) - return numpy.hstack(selection) + return np.hstack(selection) def itemAtPos(self, pos): items = [item for item in self.__plotItems() @@ -825,7 +827,7 @@ def itemAtPos(self, pos): assert pos.x() >= 0 rowh = crect.height() / item.count() - index = int(numpy.floor(pos.y() / rowh)) + index = int(np.floor(pos.y() / rowh)) index = min(index, item.count() - 1) if index >= 0: return item.items()[index] @@ -840,7 +842,7 @@ def indexAtPos(self, pos): else: item = items[0] indices = item.data(0) - assert (isinstance(indices, numpy.ndarray) and + assert (isinstance(indices, np.ndarray) and indices.shape == (item.count(),)) crect = item.contentsRect() pos = item.mapFromParent(pos) @@ -849,7 +851,7 @@ def indexAtPos(self, pos): assert pos.x() >= 0 rowh = crect.height() / item.count() - index = numpy.floor(pos.y() / rowh) + index = np.floor(pos.y() / rowh) index = min(index, indices.size - 1) if index >= 0: @@ -859,16 +861,16 @@ def indexAtPos(self, pos): def __selectionChanged(self, selected, deselected): for item, grp in zip(self.__plotItems(), self.__groups): - select = numpy.flatnonzero( - numpy.in1d(grp.indices, selected, assume_unique=True)) + select = np.flatnonzero( + np.in1d(grp.indices, selected, assume_unique=True)) items = item.items() if select.size: for i in select: - color = numpy.hstack((grp.color, numpy.array([130]))) + color = np.hstack((grp.color, np.array([130]))) items[i].setBrush(QBrush(QColor(*color))) - deselect = numpy.flatnonzero( - numpy.in1d(grp.indices, deselected, assume_unique=True)) + deselect = np.flatnonzero( + np.in1d(grp.indices, deselected, assume_unique=True)) if deselect.size: for i in deselect: items[i].setBrush(QBrush(QColor(*grp.color))) @@ -888,9 +890,9 @@ def __textItems(self): yield item def setSelection(self, indices): - indices = numpy.unique(numpy.asarray(indices, dtype=int)) - select = numpy.setdiff1d(indices, self.__selection) - deselect = numpy.setdiff1d(self.__selection, indices) + indices = np.unique(np.asarray(indices, dtype=int)) + select = np.setdiff1d(indices, self.__selection) + deselect = np.setdiff1d(self.__selection, indices) self.__selectionChanged(select, deselect) @@ -900,7 +902,7 @@ def setSelection(self, indices): self.selectionChanged.emit() def selection(self): - return numpy.asarray(self.__selection, dtype=int) + return np.asarray(self.__selection, dtype=int) class BarPlotItem(QGraphicsWidget): @@ -911,7 +913,7 @@ def __init__(self, parent=None, **kwargs): self.__pen = QPen(Qt.NoPen) self.__brush = QBrush(QColor("#3FCFCF")) self.__range = (0., 1.) - self.__data = numpy.array([], dtype=float) + self.__data = np.array([], dtype=float) self.__items = [] def count(self): @@ -961,7 +963,7 @@ def brush(self): return QBrush(self.__brush) def setPlotData(self, values): - self.__data = numpy.array(values, copy=True) + self.__data = np.array(values, copy=True) self.__update() self.updateGeometry() diff --git a/Orange/widgets/visualize/tests/test_owsilhouetteplot.py b/Orange/widgets/visualize/tests/test_owsilhouetteplot.py index 05937e7ee7a..24a1009298a 100644 --- a/Orange/widgets/visualize/tests/test_owsilhouetteplot.py +++ b/Orange/widgets/visualize/tests/test_owsilhouetteplot.py @@ -1,3 +1,4 @@ +# pylint: disable=protected-access # Test methods with long descriptive names can omit docstrings # pylint: disable=missing-docstring import random @@ -6,6 +7,7 @@ import numpy as np import Orange.data +from Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable from Orange.widgets.utils.annotated_data import ANNOTATED_DATA_SIGNAL_NAME from Orange.widgets.visualize.owsilhouetteplot import OWSilhouettePlot from Orange.widgets.tests.base import WidgetTest, WidgetOutputsTestMixin @@ -105,3 +107,21 @@ def test_memory_error(self): self.widget._effective_data = data self.widget._update() self.assertTrue(self.widget.Error.memory_error.is_shown()) + + def test_bad_data_range(self): + """ + Silhouette Plot now sets axis range properly. + GH-2377 + """ + nan = np.NaN + table = Table( + Domain( + [ContinuousVariable("a"), ContinuousVariable("b"), ContinuousVariable("c")], + [DiscreteVariable("d", values=["y", "n"])]), + list(zip([4, nan, nan], + [15, nan, nan], + [16, nan, nan], + "nyy")) + ) + self.widget.controls.add_scores.setChecked(1) + self.send_signal(self.widget.Inputs.data, table)