Skip to content

Commit 44c4b7a

Browse files
authored
Merge pull request #1748 from janezd/hc_settings-2
[FIX] Hierarchical clustering: Make annotation a context setting
2 parents f3f5ca0 + a877fe1 commit 44c4b7a

File tree

2 files changed

+89
-29
lines changed

2 files changed

+89
-29
lines changed

Orange/widgets/unsupervised/owhierarchicalclustering.py

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -723,10 +723,16 @@ class OWHierarchicalClustering(widget.OWWidget):
723723
outputs = [("Selected Data", Orange.data.Table, widget.Default),
724724
(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)]
725725

726+
settingsHandler = settings.DomainContextHandler()
727+
726728
#: Selected linkage
727729
linkage = settings.Setting(1)
728730
#: Index of the selected annotation item (variable, ...)
729-
annotation_idx = settings.Setting(0)
731+
annotation = settings.ContextSetting("Enumeration")
732+
#: Out-of-context setting for the case when the "Name" option is available
733+
annotation_if_names = settings.Setting("Name")
734+
#: Out-of-context setting for the case with just "Enumerate" and "None"
735+
annotation_if_enumerate = settings.Setting("Enumerate")
730736
#: Selected tree pruning (none/max depth)
731737
pruning = settings.Setting(0)
732738
#: Maximum depth when max depth pruning is selected
@@ -753,6 +759,7 @@ class OWHierarchicalClustering(widget.OWWidget):
753759
AttributeRole, ClassRole, MetaRole = 0, 1, 2
754760

755761
cluster_roles = ["Attribute", "Class variable", "Meta variable"]
762+
basic_annotations = ["None", "Enumeration"]
756763

757764
def __init__(self):
758765
super().__init__()
@@ -768,11 +775,11 @@ def __init__(self):
768775
self.controlArea, self, "linkage", items=LINKAGE, box="Linkage",
769776
callback=self._invalidate_clustering)
770777

778+
model = itemmodels.VariableListModel()
779+
model[:] = self.basic_annotations
771780
self.label_cb = gui.comboBox(
772-
self.controlArea, self, "annotation_idx", box="Annotation",
773-
callback=self._update_labels, contentsLength=12)
774-
self.label_cb.setModel(itemmodels.VariableListModel())
775-
self.label_cb.model()[:] = ["None", "Enumeration"]
781+
self.controlArea, self, "annotation", box="Annotation",
782+
model=model, callback=self._update_labels, contentsLength=12)
776783

777784
box = gui.radioButtons(
778785
self.controlArea, self, "pruning", box="Pruning",
@@ -952,46 +959,53 @@ def axis_view(orientation):
952959

953960
def set_distances(self, matrix):
954961
self.error()
955-
self._set_items(None)
956962
if matrix is not None:
957963
N, _ = matrix.shape
958964
if N < 2:
959965
self.error("Empty distance matrix")
960966
matrix = None
961967

962968
self.matrix = matrix
963-
self._invalidate_clustering()
964-
965969
if matrix is not None:
966970
self._set_items(matrix.row_items, matrix.axis)
971+
else:
972+
self._set_items(None)
973+
self._invalidate_clustering()
967974

968975
self.unconditional_commit()
969976

970977
def _set_items(self, items, axis=1):
978+
self.closeContext()
971979
self.items = items
972980
model = self.label_cb.model()
973-
if items is None:
974-
model[:] = ["None", "Enumeration"]
975-
elif not axis:
976-
model[:] = ["None", "Enumeration", "Attribute names"]
977-
self.annotation_idx = 2
978-
elif isinstance(items, Orange.data.Table):
981+
if len(model) == 3:
982+
self.annotation_if_names = self.annotation
983+
elif len(model) == 2:
984+
self.annotation_if_enumerate = self.annotation
985+
if isinstance(items, Orange.data.Table) and axis:
979986
model[:] = chain(
980-
["None", "Enumeration"],
987+
self.basic_annotations,
981988
[model.Separator],
982989
items.domain.class_vars,
983990
items.domain.metas,
984991
[model.Separator] if (items.domain.class_vars or items.domain.metas) and
985992
next(filter_visible(items.domain.attributes), False) else [],
986993
filter_visible(items.domain.attributes)
987994
)
988-
elif isinstance(items, list) and \
989-
all(isinstance(var, Orange.data.Variable) for var in items):
990-
model[:] = ["None", "Enumeration", "Name"]
995+
if items.domain.class_vars:
996+
self.annotation = items.domain.class_vars[0]
997+
else:
998+
self.annotation = "Enumeration"
999+
self.openContext(items.domain)
9911000
else:
992-
model[:] = ["None", "Enumeration"]
993-
self.annotation_idx = min(self.annotation_idx,
994-
len(model) - 1)
1001+
name_option = bool(
1002+
items is not None and (
1003+
not axis or
1004+
isinstance(items, list) and
1005+
all(isinstance(var, Orange.data.Variable) for var in items)))
1006+
model[:] = self.basic_annotations + ["Name"] * name_option
1007+
self.annotation = self.annotation_if_names if name_option \
1008+
else self.annotation_if_enumerate
9951009

9961010
def _clear_plot(self):
9971011
self.labels.set_labels([])
@@ -1042,17 +1056,16 @@ def _update_labels(self):
10421056
if self.root and self._displayed_root:
10431057
indices = [leaf.value.index for leaf in leaves(self.root)]
10441058

1045-
if self.annotation_idx == 0:
1059+
if self.annotation == "None":
10461060
labels = []
1047-
elif self.annotation_idx == 1:
1061+
elif self.annotation == "Enumeration":
10481062
labels = [str(i+1) for i in indices]
1049-
elif self.label_cb.model()[self.annotation_idx] == "Attribute names":
1063+
elif self.annotation == "Name":
10501064
attr = self.matrix.row_items.domain.attributes
10511065
labels = [str(attr[i]) for i in indices]
1052-
elif isinstance(self.items, Orange.data.Table):
1053-
var = self.label_cb.model()[self.annotation_idx]
1054-
col_data, _ = self.items.get_column_view(var)
1055-
labels = [var.str_val(val) for val in col_data]
1066+
elif isinstance(self.annotation, Orange.data.Variable):
1067+
col_data, _ = self.items.get_column_view(self.annotation)
1068+
labels = [self.annotation.str_val(val) for val in col_data]
10561069
labels = [labels[idx] for idx in indices]
10571070
else:
10581071
labels = []
@@ -1350,7 +1363,7 @@ def __zoom_factor_changed(self):
13501363

13511364
def send_report(self):
13521365
annot = self.label_cb.currentText()
1353-
if self.annotation_idx <= 1:
1366+
if isinstance(self.annotation, str):
13541367
annot = annot.lower()
13551368
if self.selection_method == 0:
13561369
sel = "manual"

Orange/widgets/unsupervised/tests/test_owhierarchicalclustering.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,50 @@ def test_selection_box_output(self):
5656
def test_all_zero_inputs(self):
5757
d = Orange.misc.DistMatrix(numpy.zeros((10, 10)))
5858
self.widget.set_distances(d)
59+
60+
def test_annotation_settings_retrieval(self):
61+
"""Check whether widget retrieves correct settings for annotation"""
62+
widget = self.widget
63+
64+
dist_names = Orange.misc.DistMatrix(
65+
numpy.zeros((4, 4)), self.data, axis=0)
66+
dist_no_names = Orange.misc.DistMatrix(numpy.zeros((10, 10)), axis=1)
67+
68+
self.send_signal("Distances", self.distances)
69+
# Check that default is set (class variable)
70+
self.assertEqual(widget.annotation, self.data.domain.class_var)
71+
72+
var2 = self.data.domain[2]
73+
widget.annotation = var2
74+
# Iris now has var2 as annotation
75+
76+
self.send_signal("Distances", dist_no_names)
77+
self.assertEqual(widget.annotation, "Enumeration") # Check default
78+
widget.annotation = "None"
79+
# Pure matrix with axis=1 now has None as annotation
80+
81+
self.send_signal("Distances", self.distances)
82+
self.assertIs(widget.annotation, var2)
83+
self.send_signal("Distances", dist_no_names)
84+
self.assertEqual(widget.annotation, "None")
85+
86+
self.send_signal("Distances", dist_names)
87+
self.assertEqual(widget.annotation, "Name") # Check default
88+
widget.annotation = "Enumeration"
89+
# Pure matrix with axis=1 has Enumerate as annotation
90+
91+
self.send_signal("Distances", self.distances)
92+
self.assertIs(widget.annotation, var2)
93+
self.send_signal("Distances", dist_no_names)
94+
self.assertEqual(widget.annotation, "None")
95+
self.send_signal("Distances", dist_names)
96+
self.assertEqual(widget.annotation, "Enumeration")
97+
self.send_signal("Distances", dist_no_names)
98+
self.assertEqual(widget.annotation, "None")
99+
100+
def test_domain_loses_class(self):
101+
widget = self.widget
102+
self.send_signal("Distances", self.distances)
103+
data = self.data[:, :4]
104+
distances = Euclidean(data)
105+
self.send_signal("Distances", distances)

0 commit comments

Comments
 (0)