Skip to content

Commit ff64af7

Browse files
committed
MDS: Show Kruskal stress
1 parent a25a9d2 commit ff64af7

File tree

2 files changed

+30
-0
lines changed

2 files changed

+30
-0
lines changed

Orange/widgets/unsupervised/owmds.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ def _add_controls_optimization(self):
241241
sizePolicy=(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed),
242242
callback=self.__refresh_rate_combo_changed),
243243
1, 1)
244+
self.stress_label = QLabel("Kruskal Stress: -")
245+
grid.addWidget(self.stress_label, 2, 0, 1, 3)
244246

245247
def __refresh_rate_combo_changed(self):
246248
if self.task is not None:
@@ -392,17 +394,30 @@ def on_partial_result(self, result: Result):
392394
if need_update:
393395
self.graph.update_coordinates()
394396
self.graph.update_density()
397+
self.update_stress()
395398

396399
def on_done(self, result: Result):
397400
assert isinstance(result.embedding, np.ndarray)
398401
assert len(result.embedding) == len(self.effective_matrix)
399402
self.embedding = result.embedding
400403
self.graph.update_coordinates()
401404
self.graph.update_density()
405+
self.update_stress()
402406
self.run_button.setText("Start")
403407
self.step_button.setEnabled(True)
404408
self.commit.deferred()
405409

410+
def update_stress(self):
411+
if self.embedding is None or self.effective_matrix is None:
412+
self.stress_label.setText(f"Kruskal Stress: -")
413+
return
414+
415+
actual = scipy.spatial.distance.pdist(self.embedding)
416+
actual = scipy.spatial.distance.squareform(actual)
417+
stress = np.sqrt(np.sum((actual - self.effective_matrix) ** 2)
418+
/ (np.sum(self.effective_matrix ** 2) or 1))
419+
self.stress_label.setText(f"Kruskal Stress: {stress:.3f}")
420+
406421
def on_exception(self, ex: Exception):
407422
if isinstance(ex, MemoryError):
408423
self.Error.out_of_memory()
@@ -436,6 +451,7 @@ def jitter_coord(part):
436451
# (Random or PCA), restarting the optimization if necessary.
437452
if self.effective_matrix is None:
438453
self.graph.reset_graph()
454+
self.update_stress()
439455
return
440456

441457
X = self.effective_matrix
@@ -451,6 +467,8 @@ def jitter_coord(part):
451467
# restart the optimization if it was interrupted.
452468
if self.task is not None:
453469
self._run()
470+
else:
471+
self.update_stress()
454472

455473
def handleNewSignals(self):
456474
self._initialize()

Orange/widgets/unsupervised/tests/test_owmds.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,18 @@ def test_matrix_columns_default_label(self):
320320
label_text = self.widget.controls.attr_label.currentText()
321321
self.assertEqual(label_text, "labels")
322322

323+
def test_update_stress(self):
324+
w = self.widget
325+
w.effective_matrix = np.array([[0, 4, 1],
326+
[4, 0, 1],
327+
[1, 1, 0]]) # sum of squares is 36
328+
w.embedding = [[0, 0], [0, 3],
329+
[4, 3]]
330+
# dists [[0, 3, 5], diff [[0, 1, 4], sqr [[0, 1, 16], sum = 52
331+
# [3, 0, 4], [1, 0, 3], [1, 0, 9],
332+
# [5, 4, 0]] [4, 3, 0]] [16, 9, 0]]
333+
w.update_stress()
334+
self.assertIn(f"{np.sqrt(52 / 36):.3f}", w.stress_label.text())
323335

324336
class TestOWMDSRunner(unittest.TestCase):
325337
@classmethod

0 commit comments

Comments
 (0)