source: orange/Orange/OrangeWidgets/Data/OWSelectData.py @ 11096:cf7d2ae9d22b

Revision 11096:cf7d2ae9d22b, 38.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 19 months ago (diff)

Added new svg icons for the widgets/categories.

Line 
1"""
2<name>Select Data</name>
3<description>Selects instances from the data set based on conditions over attributes.</description>
4<icon>icons/SelectData.svg</icon>
5<priority>1150</priority>
6<contact>Peter Juvan (peter.juvan@fri.uni-lj.si)</contact>
7"""
8import orange
9from OWWidget import *
10import OWGUI
11from orngDataCaching import *
12
13class OWSelectData(OWWidget):
14    settingsList = ["updateOnChange", "purgeAttributes", "purgeClasses"]
15    contextHandlers = {"": PerfectDomainContextHandler(fields = ["Conditions"], matchValues=2)}
16
17    def __init__(self, parent = None, signalManager = None, name = "Select data"):
18        OWWidget.__init__(self, parent, signalManager, name, wantMainArea = 0)  #initialize base class
19
20        self.inputs = [("Data", ExampleTable, self.setData)]
21        self.outputs = [("Matching Data", ExampleTable, Default), ("Unmatched Data", ExampleTable)]
22
23        self.name2var = {}   # key: variable name, item: orange.Variable
24        self.Conditions = []
25
26        self.currentVar = None
27        self.NegateCondition = False
28        self.currentOperatorDict = {orange.VarTypes.Continuous: Operator(Operator.operatorsC[0], orange.VarTypes.Continuous),
29                                    orange.VarTypes.Discrete: Operator(Operator.operatorsD[0],orange.VarTypes.Discrete),
30                                    orange.VarTypes.String: Operator(Operator.operatorsS[0], orange.VarTypes.String)}
31        self.Num1 = 0.0
32        self.Num2 = 0.0
33        self.Str1 = ""
34        self.Str2 = ""
35        self.attrSearchText = ""
36        self.currentVals = []
37        self.CaseSensitive = False
38        self.updateOnChange = True
39        self.purgeAttributes = True
40        self.purgeClasses = True
41        self.oldPurgeClasses = True
42
43        self.loadedVarNames = []
44        self.loadedConditions = []
45        self.loadSettings()
46
47        w = QWidget(self)
48        self.controlArea.layout().addWidget(w)
49        grid = QGridLayout()
50        grid.setMargin(0)
51        w.setLayout(grid)
52
53        boxAttrCond = OWGUI.widgetBox(self, '', orientation = QGridLayout(), addToLayout = 0)
54        grid.addWidget(boxAttrCond, 0,0,1,3)
55        glac = boxAttrCond.layout()
56        glac.setColumnStretch(0,2)
57        glac.setColumnStretch(1,1)
58        glac.setColumnStretch(2,2)
59
60        boxAttr = OWGUI.widgetBox(self, 'Attribute', addToLayout = 0)
61        glac.addWidget(boxAttr,0,0)
62        self.lbAttr = OWGUI.listBox(boxAttr, self, callback = self.lbAttrChange)
63
64        self.leSelect = OWGUI.lineEdit(boxAttr, self, "attrSearchText", label = "Search: ", orientation = "horizontal", callback = self.setLbAttr, callbackOnType = 1)
65
66        boxOper = OWGUI.widgetBox(self, 'Operator')
67        # operators 0: empty
68        self.lbOperatosNone = OWGUI.listBox(boxOper, self)
69        # operators 1: discrete
70        self.lbOperatorsD = OWGUI.listBox(boxOper, self, callback = self.lbOperatorsChange)
71        self.lbOperatorsD.hide()
72        self.lbOperatorsD.addItems(Operator.operatorsD + [Operator.operatorDef])
73        # operators 2: continuous
74        self.lbOperatorsC = OWGUI.listBox(boxOper, self, callback = self.lbOperatorsChange)
75        self.lbOperatorsC.hide()
76        self.lbOperatorsC.addItems(Operator.operatorsC + [Operator.operatorDef])
77        # operators 6: string
78        self.lbOperatorsS = OWGUI.listBox(boxOper, self, callback = self.lbOperatorsChange)
79        self.lbOperatorsS.hide()
80        self.lbOperatorsS.addItems(Operator.operatorsS + [Operator.operatorDef])
81        self.lbOperatorsDict = {0: self.lbOperatosNone,
82                                orange.VarTypes.Continuous: self.lbOperatorsC,
83                                orange.VarTypes.Discrete: self.lbOperatorsD,
84                                orange.VarTypes.String: self.lbOperatorsS}
85
86        glac.addWidget(boxOper,0,1)
87        self.cbNot = OWGUI.checkBox(boxOper, self, "NegateCondition", "Negate")
88
89        self.boxIndices = {}
90        self.valuesStack = QStackedWidget(self)
91        glac.addWidget(self.valuesStack, 0, 2)
92
93        # values 0: empty
94        boxVal = OWGUI.widgetBox(self, "Values", addToLayout = 0)
95        self.boxIndices[0] = boxVal
96        self.valuesStack.addWidget(boxVal)
97
98        # values 1: discrete
99        boxVal = OWGUI.widgetBox(self, "Values", addToLayout = 0)
100        self.boxIndices[orange.VarTypes.Discrete] = boxVal
101        self.valuesStack.addWidget(boxVal)
102        self.lbVals = OWGUI.listBox(boxVal, self, callback = self.lbValsChange)
103
104        # values 2: continuous between num and num
105        boxVal = OWGUI.widgetBox(self, "Values", addToLayout = 0)
106        self.boxIndices[orange.VarTypes.Continuous] = boxVal
107        self.valuesStack.addWidget(boxVal)
108        self.leNum1 = OWGUI.lineEdit(boxVal, self, "Num1", validator=QDoubleValidator(self))
109        self.lblAndCon = OWGUI.widgetLabel(boxVal, "and")
110        self.leNum2 = OWGUI.lineEdit(boxVal, self, "Num2", validator=QDoubleValidator(self))
111        boxAttrStat = OWGUI.widgetBox(boxVal, "Statistics")
112        self.lblMin = OWGUI.widgetLabel(boxAttrStat, "Min: ")
113        self.lblAvg = OWGUI.widgetLabel(boxAttrStat, "Avg: ")
114        self.lblMax = OWGUI.widgetLabel(boxAttrStat, "Max: ")
115        self.lblDefined = OWGUI.widgetLabel(boxAttrStat, "Defined for ---- examples")
116        OWGUI.rubber(boxAttrStat)
117
118        # values 6: string between str and str
119        boxVal = OWGUI.widgetBox(self, "Values", addToLayout = 0)
120        self.boxIndices[orange.VarTypes.String] = boxVal
121        self.valuesStack.addWidget(boxVal)
122        self.leStr1 = OWGUI.lineEdit(boxVal, self, "Str1")
123        self.lblAndStr = OWGUI.widgetLabel(boxVal, "and")
124        self.leStr2 = OWGUI.lineEdit(boxVal, self, "Str2")
125        self.cbCaseSensitive = OWGUI.checkBox(boxVal, self, "CaseSensitive", "Case sensitive")
126
127        self.boxButtons = OWGUI.widgetBox(self, orientation = "horizontal")
128        grid.addWidget(self.boxButtons, 1,0,1,3)
129        self.btnNew = OWGUI.button(self.boxButtons, self, "Add", self.OnNewCondition)
130        self.btnUpdate = OWGUI.button(self.boxButtons, self, "Modify", self.OnUpdateCondition)
131        self.btnRemove = OWGUI.button(self.boxButtons, self, "Remove", self.OnRemoveCondition)
132        self.btnOR = OWGUI.button(self.boxButtons, self, "OR", self.OnDisjunction)
133        self.btnMoveUp = OWGUI.button(self.boxButtons, self, "Move Up", self.btnMoveUpClicked)
134        self.btnMoveDown = OWGUI.button(self.boxButtons, self, "Move Down", self.btnMoveDownClicked)
135        self.btnRemove.setEnabled(False)
136        self.btnUpdate.setEnabled(False)
137        self.btnMoveUp.setEnabled(False)
138        self.btnMoveDown.setEnabled(False)
139
140
141        boxCriteria = OWGUI.widgetBox(self, 'Data Selection Criteria', addToLayout = 0)
142        grid.addWidget(boxCriteria, 2,0,1,3)
143        self.criteriaTable = QTableWidget(boxCriteria)
144        boxCriteria.layout().addWidget(self.criteriaTable)
145        self.criteriaTable.setShowGrid(False)
146        self.criteriaTable.setSelectionMode(QTableWidget.SingleSelection)
147        self.criteriaTable.setColumnCount(2)
148        self.criteriaTable.verticalHeader().setClickable(False)
149        #self.criteriaTable.verticalHeader().setResizeEnabled(False,-1)
150        self.criteriaTable.horizontalHeader().setClickable(False)
151        self.criteriaTable.setHorizontalHeaderLabels(["Active", "Condition"])
152        self.criteriaTable.resizeColumnToContents(0)
153        self.criteriaTable.setSelectionBehavior(QAbstractItemView.SelectRows)
154        self.criteriaTable.horizontalHeader().setResizeMode(1, QHeaderView.Stretch)
155        self.connect(self.criteriaTable, SIGNAL('cellClicked(int, int)'), self.currentCriteriaChange)
156
157        boxDataIn = OWGUI.widgetBox(self, 'Data In', addToLayout = 0)
158        grid.addWidget(boxDataIn, 3,0)
159        self.dataInExamplesLabel = OWGUI.widgetLabel(boxDataIn, "num examples")
160        self.dataInAttributesLabel = OWGUI.widgetLabel(boxDataIn, "num attributes")
161        OWGUI.rubber(boxDataIn)
162
163        boxDataOut = OWGUI.widgetBox(self, 'Data Out', addToLayout = 0)
164        grid.addWidget(boxDataOut, 3,1)
165        self.dataOutExamplesLabel = OWGUI.widgetLabel(boxDataOut, "num examples")
166        self.dataOutAttributesLabel = OWGUI.widgetLabel(boxDataOut, "num attributes")
167        OWGUI.rubber(boxDataOut)
168
169        boxSettings = OWGUI.widgetBox(self, 'Commit', addToLayout = 0)
170        grid.addWidget(boxSettings, 3,2)
171        cb = OWGUI.checkBox(boxSettings, self, "purgeAttributes", "Remove unused values/attributes", box=None, callback=self.OnPurgeChange)
172        self.purgeClassesCB = OWGUI.checkBox(OWGUI.indentedBox(boxSettings, sep=OWGUI.checkButtonOffsetHint(cb)), self, "purgeClasses", "Remove unused classes", callback=self.OnPurgeChange)
173        OWGUI.checkBox(boxSettings, self, "updateOnChange", "Commit on change", box=None)
174        btnUpdate = OWGUI.button(boxSettings, self, "Commit", self.setOutput, default=True)
175
176        self.icons = self.createAttributeIconDict()
177        self.setData(None)
178        self.lbOperatorsD.setCurrentRow(0)
179        self.lbOperatorsC.setCurrentRow(0)
180        self.lbOperatorsS.setCurrentRow(0)
181        self.resize(500,661)
182        grid.setRowStretch(0, 10)
183        grid.setRowStretch(2, 10)
184
185
186    def setData(self, data):
187        self.closeContext("")
188        self.data = data
189        self.bas = getCached(data, orange.DomainBasicAttrStat, (data,))
190               
191        self.name2var = {}
192        self.Conditions = []
193
194        if self.data:
195            optmetas = self.data.domain.getmetas(True).values()
196            optmetas.sort(lambda x,y: cmp(x.name, y.name))
197            self.varList = self.data.domain.variables.native() + self.data.domain.getmetas(False).values() + optmetas
198            for v in self.varList:
199                self.name2var[v.name] = v
200            self.setLbAttr()
201            self.boxButtons.setEnabled(True)
202        else:
203            self.varList = []
204            self.currentVar = None
205
206            self.lbAttr.clear()
207            self.leSelect.clear()
208            self.boxButtons.setEnabled(False)
209
210        self.openContext("", data)
211        self.synchronizeTable()
212        self.criteriaTable.setCurrentCell(-1,1)
213
214        self.updateOperatorStack()
215        self.updateValuesStack()
216        self.updateInfoIn(self.data)
217        self.setOutput()
218
219
220    def setLbAttr(self):
221        self.lbAttr.clear()
222        if not self.attrSearchText:
223            for v in self.varList:
224                self.lbAttr.addItem(QListWidgetItem(self.icons[v.varType], v.name))
225        else:
226            flen = len(self.attrSearchText)
227            for v in self.varList:
228                if v.name[:flen].lower() == self.attrSearchText.lower():
229                    self.lbAttr.addItem(QListWidgetItem(self.icons[v.varType], v.name))
230
231        if self.lbAttr.count():
232            self.lbAttr.item(0).setSelected(True)
233        else:
234            self.lbAttrChange()
235
236
237    def setOutputIf(self):
238        if self.updateOnChange:
239            self.setOutput()
240
241    def setOutput(self):
242        matchingOutput = self.data
243        nonMatchingOutput = None
244        hasClass = False
245        if self.data:
246            hasClass = bool(self.data.domain.classVar)
247            filterList = self.getFilterList(self.data.domain, self.Conditions, enabledOnly=True)
248            if len(filterList)>0:
249                filter = orange.Filter_disjunction([orange.Filter_conjunction(l) for l in filterList])
250            else:
251                filter = orange.Filter_conjunction([]) # a filter that does nothing
252            matchingOutput = filter(self.data, 1)
253            matchingOutput.name = self.data.name
254            nonMatchingOutput = filter(self.data, 1, negate=1)
255            nonMatchingOutput.name = self.data.name
256
257            if self.purgeAttributes or self.purgeClasses:
258                remover = orange.RemoveUnusedValues(removeOneValued=True)
259
260                newDomain = remover(matchingOutput, 0, True, self.purgeClasses)
261                if newDomain != matchingOutput.domain:
262                    matchingOutput = orange.ExampleTable(newDomain, matchingOutput)
263
264                newDomain = remover(nonMatchingOutput, 0, True, self.purgeClasses)
265                if newDomain != nonMatchingOutput.domain:
266                    nonmatchingOutput = orange.ExampleTable(newDomain, nonMatchingOutput)
267
268        self.send("Matching Data", matchingOutput)
269        self.send("Unmatched Data", nonMatchingOutput)
270
271        self.updateInfoOut(matchingOutput)
272
273
274    def getFilterList(self, domain, conditions, enabledOnly):
275        """Returns list of lists of orange filters, e.g. [[f1,f2],[f3]].
276        OR is always enabled (with no respect to cond.enabled)
277        """
278        fdList = [[]]
279        for cond in conditions:
280            if cond.type == "OR":
281                fdList.append([])
282            elif cond.enabled or not enabledOnly:
283                fdList[-1].append(cond.operator.getFilter(domain, cond.varName, cond.val1, cond.val2, cond.negated, cond.caseSensitive))
284        return fdList
285
286
287    def lbAttrChange(self):
288        if self.lbAttr.selectedItems() == []: return
289        text = str(self.lbAttr.selectedItems()[0].text())
290        prevVar = self.currentVar
291        if prevVar:
292            prevVarType = prevVar.varType
293            prevVarName = prevVar.name
294        else:
295            prevVarType = None
296            prevVarName = None
297        try:
298            self.currentVar = self.data.domain[text]
299        except:
300            self.currentVar = None
301        if self.currentVar:
302            currVarType = self.currentVar.varType
303            currVarName = self.currentVar.name
304        else:
305            currVarType = None
306            currVarName = None
307        if currVarType != prevVarType:
308            self.updateOperatorStack()
309        if currVarName != prevVarName:
310            self.updateValuesStack()
311
312
313    def lbOperatorsChange(self):
314        """Updates value stack, only if necessary.
315        """
316        if self.currentVar:
317            varType = self.currentVar.varType
318            selItems = self.lbOperatorsDict[varType].selectedItems()
319            if not selItems: return
320            self.currentOperatorDict[varType] = Operator(str(selItems[0].text()), varType)
321            self.updateValuesStack()
322
323
324    def lbValsChange(self):
325        """Updates list of selected discrete values (self.currentVals).
326        """
327        self.currentVals = []
328        for i in range(0, self.lbVals.count()):
329            if self.lbVals.item(i).isSelected():
330                self.currentVals.append(str(self.lbVals.item(i).text()))
331
332
333    def OnPurgeChange(self):
334        if self.purgeAttributes:
335            if not self.purgeClassesCB.isEnabled():
336                self.purgeClassesCB.setEnabled(True)
337                self.purgeClasses = self.oldPurgeClasses
338        else:
339            if self.purgeClassesCB.isEnabled():
340                self.purgeClassesCB.setEnabled(False)
341                self.oldPurgeClasses = self.purgeClasses
342                self.purgeClasses = False
343
344        self.setOutputIf()
345
346
347    def OnNewCondition(self):
348        cond = self.getConditionFromSelection()
349        if not cond:
350            return
351
352        where = min(self.criteriaTable.currentRow() + 1, self.criteriaTable.rowCount())
353        self.Conditions.insert(where, cond)
354        self.synchronizeTable()
355        self.criteriaTable.setCurrentCell(where, 1)
356        self.setOutputIf()
357        self.leSelect.clear()
358
359
360    def OnUpdateCondition(self):
361        row = self.criteriaTable.currentRow()
362        if row < 0:
363            return
364        cond = self.getConditionFromSelection()
365        if not cond:
366            return
367        self.Conditions[row] = cond
368        self.synchronizeTable()
369        self.setOutputIf()
370        self.leSelect.clear()
371
372
373    def OnRemoveCondition(self):
374        """Removes current condition table row, shifts rows up, updates conditions and sends out new data.
375        """
376        # update self.Conditions
377        currRow = self.criteriaTable.currentRow()
378        if currRow < 0:
379            return
380        self.Conditions.pop(currRow)
381        self.synchronizeTable()
382        self.criteriaTable.setCurrentCell(min(currRow, self.criteriaTable.rowCount()-1), 1)
383        self.setOutputIf()
384
385
386    def OnDisjunction(self):
387        """Updates conditions and condition table, sends out new data.
388        """
389        # update self.Conditions
390        where = min(self.criteriaTable.currentRow() + 1, self.criteriaTable.rowCount())
391        self.Conditions.insert(where, Condition(True, "OR"))
392        self.synchronizeTable()
393        self.criteriaTable.setCurrentCell(where, 1)
394        self.setOutputIf()
395
396
397    def btnMoveUpClicked(self):
398        """Moves the selected condition one row up.
399        """
400        currRow = self.criteriaTable.currentRow()
401        numRows = self.criteriaTable.rowCount()
402        if currRow < 1 or currRow >= numRows:
403            return
404        self.Conditions = self.Conditions[:currRow-1] + [self.Conditions[currRow], self.Conditions[currRow-1]] + self.Conditions[currRow+1:]
405        self.synchronizeTable()
406        self.criteriaTable.setCurrentCell(max(0, currRow-1), 1)
407        self.updateMoveButtons()
408        self.setOutputIf()
409
410
411    def btnMoveDownClicked(self):
412        """Moves the selected condition one row down.
413        """
414        currRow = self.criteriaTable.currentRow()
415        numRows = self.criteriaTable.rowCount()
416        if currRow < 0 or currRow >= numRows-1:
417            return
418        self.Conditions = self.Conditions[:currRow] + [self.Conditions[currRow+1], self.Conditions[currRow]] + self.Conditions[currRow+2:]
419        self.synchronizeTable()
420        self.criteriaTable.setCurrentCell(min(currRow+1, self.criteriaTable.rowCount()-1), 1)
421        self.updateMoveButtons()
422        self.setOutputIf()
423
424
425    def currentCriteriaChange(self, row, col):
426        """Handles current row change in criteria table;
427        select attribute and operator, and set values according to the selected condition.
428        """
429        if row < 0:
430            return
431        cond = self.Conditions[row]
432        if cond.type != "OR":
433            # attribute
434            lbItems = self.lbAttr.findItems(cond.varName, Qt.MatchExactly)
435            if lbItems != []:
436                self.lbAttr.setCurrentItem(lbItems[0])
437            # not
438            self.cbNot.setChecked(cond.negated)
439            # operator
440            for vt,lb in self.lbOperatorsDict.items():
441                if vt == self.name2var[cond.varName].varType:
442                    lb.show()
443                else:
444                    lb.hide()
445            lbItems = self.lbOperatorsDict[self.name2var[cond.varName].varType].findItems(str(cond.operator), Qt.MatchExactly)
446            if lbItems != []:
447                self.lbOperatorsDict[self.name2var[cond.varName].varType].setCurrentItem(lbItems[0])
448            # values
449            self.valuesStack.setCurrentWidget(self.boxIndices[self.name2var[cond.varName].varType])
450            if self.name2var[cond.varName].varType == orange.VarTypes.Continuous:
451                self.leNum1.setText(str(cond.val1))
452                if cond.operator.isInterval:
453                    self.leNum2.setText(str(cond.val2))
454            elif self.name2var[cond.varName].varType == orange.VarTypes.String:
455                self.leStr1.setText(str(cond.val1))
456                if cond.operator.isInterval:
457                    self.leStr2.setText(str(cond.val2))
458                self.cbCaseSensitive.setChecked(cond.caseSensitive)
459            elif self.name2var[cond.varName].varType == orange.VarTypes.Discrete:
460                self.lbVals.clearSelection()
461                for val in cond.val1:
462                    lbItems = self.lbVals.findItems(val, Qt.MatchExactly)
463                    for item in lbItems:
464                        item.setSelected(1)
465        self.updateMoveButtons()
466
467
468    def criteriaActiveChange(self, condition, active):
469        """Handles clicks on criteria table checkboxes, send out new data.
470        """
471        condition.enabled = active
472        # update the numbers of examples that matches "OR" filter
473        self.updateFilteredDataLens(condition)
474        # send out new data
475        if self.updateOnChange:
476            self.setOutput()
477
478
479    ############################################################################################################################################################
480    ## Interface state management - updates interface elements based on selection in list boxes ################################################################
481    ############################################################################################################################################################
482
483    def updateMoveButtons(self):
484        """enable/disable Move Up/Down buttons
485        """
486        row = self.criteriaTable.currentRow()
487        numRows = self.criteriaTable.rowCount()
488        if row > 0:
489            self.btnMoveUp.setEnabled(True)
490        else:
491            self.btnMoveUp.setEnabled(False)
492        if row < numRows-1:
493            self.btnMoveDown.setEnabled(True)
494        else:
495            self.btnMoveDown.setEnabled(False)
496
497
498    def updateOperatorStack(self):
499        """Raises listbox with appropriate operators.
500        """
501        if self.currentVar:
502            varType = self.currentVar.varType
503            self.btnNew.setEnabled(True)
504        else:
505            varType = 0
506            self.btnNew.setEnabled(False)
507        for vt,lb in self.lbOperatorsDict.items():
508            if vt == varType:
509                lb.show()
510                try:
511                    lb.setCurrentRow(self.data.domain.isOptionalMeta(self.currentVar) and lb.count() - 1)
512                except:
513                    lb.setCurrentRow(0)
514            else:
515                lb.hide()
516
517
518    def updateValuesStack(self):
519        """Raises appropriate widget for values from stack,
520        fills listBox for discrete attributes,
521        shows statistics for continuous attributes.
522        """
523        if self.currentVar:
524            varType = self.currentVar.varType
525        else:
526            varType = 0
527        currentOper = self.currentOperatorDict.get(varType,None)
528        if currentOper:
529            # raise widget
530            self.valuesStack.setCurrentWidget(self.boxIndices[currentOper.varType])
531            if currentOper.varType==orange.VarTypes.Discrete:
532                # store selected discrete values, refill values list box, set single/multi selection mode, restore selected item(s)
533                selectedItemNames = []
534                for i in range(self.lbVals.count()):
535                    if self.lbVals.item(i).isSelected():
536                        selectedItemNames.append(str(self.lbVals.item(i).text()))
537                self.lbVals.clear()
538                curVarValues = []
539                for value in self.currentVar:
540                    curVarValues.append(str(value))
541                curVarValues.sort()
542                for value in curVarValues:
543                    self.lbVals.addItem(str(value))
544                if currentOper.isInterval:
545                    self.lbVals.setSelectionMode(QListWidget.MultiSelection)
546                else:
547                    self.lbVals.setSelectionMode(QListWidget.SingleSelection)
548                isSelected = False
549                for name in selectedItemNames:
550                    items = self.lbVals.findItems(name, Qt.MatchExactly)
551                    for item in items:
552                        item.setSelected(1)
553                        isSelected = True
554                        if not currentOper.isInterval:
555                            break
556                if not isSelected:
557                    if self.lbVals.count() > 0:
558                        self.lbVals.item(0).setSelected(True)
559                    else:
560                        self.currentVals = []
561            elif currentOper.varType==orange.VarTypes.Continuous:
562                # show / hide "and" label and 2nd line edit box
563                if currentOper.isInterval:
564                    self.lblAndCon.show()
565                    self.leNum2.show()
566                else:
567                    self.lblAndCon.hide()
568                    self.leNum2.hide()
569                # display attribute statistics
570                if self.currentVar in self.data.domain.variables:
571                    basstat = self.bas[self.currentVar]
572                else:
573                    basstat = orange.BasicAttrStat(self.currentVar, self.data)
574
575                if basstat.n:
576                    min, avg, max = ["%.3f" % x for x in (basstat.min, basstat.avg, basstat.max)]
577                    self.Num1, self.Num2 = basstat.min, basstat.max
578                else:
579                    min = avg = max = "-"
580                    self.Num1 = self.Num2 = 0
581
582                self.lblMin.setText("Min: %s" % min)
583                self.lblAvg.setText("Avg: %s" % avg)
584                self.lblMax.setText("Max: %s" % max)
585                self.lblDefined.setText("Defined for %i example(s)" % basstat.n)
586
587            elif currentOper.varType==orange.VarTypes.String:
588                # show / hide "and" label and 2nd line edit box
589                if currentOper.isInterval:
590                    self.lblAndStr.show()
591                    self.leStr2.show()
592                else:
593                    self.lblAndStr.hide()
594                    self.leStr2.hide()
595        else:
596            self.valuesStack.setCurrentWidget(self.boxIndices[0])
597
598
599    def getConditionFromSelection(self):
600        """Returns a condition according to the currently selected attribute / operator / values.
601        """
602        if self.currentVar:
603            if self.currentVar.varType == orange.VarTypes.Continuous:
604                try:
605                    val1 = float(self.Num1)
606                    val2 = float(self.Num2)
607                except ValueError:
608                    return
609            elif self.currentVar.varType == orange.VarTypes.String:
610                val1 = self.Str1
611                val2 = self.Str2
612            elif self.currentVar.varType == orange.VarTypes.Discrete:
613                val1 = self.currentVals
614                if not val1:
615                    return
616                val2 = None
617            if not self.currentOperatorDict[self.currentVar.varType].isInterval:
618                val2 = None
619            return Condition(True, "AND", self.currentVar.name, self.currentOperatorDict[self.currentVar.varType], self.NegateCondition, val1, val2, self.CaseSensitive)
620
621
622    def synchronizeTable(self):
623#        for row in range(len(self.Conditions), self.criteriaTable.rowCount()):
624#            self.criteriaTable.clearCellWidget(row,0)
625#            self.criteriaTable.clearCell(row,1)
626
627        currentRow = self.criteriaTable.currentRow()
628        self.criteriaTable.clearContents()
629        self.criteriaTable.setRowCount(len(self.Conditions))
630
631        for row, cond in enumerate(self.Conditions):
632            if cond.type == "OR":
633                cw = QLabel("", self)
634            else:
635                cw = QCheckBox(str(len(cond.operator.getFilter(self.data.domain, cond.varName, cond.val1, cond.val2, cond.negated, cond.caseSensitive)(self.data))), self)
636#                cw.setChecked(cond.enabled)
637                self.connect(cw, SIGNAL("toggled(bool)"), lambda val, cond=cond: self.criteriaActiveChange(cond, val))
638
639            self.criteriaTable.setCellWidget(row, 0, cw)
640# This is a fix for Qt bug (4.3). When Qt is fixed, the setChecked above should suffice
641# but now it unchecks the checkbox as it is inserted
642            if cond.type != "OR":
643                cw.setChecked(cond.enabled)
644
645            # column 1
646            if cond.type == "OR":
647                txt = "OR"
648            else:
649                soper = str(cond.operator)
650                if cond.negated and soper in Operator.negations:
651                    txt = "'%s' %s " % (cond.varName, Operator.negations[soper])
652                else:
653                    txt = (cond.negated and "NOT " or "") + "'%s' %s " % (cond.varName, soper)
654                if cond.operator != Operator.operatorDef:
655                    if cond.operator.varType == orange.VarTypes.Discrete:
656                        if cond.operator.isInterval:
657                            if len(cond.val1) > 0:
658                                txt += "["
659                                for name in cond.val1:
660                                    txt += "%s, " % name
661                                txt = txt[0:-2] + "]"
662                            else:
663                                txt += "[]"
664                        else:
665                            txt += cond.val1[0]
666                    elif cond.operator.varType == orange.VarTypes.String:
667                        if cond.caseSensitive:
668                            cs = " (C)"
669                        else:
670                            cs = ""
671                        if cond.operator.isInterval:
672                            txt += "'%s'%s and '%s'%s" % (cond.val1, cs, cond.val2, cs)
673                        else:
674                            txt += "'%s'%s" % (cond.val1, cs)
675                    elif cond.operator.varType == orange.VarTypes.Continuous:
676                        if cond.operator.isInterval:
677                            txt += str(cond.val1) + " and " + str(cond.val2)
678                        else:
679                            txt += str(cond.val1)
680
681            OWGUI.tableItem(self.criteriaTable, row, 1, txt)
682
683        self.criteriaTable.setCurrentCell(max(currentRow, len(self.Conditions) - 1), 0)
684        self.criteriaTable.resizeRowsToContents()
685        self.updateFilteredDataLens()
686
687        en = len(self.Conditions)
688        self.btnUpdate.setEnabled(en)
689        self.btnRemove.setEnabled(en)
690        self.updateMoveButtons()
691
692
693    def updateFilteredDataLens(self, cond=None):
694        """Updates the number of examples that match individual conditions in criteria table.
695        If cond is given, updates the given row and the corresponding OR row;
696        if cond==None, updates the number of examples in OR rows.
697        """
698        if cond:
699            condIdx = self.Conditions.index(cond)
700            # idx1: the first non-OR condition above the clicked condition
701            # idx2: the first OR condition below the clicked condition
702            idx1 = 0
703            idx2 = len(self.Conditions)
704            for i in range(condIdx,idx1-1,-1):
705                if self.Conditions[i].type == "OR":
706                    idx1 = i+1
707                    break
708            for i in range(condIdx+1,idx2):
709                if self.Conditions[i].type == "OR":
710                    idx2 = i
711                    break
712            fdListAll = self.getFilterList(self.data.domain, self.Conditions[idx1:idx2], enabledOnly=False)
713            fdListEnabled = self.getFilterList(self.data.domain, self.Conditions[idx1:idx2], enabledOnly=True)
714            # if we click on the row which has a preceeding OR: update OR at index idx1-1
715            if idx1 > 0:
716                self.criteriaTable.cellWidget(idx1-1,0).setText(str(len(orange.Filter_conjunction(fdListEnabled[0])(self.data))))
717            # update the clicked row
718            self.criteriaTable.cellWidget(condIdx,0).setText(str(len(fdListAll[0][condIdx-idx1](self.data))))
719
720        elif len(self.Conditions) > 0:
721            # update all "OR" rows
722            fdList = self.getFilterList(self.data.domain, self.Conditions, enabledOnly=True)
723            idx = 1
724            for row,cond in enumerate(self.Conditions):
725                if cond.type == "OR":
726                    self.criteriaTable.cellWidget(row,0).setText(str(len(orange.Filter_conjunction(fdList[idx])(self.data))))
727                    idx += 1
728
729
730    def updateInfoIn(self, data):
731        """Updates data in info box.
732        """
733        if data:
734            varList = data.domain.variables.native() + data.domain.getmetas().values()
735            self.dataInAttributesLabel.setText("%s attribute%s" % self.sp(varList))
736            self.dataInExamplesLabel.setText("%s example%s" % self.sp(data))
737        else:
738            self.dataInExamplesLabel.setText("No examples.")
739            self.dataInAttributesLabel.setText("No attributes.")
740
741
742    def updateInfoOut(self, data):
743        """Updates data out info box.
744        """
745        if data:
746            varList = data.domain.variables.native() + data.domain.getmetas().values()
747            self.dataOutAttributesLabel.setText("%s attribute%s" % self.sp(varList))
748            self.dataOutExamplesLabel.setText("%s example%s" % self.sp(data))
749        else:
750            self.dataOutExamplesLabel.setText("No examples.")
751            self.dataOutAttributesLabel.setText("No attributes.")
752
753
754    ############################################################################################################################################################
755    ## Utility functions #######################################################################################################################################
756    ############################################################################################################################################################
757
758    def sp(self, l, capitalize=True):
759        """Input: list; returns tupple (str(len(l)), "s"/"")
760        """
761        n = len(l)
762        if n == 0:
763            if capitalize:
764                return "No", "s"
765            else:
766                return "no", "s"
767        elif n == 1:
768            return str(n), ''
769        else:
770            return str(n), 's'
771
772
773    def prinConditions(self):
774        """For debugging only.
775        """
776        print "idx\tE\ttype\tattr\toper\tneg\tval1\tval2\tcs"
777        for i,cond in enumerate(self.Conditions):
778            if cond.type == "OR":
779                print "%i\t%i\t%s" % (i+1, int(cond.enabled),cond.type)
780            else:
781                print "%i\t%i\t%s\t%s\t%s\t%i\t%s\t%s\t%i" % (i+1,
782                int(cond.enabled),cond.type,cond.varName,str(cond.operator),
783                int(cond.negated),str(cond.val1),str(cond.val2),int(cond.caseSensitive))
784               
785    def sendReport(self):
786        self.reportSettings("Output", [("Remove unused values/attributes", self.purgeAttributes),
787                                       ("Remove unused classes", self.purgeClasses)])
788        text = "<table>\n<th>Attribute</th><th>Condition</th><th>Value</th>/n"
789        for i, cond in enumerate(self.Conditions):
790            if cond.type == "OR":
791                text += "<tr><td span=3>\"OR\"</td></tr>\n"
792            else:
793                text += "<tr><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (cond.varName, repr(cond.operator), cond.val1)
794           
795        text += "</table>" 
796        import OWReport
797        self.reportSection("Conditions")
798        self.reportRaw(OWReport.reportTable(self.criteriaTable))
799#        self.reportTable("Conditions", self.criteriaTable)
800
801
802class Condition:
803    def __init__(self, enabled, type, attribute = None, operator = None, negate = False, value1 = None, value2 = None, caseSensitive = False):
804        self.enabled = enabled                  # True/False
805        self.type = type                        # "AND"/"OR"
806        self.varName = attribute                # orange.Variable
807        self.operator = operator                # Operator
808        self.negated = negate                   # True/False
809        self.val1 = value1                      # string/float
810        self.val2 = value2                      # string/float
811        self.caseSensitive = caseSensitive      # True/False
812
813
814class Operator:
815    operatorsD = staticmethod(["equals","in"])
816    operatorsC = staticmethod(["=","<","<=",">",">=","between","outside"])
817    operatorsS = staticmethod(["=","<","<=",">",">=","contains","begins with","ends with","between","outside"])
818    operatorDef = staticmethod("is defined")
819    getOperators = staticmethod(lambda: Operator.operatorsD + Operator.operatorsS + [Operator.operatorDef])
820   
821    negations = {"equals": "does not equal", "in": "is not in",
822                 "between": "not between", "outside": "not outside",
823                 "contains": "does not contain", "begins with": "does not begin with", "ends with": "does not end with",
824                 "is defined": "is undefined"}
825
826    _operFilter = {"=":orange.Filter_values.Equal,
827                   "<":orange.Filter_values.Less,
828                   "<=":orange.Filter_values.LessEqual,
829                   ">":orange.Filter_values.Greater,
830                   ">=":orange.Filter_values.GreaterEqual,
831                   "between":orange.Filter_values.Between,
832                   "outside":orange.Filter_values.Outside,
833                   "contains":orange.Filter_values.Contains,
834                   "begins with":orange.Filter_values.BeginsWith,
835                   "ends with":orange.Filter_values.EndsWith}
836
837    def __init__(self, operator, varType):
838        """Members: operator, varType, isInterval.
839        """
840        assert operator in Operator.getOperators(), "Unknown operator: %s" % str(operator)
841        self.operator = operator
842        self.varType = varType
843        self.isInterval = False
844        if operator in Operator.operatorsC and Operator.operatorsC.index(operator) > 4 \
845           or operator in Operator.operatorsD and Operator.operatorsD.index(operator) > 0 \
846           or operator in Operator.operatorsS and Operator.operatorsS.index(operator) > 7:
847            self.isInterval = True
848
849    def __eq__(self, other):
850        assert other in Operator.getOperators()
851        return  self.operator == other
852
853    def __ne__(self, other):
854        assert other in Operator.getOperators()
855        return self.operator != other
856
857    def __repr__(self):
858        return str(self.operator)
859
860    def __strr__(self):
861        return str(self.operator)
862
863    def getFilter(self, domain, variable, value1, value2, negate, caseSensitive):
864        """Returns orange filter.
865        """
866        if self.operator == Operator.operatorDef:
867            try:
868                id = domain.index(variable)
869            except:
870                error("Error: unknown attribute (%s)." % variable)
871
872            if id >= 0:
873                f = orange.Filter_isDefined(domain=domain)
874                for v in domain.variables:
875                    f.check[v] = 0
876                f.check[variable] = 1
877            else: # variable is a meta
878                    f = orange.Filter_hasMeta(id = domain.index(variable))
879        elif self.operator in Operator.operatorsD:
880            f = orange.Filter_values(domain=domain)
881            f[variable] = value1
882        else:
883            f = orange.Filter_values(domain=domain)
884            if value2:
885                f[variable] = (Operator._operFilter[str(self.operator)], value1, value2)
886            else:
887                f[variable] = (Operator._operFilter[str(self.operator)], value1)
888            if self.varType == orange.VarTypes.String:
889                f[variable].caseSensitive = caseSensitive
890        f.negate = negate
891        return f
892
893
894
895if __name__=="__main__":
896    import sys
897    #data = orange.ExampleTable('dicty_800_genes_from_table07.tab')
898    data = orange.ExampleTable('../../doc/datasets/adult_sample.tab')
899#    data = orange.ExampleTable(r"E:\Development\Orange Datasets\UCI\iris.tab")
900    # add meta attribute
901    #data.domain.addmeta(orange.newmetaid(), orange.StringVariable("workclass_name"))
902
903    a=QApplication(sys.argv)
904    ow=OWSelectData()
905    ow.show()
906    ow.setData(data)
907    a.exec_()
908
Note: See TracBrowser for help on using the repository browser.