source: orange/orange/OrangeWidgets/Data/OWFeatureConstructor.py @ 7870:ce4a1ec22fd7

Revision 7870:ce4a1ec22fd7, 10.3 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Moved Data generator and Feature constructor from Prototypes to Data.

Line 
1"""
2<name>Feature Constructor</name>
3<description>Construct a new continuous attribute computed from existing attributes.</description>
4<icon>icons/FeatureConstructor.png</icon>
5<priority>3100</priority>
6<contact>Janez Demsar (janez.demsar(@at@)fri.uni-lj.si)</contact>
7"""
8
9from OWWidget import *
10import OWGUI, math, re
11import random
12
13re_identifier = re.compile(r'([a-zA-Z_]\w*)|("[^"]+")')
14
15class IdentifierReplacer:
16    def __init__(self, reinserted, attributes):
17        self.reinserted = reinserted
18        self.attributes = attributes
19
20    def __call__(self, id):
21        id = id.group()
22        if id in self.reinserted:
23            return "(%s)" % self.reinserted[id]
24        if (id[0] == id[-1] == '"') and (id[1:-1] in self.attributes):
25            return "_ex[%s]" % id
26        if id in self.attributes:
27            return "_ex['%s']" % id
28        return id
29
30class AttrComputer:
31    FUNCTIONS = dict([(key, val) for key, val in math.__dict__.items() if not key.startswith("_")] +\
32                      {"normalvariate": random.normalvariate,
33                       "gauss": random.gauss,
34                       "expovariate": random.expovariate,
35                       "gammavariate": random.gammavariate,
36                       "betavariate": random.betavariate,
37                       "lognormvariate": random.lognormvariate,
38                       "paretovariate": random.paretovariate,
39                       "vonmisesvariate": random.vonmisesvariate,
40                       "weibullvariate": random.weibullvariate,
41                       "triangular": random.triangular,
42                       "uniform": random.uniform}.items())
43    def __init__(self, expression):
44        self.expression = expression
45
46    def __call__(self, ex, weight):
47        try:
48            return float(eval(self.expression, self.FUNCTIONS, {"_ex": ex}))
49        except Exception, ex:
50            return "?"
51
52class OWFeatureConstructor(OWWidget):
53    contextHandlers = {"": PerfectDomainContextHandler()}
54
55    def __init__(self,parent=None, signalManager = None):
56        OWWidget.__init__(self, parent, signalManager, "FeatureConstructor")
57
58        self.inputs = [("Examples", orange.ExampleTable, self.setData)]
59        self.outputs = [("Examples", ExampleTable)]
60
61        self.expression = self.attrname = ""
62        self.selectedDef = []
63        self.defLabels = []
64        self.data = None
65        self.definitions = []
66
67        self.selectedAttr = 0
68        self.selectedFunc = 0
69        self.autosend = True
70        self.loadSettings()
71
72        db = OWGUI.widgetBox(self.controlArea, "Attribute definitions", addSpace = True)
73
74        hb = OWGUI.widgetBox(db, None, "horizontal")
75        hbv = OWGUI.widgetBox(hb)
76        self.leAttrName = OWGUI.lineEdit(hbv, self, "attrname", "New attribute")
77        OWGUI.rubber(hbv)
78        vb = OWGUI.widgetBox(hb, None, "vertical", addSpace=True)
79        self.leExpression = OWGUI.lineEdit(vb, self, "expression", "Expression")
80        hhb = OWGUI.widgetBox(vb, None, "horizontal")
81        self.cbAttrs = OWGUI.comboBox(hhb, self, "selectedAttr", items = ["(all attributes)"], callback = self.attrListSelected)
82        sortedFuncs = sorted(m for m in AttrComputer.FUNCTIONS.keys())
83        self.cbFuncs = OWGUI.comboBox(hhb, self, "selectedFunc", items = ["(all functions)"] + sortedFuncs, callback = self.funcListSelected)
84        model = self.cbFuncs.model()
85        for i, func in enumerate(sortedFuncs):
86            model.item(i + 1).setToolTip(AttrComputer.FUNCTIONS[func].__doc__)
87       
88        hb = OWGUI.widgetBox(db, None, "horizontal", addSpace=True)
89        OWGUI.button(hb, self, "Add", callback = self.addAttr, autoDefault=True)
90        OWGUI.button(hb, self, "Update", callback = self.updateAttr)
91        OWGUI.button(hb, self, "Remove", callback = self.removeAttr)
92        OWGUI.button(hb, self, "Remove All", callback = self.removeAllAttr)
93
94        self.lbDefinitions = OWGUI.listBox(db, self, "selectedDef", "defLabels", callback=self.selectAttr)
95        self.lbDefinitions.setFixedHeight(160)
96
97        hb = OWGUI.widgetBox(self.controlArea, "Apply", "horizontal")
98        OWGUI.button(hb, self, "Apply", callback = self.apply)
99        cb = OWGUI.checkBox(hb, self, "autosend", "Apply automatically", callback=self.enableAuto)
100        cb.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
101
102        self.adjustSize()
103
104    def settingsFromWidgetCallback(self, handler, context):
105        context.definitions = self.definitions
106
107    def settingsToWidgetCallback(self, handler, context):
108        self.definitions = getattr(context, "definitions", [])
109        self.defLabels = ["%s := %s" % t for t in self.definitions]
110        self.selectedDef = []
111
112    def setData(self, data):
113        self.closeContext()
114        self.data = data
115        self.cbAttrs.clear()
116        self.cbAttrs.addItem("(all attributes)")
117        if self.data:
118            self.cbAttrs.addItems([attr.name for attr in self.data.domain])
119        self.removeAllAttr()
120        self.openContext("", data)
121        self.apply()
122
123    def getAttrExpression(self, thisRow=-1):
124        attrname = self.attrname.strip()
125        if not attrname:
126            self.leAttrName.setFocus()
127            return
128        for row, (attr, expr) in enumerate(self.definitions):
129            if row!=thisRow and attr==attrname:
130                QMessageBox.critical(self, "Duplicate name", "Attribute with that name already exists.\nPlease choose a different name.")
131                self.leAttrName.setFocus()
132                return
133        expression = self.expression.strip()
134        if not expression:
135            self.leExpression.setFocus()
136            return
137        return attrname, expression
138       
139    def addAttr(self):
140        attrexpr = self.getAttrExpression()
141        if not attrexpr:
142            return
143        self.defLabels = self.defLabels + ["%s := %s" % attrexpr] # should be like this to update the listbox
144        self.definitions.append(attrexpr)
145        self.expression = self.attrname = ""
146        self.applyIf()
147
148    def updateAttr(self):
149        if self.selectedDef:
150            selected = self.selectedDef[0]
151            attrexpr = self.getAttrExpression(selected)
152            if not attrexpr:
153                return
154             # should be like this to reset the listbox
155            self.defLabels = self.defLabels[:selected] + ["%s := %s" % attrexpr] + self.defLabels[selected+1:]
156            self.definitions[selected] = attrexpr
157            self.applyIf()
158
159    def removeAttr(self):
160        if self.selectedDef:
161            selected = self.selectedDef[0]
162            if 0 <= selected < self.lbDefinitions.count():
163                self.defLabels = self.defLabels[:selected] + self.defLabels[selected+1:]
164                del self.definitions[selected]
165                self.applyIf()
166
167    def removeAllAttr(self):
168        self.defLabels = []
169        self.definitions = []
170        self.selectedDef = []
171        self.expression = ""
172        self.applyIf()
173
174    def selectAttr(self):
175        if self.selectedDef:
176            self.attrname, self.expression = self.definitions[self.selectedDef[0]]
177        else:
178            self.attrname = self.expression = ""
179
180    def insertIntoExpression(self, what):
181        # Doesn't work: clicking the listbox removes the selection
182        if self.leExpression.hasSelectedText():
183            self.leExpression.del_()
184        cp = self.leExpression.cursorPosition()
185        self.expression = self.expression[:cp] + what + self.expression[cp:]
186        self.leExpression.setFocus()
187
188    def attrListSelected(self):
189        if self.selectedAttr:
190            attr = str(self.cbAttrs.itemText(self.selectedAttr))
191            mo = re_identifier.match(attr)
192            if not mo or mo.span()[1] != len(attr):
193                attr = '"%s"' % attr
194            self.insertIntoExpression(attr)
195            self.selectedAttr = 0
196
197    def funcListSelected(self):
198        if self.selectedFunc:
199            print self.selectedFunc
200            func = str(self.cbFuncs.itemText(self.selectedFunc))
201            if func in ["atan2", "fmod", "ldexp", "log", "pow", "normalvariate",
202                        "gauss", "lognormvariate", "betavariate", "gammavariate",
203                        "triangular", "uniform", "vonmisesvariate", "weibullvariate"]:
204                self.insertIntoExpression(func + "(,)")
205                self.leExpression.cursorBackward(False, 2)
206            elif func in ["e", "pi"]:
207                self.insertIntoExpression(func)
208            else:
209                self.insertIntoExpression(func + "()")
210                self.leExpression.cursorBackward(False)
211            self.selectedFunc = 0
212
213    def applyIf(self):
214        self.dataChanged = True
215        if self.autosend:
216            self.apply()
217
218    def enableAuto(self):
219        if self.dataChanged:
220            self.apply()
221
222    def apply(self):
223        self.dataChanged = False
224        if not self.data:
225            self.send("Examples", None)
226            return
227
228        oldDomain = self.data.domain
229
230        names = [d[0] for d in self.definitions]
231        for name in names:
232            if names.count(name)>1 or name in oldDomain > 1:
233                self.error(1, "Multiple attributes with the same name (%s)" % name)
234                self.send("Examples", None)
235                return
236
237        unknown = [[name, exp, set([id[0] or id[1] for id in re_identifier.findall(exp) if id[0] in names or id[1][1:-1] in names])] for name, exp in self.definitions]
238        reinserted = {}
239        replacer = IdentifierReplacer(reinserted, [n.name for n in oldDomain])
240        while unknown:
241            solved = set()
242            for i, (name, exp, unk_attrs) in enumerate(unknown):
243                if not unk_attrs:
244                    reinserted[name] = re_identifier.sub(replacer, exp)
245                    del unknown[i]
246                    solved.add(name)
247            if not solved:
248                self.error(1, "Circular attribute definitions (%s)" % ", ".join([x[0] for x in unknown]))
249                self.send("Examples", None)
250                return
251            for name, exp, unk_attrs in unknown:
252                unk_attrs -= solved
253
254        self.error(1)
255
256        newDomain = orange.Domain(oldDomain.attributes + [orange.FloatVariable(str(attrname), getValueFrom = AttrComputer(reinserted[attrname])) for attrname in names], oldDomain.classVar)
257        newDomain.addmetas(oldDomain.getmetas())
258        self.send("Examples", orange.ExampleTable(newDomain, self.data))
Note: See TracBrowser for help on using the repository browser.