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

[FIX] MDS and Distances widges fix #1435

Merged
merged 3 commits into from
Jul 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 44 additions & 18 deletions Orange/widgets/unsupervised/owdistances.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from inspect import getmembers

import numpy
from PyQt4.QtCore import Qt
from scipy.sparse import issparse
Expand All @@ -10,9 +8,17 @@
from Orange.widgets import widget, gui, settings
from Orange.widgets.utils.sql import check_sql_input

DENSE_METRICS = [obj for name, obj in getmembers(distance,
lambda x: isinstance(x, distance.Distance))]
SPARSE_METRICS = list(filter(lambda x: x.supports_sparse, DENSE_METRICS))
METRICS = [
distance.Euclidean,
distance.Manhattan,
distance.Mahalanobis,
distance.Cosine,
distance.Jaccard,
distance.SpearmanR,
distance.SpearmanRAbsolute,
distance.PearsonR,
distance.PearsonRAbsolute,
]


class OWDistances(widget.OWWidget):
Expand All @@ -34,14 +40,13 @@ def __init__(self):
super().__init__()

self.data = None
self.available_metrics = DENSE_METRICS

gui.radioButtons(self.controlArea, self, "axis", ["Rows", "Columns"],
box="Distances between", callback=self._invalidate
)
self.metrics_combo = gui.comboBox(self.controlArea, self, "metric_idx",
box="Distance Metric",
items=[m.name for m in self.available_metrics],
items=[m.name for m in METRICS],
callback=self._invalidate
)
box = gui.auto_commit(self.buttonsArea, self, "autocommit", "Apply",
Expand All @@ -53,26 +58,46 @@ def __init__(self):

@check_sql_input
def set_data(self, data):
"""
Set the input data set from which to compute the distances
"""
self.data = data
self.refresh_metrics()
self.unconditional_commit()

def refresh_metrics(self):
sparse = self.data and issparse(self.data.X)
self.available_metrics = SPARSE_METRICS if sparse else DENSE_METRICS

self.metrics_combo.clear()
self.metric_idx = 0
for m in self.available_metrics:
self.metrics_combo.addItem(m.name)
"""
Refresh available metrics depending on the input data's sparsenes
"""
sparse = self.data is not None and issparse(self.data.X)
for i, metric in enumerate(METRICS):
item = self.metrics_combo.model().item(i)
item.setEnabled(not sparse or metric.supports_sparse)

self._checksparse()

def _checksparse(self):
# Check the current metric for input data compatibility and set/clear
# appropriate informational GUI state
metric = METRICS[self.metric_idx]
data = self.data
if data is not None and issparse(data.X) and \
not metric.supports_sparse:
self.error(2, "Selected metric does not support sparse data")
else:
self.error(2)

def commit(self):
self.warning(1)
self.error(1)
metric = METRICS[self.metric_idx]
distances = None
data = self.data
if data is not None and issparse(data.X) and \
not metric.supports_sparse:
data = None

data = distances = None
if self.data is not None:
metric = self.available_metrics[self.metric_idx]
if data is not None:
if isinstance(metric, distance.MahalanobisDistance):
metric.fit(self.data, axis=1-self.axis)

Expand All @@ -97,10 +122,11 @@ def commit(self):
self.send("Distances", distances)

def _invalidate(self):
self._checksparse()
self.commit()

def send_report(self):
self.report_items((
("Distances Between", ["Rows", "Columns"][self.axis]),
("Metric", self.available_metrics[self.metric_idx].name)
("Metric", METRICS[self.metric_idx].name)
))
46 changes: 33 additions & 13 deletions Orange/widgets/unsupervised/owmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,17 @@ class OWMDS(widget.OWWidget):

def __init__(self):
super().__init__()
self.matrix = None
self.data = None
#: Input dissimilarity matrix
self.matrix = None # type: Optional[Orange.misc.DistMatrix]
#: Effective data used for plot styling/annotations. Can be from the
#: input signal (`self.signal_data`) or the input matrix
#: (`self.matrix.data`)
self.data = None # type: Optional[Orange.data.Table]
#: Input subset data table
self.subset_data = None # type: Optional[Orange.data.Table]
self.matrix_data = None
#: Data table from the `self.matrix.row_items` (if present)
self.matrix_data = None # type: Optional[Orange.data.Table]
#: Input data table
self.signal_data = None

self._pen_data = None
Expand Down Expand Up @@ -371,6 +378,12 @@ def set_data(self, data):
----------
data : Optional[Orange.data.Table]
"""
if data is not None and len(data) < 2:
self.error(0, "Input data needs at least 2 rows")
data = None
else:
self.error(0)

self.signal_data = data

if self.matrix is not None and data is not None and len(self.matrix) == len(data):
Expand All @@ -389,6 +402,13 @@ def set_disimilarity(self, matrix):
----------
matrix : Optional[Orange.misc.DistMatrix]
"""

if matrix is not None and len(matrix) < 2:
self.error(1, "Input matrix must be at least 2x2")
matrix = None
else:
self.error(1)

self.matrix = matrix
if matrix is not None and matrix.row_items:
self.matrix_data = matrix.row_items
Expand Down Expand Up @@ -452,7 +472,6 @@ def update_controls(self):
self.color_value = attr
self.shape_value = attr
else:
# initialize the graph state from data
domain = self.data.domain
all_vars = list(filter_visible(domain.variables + domain.metas))
cd_vars = [var for var in all_vars if var.is_primitive()]
Expand Down Expand Up @@ -483,30 +502,32 @@ def _initialize(self):
self.data = None
self._effective_matrix = None
self.embedding = None
self.error([2, 3])

# if no data nor matrix is present reset plot
if self.signal_data is None and self.matrix is None:
return

if self.signal_data and self.matrix is not None and len(self.signal_data) != len(self.matrix):
self.error(1, "Data and distances dimensions do not match.")
self.error(2, "Data and distances dimensions do not match.")
self._update_plot()
return

self.error(1)

if self.signal_data:
if self.signal_data is not None:
self.data = self.signal_data
elif self.matrix_data:
elif self.matrix_data is not None:
self.data = self.matrix_data

if self.matrix is not None:
self._effective_matrix = self.matrix
if self.matrix.axis == 0 and self.data is self.matrix_data:
self.data = None
else:
elif self.data.domain.attributes:
preprocessed_data = Orange.projection.MDS().preprocess(self.data)
self._effective_matrix = Orange.distance.Euclidean(preprocessed_data)
else:
self.error(3, "Cannot compute distances from data with no attributes")
return

self.update_controls()
self.openContext(self.data)
Expand Down Expand Up @@ -1341,9 +1362,8 @@ def make_brush(color, ):

def main_test(argv=sys.argv):
import gc
argv = list(argv)
app = QtGui.QApplication(argv)

app = QtGui.QApplication(list(argv))
argv = app.arguments()
if len(argv) > 1:
filename = argv[1]
else:
Expand Down