File: owfeatureaspredictor.py

package info (click to toggle)
orange3 3.40.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 15,908 kB
  • sloc: python: 162,745; ansic: 622; makefile: 322; sh: 93; cpp: 77
file content (179 lines) | stat: -rw-r--r-- 6,119 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
from itertools import chain

from AnyQt.QtWidgets import QComboBox, QCheckBox

from orangewidget import gui
from orangewidget.settings import Setting
from orangewidget.widget import Msg

from Orange.data import Variable, Table
from Orange.modelling.column import (
    ColumnModel, ColumnLearner, valid_value_sets, valid_prob_range)
from Orange.widgets.widget import OWWidget, Input, Output
from Orange.widgets.utils.itemmodels import VariableListModel
from Orange.widgets.utils.widgetpreview import WidgetPreview


class OWFeatureAsPredictor(OWWidget):
    name = "Feature as Predictor"
    description = "Use a column as probabilities or predictions"
    icon = "icons/FeatureAsPredictor.svg"
    priority = 1000
    keywords = "column predictor"

    want_main_area = False
    resizing_enabled = False

    class Inputs:
        data = Input("Data", Table)

    class Outputs:
        learner = Output("Learner", ColumnLearner)
        model = Output("Model", ColumnModel)

    class Error(OWWidget.Error):
        no_class = Msg("Data has no target variable.")
        no_variables = Msg("No useful variables")

    column_hint: Variable = Setting(None, schema_only=True)
    # Stores the last user setting.
    # apply_transformation tells what will actually happens;
    # checkbox may be disabled and set to reflect apply_transformation.
    apply_transformation_setting = Setting(False)
    auto_apply = Setting(True)

    def __init__(self):
        super().__init__()
        self.data = None
        self.column = None
        self.apply_transformation = False
        self.pars_to_report = (False, False)

        box = gui.vBox(self.controlArea, True)

        self.column_combo = combo = QComboBox()
        combo.setModel(VariableListModel())
        box.layout().addWidget(combo)
        @combo.activated.connect
        def on_column_changed(index):
            self.column = combo.model()[index]
            self.column_hint = self.column.name
            self._update_controls()
            self.commit.deferred()

        self.cb_transformation = cb = QCheckBox("", self)
        box.layout().addWidget(cb)
        @cb.clicked.connect
        def on_apply_transformation_changed(checked):
            self.apply_transformation_setting \
                = self.apply_transformation = checked
            self.commit.deferred()

        gui.auto_apply(self.controlArea, self, "auto_apply")
        self._update_controls()

    def _update_controls(self):
        cb = self.cb_transformation
        data = self.data

        if data is None or self.column is None:
            cb.setChecked(self.apply_transformation_setting)
            cb.setDisabled(False)
            return

        if self.column.is_discrete:
            self.apply_transformation = False
            cb.setChecked(False)
            cb.setDisabled(True)
        elif (data.domain.class_var.is_discrete
                and not valid_prob_range(data.get_column(self.column))):
            self.apply_transformation = True
            cb.setChecked(True)
            cb.setDisabled(True)
        else:
            self.apply_transformation = self.apply_transformation_setting
            cb.setChecked(self.apply_transformation_setting)
            cb.setDisabled(False)

        shape = "logistic" if data.domain.class_var.is_discrete else "linear"
        cb.setText(f"Transform through {shape} function")
        cb.setToolTip(f"Use {shape} regression to fit the model's coefficients")

    @Inputs.data
    def set_data(self, data):
        self._set_data(data)
        self._update_controls()
        self.commit.now()

    def _set_data(self, data):
        column_model: VariableListModel = self.column_combo.model()

        self.Error.clear()
        column_model.clear()
        self.column = None
        self.data = None

        if data is None:
            return

        class_var = data.domain.class_var
        if class_var is None:
            self.Error.no_class()
            return

        allow_continuous = (class_var.is_continuous
                            or len(class_var.values) == 2)
        column_model[:] = (
            var
            for var in chain(data.domain.attributes, data.domain.metas)
            if (var.is_continuous and allow_continuous
                or (var.is_discrete and class_var.is_discrete
                    and valid_value_sets(class_var, var))
                )
        )
        if not column_model:
            self.Error.no_variables()
            return

        self.data = data
        if self.column_hint \
                and self.column_hint in self.data.domain \
                and (var := self.data.domain[self.column_hint]) in column_model:
            self.column = var
            self.column_combo.setCurrentIndex(column_model.indexOf(self.column))
        else:
            self.column = column_model[0]
            self.column_combo.setCurrentIndex(0)
            self.column_hint = self.column.name

    @gui.deferred
    def commit(self):
        self.pars_to_report = (False, False)
        if self.column is None:
            self.Outputs.learner.send(None)
            self.Outputs.model.send(None)
            return

        learner = ColumnLearner(
            self.data.domain.class_var, self.column, self.apply_transformation)
        model = learner(self.data)
        self.Outputs.learner.send(learner)
        self.Outputs.model.send(model)
        if self.apply_transformation:
            self.pars_to_report = (model.intercept, model.coefficient)

    def send_report(self):
        if self.column is None:
            return
        self.report_items((
            ("Predict values from", self.column.name),
            ("Applied transformation",
             self.apply_transformation and self.data is not None and
             ("logistic" if self.data.domain.class_var.is_discrete else "linear")),
            ("Intercept", self.pars_to_report[0]),
            ("Coefficient", self.pars_to_report[1])
        ))


if __name__ == "__main__":  # pragma: no cover
    WidgetPreview(OWFeatureAsPredictor).run(Table("heart_disease"))