Skip to content

Commit a08cf60

Browse files
committed
Base learner widget: Improve apply; and warn when overriding preprocessors
1 parent 3408e32 commit a08cf60

File tree

2 files changed

+96
-11
lines changed

2 files changed

+96
-11
lines changed

Orange/widgets/utils/owlearnerwidget.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ class Error(OWWidget.Error):
7979
class Warning(OWWidget.Warning):
8080
outdated_learner = Msg("Press Apply to submit changes.")
8181

82+
class Information(OWWidget.Information):
83+
ignored_preprocessors = Msg(
84+
"Ignoring default preprocessing.\n"
85+
"Default preprocessing, such as scaling, one-hot encoding and "
86+
"treatment of missing data, has been replaced with user-specified "
87+
"preprocessors. Problems may occur if these are inadequate "
88+
"for the given data.")
89+
8290
class Inputs:
8391
data = Input("Data", Table)
8492
preprocessor = Input("Preprocessor", Preprocess)
@@ -144,7 +152,8 @@ def set_default_learner_name(self, name: str) -> None:
144152
@Inputs.preprocessor
145153
def set_preprocessor(self, preprocessor):
146154
self.preprocessors = preprocessor
147-
self.apply()
155+
# invalidate learner and model, so handleNewSignals will renew them
156+
self.learner = self.model = None
148157

149158
@Inputs.data
150159
@check_sql_input
@@ -164,12 +173,15 @@ def set_data(self, data):
164173
"Select one with the Select Columns widget.")
165174
self.data = None
166175

167-
self.update_model()
176+
# invalidate the model so that handleNewSignals will update it
177+
self.model = None
168178

169-
def apply(self):
179+
def apply(self, *, hard=True):
170180
"""Applies learner and sends new model."""
171-
self.update_learner()
172-
self.update_model()
181+
if hard or self.learner is None:
182+
self.update_learner()
183+
if hard or self.model is None:
184+
self.update_model()
173185

174186
def update_learner(self):
175187
self.learner = self.create_learner()
@@ -181,6 +193,13 @@ def update_learner(self):
181193
self.outdated_settings = False
182194
self.Warning.outdated_learner.clear()
183195

196+
def handleNewSignals(self):
197+
self.unconditional_apply(hard=False)
198+
self.Information.ignored_preprocessors(
199+
shown=not getattr(self.learner, "use_default_preprocessors", False)
200+
and getattr(self.LEARNER, "preprocessors", False)
201+
and self.preprocessors is not None)
202+
184203
def show_fitting_failed(self, exc):
185204
"""Show error when fitting fails.
186205
Derived widgets can override this to show more specific messages."""
@@ -226,8 +245,7 @@ def settings_changed(self, *args, **kwargs):
226245
def _change_name(self, instance, output):
227246
if instance:
228247
instance.name = self.effective_learner_name()
229-
if self.auto_apply:
230-
output.send(instance)
248+
self.apply()
231249

232250
def learner_name_changed(self):
233251
self._change_name(self.learner, self.Outputs.learner)
@@ -272,7 +290,6 @@ def add_main_layout(self):
272290
Override this method for laying out any learner-specific parameter controls.
273291
See setup_layout() method for execution order.
274292
"""
275-
pass
276293

277294
def add_classification_layout(self, box):
278295
"""Creates layout for classification specific options.
@@ -281,7 +298,6 @@ def add_classification_layout(self, box):
281298
and regression learners require different options.
282299
See `setup_layout()` method for execution order.
283300
"""
284-
pass
285301

286302
def add_regression_layout(self, box):
287303
"""Creates layout for regression specific options.
@@ -290,7 +306,6 @@ def add_regression_layout(self, box):
290306
and regression learners require different options.
291307
See `setup_layout()` method for execution order.
292308
"""
293-
pass
294309

295310
def add_learner_name_widget(self):
296311
self.name_line_edit = gui.lineEdit(

Orange/widgets/utils/tests/test_owlearnerwidget.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from unittest.mock import Mock
1+
from unittest.mock import Mock, patch
22

33
import scipy.sparse as sp
44

@@ -218,3 +218,73 @@ def check_name(name):
218218
check_name("Bar")
219219
w.set_default_learner_name("")
220220
check_name("Blarg")
221+
222+
def test_preprocessor_warning(self):
223+
class TestLearnerNoPreprocess(Learner):
224+
name = "Test"
225+
__returns__ = Mock()
226+
227+
class TestWidgetNoPreprocess(OWBaseLearner):
228+
name = "Test"
229+
LEARNER = TestLearnerNoPreprocess
230+
231+
class TestLearnerPreprocess(Learner):
232+
name = "Test"
233+
preprocessors = [Mock()]
234+
__returns__ = Mock()
235+
236+
class TestWidgetPreprocess(OWBaseLearner):
237+
name = "Test"
238+
LEARNER = TestLearnerPreprocess
239+
240+
class TestFitterPreprocess(Fitter):
241+
name = "Test"
242+
preprocessors = [Mock()]
243+
__returns__ = Mock()
244+
245+
class TestWidgetPreprocessFit(OWBaseLearner):
246+
name = "Test"
247+
LEARNER = TestFitterPreprocess
248+
249+
wno = self.create_widget(TestWidgetNoPreprocess)
250+
wyes = self.create_widget(TestWidgetPreprocess)
251+
wfit = self.create_widget(TestWidgetPreprocessFit)
252+
253+
self.assertFalse(wno.Information.ignored_preprocessors.is_shown())
254+
self.assertFalse(wyes.Information.ignored_preprocessors.is_shown())
255+
self.assertFalse(wfit.Information.ignored_preprocessors.is_shown())
256+
257+
pp = continuize.Continuize()
258+
self.send_signal(wno.Inputs.preprocessor, pp)
259+
self.send_signal(wyes.Inputs.preprocessor, pp)
260+
self.send_signal(wfit.Inputs.preprocessor, pp)
261+
262+
self.assertFalse(wno.Information.ignored_preprocessors.is_shown())
263+
self.assertTrue(wyes.Information.ignored_preprocessors.is_shown())
264+
self.assertFalse(wfit.Information.ignored_preprocessors.is_shown())
265+
266+
self.send_signal(wno.Inputs.preprocessor, None)
267+
self.send_signal(wyes.Inputs.preprocessor, None)
268+
self.send_signal(wfit.Inputs.preprocessor, None)
269+
270+
self.assertFalse(wno.Information.ignored_preprocessors.is_shown())
271+
self.assertFalse(wyes.Information.ignored_preprocessors.is_shown())
272+
self.assertFalse(wfit.Information.ignored_preprocessors.is_shown())
273+
274+
def test_multiple_sends(self):
275+
class TestLearner(Learner):
276+
name = "Test"
277+
__returns__ = Mock()
278+
279+
class TestWidget(OWBaseLearner):
280+
name = "Test"
281+
LEARNER = TestLearner
282+
283+
widget = self.create_widget(TestWidget)
284+
pp = continuize.Continuize()
285+
with patch.object(widget.Outputs.learner, "send") as model_send, \
286+
patch.object(widget.Outputs.model, "send") as learner_send:
287+
self.send_signals([(widget.Inputs.data, self.iris),
288+
(widget.Inputs.preprocessor, pp)])
289+
learner_send.assert_called_once()
290+
model_send.assert_called_once()

0 commit comments

Comments
 (0)