Skip to content

Commit

Permalink
OWImpute; Add general default for numeric and time variables
Browse files Browse the repository at this point in the history
  • Loading branch information
janezd committed Dec 4, 2020
1 parent 0b5b0d8 commit b240aae
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 7 deletions.
79 changes: 73 additions & 6 deletions Orange/widgets/data/owimpute.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
from AnyQt.QtWidgets import (
QGroupBox, QRadioButton, QPushButton, QHBoxLayout, QGridLayout,
QVBoxLayout, QStackedWidget, QComboBox,
QButtonGroup, QStyledItemDelegate, QListView, QDoubleSpinBox
)
from AnyQt.QtCore import Qt, QThread, QModelIndex
QButtonGroup, QStyledItemDelegate, QListView, QDoubleSpinBox, QLabel)
from AnyQt.QtCore import Qt, QThread, QModelIndex, QDateTime, QLocale
from AnyQt.QtCore import pyqtSlot as Slot
from AnyQt.QtGui import QDoubleValidator

from orangewidget.utils.listview import ListViewSearch

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

autocommit = settings.Setting(True)
default_numeric = settings.Setting("")
default_time = settings.Setting(0)

want_main_area = False
resizing_enabled = False
Expand All @@ -171,11 +174,13 @@ def __init__(self):
main_layout.setContentsMargins(10, 10, 10, 10)
self.controlArea.layout().addLayout(main_layout)

box = QGroupBox(title=self.tr("Default Method"), flat=False)
box_layout = QGridLayout(box)
box_layout.setContentsMargins(5, 0, 0, 0)
box = gui.vBox(None, "Default Method")
main_layout.addWidget(box)

box_layout = QGridLayout(box)
box_layout.setSpacing(8)
box.layout().addLayout(box_layout)

button_group = QButtonGroup()
button_group.buttonClicked[int].connect(self.set_default_method)

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

def set_to_fixed_value():
self.set_default_method(Method.Default)

def set_default_time(datetime):
self.default_time = datetime.toMSecsSinceEpoch()
if self.default_method_index != Method.Default:
set_to_fixed_value()
else:
self._invalidate()

hlayout = QHBoxLayout()
box.layout().addLayout(hlayout)
button = QRadioButton("Fixed values; numeric variables:")
button_group.addButton(button, Method.Default)
button.setChecked(Method.Default == self.default_method_index)
hlayout.addWidget(button)

locale = QLocale()
locale.setNumberOptions(locale.NumberOption.RejectGroupSeparator)
validator = QDoubleValidator()
validator.setLocale(locale)
le = gui.lineEdit(
None, self, "default_numeric",
validator=validator, alignment=Qt.AlignRight,
callback=self._invalidate, focusInCallback=set_to_fixed_value)
hlayout.addWidget(le)

hlayout.addWidget(QLabel(", time:"))

self.time_widget = gui.DateTimeEditWCalendarTime(self)
self.time_widget.setContentsMargins(0, 0, 0, 0)
self.default_time = QDateTime.currentDateTime().toMSecsSinceEpoch()
self.time_widget.dateTimeChanged.connect(set_default_time)
hlayout.addWidget(self.time_widget)

self.default_button_group = button_group

box = QGroupBox(title=self.tr("Individual Attribute Settings"),
Expand Down Expand Up @@ -267,6 +307,17 @@ def create_imputer(self, method, *args):
m = AsDefault()
m.method = default
return m
elif method == Method.Default and not args: # global default values
if self.default_numeric == "":
default_num = np.nan
else:
default_num, ok = QLocale().toDouble(self.default_numeric)
if not ok:
default_num = np.nan
return impute.FixedValueByType(
default_continuous=default_num,
default_time=self.default_time or np.nan
)
else:
return METHODS[method](*args)

Expand Down Expand Up @@ -302,6 +353,8 @@ def set_data(self, data):
if data is not None:
self.varmodel[:] = data.domain.variables
self.openContext(data.domain)
self.time_widget.set_datetime(
QDateTime.fromMSecsSinceEpoch(self.default_time))
# restore per variable imputation state
self._restore_state(self._variable_imputation_state)

Expand Down Expand Up @@ -660,5 +713,19 @@ def storeSpecificSettings(self):
super().storeSpecificSettings()


def __sample_data(): # pragma: no cover
domain = Orange.data.Domain(
[Orange.data.ContinuousVariable(f"c{i}") for i in range(3)]
+ [Orange.data.TimeVariable(f"t{i}") for i in range(3)],
[])
n = np.nan
x = np.array([
[1, 2, n, 1000, n, n],
[2, n, 1, n, 2000, 2000]
])
return Orange.data.Table(domain, x, np.empty((2, 0)))


if __name__ == "__main__": # pragma: no cover
# WidgetPreview(OWImpute).run(__sample_data())
WidgetPreview(OWImpute).run(Orange.data.Table("brown-selected"))
27 changes: 26 additions & 1 deletion Orange/widgets/data/tests/test_owimpute.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from AnyQt.QtCore import Qt, QItemSelection
from AnyQt.QtTest import QTest

from Orange.data import Table, Domain
from Orange.data import Table, Domain, ContinuousVariable, TimeVariable
from Orange.preprocess import impute
from Orange.widgets.data.owimpute import OWImpute, AsDefault, Learner, Method
from Orange.widgets.tests.base import WidgetTest
Expand Down Expand Up @@ -119,6 +119,31 @@ def test_select_method(self):
self.assertIsInstance(widget.get_method_for_column(0), AsDefault)
self.assertIsInstance(widget.get_method_for_column(2), AsDefault)

def test_overall_default(self):
domain = Domain(
[ContinuousVariable(f"c{i}") for i in range(3)]
+ [TimeVariable(f"t{i}") for i in range(3)],
[])
n = np.nan
x = np.array([
[1, 2, n, 1000, n, n],
[2, n, 1, n, 2000, 2000]
])
data = Table(domain, x, np.empty((2, 0)))

widget = self.widget
widget.default_numeric = 3.14
widget.default_time = 42
widget.default_method_index = Method.Default

self.send_signal(self.widget.Inputs.data, data)
imp_data = self.get_output(self.widget.Outputs.data)
np.testing.assert_almost_equal(
imp_data.X,
[[1, 2, 3.14, 1000, 42, 42],
[2, 3.14, 1, 42, 2000, 2000]]
)

def test_value_edit(self):
data = Table("heart_disease")[::10]
self.send_signal(self.widget.Inputs.data, data)
Expand Down

0 comments on commit b240aae

Please sign in to comment.