source: orange/orange/OrangeWidgets/Data/OWImpute.py @ 8042:ffcb93bc9028

Revision 8042:ffcb93bc9028, 17.2 KB checked in by markotoplak, 3 years ago (diff)

Hierarchical clustering: also catch RuntimeError when importing matplotlib (or the documentation could not be built on server).

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