source: orange/Orange/OrangeWidgets/OWMosaicOptimization.py @ 9671:a7b056375472

Revision 9671:a7b056375472, 58.0 KB checked in by anze <anze.staric@…>, 2 years ago (diff)

Moved orange to Orange (part 2)

Line 
1from OWBaseWidget import *
2from OWWidget import OWWidget
3import os
4import OWGUI, orngVisFuncts, OWQCanvasFuncts
5from orngMosaic import *
6from orngScaleData import getVariableValuesSorted
7
8mosaicMeasures = [("Pearson's Chi Square", CHI_SQUARE),
9                  ("Pearson's Chi Square (with class)", CHI_SQUARE_CLASS),
10                  ("Cramer's Phi (with class)", CRAMERS_PHI_CLASS),
11                  ("Information Gain (in % of class entropy removed)", INFORMATION_GAIN),
12#                  ("Gain Ratio", GAIN_RATIO),
13                  ("Distance Measure", DISTANCE_MEASURE),
14                  ("Minimum Description Length", MDL),
15                  ("Interaction Gain (in % of class entropy removed)", INTERACTION_GAIN),
16                  ("Average Probability Of Correct Classification", AVERAGE_PROBABILITY_OF_CORRECT_CLASSIFICATION),
17                  ("Gini index", GINI_INDEX),
18                  ("CN2 Rules", CN2_RULES)]
19
20allExamplesText = "<All examples>"
21
22class OWMosaicOptimization(OWWidget, orngMosaic):
23    resultsListLenNums = [ 100 ,  250 ,  500 ,  1000 ,  5000 ,  10000, 20000, 50000, 100000]
24    resultsListLenList = [str(x) for x in resultsListLenNums]
25    settingsList = ["optimizationType", "attributeCount", "attrDisc", "qualityMeasure", "percentDataUsed", "ignoreTooSmallCells",
26                    "timeLimit", "useTimeLimit", "VizRankClassifierName", "mValue", "probabilityEstimation",
27                    "optimizeAttributeOrder", "optimizeAttributeValueOrder", "attributeOrderTestingMethod",
28                    "classificationMethod", "classConfidence", "lastSaveDirName",
29                    "projectionLimit", "useProjectionLimit"]
30
31    percentDataNums = [1, 5 ,  10 ,  15 ,  20 ,  30 ,  40 ,  50 ,  60 ,  70 ,  80 ,  90 ,  100 ]
32    #evaluationTimeNums = [0.5, 1, 2, 5, 10, 20, 30, 40, 60, 80, 120]
33
34    def __init__(self, visualizationWidget = None, signalManager = None):
35        OWWidget.__init__(self, None, signalManager, "Mosaic Evaluation Dialog", savePosition = True, wantMainArea = 0, wantStatusBar = 1)
36        orngMosaic.__init__(self)
37
38        self.resize(390,620)
39        self.setCaption("Mosaic Evaluation Dialog")
40       
41        # loaded variables
42        self.visualizationWidget = visualizationWidget
43        self.showConfidence = 1
44        self.optimizeAttributeOrder = 0
45        self.optimizeAttributeValueOrder = 0
46        self.VizRankClassifierName = "Mosaic Learner"
47        self.useTimeLimit = 0
48        self.useProjectionLimit = 0
49
50        self.lastSaveDirName = os.getcwd()
51        self.selectedClasses = []
52        self.cancelArgumentation = 0
53
54        # explorer variables
55        self.wholeDataSet = None
56        self.processingSubsetData = 0       # this is a flag that we set when we call visualizationWidget.setData function
57        self.showDataSubset = 1
58        self.invertSelection = 0
59        self.mosaicSize = 300
60
61        self.loadSettings()
62        self.layout().setMargin(0)
63        self.tabs = OWGUI.tabWidget(self.controlArea)
64        self.MainTab = OWGUI.createTabPage(self.tabs, "Main")
65        self.SettingsTab = OWGUI.createTabPage(self.tabs, "Settings")
66        self.ArgumentationTab = OWGUI.createTabPage(self.tabs, "Argumentation")
67        self.ClassificationTab = OWGUI.createTabPage(self.tabs, "Classification")
68        self.TreeTab = OWGUI.createTabPage(self.tabs, "Tree")
69        self.ManageTab = OWGUI.createTabPage(self.tabs, "Manage")
70
71        # ###########################
72        # MAIN TAB
73        self.optimizationBox = OWGUI.widgetBox(self.MainTab, "Evaluate")
74        self.buttonBox = OWGUI.widgetBox(self.optimizationBox, orientation = "horizontal")
75        self.resultsBox = OWGUI.widgetBox(self.MainTab, "Projection List, Most Interesting Projections First")
76        self.optimizeOrderBox = OWGUI.widgetBox(self.MainTab, "Attribute and Value Order")
77        self.optimizeOrderSubBox = OWGUI.widgetBox(self.optimizeOrderBox, orientation = "horizontal")
78        self.buttonsBox = OWGUI.widgetBox(self.MainTab, box = 1)
79
80        self.label1 = OWGUI.widgetLabel(self.buttonBox, 'Projections with ')
81        self.optimizationTypeCombo = OWGUI.comboBox(self.buttonBox, self, "optimizationType", items = ["    exactly    ", "  maximum  "] )
82        self.attributeCountCombo = OWGUI.comboBox(self.buttonBox, self, "attributeCount", items = range(1, 5), tooltip = "Evaluate only projections with exactly (or maximum) this number of attributes", sendSelectedValue = 1, valueType = int)
83        self.attributeLabel = OWGUI.widgetLabel(self.buttonBox, ' attributes')
84
85        self.startOptimizationButton = OWGUI.button(self.optimizationBox, self, "Start Evaluating Projections", callback = self.evaluateProjections)
86        f = self.startOptimizationButton.font(); f.setBold(1);   self.startOptimizationButton.setFont(f)
87        self.stopOptimizationButton = OWGUI.button(self.optimizationBox, self, "Stop Evaluation", callback = self.stopEvaluationClick)
88        self.stopOptimizationButton.setFont(f)
89        self.stopOptimizationButton.hide()
90
91        self.resultList = OWGUI.listBox(self.resultsBox, self, callback = self.showSelectedAttributes)
92        self.resultList.setMinimumHeight(200)
93
94        OWGUI.checkBox(self.optimizeOrderSubBox, self, "optimizeAttributeOrder", "Optimize attributes", callback = self.optimizeCurrentAttributeOrder, tooltip = "Order the visualized attributes so that it will enhance class separation")
95        OWGUI.checkBox(self.optimizeOrderSubBox, self, "optimizeAttributeValueOrder", "Optimize attribute values", callback = self.optimizeCurrentAttributeOrder, tooltip = "Order also the values of visualized attributes so that it will enhance class separation.\nWARNING: This can take a lot of time when visualizing attributes with many values.")
96
97        self.optimizeOrderButton = OWGUI.button(self.buttonsBox, self, "Optimize Current Attribute Order", callback = self.optimizeCurrentAttributeOrder, tooltip = "Optimize the order of currently visualized attributes", debuggingEnabled=0)
98
99        # ##########################
100        # SETTINGS TAB
101        self.measureCombo = OWGUI.comboBox(self.SettingsTab, self, "qualityMeasure", box = "Measure of Projection Interestingness", items = [item[0] for item in mosaicMeasures], tooltip = "What is interesting?", callback = self.updateGUI)
102
103        self.ignoreSmallCellsBox = OWGUI.widgetBox(self.SettingsTab, "Ignore small cells")
104        OWGUI.checkBox(self.ignoreSmallCellsBox, self, "ignoreTooSmallCells", "Ignore cells where expected number of cases is less than 5", tooltip = "Statisticians advise that in cases when the number of expected examples is less than 5 we ignore the cell \nsince it can significantly influence the chi-square value.")
105       
106        OWGUI.comboBoxWithCaption(self.SettingsTab, self, "percentDataUsed", "Percent of data used: ", box = "Data settings", items = self.percentDataNums, sendSelectedValue = 1, valueType = int, tooltip = "In case that we have a large dataset the evaluation of each projection can take a lot of time.\nWe can therefore use only a subset of randomly selected examples, evaluate projection on them and thus make evaluation faster.")
107
108        self.testingBox = OWGUI.widgetBox(self.SettingsTab, "Testing Method")
109        self.testingCombo = OWGUI.comboBox(self.testingBox, self, "testingMethod", items = ["10 fold cross validation", "70/30 separation 10 times "], tooltip = "Method for evaluating the class separation in the projection.")
110
111        OWGUI.comboBox(self.SettingsTab, self, "attrDisc", box = "Measure for Ranking Attributes", items = [val for (val, m) in discMeasures], callback = self.removeEvaluatedAttributes)
112
113        self.testingCombo2 = OWGUI.comboBox(self.SettingsTab, self, "attributeOrderTestingMethod", box = "Testing Method Used for Optimizing Attribute Orders", items = ["10 fold cross validation", "Learn and test on learn data"], tooltip = "Method used when evaluating different attribute orders.")
114
115        self.stopOptimizationBox = OWGUI.widgetBox(self.SettingsTab, "When to Stop Evaluation or Optimization?")
116        OWGUI.checkWithSpin(self.stopOptimizationBox, self, "Time limit:                     ", 1, 1000, "useTimeLimit", "timeLimit", "  (minutes)", debuggingEnabled = 0)      # disable debugging. we always set this to 1 minute
117        OWGUI.checkWithSpin(self.stopOptimizationBox, self, "Use projection count limit:  ", 1, 1000000, "useProjectionLimit", "projectionLimit", "  (projections)", debuggingEnabled = 0)
118        OWGUI.rubber(self.SettingsTab)
119
120        # ##########################
121        # ARGUMENTATION TAB
122        self.argumentationBox = OWGUI.widgetBox(self.ArgumentationTab, "Arguments")
123        self.findArgumentsButton = OWGUI.button(self.argumentationBox, self, "Find Arguments", callback = self.findArguments, tooltip = "Evaluate arguments for each possible class value using settings in the Classification tab.", debuggingEnabled = 0)
124        f = self.findArgumentsButton.font(); f.setBold(1);  self.findArgumentsButton.setFont(f)
125        self.stopArgumentationButton = OWGUI.button(self.argumentationBox, self, "Stop Searching", callback = self.stopArgumentationClick)
126        self.stopArgumentationButton.setFont(f)
127        self.stopArgumentationButton.hide()
128
129        self.argumentsClassBox = OWGUI.widgetBox(self.ArgumentationTab, "Show Arguments for Class:", orientation = "horizontal")
130        self.classValueCombo = OWGUI.comboBox(self.argumentsClassBox, self, "argumentationClassValue", tooltip = "Select the class value that you wish to see arguments for", callback = self.updateShownArguments)
131        self.logitLabel = OWGUI.widgetLabel(self.argumentsClassBox, " ", labelWidth = 100)
132
133        self.argumentBox = OWGUI.widgetBox(self.ArgumentationTab, "Arguments/Odds Ratios for the Selected Class Value")
134        self.argumentList = OWGUI.listBox(self.argumentBox, self, callback = self.argumentSelected)
135        self.argumentList.setMinimumHeight(200)
136        self.resultsDetailsBox = OWGUI.widgetBox(self.ArgumentationTab, "Shown Details in Arguments List" , orientation = "horizontal")
137        self.showConfidenceCheck = OWGUI.checkBox(self.resultsDetailsBox, self, 'showConfidence', '95% confidence interval', callback = self.updateShownArguments, tooltip = "Show confidence interval of the argument.")
138
139        #Remove Argumentation tab (Argumentation does not work)
140        self.tabs.removeTab(2)
141        self.ArgumentationTab.hide()
142       
143       
144        # ##########################
145        # CLASSIFICATION TAB
146        self.classifierNameEdit = OWGUI.lineEdit(self.ClassificationTab, self, 'VizRankClassifierName', box = ' Learner / Classifier Name ', tooltip='Name to be used by other widgets to identify your learner/classifier.')
147
148        #self.argumentValueFormulaIndex = OWGUI.comboBox(self.ClassificationTab, self, "argumentValueFormula", box="Argument Value is Computed As ...", items=["1.0 x Projection Value", "0.5 x Projection Value + 0.5 x Predicted Example Probability", "1.0 x Predicted Example Probability"], tooltip=None)
149        probBox = OWGUI.widgetBox(self.ClassificationTab, box = "Probability Estimation")
150        self.probCombo = OWGUI.comboBox(probBox, self, "probabilityEstimation", items = ["Relative Frequency", "Laplace", "m-Estimate"], callback = self.updateMEstimateComboState)
151
152        self.mEditBox = OWGUI.lineEdit(probBox, self, 'mValue', label='              Parameter for m-estimate:   ', orientation='horizontal', valueType = float, validator = QDoubleValidator(0,10000,1, self))
153
154        b = OWGUI.widgetBox(self.ClassificationTab, "Evaluation Time")
155        OWGUI.checkWithSpin(b, self, "Use time limit:    ", 1, 1000, "useTimeLimit", "timeLimit", "(minutes)", debuggingEnabled = 0)      # disable debugging. we always set this to 1 minute
156        classBox = OWGUI.widgetBox(self.ClassificationTab, "Class Prediction Settings")
157        classMethodsCombo = OWGUI.comboBox(classBox, self, "classificationMethod", items = ["Top-ranked projections", "Semi-naive Bayes", "Naive Bayes with combining attribute values"], callback = self.updateClassMethodsCombo)
158
159        # top projection settings
160        self.classTopProjCount = OWGUI.widgetBox(classBox, orientation="horizontal")
161        OWGUI.comboBoxWithCaption(self.classTopProjCount, self, "clsTopProjCount", "Number of top projections used:", tooltip = "How many of the top projections do you want to consider in class prediction?", items = [1, 2, 3, 5, 10, 15, 20, 30, 50, 100], sendSelectedValue = 1, valueType = int)
162
163        # semi naive bayes parameters
164        self.classTau = OWGUI.widgetBox(classBox, orientation="horizontal")
165        OWGUI.comboBoxWithCaption(self.classTau, self, "clsTau", "Treshold value (tau):", tooltip = "Value above which we join attribute values.", items = [0.5, 0.6, 0.7, 0.8, 0.9, 1.0], sendSelectedValue = 1, valueType = float)
166
167        # combining attribute values
168        self.classConfidenceBox = OWGUI.widgetBox(classBox, orientation="horizontal")
169        OWGUI.separator(self.classConfidenceBox, 20, 0)
170        OWGUI.spin(self.classConfidenceBox, self, "classConfidence", 0, 99, 1, label = 'Confidence Interval (%):    ', tooltip = 'Confidence interval used in deciding whether to use a set of attributes independently or dependently')
171
172        OWGUI.button(self.ClassificationTab, self, "Resend Learner", callback = self.resendLearner, tooltip = "Resend learner with new settings. You need to press this \nonly when you are sending mosaic learner signal to other widgets.")
173        OWGUI.rubber(self.ClassificationTab)
174
175        # ##########################
176        # TREE TAB
177        subsetBox = OWGUI.widgetBox(self.TreeTab, "Example Subset Analysis")
178        self.splitter = QSplitter(Qt.Vertical, subsetBox)
179        subsetBox.layout().addWidget(self.splitter)
180        self.subsetTree = QTreeWidget(self.splitter)
181        self.splitter.addWidget(self.subsetTree)
182        self.subsetTree.setColumnCount(2)
183        self.subsetTree.setHeaderLabels(['Visualized Attributes', '# inst.'])
184        #self.subsetTree.setAllColumnsShowFocus(1)
185        self.subsetTree.header().setStretchLastSection(True)
186        self.subsetTree.header().setClickable(0)
187        self.subsetTree.header().setSortIndicatorShown(0)
188        self.subsetTree.header().setResizeMode(0, QHeaderView.Stretch)
189
190#        self.subsetTree.addColumn()
191#        self.subsetTree.addColumn()
192#        self.subsetTree.setColumnWidth(0, 300)
193#        self.subsetTree.setColumnWidthMode(0, QListView.Maximum)
194#        self.subsetTree.setColumnAlignment(0, QListView.AlignLeft)
195#        self.subsetTree.setColumnWidth(1, 50)
196#        self.subsetTree.setColumnWidthMode(1, QListView.Manual)
197#        self.subsetTree.setColumnAlignment(1, QListView.AlignRight)
198
199        self.connect(self.subsetTree, SIGNAL("itemSelectionChanged()"), self.mtSelectedTreeItemChanged)
200        #self.connect(self.subsetTree, SIGNAL("rightButtonPressed(QListViewItem *, const QPoint &, int )"), self.mtSubsetTreeRemoveItemPopup)
201
202        self.selectionsList = OWGUI.listBox(self.splitter, self, callback = self.mtSelectedListItemChanged)
203        self.connect(self.selectionsList, SIGNAL('itemDoubleClicked(QListWidgetItem *)'), self.mtSelectedListItemDoubleClicked)
204
205        self.subsetItems = {}
206        self.subsetUpdateInProgress = 0
207        self.treeRoot = None
208
209        explorerBox = OWGUI.widgetBox(self.TreeTab, 1)
210        OWGUI.button(explorerBox, self, "Explore Currently Selected Examples", callback = self.mtEploreCurrentSelection, tooltip = "Visualize only selected examples and find interesting projections of them", debuggingEnabled=0)
211        OWGUI.checkBox(explorerBox, self, 'showDataSubset', 'Show unselected data as example subset', tooltip = "This option determines what to do with the examples that are not selected in the projection.\nIf checked then unselected examples will be visualized in the same way as examples that are received through the 'Example Subset' signal.")
212
213        self.mosaic = orngMosaic()
214        autoBuildTreeBox = OWGUI.widgetBox(self.TreeTab, "Mosaic Tree", orientation = "vertical")
215        autoBuildTreeButtonBox = OWGUI.widgetBox(autoBuildTreeBox, orientation = "horizontal")
216        self.autoBuildTreeButton = OWGUI.button(autoBuildTreeButtonBox, self, "Build Tree", callback = self.mtMosaicAutoBuildTree, tooltip = "Evaluate different mosaic diagrams and automatically build a tree of mosaic diagrams with clear class separation", debuggingEnabled = 0)
217        OWGUI.button(autoBuildTreeButtonBox, self, "Visualize Tree", callback = self.mtVisualizeMosaicTree, tooltip = "Visualize a tree where each node is a mosaic diagram", debuggingEnabled = 0)
218        OWGUI.lineEdit(autoBuildTreeBox, self, "mosaicSize", "Size of individual mosaic diagrams: ", orientation = "horizontal", tooltip = "What are the X and Y dimensions of individual mosaics in the tree?", valueType = int, validator = QIntValidator(self))
219
220        loadSaveBox = OWGUI.widgetBox(self.TreeTab, "Load/Save Mosaic Tree", orientation = "horizontal")
221        OWGUI.button(loadSaveBox, self, "Load", callback = self.mtLoadTree, tooltip = "Load a tree from a file", debuggingEnabled = 0)
222        OWGUI.button(loadSaveBox, self, "Save", callback = self.mtSaveTree, tooltip = "Save tree to a file", debuggingEnabled = 0)
223
224        self.subsetPopupMenu = QMenu(self)
225        self.subsetPopupMenu.addAction("Explore currently selected examples", self.mtEploreCurrentSelection)
226        self.subsetPopupMenu.addAction("Find interesting projection", self.evaluateProjections)
227        self.subsetPopupMenu.addSeparator()
228        self.subsetPopupMenu.addAction("Remove node", self.mtRemoveSelectedItem)
229        self.subsetPopupMenu.addAction("Clear tree", self.mtInitSubsetTree)
230
231        # ##########################
232        # SAVE TAB
233        self.visualizedAttributesBox = OWGUI.widgetBox(self.ManageTab, "Number of Concurrently Visualized Attributes")
234        self.dialogsBox = OWGUI.widgetBox(self.ManageTab, "Dialogs")
235        self.manageResultsBox = OWGUI.widgetBox(self.ManageTab, "Manage projections")
236
237        self.attrLenList = OWGUI.listBox(self.visualizedAttributesBox, self, selectionMode = QListWidget.MultiSelection, callback = self.attrLenListChanged)
238        self.attrLenList.setMinimumHeight(60)
239
240        self.buttonBox7 = OWGUI.widgetBox(self.dialogsBox, orientation = "horizontal")
241        OWGUI.button(self.buttonBox7, self, "Attribute Ranking", self.attributeAnalysis, debuggingEnabled = 0)
242        OWGUI.button(self.buttonBox7, self, "Attribute Interactions", self.interactionAnalysis, debuggingEnabled = 0)
243
244        self.buttonBox8 = OWGUI.widgetBox(self.dialogsBox, orientation = "horizontal")
245        OWGUI.button(self.buttonBox8, self, "Graph Projection Scores", self.graphProjectionQuality, debuggingEnabled = 0)
246        OWGUI.button(self.buttonBox8, self, "Outlier Identification", self.identifyOutliers, debuggingEnabled = 0)
247
248        self.buttonBox6 = OWGUI.widgetBox(self.manageResultsBox, orientation = "horizontal")
249        self.loadButton = OWGUI.button(self.buttonBox6, self, "Load", self.load, debuggingEnabled = 0)
250        self.saveButton = OWGUI.button(self.buttonBox6, self, "Save", self.save, debuggingEnabled = 0)
251
252        self.buttonBox5 = OWGUI.widgetBox(self.manageResultsBox, orientation = "horizontal")
253        self.clearButton = OWGUI.button(self.buttonBox5, self, "Clear results", self.clearResults)
254
255        # reset some parameters if we are debugging so that it won't take too much time
256        if orngDebugging.orngDebuggingEnabled:
257            self.useTimeLimit = 1
258            self.timeLimit = 0.3
259            self.useProjectionLimit = 1
260            self.projectionLimit = 100
261
262        self.updateMEstimateComboState()
263        self.updateClassMethodsCombo()
264        self.updateGUI()
265
266
267
268    # a group of attributes was selected in the list box - draw the mosic plot of them
269    def showSelectedAttributes(self, attrs = None):
270        if not self.visualizationWidget: return
271        if not attrs:
272            projection = self.getSelectedProjection()
273            if not projection: return
274            (score, attrs, index, extraInfo) = projection
275            if extraInfo:
276                ruleVals = []   # for which values of the attributes do we have a rule
277                for (q, a, vals) in extraInfo:
278                    ruleVals.append([vals[a.index(attr)] for attr in attrs])
279                self.visualizationWidget.activeRule = (attrs, ruleVals)
280        valueOrder = None
281        if self.optimizeAttributeOrder:
282            self.resultList.setEnabled(0)
283            self.optimizeCurrentAttributeOrder(attrs)
284            self.resultList.setEnabled(1)
285        else:
286            self.visualizationWidget.setShownAttributes(attrs)
287        self.resultList.setFocus()
288
289
290    def optimizeCurrentAttributeOrder(self, attrs = None, updateGraph = 1):
291        if str(self.optimizeOrderButton.text()) == "Optimize Current Attribute Order":
292            self.cancelOptimization = 0
293            self.optimizeOrderButton.setText("Stop Optimization")
294
295            if not attrs:
296                attrs = self.visualizationWidget.getShownAttributeList()
297
298            bestPlacements = self.findOptimalAttributeOrder(attrs, self.optimizeAttributeValueOrder)
299            if updateGraph:
300                self.visualizationWidget.bestPlacements = bestPlacements
301                if bestPlacements:
302                    attrList, valueOrder = bestPlacements[0][1], bestPlacements[0][2]
303                    self.visualizationWidget.setShownAttributes(attrList, customValueOrderDict = dict([(attrList[i], tuple(valueOrder[i])) for i in range(len(attrList))]) )
304
305            self.optimizeOrderButton.setText("Optimize Current Attribute Order")
306            return bestPlacements
307        else:
308            self.cancelOptimization = 1
309            return []
310
311
312    def updateGUI(self):
313        if self.qualityMeasure in [CHI_SQUARE, CHI_SQUARE_CLASS, CRAMERS_PHI_CLASS]: 
314            self.ignoreSmallCellsBox.show()
315        else:                                                self.ignoreSmallCellsBox.hide()
316        if self.qualityMeasure == AVERAGE_PROBABILITY_OF_CORRECT_CLASSIFICATION: self.testingBox.show()
317        else:   self.testingBox.hide()
318
319    def updateMEstimateComboState(self):
320        self.mEditBox.setEnabled(self.probabilityEstimation == M_ESTIMATE)
321
322    # based on selected classification method show or hide specific controls
323    def updateClassMethodsCombo(self):
324        self.classTopProjCount.hide()
325        self.classTau.hide()
326        self.classConfidenceBox.hide()
327
328        if self.classificationMethod == MOS_TOPPROJ:
329            self.classTopProjCount.show()
330        elif self.classificationMethod == MOS_SEMINAIVE:
331            self.classTau.show()
332        elif self.classificationMethod == MOS_COMBINING:
333            self.classConfidenceBox.show()
334
335
336    # selected measure for attribute ranking has changed. recompute attribute importances
337    def removeEvaluatedAttributes(self):
338        self.evaluatedAttributes = None
339
340    # result list can contain projections with different number of attributes
341    # user clicked in the listbox that shows possible number of attributes of result list
342    # result list must be updated accordingly
343    def attrLenListChanged(self):
344        # check which attribute lengths do we want to show
345        if hasattr(self, "skipUpdate"): return
346
347        self.attrLenDict = {}
348        for i in range(self.attrLenList.count()):
349            self.attrLenDict[int(str(self.attrLenList.item(i).text()))] = self.attrLenList.item(i).isSelected()
350        self.updateShownProjections()
351
352    def clearResults(self):
353        orngMosaic.clearResults(self)
354        self.resultList.clear()
355        self.shownResults = []
356        self.attrLenDict = {}
357        self.attrLenList.clear()
358
359    # ##############################################################
360    # ##############################################################
361
362    def updateShownProjections(self, *args):
363        self.resultList.clear()
364        self.shownResults = []
365
366        for i in range(len(self.results)):
367            if self.attrLenDict.get(len(self.results[i][ATTR_LIST]), 0):
368                self.resultList.addItem("%.3f : %s" % (self.results[i][SCORE], self.buildAttrString(self.results[i][ATTR_LIST])))
369                self.shownResults.append(self.results[i])
370        qApp.processEvents()
371
372        if self.resultList.count() > 0:
373            self.resultList.setCurrentItem(self.resultList.item(0))
374
375    def setData(self, data, removeUnusedValues = 0): 
376        orngMosaic.setData(self, data, removeUnusedValues)
377
378        self.setStatusBarText("")
379        self.classValueCombo.clear()
380        self.argumentList.clear()
381        self.selectedClasses = []
382
383        # for mosaic tree
384        if self.processingSubsetData == 0:
385            self.wholeDataSet = self.data        # we have to use self.data and not data, since in self.data we already have discretized attributes
386            self.mtInitSubsetTree()
387
388        if not self.data: return None
389
390        if self.data.domain.classVar and self.data.domain.classVar.varType == orange.VarTypes.Discrete:
391            # add class values
392            if orange.RemoveUnusedValues(self.data.domain.classVar, self.data) is not None:
393                self.classValueCombo.addItems(getVariableValuesSorted(self.data.domain.classVar))
394                self.updateShownArguments()
395                if len(self.data.domain.classVar.values) > 0:
396                    self.classValueCombo.setCurrentIndex(0)
397
398        return self.data
399
400
401    # ######################################################
402    # Argumentation functions
403    def findArguments(self, example = None, selectBest = 1, showClassification = 1):
404        self.cancelArgumentation = 0
405
406        self.argumentList.clear()
407        self.arguments = [[] for i in range(self.classValueCombo.count())]
408
409        if not example and not self.visualizationWidget.subsetData:
410            QMessageBox.information( None, "Argumentation", 'To find arguments you first have to provide an example that you wish to classify. \nYou can do this by sending the example to the Mosaic display widget through the "Example Subset" signal.', QMessageBox.Ok + QMessageBox.Default)
411            return None, None
412        if len(self.shownResults) == 0:
413            QMessageBox.information( None, "Argumentation", 'To find arguments you first have to evaluate some projections by clicking "Start evaluating projections" in the Main tab.', QMessageBox.Ok + QMessageBox.Default)
414            return None, None
415
416        if not self.data:
417            QMessageBox.critical(None,'No data','There is no data or no class value is selected in the Manage tab.',QMessageBox.Ok)
418            return None, None
419
420        if example == None: example = self.visualizationWidget.subsetData[0]
421
422        self.findArgumentsButton.hide()
423        self.stopArgumentationButton.show()
424
425        classValue, dist = orngMosaic.findArguments(self, example)
426
427        self.stopArgumentationButton.hide()
428        self.findArgumentsButton.show()
429
430        values = getVariableValuesSorted(self.data.domain.classVar)
431        self.argumentationClassValue = values.index(classValue)     # activate the class that has the highest probability
432        self.updateShownArguments()
433        if self.argumentList.count() > 0 and selectBest: self.argumentList.setCurrentIndex(0)
434
435        if showClassification:
436            s = '<nobr>Based on current classification settings, the example would be classified </nobr><br><nobr>to class <b>%(cls)s</b> with probability <b>%(prob).2f%%</b>.</nobr><br><nobr>Predicted class distribution is:</nobr><br>' % {"cls": str(classValue), "prob": max(dist)*100. / float(sum(dist))}
437            for key in values:
438                s += "<nobr>&nbsp &nbsp &nbsp &nbsp %s : %.2f%%</nobr><br>" % (key, dist[key]*100)
439            QMessageBox.information(None, "Classification results", s, QMessageBox.Ok + QMessageBox.Default)
440
441        return (classValue, dist)
442
443
444    def finishedAddingResults(self):
445        self.skipUpdate = 1
446
447        self.attrLenList.clear()
448        for i in range(1,5):
449            if self.attrLenDict.has_key(i):
450                self.attrLenList.addItem(str(i))
451
452        self.attrLenList.selectAll()
453        delattr(self, "skipUpdate")
454        self.updateShownProjections()
455        self.resultList.setCurrentItem(self.resultList.item(0))
456
457
458    # ##############################################################
459    # Loading and saving projection files
460    # ##############################################################
461
462    # save the list into a file - filename can be set if you want to call this function without showing the dialog
463    def save(self, filename = None):
464        if filename == None:
465            # get file name
466            datasetName = getattr(self.data, "name", "")
467            if datasetName != "":
468                filename = "%s - Interesting plots" % (os.path.splitext(os.path.split(datasetName)[1])[0])
469            else:
470                filename = "%s" % (self.parentName)
471            qname = QFileDialog.getSaveFileName(self, "Save the List of Visualizations",  os.path.join(self.lastSaveDirName, filename), "Interesting visualizations (*.mproj)")
472            if qname.isEmpty(): return
473            name = str(qname)
474        else:
475            name = filename
476        self.setStatusBarText("Saving visualizations")
477
478        # take care of extension
479        if os.path.splitext(name)[1] != ".mproj":
480            name = name + ".mproj"
481
482        dirName, shortFileName = os.path.split(name)
483        self.lastSaveDirName = dirName
484
485        orngMosaic.save(self, name)
486
487        self.setStatusBarText("Saved %d visualizations" % (len(self.shownResults)))
488
489
490    # load projections from a file
491    def load(self, name = None, ignoreCheckSum = 0):
492        self.setStatusBarText("Loading visualizations")
493        if self.data == None:
494            QMessageBox.critical(None,'Load','There is no data. First load a data set and then load projection file',QMessageBox.Ok)
495            return
496
497        if name == None:
498            name = QFileDialog.getOpenFileName(self, "Open a List of Visualizations", self.lastSaveDirName, "Interesting visualizations (*.mproj)")
499            if name.isEmpty(): return
500            name = str(name)
501
502        dirName, shortFileName = os.path.split(name)
503        self.lastSaveDirName = dirName
504
505        count = orngMosaic.load(self, name, ignoreCheckSum)
506
507        # update loaded results
508        self.finishedAddingResults()
509        self.setStatusBarText("Loaded %d visualizations" % (count))
510
511
512    # disable all controls while evaluating projections
513    def disableControls(self):
514        self.startOptimizationButton.hide()
515        self.stopOptimizationButton.show()
516        self.SettingsTab.setEnabled(0)
517        self.ManageTab.setEnabled(0)
518
519    def enableControls(self):
520        self.startOptimizationButton.show()
521        self.stopOptimizationButton.hide()
522        self.SettingsTab.setEnabled(1)
523        self.ManageTab.setEnabled(1)
524
525
526    def attrsToString(self, attrList):
527        return ", ".join(attrList)
528
529    def insertItem(self, score, attrList, index, tryIndex, extraInfo = []):
530        orngMosaic.insertItem(self, score, attrList, index, tryIndex, extraInfo)
531        self.resultList.insertItem(index, "%.3f : %s" % (score, self.buildAttrString(attrList)))
532
533
534    # ######################################################
535    # Mosaic tree functions
536    # ######################################################
537    # clear subset tree and create a new root
538    def mtInitSubsetTree(self):
539        self.subsetItems = {}
540        self.subsetTree.clear()
541        self.selectionsList.clear()
542        self.treeRoot = None
543        #self.subsetTree.setColumnWidth(0, self.subsetTree.width() - self.subsetTree.columnWidth(1)-4)
544
545        if self.wholeDataSet:
546            root = QTreeWidgetItem(self.subsetTree, [allExamplesText, str(len(self.wholeDataSet))])
547            root.details = {"data": self.wholeDataSet, "exampleCount": len(self.wholeDataSet)}
548            root.selections = {}
549            self.treeRoot = root
550            root.setExpanded(1)
551            self.processingSubsetData = 1
552            root.setSelected(1)
553            self.processingSubsetData = 0
554
555    # find out which attributes are currently visualized, which examples are selected and what is the additional info
556    def mtGetProjectionState(self, getSelectionIndices = 1):
557        selectedIndices = None
558        attrList = self.visualizationWidget.getShownAttributeList()
559        exampleCount = self.visualizationWidget.data and len(self.visualizationWidget.data) or 0
560        projDict = {"attrs": list(attrList), "exampleCount": exampleCount}
561        selectionDict = {"selectionConditions": list(self.visualizationWidget.selectionConditions), "selectionConditionsHistorically": list(self.visualizationWidget.selectionConditionsHistorically)}
562        if getSelectionIndices:
563            selectedIndices = self.visualizationWidget.getSelectedExamples(asExampleTable = 0)
564            selectionDict["selectedIndices"] = selectedIndices
565        return attrList, selectedIndices, projDict, selectionDict
566
567    # new element is added into the subsetTree
568    def mtEploreCurrentSelection(self):
569        if not self.wholeDataSet:
570            return
571
572        attrList, selectedIndices, projDict, selectionDict = self.mtGetProjectionState()
573
574        if sum(selectedIndices) == 0:
575            QMessageBox.information(self, "No data selection", "To explore a subset of examples you first have to select them in the projection.", QMessageBox.Ok)
576            return
577
578        selectedData = self.visualizationWidget.data.selectref(selectedIndices)
579        unselectedData = self.visualizationWidget.data.selectref(selectedIndices, negate = 1)
580
581        if self.subsetTree.selectedItems() == []: return
582        selectedTreeItem = self.subsetTree.selectedItems()[0]     # current selection
583
584        # add a new item into the list box
585        newListItem = QListWidgetItem(self.mtSelectionsToString(selectionDict), self.selectionsList)
586        newListItem.selections = selectionDict
587        newListItem.setSelected(1)
588
589        # add a child into the tree view
590        attrListStr = self.attrsToString(attrList)
591        newTreeItem = QTreeWidgetItem(selectedTreeItem, [attrListStr, str(len(selectedData))])
592        newTreeItem.details = {"attrs": list(attrList), "exampleCount": len(selectedData)}
593        newTreeItem.selections = selectionDict
594        newTreeItem.setExpanded(1)
595
596
597    # a different attribute set was selected in mosaic. update the attributes in the selected node
598    def mtUpdateState(self):
599        if not self.wholeDataSet: return
600        if self.processingSubsetData: return
601        if self.subsetTree.selectedItems() == []: return
602
603        selectedTreeItem = self.subsetTree.selectedItems()[0]
604        selectedListItem = self.selectionsList.currentItem()
605        attrList, selectionIndices, projDict, selectionDict = self.mtGetProjectionState(getSelectionIndices = 0)
606        if not selectedTreeItem: return
607
608        # if this is the last element in the tree, then update the element's values
609        if selectedTreeItem.child(0) == None:
610            selectedTreeItem.setText(0, self.attrsToString(attrList))
611            selectedTreeItem.details.update(projDict)
612            if selectedListItem:
613                selectedListItem.selections = selectionDict
614                selectedListItem.setText(self.mtSelectionsToString(selectionDict))
615        # add a sibling if we changed any value
616        else:
617            # did we change the visualized attributes. If yes then we have to add a new node into the tree
618            if 0 in [selectedTreeItem.details[key] == projDict[key] for key in projDict.keys()]:
619                newTreeItem = QTreeWidgetItem(selectedTreeItem.parent() or self.subsetTree, [self.attrsToString(attrList), str(selectedTreeItem.text(1))])
620                newTreeItem.setExpanded (1)
621                newTreeItem.details = projDict
622                newTreeItem.selections = {}
623                self.mtClearTreeSelections()
624                newTreeItem.setSelected(1)
625                self.selectionsList.clear()
626
627
628    # we selected a different item in the tree
629    def mtSelectedTreeItemChanged(self, newSelection = None):
630        if self.processingSubsetData:
631            return
632        if newSelection == None:
633            items = self.subsetTree.selectedItems()
634            if len(items) == 0: return
635            newSelection = items[0]
636
637        self.processingSubsetData = 1
638
639        indices = self.mtGetItemIndices(newSelection)
640        selectedData = self.wholeDataSet
641        unselectedData = orange.ExampleTable(self.wholeDataSet.domain)
642        for ind in indices:
643            unselectedData.extend(selectedData.selectref(ind, negate = 1))
644            selectedData = selectedData.selectref(ind)
645
646        # set data
647        if self.invertSelection:
648            temp = selectedData
649            selectedData = unselectedData
650            unselectedData = temp
651        self.visualizationWidget.setData(selectedData)  #self.visualizationWidget.setData(selectedData, onlyDrilling = 1)
652        if self.showDataSubset and len(unselectedData) > 0:
653            self.visualizationWidget.setSubsetData(unselectedData)      #self.visualizationWidget.subsetData = unselectedData
654        else:
655            self.visualizationWidget.setSubsetData(None)
656        self.visualizationWidget.handleNewSignals()
657
658        self.selectionsList.clear()
659        for i in range(newSelection.childCount()):
660            child = newSelection.child(i)
661            selectionDict = child.selections
662            newListItem = QListWidgetItem(self.mtSelectionsToString(selectionDict), self.selectionsList)
663            newListItem.selections = selectionDict
664
665        self.visualizationWidget.setShownAttributes(newSelection.details.get("attrs", None))
666        self.visualizationWidget.updateGraph()
667        self.processingSubsetData = 0
668
669    # a new selection was selected in the selection list. update the graph
670    def mtSelectedListItemChanged(self):
671        selectedListItem = self.selectionsList.currentItem()
672        if not selectedListItem:
673            return
674
675        selectionDict = selectedListItem.selections
676        self.visualizationWidget.selectionConditions = list(selectionDict.get("selectionConditions", []))
677        self.visualizationWidget.selectionConditionsHistorically = list(selectionDict.get("selectionConditionsHistorically", []))
678        self.visualizationWidget.updateGraph()
679
680    def mtSelectedListItemDoubleClicked(self, item):
681        if self.subsetTree.selectedItems() == []: return
682        pos = self.selectionsList.currentRow()
683        treeItem = self.subsetTree.selectedItems()[0].child(pos)
684        self.mtClearTreeSelections()
685        treeItem.setSelected(1)
686        self.mtSelectedTreeItemChanged(treeItem)
687
688    def mtClearTreeSelections(self, item = None):
689        if item == None:
690            item  = self.subsetTree.invisibleRootItem()
691        item.setSelected(0)
692        for i in range(item.childCount()):
693            self.mtClearTreeSelections(item.child(i))
694
695    def mtGetItemIndices(self, item):
696        indices = []
697        while item:
698            ind = item.selections.get("selectedIndices", None)
699            if ind:
700                indices.insert(0, ind)        # insert indices in reverse order
701            item = item.parent()
702        return indices
703
704    def mtGetData(self, indices):
705        data = self.wholeDataSet
706        unselectedData = orange.ExampleTable(data.domain)
707        for ind in indices:
708            unselectedData.extend(data.selectref(ind, negate = 1))
709            data = data.selectref(ind)
710        return data, unselectedData
711
712    # popup menu items
713    def mtRemoveSelectedItem(self):
714        items = self.subsetTree.selectedItems()
715        if not items: return
716
717        parent = items[0].parent()
718        if parent == None:
719            self.mtInitSubsetTree()
720        else:
721            self.mtRemoveTreeItem(item)
722            self.mtClearTreeSelections()
723            parent.setSelected(1)
724            self.mtSelectedTreeItemChanged(parent)
725
726    def mtSubsetTreeRemoveItemPopup(self, item, point, i):
727        self.subsetPopupMenu.popup(point, 0)
728
729    def resizeEvent(self, ev):
730        OWBaseWidget.resizeEvent(self, ev)
731        #self.subsetTree.setColumnWidth(0, self.subsetTree.width()-self.subsetTree.columnWidth(1)-4 - 20)
732
733    def getTreeItemSibling(self, item):
734        parent = item.parent()
735        if not parent:
736            parent = self.subsetTree.invisibleRootItem()
737        ind = parent.indexOfChild(item)
738        return parent.child(ind+1)
739
740    def mtSelectionsToString(self, settings):
741        attrCombs = ["-".join(sel) for sel in settings.get("selectionConditions", [])]
742        return "+".join(attrCombs)
743
744
745    # return actual item in the tree to that str(item) == strItem
746    def mtStrToItem(self, strItem, currItem = -1):
747        if currItem == -1:
748            currItem = self.subsetTree.invisibleRootItem()
749        if currItem == None:
750            return None
751        if currItem.text(0) == strItem:
752            return currItem
753        i = 0
754        for i in range(currItem.childCount()):
755            item = self.mtStrToItem(strItem, currItem.child(i))
756            if item:
757                return item
758        return self.mtStrToItem(strItem, self.getTreeItemSibling(currItem))
759
760
761    # save tree to a file
762    def mtSaveTree(self, name = None):
763        if name == None:
764            qname = QFileDialog.getSaveFileName(self, "Save tree", os.path.join(self.lastSaveDirName, "explorer tree.tree"), "Explorer tree (*.tree)")
765            if qname.isEmpty():
766                return
767            name = str(qname)
768        self.lastSaveDirName = os.path.split(name)[0]
769
770        tree = {}
771        self.mtTreeToDict(self.treeRoot, tree)
772        import cPickle
773        f = open(name, "w")
774        cPickle.dump(tree, f)
775        f.close()
776
777    # load tree from a file
778    def mtLoadTree(self, name = None):
779        self.subsetItems = {}
780        self.subsetTree.clear()
781        self.treeRoot = None
782
783        if name == None:
784            name = QFileDialog.getOpenFileName(self, "Load tree", self.lastSaveDirName, "Explorer tree (*.tree)")
785            if name.isEmpty(): return
786            name = str(name)
787
788        self.lastSaveDirName = os.path.split(name)[0]
789        import cPickle
790        f = open(name, "r")
791        tree = cPickle.load(f)
792        self.mtDictToTree(tree, "None", self.subsetTree)
793        root = self.subsetTree.invisibleRootItem().child(0)
794        if root:
795            self.treeRoot = root
796            root.setSelected(1)
797            self.mtSelectedTreeItemChanged(root)
798
799    # generate a dictionary from the tree that can be pickled
800    def mtTreeToDict(self, node, tree):
801        if not node: return
802
803        child = node.child(0)
804        if child:
805            self.mtTreeToDict(child, tree)
806
807        tree[str(node.parent())] = tree.get(str(node.parent()), []) + [(str(node), node.details, node.selections)]
808        self.mtTreeToDict(self.getTreeItemSibling(node), tree)
809
810    # create a tree from a dictionary
811    def mtDictToTree(self, tree, currItemKey, parentItem):
812        if tree.has_key(currItemKey):
813            children = tree[currItemKey]
814            for (strChildNode, details, selections) in children:
815                strAttrs = self.attrsToString(details["attrs"])
816                exampleCount = details["exampleCount"]
817                item = QTreeWidgetItem(parentItem, [strAttrs, str(exampleCount)])
818                item.details = details
819                item.selections = selections
820                item.setExpanded(1)
821                self.mtDictToTree(tree, strChildNode, item)
822
823    #################################################
824    # build mosaic tree methods
825    def mtMosaicAutoBuildTree(self):
826        if str(self.autoBuildTreeButton.text()) != "Build Tree":
827            self.mosaic.cancelTreeBuilding = 1
828            self.mosaic.cancelEvaluation = 1
829        else:
830            try:
831                self.mosaic.cancelTreeBuilding = 0
832                self.mosaic.cancelEvaluation = 0
833                self.autoBuildTreeButton.setText("Stop Building")
834                qApp.processEvents()
835
836                examples = self.visualizationWidget.data
837                #selectedItems = self.subsetTree.selectedItems()
838                selectedItem = self.subsetTree.currentItem()
839
840                if selectedItem and selectedItem.parent() != None:
841                    box = QMessageBox()
842                    box.setIcon(QMessageBox.Question)
843                    box.setWindowTitle("Tree Building")
844                    box.setText("Currently you are visualizing only a subset of examples. Do you want to build the tree only for these examples or for all examples?")
845                    onlyTheseButton = box.addButton("Only for these", QMessageBox.YesRole)
846                    allExamplesButton = box.addButton("For all examples", QMessageBox.NoRole)
847                    box.exec_()
848                    if box.clickedButton() == allExamplesButton:
849                        examples = self.wholeDataSet
850                        parent = self.subsetTree
851                    else:
852                        parent = selectedItem.parent()
853                else:
854                    parent = self.subsetTree
855                selections = selectedItem and selectedItem.selections or {}
856
857                 #create a mosaic and use a classifier to generate a mosaic tree so that we don't set data to the main mosaic (which would mean that we would have to prevent the user from clicking the current tree)
858                for setting in self.settingsList:
859                    setattr(self.mosaic, setting, getattr(self, setting, None))
860                if self.qualityMeasure == CN2_RULES:
861                    self.mosaic.qualityMeasure == MDL
862                self.mosaic.qApp = qApp
863                root = MosaicTreeClassifier(self.mosaic, examples, self.setStatusBarText).mosaicTree
864
865                # create tree items in the listview based on the tree in classifier.mosaicTree
866                if root:
867                    # if the selected item doesn't have any children we remove it and it will be replaced with the root of the tree that we generate
868                    if not selectedItem:
869                        self.subsetTree.clear()
870                    elif selectedItem.child(0) == None:
871                        self.mtRemoveTreeItem(selectedItem)
872
873                    item = QTreeWidgetItem(parent, [self.attrsToString(root.attrs), str(len(root.branchSelector.data))])
874                    item.details = {"attrs": root.attrs, "exampleCount": len(root.branchSelector.data)}
875                    item.selections = selections
876                    item.setExpanded(1)
877                    if parent == self.subsetTree:
878                        self.treeRoot = item
879                    self.mtGenerateTreeFromClassifier(root, item)
880            except:
881                import sys
882                type, val, traceback = sys.exc_info()
883                sys.excepthook(type, val, traceback)  # print the exception
884            self.autoBuildTreeButton.setText("Build Tree")
885
886    def mtGenerateTreeFromClassifier(self, treeNode, parentTreeItem):
887        for key in treeNode.branches.keys():
888            branch = treeNode.branches[key]
889            strAttrs = self.attrsToString(branch.attrs)
890            selections = treeNode.branchSelector.values[key]
891            exampleCount = len(branch.branchSelector.data)
892            item = QTreeWidgetItem(parentTreeItem, [strAttrs, str(exampleCount)])
893            item.details = {"attrs": branch.attrs, "exampleCount": exampleCount}
894            item.selections = {'selectionConditions': selections, 'selectionConditionsHistorically': [selections], "selectedIndices": self.visualizationWidget.getSelectedExamples(asExampleTable = 0, selectionConditions = selections, data = treeNode.branchSelector.data, attrs = treeNode.attrs)}
895            item.setExpanded(1)
896            self.mtGenerateTreeFromClassifier(branch, item)
897
898    # remove a tree item and also remove selections dict from its parent
899    def mtRemoveTreeItem(self, item):
900        parent = item.parent()
901        if parent == None:
902            parent = self.subsetTree
903        ind = parent.indexOfChild(item)
904        parent.takeChild(ind)
905
906    def mtVisualizeMosaicTree(self):
907        tree = {}
908        self.mtTreeToDict(self.treeRoot, tree)
909        treeDialog = OWBaseWidget(self, self.signalManager, "Mosaic Tree")
910        treeDialog.setLayout(QVBoxLayout())
911        treeDialog.layout().setMargin(2)
912
913        treeDialog.canvas = QGraphicsScene()
914        treeDialog.canvasView = QGraphicsView(treeDialog.canvas, treeDialog)
915        treeDialog.canvasView.setAlignment(Qt.AlignLeft | Qt.AlignTop)
916        treeDialog.layout().addWidget(treeDialog.canvasView)
917        #treeDialog.canvasView.show()
918        treeDialog.resize(800, 800)
919        treeDialog.move(0,0)
920
921        xMosOffset = 80
922        xMosaicSize = self.mosaicSize + 2 * 50     # we need some space also for text labels
923        yMosaicSize = self.mosaicSize + 2 * 25
924
925        mosaicCanvas = self.visualizationWidget.canvas
926        mosaicCanvasView = self.visualizationWidget.canvasView
927        cellSpace = self.visualizationWidget.cellspace
928        self.visualizationWidget.canvas = treeDialog.canvas
929        self.visualizationWidget.canvasView = treeDialog.canvasView
930        self.visualizationWidget.cellspace = 5
931        oldMosaicSelectionConditions = self.visualizationWidget.selectionConditions;                         self.visualizationWidget.selectionConditions = []
932        oldMosaicSelectionConditionsHistorically = self.visualizationWidget.selectionConditionsHistorically; self.visualizationWidget.selectionConditionsHistorically = []
933
934        nodeDict = {}
935        rootNode = {"treeNode": tree["None"][0][0], "parentNode": None, "childNodes": []}
936        rootNode.update(tree["None"][0][1])
937        nodeDict[tree["None"][0][0]] = rootNode
938        itemsToDraw = {0: [(rootNode,)]}
939        treeDepth = 0
940        canvasItems = {}
941
942        # generate the basic structure of the tree
943        while itemsToDraw.has_key(treeDepth):
944            groups = itemsToDraw[treeDepth]
945            xPos = 0
946            for group in groups:
947                for node in group:
948                    node["currXPos"] = xPos
949                    xPos += xMosaicSize        # next mosaic will be to the right
950
951                    toDraw = []
952                    children = tree.get(node["treeNode"], [])
953                    for (strNode, details, selections) in children:
954                        childNode = {"treeNode":strNode, "parentNode":node["treeNode"]}
955                        childNode.update(details)
956                        childNode.update(selections)
957                        childNode["childNodes"] = []
958                        node["childNodes"].append(childNode)
959                        nodeDict[strNode] = childNode
960                        toDraw.append(childNode)
961                    if toDraw != []:
962                        itemsToDraw[treeDepth+1] = itemsToDraw.get(treeDepth+1, []) + [toDraw]
963            treeDepth += 1
964
965        # fix positions of mosaic so that child nodes will be under parent
966        changedPosition = 1
967        while changedPosition:
968            changedPosition = 0
969            treeDepth = max(itemsToDraw.keys())
970            while treeDepth > 0:
971                groups = itemsToDraw[treeDepth]
972                xPos = 0
973                for group in groups:
974                    # the current XPositions of the group might not be valid if we moved items in the previous groups. We therefore have to move the items if their xpos is smaller than xPos
975                    if xPos > group[0]["currXPos"]:
976                        for i in range(len(group)):
977                            group[i]["currXPos"] = xPos + i* xMosaicSize
978
979                    groupMidXPos = (group[0]["currXPos"] + group[-1]["currXPos"]) / 2
980                    parentXPos = nodeDict[group[0]["parentNode"]]["currXPos"]
981                    if abs(parentXPos - groupMidXPos) < 5:
982                        xPos = group[-1]["currXPos"] + xMosaicSize
983                        continue
984                    changedPosition = 1        # we obviously have to move the parent or its children
985                    if parentXPos < groupMidXPos:    # move the parent forward
986                        self.mtRepositionNode(itemsToDraw[treeDepth-1], group[0]["parentNode"], groupMidXPos, xMosaicSize)
987                    elif parentXPos > groupMidXPos:    # move the children backwards
988                        xPos = self.mtRepositionNode(itemsToDraw[treeDepth], group[0]["treeNode"], parentXPos - (group[-1]["currXPos"] - group[0]["currXPos"])/2, xMosaicSize)
989                treeDepth -= 1
990
991        # visualize each mosaic diagram
992        colors = self.visualizationWidget.selectionColorPalette
993
994        maxX = 0
995        maxY = 100 + (max(itemsToDraw.keys())+1) * yMosaicSize
996        for depth in range(max(itemsToDraw.keys())+1):
997            groups = itemsToDraw[depth]
998            yPos = 50 + (depth > 0) * 50 + depth * yMosaicSize
999
1000            for group in groups:
1001                for node in group:
1002                    # create a dict with colors to be used to mark the selected rectangles
1003                    selectionDict = {}
1004                    for ind, selections in enumerate([child["selectionConditions"] for child in node["childNodes"]]):
1005                        for selection in selections:
1006                            selectionDict[tuple(selection)] = colors[ind]
1007                    data, unselectedData = self.mtGetData(self.mtGetItemIndices(self.mtStrToItem(node["treeNode"])))
1008                    # draw the mosaic
1009                    self.visualizationWidget.updateGraph(data, unselectedData, node["attrs"], erasePrevious = 0, positions = (node["currXPos"]+xMosOffset, yPos, self.mosaicSize), drawLegend = (depth == 0), drillUpdateSelection = 0, selectionDict = selectionDict)
1010                    maxX = max(maxX, node["currXPos"])
1011
1012                    # draw a line between the parent and this node
1013                    if node["parentNode"]:
1014                        parent = nodeDict[node["parentNode"]]
1015                        nodeIndex = parent["childNodes"].index(node)
1016                        parentXPos = parent["currXPos"] + xMosaicSize/2 + 10*(-(len(parent["childNodes"])-1)/2 + nodeIndex)
1017                        OWQCanvasFuncts.OWCanvasLine(treeDialog.canvas, parentXPos, yPos - 30, node["currXPos"] + xMosaicSize/2, yPos - 10, penWidth = 4, penColor = colors[nodeIndex])
1018
1019        #treeDialog.canvas.resize(maxX + self.mosaicSize + 200, maxY)
1020        treeDialog.canvasView.setSceneRect(0,0,maxX + self.mosaicSize + 200, maxY)
1021
1022        # restore the original canvas and canvas view
1023        self.visualizationWidget.canvas = mosaicCanvas
1024        self.visualizationWidget.canvasView = mosaicCanvasView
1025        self.visualizationWidget.cellspace = cellSpace
1026        self.visualizationWidget.selectionConditions = oldMosaicSelectionConditions
1027        self.visualizationWidget.selectionConditionsHistorically = oldMosaicSelectionConditionsHistorically
1028        treeDialog.show()
1029
1030    # find the node nodeToMove in the groups and move it to newPos. reposition also all nodes that follow this node.
1031    def mtRepositionNode(self, groups, nodeToMove, newPos, xMosaicSize):
1032        found = 0
1033        for group in groups:
1034            for node in group:
1035                if node["treeNode"] == nodeToMove:        # we found the node to move
1036                    node["currXPos"] = newPos
1037                    found = 1
1038                    xPos = newPos + xMosaicSize
1039                elif found == 1:
1040                    node["currXPos"] = xPos
1041                    xPos += xMosaicSize
1042        return xPos     # return next valid position where to put a mosaic
1043
1044
1045    # ######################################################
1046    # Auxiliary functions
1047    # ######################################################
1048
1049    def getSelectedProjection(self):
1050        if self.resultList.selectedItems() == []: return None
1051        return self.shownResults[self.resultList.row(self.resultList.selectedItems()[0])]
1052
1053    def stopEvaluationClick(self):
1054        self.cancelEvaluation = 1
1055
1056    def isEvaluationCanceled(self):
1057        if self.cancelEvaluation:   return 1
1058        if self.useTimeLimit:       return orngMosaic.isEvaluationCanceled(self)
1059
1060    def destroy(self, dw = 1, dsw = 1):
1061        self.saveSettings()
1062
1063    def insertArgument(self, argScore, error, attrList, index):
1064        s = "%.3f " % argScore
1065        if self.showConfidence and type(error) != tuple: s += "+-%.2f " % error
1066        s += "- " + self.buildAttrString(attrList)
1067        self.argumentList.insertItem(index, s)
1068
1069    def updateShownArguments(self):
1070        self.argumentList.clear()
1071        if len(self.arguments) == 0: return
1072        classVal = str(self.classValueCombo.currentText())
1073        self.logitLabel.setText("log odds = %.2f" % self.logits.get(classVal, -1))
1074
1075        if self.classificationMethod == MOS_COMBINING:
1076            self.logitLabel.show()
1077        else:
1078            self.logitLabel.hide()
1079
1080        if not self.arguments.has_key(classVal): return
1081        for i in range(len(self.arguments[classVal])):
1082            (argScore, accuracy, attrList, index, error) = self.arguments[classVal][i]
1083            self.insertArgument(argScore, error, attrList, i)
1084
1085
1086    def argumentSelected(self):
1087        ind = self.argumentList.currentItem()
1088        classVal = str(self.classValueCombo.currentText())
1089        self.showSelectedAttributes(self.arguments[classVal][ind][2])
1090
1091
1092    def resendLearner(self):
1093        self.visualizationWidget.send("Learner", self.visualizationWidget.VizRankLearner)
1094
1095    def stopArgumentationClick(self):
1096        self.cancelArgumentation = 1
1097
1098    # ##############################################################
1099    # create different dialogs
1100    def interactionAnalysis(self):
1101        import OWkNNOptimization
1102        dialog = OWkNNOptimization.OWInteractionAnalysis(self, OWkNNOptimization.VIZRANK_MOSAIC, signalManager = self.signalManager)
1103        dialog.setResults(self.data, self.shownResults)
1104        dialog.show()
1105
1106
1107    def attributeAnalysis(self):
1108        import OWkNNOptimization
1109        dialog = OWkNNOptimization.OWGraphAttributeHistogram(self, OWkNNOptimization.VIZRANK_MOSAIC, signalManager = self.signalManager)
1110        dialog.setResults(self.data, self.shownResults)
1111        dialog.show()
1112
1113    def graphProjectionQuality(self):
1114        import OWkNNOptimization
1115        dialog = OWkNNOptimization.OWGraphProjectionQuality(self, OWkNNOptimization.VIZRANK_MOSAIC, signalManager = self.signalManager)
1116        dialog.setResults(self.shownResults)
1117        dialog.show()
1118
1119    def identifyOutliers(self):
1120        import OWkNNOptimization
1121        dialog = OWkNNOptimization.OWGraphIdentifyOutliers(self, OWkNNOptimization.VIZRANK_MOSAIC, signalManager = self.signalManager, widget = self.visualizationWidget)
1122        dialog.setResults(self.data, self.shownResults)
1123        dialog.show()
1124
1125#test widget appearance
1126if __name__=="__main__":
1127    import sys
1128    a = QApplication(sys.argv)
1129    ow = OWMosaicOptimization()
1130    a.setMainWidget(ow)
1131    ow.show()
1132    a.exec_()
Note: See TracBrowser for help on using the repository browser.