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

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

Changes in headers, widget descriptions text.

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