Skip to content

Commit 2d216ee

Browse files
authored
Merge pull request #5224 from janezd/multiple-targets-warning
Multiple targets warning
2 parents 79d08e1 + 6b46529 commit 2d216ee

File tree

6 files changed

+112
-10
lines changed

6 files changed

+112
-10
lines changed

Orange/widgets/data/owfile.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ class Warning(widget.OWWidget.Warning):
128128
"Categorical variables with >100 values may decrease performance.")
129129
renamed_vars = Msg("Some variables have been renamed "
130130
"to avoid duplicates.\n{}")
131+
multiple_targets = Msg("Most widgets do not support multiple targets")
131132

132133
class Error(widget.OWWidget.Error):
133134
file_not_found = Msg("File not found.")
@@ -504,6 +505,8 @@ def apply_domain_edit(self):
504505
if renamed:
505506
self.Warning.renamed_vars(f"Renamed: {', '.join(renamed)}")
506507

508+
self.Warning.multiple_targets(
509+
shown=table is not None and len(table.domain.class_vars) > 1)
507510
summary = len(table) if table else self.info.NoOutput
508511
details = format_summary_details(table) if table else ""
509512
self.info.set_output_summary(summary, details)

Orange/widgets/data/owselectcolumns.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ class Outputs:
173173

174174
class Warning(widget.OWWidget.Warning):
175175
mismatching_domain = Msg("Features and data domain do not match")
176+
multiple_targets = Msg("Most widgets do not support multiple targets")
176177

177178
def __init__(self):
178179
super().__init__()
@@ -593,6 +594,7 @@ def selected_vars(view):
593594

594595
def commit(self):
595596
self.update_domain_role_hints()
597+
self.Warning.multiple_targets.clear()
596598
if self.data is not None:
597599
attributes = list(self.used_attrs)
598600
class_var = list(self.class_attrs)
@@ -605,6 +607,7 @@ def commit(self):
605607
self.Outputs.features.send(AttributeList(attributes))
606608
self.info.set_output_summary(len(newdata),
607609
format_summary_details(newdata))
610+
self.Warning.multiple_targets(shown=len(class_var) > 1)
608611
else:
609612
self.output_data = None
610613
self.Outputs.data.send(None)

Orange/widgets/utils/owlearnerwidget.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,14 @@ def set_data(self, data):
136136
self.set_input_summary()
137137

138138
if data is not None and data.domain.class_var is None:
139-
self.Error.data_error("Data has no target variable.\n"
140-
"You can set a target variable with the Select Columns widget.")
139+
if data.domain.class_vars:
140+
self.Error.data_error(
141+
"Data contains multiple target variables.\n"
142+
"Select a single one with the Select Columns widget.")
143+
else:
144+
self.Error.data_error(
145+
"Data has no target variable.\n"
146+
"Select one with the Select Columns widget.")
141147
self.data = None
142148

143149
self.update_model()

Orange/widgets/utils/tests/test_owlearnerwidget.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from unittest.mock import Mock
2+
13
import scipy.sparse as sp
24

35
from orangewidget.widget import StateInfo
@@ -6,7 +8,7 @@
68
from Orange.base import Learner, Model
79
from Orange.classification import KNNLearner
810
from Orange.data import Table, Domain
9-
from Orange.modelling import TreeLearner
11+
from Orange.modelling import TreeLearner, Fitter
1012
from Orange.preprocess import continuize
1113
from Orange.regression import MeanLearner, LinearRegressionLearner
1214
from Orange.widgets.utils.owlearnerwidget import OWBaseLearner
@@ -151,3 +153,52 @@ class WidgetA(OWBaseLearner):
151153
self.send_signal(widget.Inputs.data, None)
152154
self.assertIsInstance(info._StateInfo__input_summary, StateInfo.Empty)
153155
self.assertEqual(info._StateInfo__input_summary.details, no_input)
156+
157+
def test_invalid_number_of_targets(self):
158+
class MockLearner(Fitter):
159+
name = 'mock'
160+
__fits__ = {'classification': Mock()}
161+
__returns__ = Mock()
162+
163+
class WidgetLR(OWBaseLearner):
164+
name = "lr"
165+
LEARNER = MockLearner
166+
167+
w = self.create_widget(WidgetLR)
168+
error = w.Error.data_error
169+
heart = Table("heart_disease")
170+
domain = heart.domain
171+
172+
no_target = heart.transform(
173+
Domain(domain.attributes,
174+
[]))
175+
two_targets = heart.transform(
176+
Domain([domain["age"]],
177+
[domain["gender"], domain["chest pain"]]))
178+
179+
self.send_signal(w.Inputs.data, heart)
180+
self.assertFalse(error.is_shown())
181+
self.assertIs(w.data, heart)
182+
183+
self.send_signal(w.Inputs.data, no_target)
184+
self.assertTrue(error.is_shown())
185+
self.assertIsNone(w.data)
186+
err_no_target = str(error)
187+
self.assertIn("target", err_no_target)
188+
189+
self.send_signal(w.Inputs.data, two_targets)
190+
self.assertTrue(error.is_shown())
191+
self.assertIsNone(w.data)
192+
err_two_targets = str(error)
193+
self.assertIn("target", err_no_target)
194+
self.assertNotEqual(err_no_target, err_two_targets)
195+
196+
self.send_signal(w.Inputs.data, None)
197+
self.assertIsNone(w.data)
198+
self.assertFalse(error.is_shown())
199+
200+
self.send_signal(w.Inputs.data, two_targets)
201+
self.assertTrue(error.is_shown())
202+
203+
self.send_signal(w.Inputs.data, None)
204+
self.assertFalse(error.is_shown())

Orange/widgets/visualize/owfreeviz.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,16 @@ class OWFreeViz(OWAnchorProjectionWidget, ConcurrentWidgetMixin):
139139
graph = settings.SettingProvider(OWFreeVizGraph)
140140

141141
class Error(OWAnchorProjectionWidget.Error):
142-
no_class_var = widget.Msg("Data has no target variable")
142+
no_class_var = widget.Msg("Data must have a target variable.")
143+
multiple_class_vars = widget.Msg(
144+
"Data must have a single target variable.")
143145
not_enough_class_vars = widget.Msg(
144-
"Target variable is not at least binary")
146+
"Target variable must have at least two unique values.")
145147
features_exceeds_instances = widget.Msg(
146148
"Number of features exceeds the number of instances.")
147149
too_many_data_instances = widget.Msg("Data is too large.")
148150
constant_data = widget.Msg("All data columns are constant.")
149-
not_enough_features = widget.Msg("At least two features are required")
151+
not_enough_features = widget.Msg("At least two features are required.")
150152

151153
class Warning(OWAnchorProjectionWidget.Warning):
152154
removed_features = widget.Msg("Categorical features with more than"
@@ -257,10 +259,12 @@ def error(err):
257259

258260
super().check_data()
259261
if self.data is not None:
260-
class_var, domain = self.data.domain.class_var, self.data.domain
261-
if class_var is None:
262+
class_vars, domain = self.data.domain.class_vars, self.data.domain
263+
if not class_vars:
262264
error(self.Error.no_class_var)
263-
elif class_var.is_discrete and len(np.unique(self.data.Y)) < 2:
265+
elif len(class_vars) > 1:
266+
error(self.Error.multiple_class_vars)
267+
elif class_vars[0].is_discrete and len(np.unique(self.data.Y)) < 2:
264268
error(self.Error.not_enough_class_vars)
265269
elif len(self.data.domain.attributes) < 2:
266270
error(self.Error.not_enough_features)

Orange/widgets/visualize/tests/test_owfreeviz.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import numpy as np
77

8-
from Orange.data import Table
8+
from Orange.data import Table, Domain
99
from Orange.projection import FreeViz
1010
from Orange.projection.freeviz import FreeVizModel
1111
from Orange.widgets.tests.base import (
@@ -48,6 +48,41 @@ def test_error_msg(self):
4848
self.assertFalse(self.widget.Error.no_class_var.is_shown())
4949
self.assertFalse(self.widget.Error.not_enough_class_vars.is_shown())
5050

51+
def test_number_of_targets(self):
52+
data = self.heart_disease
53+
domain = data.domain
54+
55+
no_target = data.transform(
56+
Domain(domain.attributes,
57+
[]))
58+
two_targets = data.transform(
59+
Domain([domain["age"]],
60+
[domain["gender"], domain["chest pain"]]))
61+
62+
self.send_signal(self.widget.Inputs.data, data)
63+
self.assertFalse(self.widget.Error.no_class_var.is_shown())
64+
self.assertFalse(self.widget.Error.multiple_class_vars.is_shown())
65+
66+
self.send_signal(self.widget.Inputs.data, no_target)
67+
self.assertTrue(self.widget.Error.no_class_var.is_shown())
68+
self.assertFalse(self.widget.Error.multiple_class_vars.is_shown())
69+
70+
self.send_signal(self.widget.Inputs.data, two_targets)
71+
self.assertFalse(self.widget.Error.no_class_var.is_shown())
72+
self.assertTrue(self.widget.Error.multiple_class_vars.is_shown())
73+
74+
self.send_signal(self.widget.Inputs.data, data)
75+
self.assertFalse(self.widget.Error.no_class_var.is_shown())
76+
self.assertFalse(self.widget.Error.multiple_class_vars.is_shown())
77+
78+
self.send_signal(self.widget.Inputs.data, two_targets)
79+
self.assertFalse(self.widget.Error.no_class_var.is_shown())
80+
self.assertTrue(self.widget.Error.multiple_class_vars.is_shown())
81+
82+
self.send_signal(self.widget.Inputs.data, None)
83+
self.assertFalse(self.widget.Error.no_class_var.is_shown())
84+
self.assertFalse(self.widget.Error.multiple_class_vars.is_shown())
85+
5186
def test_optimization(self):
5287
self.send_signal(self.widget.Inputs.data, self.heart_disease)
5388
self.widget.run_button.click()

0 commit comments

Comments
 (0)