source: orange/Orange/OrangeWidgets/Data/OWImpute.py @ 11748:467f952c108d

Revision 11748:467f952c108d, 17.5 KB checked in by blaz <blaz.zupan@…>, 6 months ago (diff)

Changes in headers, widget descriptions text.

Line 
1import OWGUI
2from OWWidget import *
3
4NAME = "Impute"
5DESCRIPTION = "Imputes missing values in the data table."
6LONG_DESCRIPTION = ""
7ICON = "icons/Impute.svg"
8PRIORITY = 2130
9AUTHOR = "Janez Demsar"
10AUTHOR_EMAIL = "janez.demsar(@at@)fri.uni-lj.si"
11INPUTS = [("Data", Orange.data.Table, "setData"),
12          ("Learner for Imputation", orange.Learner, "setModel")]
13OUTPUTS = [("Data", Orange.data.Table, ),
14           ("Imputer", orange.ImputerConstructor, )]
15
16
17class ImputeListItemDelegate(QItemDelegate):
18    def __init__(self, widget, parent = None):
19        QItemDelegate.__init__(self, parent)
20        self.widget = widget
21
22    def drawDisplay(self, painter, option, rect, text):
23        text = str(text)
24        meth, val = self.widget.methods.get(text, (0, None))
25        if meth:
26            if meth == 2:
27                ntext = self.widget.data.domain[text].varType == orange.VarTypes.Discrete and "major" or "avg"
28            elif meth < 6:
29                ntext = self.widget.indiShorts[meth]
30            elif meth:
31                attr = self.widget.data.domain[text]
32                if attr.varType == orange.VarTypes.Discrete:
33                    if val < len(attr.values):
34                        ntext = attr.values[val]
35                    else:
36                        ntext = "?"
37                else:
38                    ntext = str(val)
39            rect.setWidth(self.widget.attrList.width())
40            QItemDelegate.drawDisplay(self, painter, option, rect, text + " -> " + ntext)
41        else:
42            QItemDelegate.drawDisplay(self, painter, option, rect, text)
43        #QItemDelegate.drawDisplay(self, painter, option, rect, text + " -> " + ntext)
44
45
46
47class OWImpute(OWWidget):
48    settingsList = ["defaultMethod", "imputeClass", "selectedAttr", "autosend"]
49    contextHandlers = {"": PerfectDomainContextHandler("", ["methods"], matchValues = DomainContextHandler.MatchValuesAttributes, syncWithGlobal=False)}
50    indiShorts = ["", "leave", "avg", "model", "random", "remove", ""]
51    defaultMethods = ["Don't Impute", "Average/Most frequent", "Model-based imputer", "Random values", "Remove examples with missing values"]
52   
53    def __init__(self,parent=None, signalManager = None, name = "Impute"):
54        OWWidget.__init__(self, parent, signalManager, name, wantMainArea = 0)
55
56        self.inputs = [("Data", ExampleTable, self.setData, Default),
57                       ("Learner for Imputation", orange.Learner, self.setModel)]
58        self.outputs = [("Data", ExampleTable),
59                        ("Imputer", orange.ImputerConstructor)]
60
61        self.attrIcons = self.createAttributeIconDict()
62
63        self.defaultMethod = 0
64        self.selectedAttr = 0
65        self.indiType = 0
66        self.imputeClass = 0
67        self.autosend = 1
68        self.methods = {}
69        self.dataChanged = False
70
71        self.model = self.data = None
72
73        self.indiValue = ""
74        self.indiValCom = 0
75
76        self.loadSettings()
77
78        bgTreat = OWGUI.radioButtonsInBox(self.controlArea, self, "defaultMethod", self.defaultMethods,
79                                          "Default imputation method", callback=self.sendIf,
80                                          addSpace=True)
81
82        self.indibox = OWGUI.widgetBox(self.controlArea, "Individual attribute settings", orientation="horizontal")#, addSpace=True)
83
84        attrListBox = OWGUI.widgetBox(self.indibox)
85        self.attrList = OWGUI.listBox(attrListBox, self, callback = self.individualSelected)
86        self.attrList.setMinimumWidth(220)
87        self.attrList.setItemDelegate(ImputeListItemDelegate(self, self.attrList))
88
89        indiMethBox = OWGUI.widgetBox(self.indibox)
90        indiMethBox.setFixedWidth(160)
91        self.indiButtons = OWGUI.radioButtonsInBox(indiMethBox, self, "indiType", ["Default (above)", "Don't impute", "Avg/Most frequent", "Model-based", "Random", "Remove examples", "Value"], 1, callback=self.indiMethodChanged)
92        self.indiValueCtrlBox = OWGUI.indentedBox(self.indiButtons)
93
94        self.indiValueLineEdit = OWGUI.lineEdit(self.indiValueCtrlBox, self, "indiValue", callback = self.lineEditChanged)
95        #self.indiValueLineEdit.hide()
96        valid = QDoubleValidator(self)
97        valid.setRange(-1e30, 1e30, 10)
98        self.indiValueLineEdit.setValidator(valid)
99
100        self.indiValueComboBox = OWGUI.comboBox(self.indiValueCtrlBox, self, "indiValCom", callback = self.valueComboChanged)
101        self.indiValueComboBox.hide()
102        OWGUI.rubber(indiMethBox)
103        self.btAllToDefault = OWGUI.button(indiMethBox, self, "Set All to Default", callback = self.allToDefault)
104
105        OWGUI.separator(self.controlArea)
106        box = OWGUI.widgetBox(self.controlArea, "Class Imputation")#, addSpace=True)
107        self.cbImputeClass = OWGUI.checkBox(box, self, "imputeClass", "Impute class values", callback=self.sendIf)
108
109        OWGUI.separator(self.controlArea)
110        snbox = OWGUI.widgetBox(self.controlArea, "Send data and imputer")
111        self.btApply = OWGUI.button(snbox, self, "Apply", callback=self.sendDataAndImputer, default=True)
112        OWGUI.checkBox(snbox, self, "autosend", "Send automatically", callback=self.enableAuto, disables = [(-1, self.btApply)])
113
114        self.individualSelected(self.selectedAttr)
115        self.btApply.setDisabled(self.autosend)
116        self.setBtAllToDefault()
117        self.resize(200,200)
118
119
120    def allToDefault(self):
121        self.methods = {}
122        self.attrList.reset()
123        self.setBtAllToDefault()
124        self.setIndiType()
125        self.sendIf()
126
127    def setBtAllToDefault(self):
128        self.btAllToDefault.setDisabled(not self.methods)
129
130    def setIndiType(self):
131        if self.data:
132            attr = self.data.domain[self.selectedAttr]
133            specific = self.methods.get(attr.name, False)
134            if specific:
135                self.indiType = specific[0]
136                if self.indiType == 6:
137                    if attr.varType == orange.VarTypes.Discrete:
138                        self.indiValCom = specific[1]
139                    else:
140                        self.indiValue = specific[1]
141            else:
142                self.indiType = 0
143
144    def individualSelected(self, i = -1):
145        if i == -1:
146            if self.attrList.selectedItems() != []:
147                i = self.attrList.row(self.attrList.selectedItems()[0])
148            else:
149                i = 0
150        if self.data:
151            self.selectedAttr = i
152            attr = self.data.domain[i]
153            attrName = attr.name
154            self.indiType = self.methods.get(attrName, (0, ""))[0]
155        else:
156            attr = None
157
158        if attr and attr.varType == orange.VarTypes.Discrete:
159            self.indiValueComboBox.clear()
160            self.indiValueComboBox.addItems(list(attr.values))
161
162            self.indiValCom = self.methods.get(attrName, (0, 0))[1] or 0
163            self.indiValueLineEdit.hide()
164            self.indiValueComboBox.show()
165        else:
166            if attr and self.methods.has_key(attrName):
167                self.indiValue = self.methods[attrName][1]
168            self.indiValueComboBox.hide()
169            self.indiValueLineEdit.show()
170
171        self.indiValueCtrlBox.update()
172
173
174    def indiMethodChanged(self):
175        if self.data:
176            attr = self.data.domain[self.selectedAttr]
177            attrName = attr.name
178            if self.indiType:
179                if self.indiType == 6:
180                    if attr.varType == orange.VarTypes.Discrete:
181                        self.methods[attrName] = 6, self.indiValCom
182                    else:
183                        self.methods[attrName] = 6, str(self.indiValue)
184                else:
185                    self.methods[attrName] = self.indiType, None
186            else:
187                if self.methods.has_key(attrName):
188                    del self.methods[attrName]
189            self.attrList.reset()
190            self.setBtAllToDefault()
191            self.sendIf()
192
193
194    def lineEditChanged(self):
195        if self.data:
196            self.indiType = 6
197            self.methods[self.data.domain[self.selectedAttr].name] = 6, str(self.indiValue)
198            self.attrList.reset()
199            self.setBtAllToDefault()
200            self.sendIf()
201
202
203    def valueComboChanged(self):
204        self.indiType = 6
205        self.methods[self.data.domain[self.selectedAttr].name] = 6, self.indiValCom
206        self.attrList.reset()
207        self.setBtAllToDefault()
208        self.sendIf()
209
210
211    def enableAuto(self):
212        if self.dataChanged:
213            self.sendDataAndImputer()
214
215
216    def setData(self,data):
217        self.closeContext()
218
219        self.methods = {}
220        if not data or not len(data.domain):
221            self.indibox.setDisabled(True)
222            self.data = None
223            self.send("Data", data)
224            self.attrList.clear()
225        else:
226            self.indibox.setDisabled(False)
227            if not self.data or data.domain != self.data.domain:
228                self.data = data
229                if not data.domain.classVar:
230                    self.imputeClass = 0
231                    self.cbImputeClass.setDisabled(True)
232                else:
233                    self.cbImputeClass.setDisabled(False)
234                    pass
235
236                self.attrList.clear()
237                for i, attr in enumerate(self.data.domain):
238                    self.attrList.addItem(QListWidgetItem(self.attrIcons[attr.varType], attr.name))
239
240                if 0 <= self.selectedAttr < self.attrList.count():
241                    self.attrList.setCurrentRow(self.selectedAttr)
242                else:
243                    self.attrList.setCurrentRow(0)
244                    self.selectedAttr = 0
245
246        self.openContext("", data)
247        self.setBtAllToDefault()
248        self.setIndiType()
249        self.sendIf()
250
251
252    def setModel(self, model):
253        self.model = model
254        self.sendIf()
255
256
257    class RemoverAndImputerConstructor:
258        def __init__(self, removerConstructor, imputerConstructor):
259            self.removerConstructor = removerConstructor
260            self.imputerConstructor = imputerConstructor
261
262        def __call__(self, data):
263            return lambda data2, remover=self.removerConstructor(data), imputer=self.imputerConstructor(data): imputer(data2 if isinstance(data2, orange.Example) else remover(data2))
264
265    class SelectDefined:
266        # This argument can be a list of attributes or a bool
267        # in which case it means 'onlyAttributes' (e.g. do not mind about the class)
268        def __init__(self, attributes):
269            self.attributes = attributes
270
271        def __call__(self, data):
272            f = orange.Filter_isDefined(domain = data.domain)
273            if isinstance(self.attributes, bool):
274                if self.attributes and data.domain.classVar:
275                    f.check[data.domain.classVar] = False
276            else:
277                for attr in data.domain:
278                    f.check[attr] = attr in self.attributes
279            return f
280
281    def constructImputer(self, *a):
282        if not self.methods:
283            if self.defaultMethod == 0:
284                self.imputer = lambda *x: (lambda x,w=0: x)
285            elif self.defaultMethod == 2:
286                model = self.model or orange.kNNLearner()
287                self.imputer = orange.ImputerConstructor_model(learnerDiscrete = model, learnerContinuous = model, imputeClass = self.imputeClass)
288            elif self.defaultMethod == 3:
289                self.imputer = orange.ImputerConstructor_random(imputeClass = self.imputeClass)
290            elif self.defaultMethod == 4:
291                self.imputer = self.SelectDefined(not self.imputeClass)
292            else:
293                self.imputer = orange.ImputerConstructor_average(imputeClass = self.imputeClass)
294            return
295
296        class AttrMajorityLearner:
297            def __init__(self, attr):
298                self.attr = attr
299
300            def __call__(self, examples, weight):
301                return orange.DefaultClassifier(orange.Distribution(self.attr, examples, weight).modus())
302
303        class AttrRandomLearner:
304            def __init__(self, attr):
305                self.attr = attr
306
307            def __call__(self, examples, weight):
308                if self.attr.varType == orange.VarTypes.Discrete:
309                    probabilities = orange.Distribution(self.attr, examples, weight)
310                else:
311                    basstat = orange.BasicAttrStat(self.attr, examples, weight)
312                    probabilities = orange.GaussianDistribution(basstat.avg, basstat.dev)
313                return orange.RandomClassifier(classVar = self.attr, probabilities = probabilities)
314
315        class AttrModelLearner:
316            def __init__(self, attr, model):
317                self.attr = attr
318                self.model = model
319
320            def __call__(self, examples, weight):
321                newdata = orange.ExampleTable(orange.Domain([attr for attr in examples.domain.attributes if attr != self.attr] + [self.attr]), examples)
322                newdata = orange.Filter_hasClassValue(newdata)
323                return self.model(newdata, weight)
324
325        classVar = self.data.domain.classVar
326        imputeClass = self.imputeClass or classVar and self.methods.get(classVar.name, (0, None))[0]
327        imputerModels = []
328        missingValues = []
329        toRemove = []
330        usedModel = None
331        for attr in (self.data.domain if imputeClass else self.data.domain.attributes):
332            method, value = self.methods.get(attr.name, (0, None))
333            if not method:
334                method = self.defaultMethod + 1
335
336            if method == 1:
337                imputerModels.append(lambda e, wei=0: None)
338            elif method==2:
339                imputerModels.append(AttrMajorityLearner(attr))
340            elif method == 3:
341                if not usedModel:
342                    usedModel = self.model or orange.kNNLearner()
343                imputerModels.append(AttrModelLearner(attr, usedModel))
344            elif method == 4:
345                imputerModels.append(AttrRandomLearner(attr))
346            elif method == 5:
347                toRemove.append(attr)
348                imputerModels.append(lambda e, wei=0: None)
349            elif method == 6:
350                if (attr.varType == orange.VarTypes.Discrete or value):
351                    imputerModels.append(lambda e, v=0, attr=attr, value=value: orange.DefaultClassifier(attr, attr(value)))
352                else:
353                    missingValues.append("'"+attr.name+"'")
354                    imputerModels.append(AttrMajorityLearner(attr))
355
356        self.warning(0)
357        if missingValues:
358            if len(missingValues) <= 3:
359                msg = "The imputed values for some attributes (%s) are not specified." % ", ".join(missingValues)
360            else:
361                msg = "The imputed values for some attributes (%s, ...) are not specified." % ", ".join(missingValues[:3])
362            self.warning(0, msg + "\n"+"Averages and/or majority values are used instead.")
363
364        if classVar and not imputeClass:
365            imputerModels.append(lambda e, wei=0: None)
366
367        self.imputer = lambda ex, wei=0, ic=imputerModels: orange.Imputer_model(models=[i(ex, wei) for i in ic])
368
369        if toRemove:
370            remover = self.SelectDefined(toRemove)
371            self.imputer = self.RemoverAndImputerConstructor(remover, self.imputer)
372
373
374    def sendReport(self):
375        self.reportData(self.data, "Input data")
376        self.reportSettings("Imputed values",
377                            [("Default method", self.defaultMethods[self.defaultMethod]),
378                             ("Impute class values", OWGUI.YesNo[self.imputeClass])])
379       
380        if self.data:
381            attrs = []
382            eex = getattr(self, "imputedValues", None) 
383            classVar = self.data.domain.classVar
384            imputeClass = self.imputeClass or classVar and self.methods.get(classVar.name, (0, None))[0]
385            for attr in (self.data.domain if imputeClass else self.data.domain.attributes):
386                method, value = self.methods.get(attr.name, (0, None))
387                if method == 6:
388                    attrs.append((attr.name, "%s (%s)" % (attr(value), "set manually")))
389                elif eex and method != 3:
390                    attrs.append((attr.name, str(eex[attr]) + (" (%s)" % self.defaultMethods[method-1] if method else "")))
391                elif method:
392                    attrs.append((attr.name, self.defaultMethods[method-1]))
393            if attrs:
394                self.reportRaw("<br/>")
395                self.reportSettings("", attrs)
396           
397
398    def sendIf(self):
399        if self.autosend:
400            self.sendDataAndImputer()
401        else:
402            self.dataChanged = True
403
404
405    def sendDataAndImputer(self):
406        self.error(0)
407        self.constructImputer()
408        self.send("Imputer", self.imputer)
409        if self.data:
410            if self.imputer:
411                try:
412                    constructed = self.imputer(self.data)
413                    data = constructed(self.data)
414                    ## meta-comment: is there a missing 'not' in the below comment?
415                    # if the above fails, dataChanged should be set to False
416                    self.imputedValues = constructed(orange.Example(self.data.domain)) 
417                    self.dataChanged = False
418                except:
419                    self.error(0, "Imputation failed; this is typically due to unsuitable model.\nIt can also happen with some imputation techniques if no values are defined.")
420                    data = None
421            else:
422                data = None
423            self.send("Data", data)
424        else:
425            self.dataChanged = False
426
427
428
429if __name__ == "__main__":
430    a = QApplication(sys.argv)
431    ow = OWImpute()
432    data = orange.ExampleTable(r'../../doc/datasets/imports-85')
433    ow.show()
434    ow.setData(data)
435    a.exec_()
436    ow.saveSettings()
Note: See TracBrowser for help on using the repository browser.