Skip to content

Commit 3588d2d

Browse files
authored
Merge pull request #6322 from janezd/linearprojection-no-lda
Linear Projection: Explain disabled LDA
2 parents 08b97ad + 4fa43d9 commit 3588d2d

File tree

3 files changed

+38
-6
lines changed

3 files changed

+38
-6
lines changed

Orange/widgets/visualize/owlinearprojection.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from Orange.widgets import gui, report
2525
from Orange.widgets.gui import OWComponent
2626
from Orange.widgets.settings import Setting, ContextSetting, SettingProvider
27+
from Orange.widgets.utils.localization import pl
2728
from Orange.widgets.utils.plot import variables_selection
2829
from Orange.widgets.utils.plot.owplotgui import VariableSelectionModel
2930
from Orange.widgets.utils.widgetpreview import WidgetPreview
@@ -285,6 +286,9 @@ class OWLinearProjection(OWAnchorProjectionWidget):
285286
class Error(OWAnchorProjectionWidget.Error):
286287
no_cont_features = Msg("Plotting requires numeric features")
287288

289+
class Information(OWAnchorProjectionWidget.Information):
290+
no_lda = Msg("LDA placement is disabled due to unsuitable target.\n{}")
291+
288292
def _add_controls(self):
289293
box = gui.vBox(self.controlArea, box="Features")
290294
self._add_controls_variables(box)
@@ -377,12 +381,27 @@ def _check_options(self):
377381
for btn in buttons:
378382
btn.setEnabled(True)
379383

384+
problem = None
380385
if self.data is not None:
381-
has_discrete_class = self.data.domain.has_discrete_class
382-
if not has_discrete_class or len(np.unique(self.data.Y)) < 3:
383-
buttons[Placement.LDA].setEnabled(False)
384-
if self.placement == Placement.LDA:
385-
self.placement = Placement.Circular
386+
if (class_var := self.data.domain.class_var) is None:
387+
problem = "Current data has no target variable"
388+
elif not class_var.is_discrete:
389+
problem = f"{class_var.name} is not categorical"
390+
elif (nclasses := len(distinct := np.unique(self.data.Y))) == 0:
391+
problem = f"Data has no defined values for {class_var.name}"
392+
elif nclasses < 3:
393+
vals = " and ".join(f"'{class_var.values[int(i)]}'" for i in distinct)
394+
problem = \
395+
f"Data contains just {['one', 'two'][nclasses - 1]} distinct " \
396+
f"{pl(nclasses, 'value')} ({vals}) for '{class_var.name}'; " \
397+
"at least three are required."
398+
if problem is None:
399+
self.Information.no_lda.clear()
400+
else:
401+
self.Information.no_lda(problem)
402+
buttons[Placement.LDA].setEnabled(False)
403+
if self.placement == Placement.LDA:
404+
self.placement = Placement.Circular
386405

387406
self.controls.graph.hide_radius.setEnabled(
388407
self.placement != Placement.Circular)

Orange/widgets/visualize/tests/test_owlinearprojection.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ def test_no_data_for_lda(self):
9292
self.send_signal(self.widget.Inputs.data, None)
9393
self.assertTrue(buttons[Placement.LDA].isEnabled())
9494

95+
def test_lda_not_enough_distinct(self):
96+
buttons = self.widget.radio_placement.buttons
97+
self.send_signal(self.widget.Inputs.data, self.data)
98+
self.assertTrue(buttons[Placement.LDA].isEnabled())
99+
self.send_signal(self.widget.Inputs.data, self.data[:10])
100+
self.assertFalse(buttons[Placement.LDA].isEnabled())
101+
self.send_signal(self.widget.Inputs.data, None)
102+
self.assertTrue(buttons[Placement.LDA].isEnabled())
103+
self.send_signal(self.widget.Inputs.data, self.data[40:60])
104+
self.assertFalse(buttons[Placement.LDA].isEnabled())
105+
self.send_signal(self.widget.Inputs.data, self.data[40:110])
106+
self.assertTrue(buttons[Placement.LDA].isEnabled())
107+
95108
def test_data_no_cont_features(self):
96109
data = Table("titanic")
97110
self.assertFalse(self.widget.Error.no_cont_features.is_shown())

doc/visual-programming/source/widgets/visualize/linearprojection.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Consider, for a start, a projection of the *Iris* dataset shown below. Notice th
2424
1. Axes in the projection that are displayed and other available axes. Optimize your projection by using **Suggest Features**. This feature scores attributes and returns the top scoring attributes with a simultaneous visualization update. Feature scoring computes the classification accuracy (for classification) or MSE (regression) of k-nearest neighbors classifier on the projected, two-dimensional data. The score reflects how well the classes in the projection are separated.
2525
2. Choose the type of projection:
2626
- Circular Placement
27-
- [Linear Discriminant Analysis](https://en.wikipedia.org/wiki/Linear_discriminant_analysis)
27+
- [Linear Discriminant Analysis](https://en.wikipedia.org/wiki/Linear_discriminant_analysis); available only for categorical target variable with at least three distinct values in the data.
2828
- [Principal Component Analysis](https://en.wikipedia.org/wiki/Principal_component_analysis)
2929
3. Set the color of the displayed points. Set shape, size, and label to differentiate between points.
3030
*Label only selected points* labels only selected data instances.

0 commit comments

Comments
 (0)