Skip to content

Commit b34cbcb

Browse files
janezdastaric
authored andcommitted
Merge pull request #1966 from ales-erjavec/fixes/impute-reset-default
[FIX] owimpute: Fix editing of individual imputers (cherry picked from commit d262827)
1 parent fb8d36d commit b34cbcb

File tree

2 files changed

+205
-52
lines changed

2 files changed

+205
-52
lines changed

Orange/widgets/data/owimpute.py

Lines changed: 82 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import sys
2+
import copy
3+
24
import numpy as np
35

46
from AnyQt.QtWidgets import (
5-
QWidget, QGroupBox, QRadioButton, QPushButton, QHBoxLayout,
6-
QVBoxLayout, QStackedLayout, QComboBox, QLineEdit,
7+
QGroupBox, QRadioButton, QPushButton, QHBoxLayout,
8+
QVBoxLayout, QStackedWidget, QComboBox,
79
QButtonGroup, QStyledItemDelegate, QListView, QDoubleSpinBox
810
)
911
from AnyQt.QtCore import Qt
@@ -75,13 +77,15 @@ class Error(OWWidget.Error):
7577
_default_method_index = settings.Setting(DO_NOT_IMPUTE)
7678
variable_methods = settings.ContextSetting({})
7779
autocommit = settings.Setting(False)
78-
default_value = settings.Setting(0.)
7980

8081
want_main_area = False
8182
resizing_enabled = False
8283

8384
def __init__(self):
8485
super().__init__()
86+
# copy METHODS (some are modified by the widget)
87+
self.methods = copy.deepcopy(OWImpute.METHODS)
88+
8589
main_layout = QVBoxLayout()
8690
main_layout.setContentsMargins(10, 10, 10, 10)
8791
self.controlArea.layout().addLayout(main_layout)
@@ -92,7 +96,7 @@ def __init__(self):
9296

9397
button_group = QButtonGroup()
9498
button_group.buttonClicked[int].connect(self.set_default_method)
95-
for i, method in enumerate(self.METHODS):
99+
for i, method in enumerate(self.methods):
96100
if not method.columns_only:
97101
button = QRadioButton(method.name)
98102
button.setChecked(i == self.default_method_index)
@@ -125,7 +129,7 @@ def __init__(self):
125129
horizontal_layout.addLayout(method_layout)
126130

127131
button_group = QButtonGroup()
128-
for i, method in enumerate(self.METHODS):
132+
for i, method in enumerate(self.methods):
129133
button = QRadioButton(text=method.name)
130134
button_group.addButton(button, i)
131135
method_layout.addWidget(button)
@@ -135,16 +139,14 @@ def __init__(self):
135139
sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength,
136140
activated=self._on_value_selected
137141
)
138-
self.value_combo.currentIndexChanged.connect(self._on_value_changed)
139142
self.value_double = QDoubleSpinBox(
140143
editingFinished=self._on_value_selected,
141144
minimum=-1000., maximum=1000., singleStep=.1, decimals=3,
142-
value=self.default_value
143145
)
144-
self.value_stack = value_stack = QStackedLayout()
146+
self.value_stack = value_stack = QStackedWidget()
145147
value_stack.addWidget(self.value_combo)
146148
value_stack.addWidget(self.value_double)
147-
method_layout.addLayout(value_stack)
149+
method_layout.addWidget(value_stack)
148150

149151
button_group.buttonClicked[int].connect(
150152
self.set_method_for_current_selection
@@ -167,9 +169,9 @@ def __init__(self):
167169
box.layout().insertWidget(0, self.report_button)
168170

169171
self.data = None
172+
self.learner = None
170173
self.modified = False
171-
self.default_method = self.METHODS[self.default_method_index]
172-
self.update_varview()
174+
self.default_method = self.methods[self.default_method_index]
173175

174176
@property
175177
def default_method_index(self):
@@ -180,14 +182,14 @@ def default_method_index(self, index):
180182
if self._default_method_index != index:
181183
self._default_method_index = index
182184
self.default_button_group.button(index).setChecked(True)
183-
self.default_method = self.METHODS[self.default_method_index]
184-
self.METHODS[self.DEFAULT].method = self.default_method
185+
self.default_method = self.methods[self.default_method_index]
186+
self.methods[self.DEFAULT].method = self.default_method
185187

186188
# update variable view
187189
for index in map(self.varmodel.index, range(len(self.varmodel))):
188-
self.varmodel.setData(index,
189-
self.variable_methods.get(index.row(), self.METHODS[self.DEFAULT]),
190-
Qt.UserRole)
190+
method = self.variable_methods.get(
191+
index.row(), self.methods[self.DEFAULT])
192+
self.varmodel.setData(index, method, Qt.UserRole)
191193
self._invalidate()
192194

193195
def set_default_method(self, index):
@@ -212,7 +214,7 @@ def set_data(self, data):
212214

213215
def set_learner(self, learner):
214216
self.learner = learner or self.DEFAULT_LEARNER
215-
imputer = self.METHODS[self.MODEL_BASED_IMPUTER]
217+
imputer = self.methods[self.MODEL_BASED_IMPUTER]
216218
imputer.learner = self.learner
217219

218220
button = self.default_button_group.button(self.MODEL_BASED_IMPUTER)
@@ -224,6 +226,7 @@ def set_learner(self, learner):
224226
if learner is not None:
225227
self.default_method_index = self.MODEL_BASED_IMPUTER
226228

229+
self.update_varview()
227230
self.commit()
228231

229232
def get_method_for_column(self, column_index):
@@ -233,7 +236,7 @@ def get_method_for_column(self, column_index):
233236
column_index = column_index.row()
234237

235238
return self.variable_methods.get(column_index,
236-
self.METHODS[self.DEFAULT])
239+
self.methods[self.DEFAULT])
237240

238241
def _invalidate(self):
239242
self.modified = True
@@ -267,7 +270,7 @@ def commit(self):
267270
drop_mask |= method(self.data, var)
268271
else:
269272
var = method(self.data, var)
270-
except:
273+
except Exception: # pylint: disable=broad-except
271274
self.Error.imputation_failed(var.name)
272275
attributes = class_vars = None
273276
break
@@ -310,40 +313,74 @@ def send_report(self):
310313

311314
def _on_var_selection_changed(self):
312315
indexes = self.selection.selectedIndexes()
313-
methods = set(self.get_method_for_column(i.row()).name for i in indexes)
316+
methods = [self.get_method_for_column(i.row()) for i in indexes]
317+
318+
def method_key(method):
319+
"""
320+
Decompose method into its type and parameters.
321+
"""
322+
# The return value should be hashable and __eq__ comparable
323+
if isinstance(method, AsDefault):
324+
return AsDefault, (method.method,)
325+
elif isinstance(method, impute.Model):
326+
return impute.Model, (method.learner,)
327+
elif isinstance(method, impute.Default):
328+
return impute.Default, (method.default,)
329+
else:
330+
return type(method), None
314331

332+
methods = set(method_key(m) for m in methods)
315333
selected_vars = [self.varmodel[index.row()] for index in indexes]
316334
has_discrete = any(var.is_discrete for var in selected_vars)
335+
fixed_value = None
336+
value_stack_enabled = False
337+
current_value_widget = None
317338

318339
if len(methods) == 1:
319-
method = methods.pop()
320-
for i, m in enumerate(self.METHODS):
321-
if method == m.name:
340+
method_type, parameters = methods.pop()
341+
for i, m in enumerate(self.methods):
342+
if method_type == type(m):
322343
self.variable_button_group.button(i).setChecked(True)
344+
345+
if method_type is impute.Default:
346+
(fixed_value,) = parameters
347+
323348
elif self.variable_button_group.checkedButton() is not None:
349+
# Uncheck the current button
324350
self.variable_button_group.setExclusive(False)
325351
self.variable_button_group.checkedButton().setChecked(False)
326352
self.variable_button_group.setExclusive(True)
353+
assert self.variable_button_group.checkedButton() is None
327354

328-
for method, button in zip(self.METHODS,
355+
for method, button in zip(self.methods,
329356
self.variable_button_group.buttons()):
330357
enabled = all(method.supports_variable(var) for var in
331358
selected_vars)
332359
button.setEnabled(enabled)
333360

334361
if not has_discrete:
335-
self.value_stack.setEnabled(True)
336-
self.value_stack.setCurrentWidget(self.value_double)
337-
self._on_value_changed()
362+
value_stack_enabled = True
363+
current_value_widget = self.value_double
338364
elif len(selected_vars) == 1:
339-
self.value_stack.setEnabled(True)
340-
self.value_stack.setCurrentWidget(self.value_combo)
365+
value_stack_enabled = True
366+
current_value_widget = self.value_combo
341367
self.value_combo.clear()
342368
self.value_combo.addItems(selected_vars[0].values)
343-
self._on_value_changed()
344369
else:
370+
value_stack_enabled = False
371+
current_value_widget = None
345372
self.variable_button_group.button(self.AS_INPUT).setEnabled(False)
346-
self.value_stack.setEnabled(False)
373+
374+
self.value_stack.setEnabled(value_stack_enabled)
375+
if current_value_widget is not None:
376+
self.value_stack.setCurrentWidget(current_value_widget)
377+
if fixed_value is not None:
378+
if current_value_widget is self.value_combo:
379+
self.value_combo.setCurrentIndex(fixed_value)
380+
elif current_value_widget is self.value_double:
381+
self.value_double.setValue(fixed_value)
382+
else:
383+
assert False
347384

348385
def set_method_for_current_selection(self, method_index):
349386
indexes = self.selection.selectedIndexes()
@@ -352,9 +389,18 @@ def set_method_for_current_selection(self, method_index):
352389
def set_method_for_indexes(self, indexes, method_index):
353390
if method_index == self.DEFAULT:
354391
for index in indexes:
355-
self.variable_methods.pop(index, None)
392+
self.variable_methods.pop(index.row(), None)
393+
elif method_index == OWImpute.AS_INPUT:
394+
current = self.value_stack.currentWidget()
395+
if current is self.value_combo:
396+
value = self.value_combo.currentIndex()
397+
else:
398+
value = self.value_double.value()
399+
for index in indexes:
400+
method = impute.Default(default=value)
401+
self.variable_methods[index.row()] = method
356402
else:
357-
method = self.METHODS[method_index].copy()
403+
method = self.methods[method_index].copy()
358404
for index in indexes:
359405
self.variable_methods[index.row()] = method
360406

@@ -369,24 +415,12 @@ def update_varview(self, indexes=None):
369415
self.varmodel.setData(index, self.get_method_for_column(index.row()), Qt.UserRole)
370416

371417
def _on_value_selected(self):
418+
# The fixed 'Value' in the widget has been changed by the user.
372419
self.variable_button_group.button(self.AS_INPUT).setChecked(True)
373-
self._on_value_changed()
374-
375-
def _on_value_changed(self):
376-
widget = self.value_stack.currentWidget()
377-
if widget is self.value_combo:
378-
value = self.value_combo.currentText()
379-
else:
380-
value = self.value_double.value()
381-
self.default_value = value
382-
383-
self.METHODS[self.AS_INPUT].default = value
384-
index = self.variable_button_group.checkedId()
385-
if index == self.AS_INPUT:
386-
self.set_method_for_current_selection(index)
420+
self.set_method_for_current_selection(self.AS_INPUT)
387421

388422
def reset_variable_methods(self):
389-
indexes = map(self.varmodel.index, range(len(self.varmodel)))
423+
indexes = list(map(self.varmodel.index, range(len(self.varmodel))))
390424
self.set_method_for_indexes(indexes, self.DEFAULT)
391425
self.variable_button_group.button(self.DEFAULT).setChecked(True)
392426

0 commit comments

Comments
 (0)