Skip to content

Commit a7abf4f

Browse files
committed
OWImpute; Add general default for numeric and time variables
1 parent 0073703 commit a7abf4f

File tree

2 files changed

+100
-8
lines changed

2 files changed

+100
-8
lines changed

Orange/widgets/data/owimpute.py

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
from AnyQt.QtWidgets import (
1212
QGroupBox, QRadioButton, QPushButton, QHBoxLayout, QGridLayout,
1313
QVBoxLayout, QStackedWidget, QComboBox,
14-
QButtonGroup, QStyledItemDelegate, QListView, QDoubleSpinBox
15-
)
16-
from AnyQt.QtCore import Qt, QThread, QModelIndex
14+
QButtonGroup, QStyledItemDelegate, QListView, QDoubleSpinBox, QLabel)
15+
from AnyQt.QtCore import Qt, QThread, QModelIndex, QDateTime, QLocale
1716
from AnyQt.QtCore import pyqtSlot as Slot
17+
from AnyQt.QtGui import QDoubleValidator
18+
1819
from orangewidget.utils.listview import ListViewSearch
1920

2021
import Orange.data
@@ -154,6 +155,8 @@ class Warning(OWWidget.Warning):
154155
_variable_imputation_state = settings.ContextSetting({}) # type: VariableState
155156

156157
autocommit = settings.Setting(True)
158+
default_numeric = settings.Setting("")
159+
default_time = settings.Setting(0)
157160

158161
want_main_area = False
159162
resizing_enabled = False
@@ -171,11 +174,13 @@ def __init__(self):
171174
main_layout.setContentsMargins(10, 10, 10, 10)
172175
self.controlArea.layout().addLayout(main_layout)
173176

174-
box = QGroupBox(title=self.tr("Default Method"), flat=False)
175-
box_layout = QGridLayout(box)
176-
box_layout.setContentsMargins(5, 0, 0, 0)
177+
box = gui.vBox(None, "Default Method")
177178
main_layout.addWidget(box)
178179

180+
box_layout = QGridLayout(box)
181+
box_layout.setSpacing(8)
182+
box.layout().addLayout(box_layout)
183+
179184
button_group = QButtonGroup()
180185
button_group.buttonClicked[int].connect(self.set_default_method)
181186

@@ -186,6 +191,41 @@ def __init__(self):
186191
button_group.addButton(button, method)
187192
box_layout.addWidget(button, i % 3, i // 3)
188193

194+
def set_to_fixed_value():
195+
self.set_default_method(Method.Default)
196+
197+
def set_default_time(datetime):
198+
self.default_time = datetime.toSecsSinceEpoch()
199+
if self.default_method_index != Method.Default:
200+
set_to_fixed_value()
201+
else:
202+
self._invalidate()
203+
204+
hlayout = QHBoxLayout()
205+
box.layout().addLayout(hlayout)
206+
button = QRadioButton("Fixed values; numeric variables:")
207+
button_group.addButton(button, Method.Default)
208+
button.setChecked(Method.Default == self.default_method_index)
209+
hlayout.addWidget(button)
210+
211+
locale = QLocale()
212+
locale.setNumberOptions(locale.NumberOption.RejectGroupSeparator)
213+
validator = QDoubleValidator()
214+
validator.setLocale(locale)
215+
le = gui.lineEdit(
216+
None, self, "default_numeric",
217+
validator=validator, alignment=Qt.AlignRight,
218+
callback=self._invalidate, focusInCallback=set_to_fixed_value)
219+
hlayout.addWidget(le)
220+
221+
hlayout.addWidget(QLabel(", time:"))
222+
223+
self.time_widget = gui.DateTimeEditWCalendarTime(self)
224+
self.time_widget.setContentsMargins(0, 0, 0, 0)
225+
self.default_time = QDateTime.currentDateTime().toSecsSinceEpoch()
226+
self.time_widget.dateTimeChanged.connect(set_default_time)
227+
hlayout.addWidget(self.time_widget)
228+
189229
self.default_button_group = button_group
190230

191231
box = QGroupBox(title=self.tr("Individual Attribute Settings"),
@@ -267,6 +307,17 @@ def create_imputer(self, method, *args):
267307
m = AsDefault()
268308
m.method = default
269309
return m
310+
elif method == Method.Default and not args: # global default values
311+
if self.default_numeric == "":
312+
default_num = np.nan
313+
else:
314+
default_num, ok = QLocale().toDouble(self.default_numeric)
315+
if not ok:
316+
default_num = np.nan
317+
return impute.FixedValueByType(
318+
default_continuous=default_num,
319+
default_time=self.default_time or np.nan
320+
)
270321
else:
271322
return METHODS[method](*args)
272323

@@ -302,6 +353,8 @@ def set_data(self, data):
302353
if data is not None:
303354
self.varmodel[:] = data.domain.variables
304355
self.openContext(data.domain)
356+
self.time_widget.set_datetime(
357+
QDateTime.fromSecsSinceEpoch(self.default_time))
305358
# restore per variable imputation state
306359
self._restore_state(self._variable_imputation_state)
307360

@@ -660,5 +713,19 @@ def storeSpecificSettings(self):
660713
super().storeSpecificSettings()
661714

662715

716+
def __sample_data(): # pragma: no cover
717+
domain = Orange.data.Domain(
718+
[Orange.data.ContinuousVariable(f"c{i}") for i in range(3)]
719+
+ [Orange.data.TimeVariable(f"t{i}") for i in range(3)],
720+
[])
721+
n = np.nan
722+
x = np.array([
723+
[1, 2, n, 1000, n, n],
724+
[2, n, 1, n, 2000, 2000]
725+
])
726+
return Orange.data.Table(domain, x, np.empty((2, 0)))
727+
728+
663729
if __name__ == "__main__": # pragma: no cover
730+
# WidgetPreview(OWImpute).run(__sample_data())
664731
WidgetPreview(OWImpute).run(Orange.data.Table("brown-selected"))

Orange/widgets/data/tests/test_owimpute.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
from unittest.mock import Mock
44
import numpy as np
55

6-
from AnyQt.QtCore import Qt, QItemSelection
6+
from AnyQt.QtCore import Qt, QItemSelection, QLocale
77
from AnyQt.QtTest import QTest
88

9-
from Orange.data import Table, Domain
9+
from Orange.data import Table, Domain, ContinuousVariable, TimeVariable
1010
from Orange.preprocess import impute
1111
from Orange.widgets.data.owimpute import OWImpute, AsDefault, Learner, Method
1212
from Orange.widgets.tests.base import WidgetTest
@@ -119,6 +119,31 @@ def test_select_method(self):
119119
self.assertIsInstance(widget.get_method_for_column(0), AsDefault)
120120
self.assertIsInstance(widget.get_method_for_column(2), AsDefault)
121121

122+
def test_overall_default(self):
123+
domain = Domain(
124+
[ContinuousVariable(f"c{i}") for i in range(3)]
125+
+ [TimeVariable(f"t{i}") for i in range(3)],
126+
[])
127+
n = np.nan
128+
x = np.array([
129+
[1, 2, n, 1000, n, n],
130+
[2, n, 1, n, 2000, 2000]
131+
])
132+
data = Table(domain, x, np.empty((2, 0)))
133+
134+
widget = self.widget
135+
widget.default_numeric = QLocale().toString(3.14)
136+
widget.default_time = 42
137+
widget.default_method_index = Method.Default
138+
139+
self.send_signal(self.widget.Inputs.data, data)
140+
imp_data = self.get_output(self.widget.Outputs.data)
141+
np.testing.assert_almost_equal(
142+
imp_data.X,
143+
[[1, 2, 3.14, 1000, 42, 42],
144+
[2, 3.14, 1, 42, 2000, 2000]]
145+
)
146+
122147
def test_value_edit(self):
123148
data = Table("heart_disease")[::10]
124149
self.send_signal(self.widget.Inputs.data, data)

0 commit comments

Comments
 (0)