source: orange/orange/OrangeWidgets/Data/OWRank.py @ 9344:51788708d200

Revision 9344:51788708d200, 34.9 KB checked in by ales_erjavec <ales.erjavec@…>, 2 years ago (diff)

Added max num. of terms to Earth Importance score.

Line 
1"""
2<name>Rank</name>
3<description>Ranks and filters attributes by their relevance.</description>
4<icon>icons/Rank.png</icon>
5<contact>Janez Demsar (janez.demsar(@at@)fri.uni-lj.si)</contact>
6<priority>1102</priority>
7"""
8from OWWidget import *
9   
10import OWGUI
11import orange
12
13from functools import partial
14
15def _toPyObject(variant):
16    val = variant.toPyObject()
17    if isinstance(val, type(NotImplemented)):
18        # PyQt 4.4 converts python int, floats ... to C types and
19        # cannot convert them back again and returns an exception instance.
20        qtype = variant.type()
21        if qtype == QVariant.Double:
22            val, ok = variant.toDouble()
23        elif qtype == QVariant.Int:
24            val, ok = variant.toInt()
25        elif qtype == QVariant.LongLong:
26            val, ok = variant.toLongLong()
27        elif qtype == QVariant.String:
28            val = variant.toString()
29    return val
30
31def is_class_discrete(data):
32    return isinstance(data.domain.classVar, orange.EnumVariable)
33
34def is_class_continuous(data):
35    return isinstance(data.domain.classVar, orange.FloatVariable)
36
37def table(shape, fill=None):
38    """ Return a 2D table with shape filed with ``fill``
39    """
40    return [[fill for j in range(shape[1])] for i in range(shape[0])]
41 
42from Orange.regression.earth import ScoreEarthImportance
43from orngSVM import MeasureAttribute_SVMWeights
44from orngEnsemble import MeasureAttribute_randomForests
45
46MEASURE_PARAMS = {ScoreEarthImportance: \
47                    [{"name": "t",
48                      "type": int,
49                      "display_name": "Num. models.",
50                      "range": range(1, 21),
51                      "default": 10,
52                      "doc": "Number of models to train for feature scoring."
53                      },
54                     {"name": "terms",
55                      "type": int,
56                      "display_name": "Max. num of terms",
57                      "range": range(3, 200),
58                      "default": 10,
59                      "doc": "Maximum number of terms in the forward pass" 
60                      },
61                     {"name": "degree", 
62                      "type": int,
63                      "display_name": "Max. term degree",
64                      "range": range(1, 4),
65                      "default": 2,
66                      "doc": "Maximum degree of terms included in the model." 
67                     },
68#                     {"name": "score_what",
69#                      "type": int,
70#                      "display_name": "Score what",
71#                      "range": range(0, 3),
72#                      "display_role": ["Num. Subsets", "RSS", "GCV"]
73#                      "default": 2,
74#                      "doc": ""}
75                     ],
76                  orange.MeasureAttribute_relief: \
77                     [{"name": "k",
78                       "type": int,
79                       "display_name": "Neighbours",
80                       "range": range(1, 21),
81                       "default": 10,
82                       "doc": "Number of neighbors to consider."},
83                      {"name":"m",
84                       "type": int,
85                       "display_name": "Examples",
86                       "range": range(20, 101),
87                       "default": 20,
88                       "doc": ""}
89                      ],
90                  MeasureAttribute_randomForests:\
91                     [{"name": "trees",
92                       "type": int,
93                       "display_name": "Num. of trees",
94                       "range": range(20, 101),
95                       "default": 100,
96                       "doc": "Number of trees in the random forest."}
97                      ]
98                  }
99
100
101MEASURES = [("ReliefF", "ReliefF", orange.MeasureAttribute_relief),
102            ("Information Gain", "Inf. gain", orange.MeasureAttribute_info),
103            ("Gain Ratio", "Gain Ratio", orange.MeasureAttribute_gainRatio),
104            ("Gini Gain", "Gini", orange.MeasureAttribute_gini),
105            ("Log Odds Ratio", "log OR", orange.MeasureAttribute_logOddsRatio),
106            ("MSE", "MSE", orange.MeasureAttribute_MSE),
107            ("Earth Importance", "Earth imp.", ScoreEarthImportance),
108            ("Linear SVM Weights", "SVM weight", MeasureAttribute_SVMWeights),
109            ("Random Forests", "RF", MeasureAttribute_randomForests),
110            ]
111
112MEASURES_HANDLES_CONTINUOUS = {"ReliefF": True,
113                               "Earth Importance": True,
114                               "Linear SVM Weights": True,
115                               "Random Forests": True,
116                               }
117
118MEASURES_SUPPORTS_REGRESSION = {"ReliefF": True,
119                                "MSE": True,
120                                "Earth Importance": True,
121                                "Random Forests": True,
122                                }
123
124MEASURES_SUPPORTS_CLASSIFICATION = {"MSE": False,
125                                    "Random Forests": True,
126                                    }
127
128MEASURES_DEFAULT_SELECTED = dict([(mname, True) for mname, _, _ in MEASURES[:6]] + \
129                                 [(mname, False) for mname, _, _ in MEASURES[6:]]) # The Earth imp. and SVM are not selected by default
130
131
132class MethodParameter(object):
133    def __init__(self, name="", type=None, display_name="Parameter",
134                 range=None, default=None, doc=""):
135        self.name = name
136        self.type = type
137        self.display_name = display_name
138        self.range = range
139        self.default = default
140        self.doc = doc
141   
142def supports_classification(name):
143    return MEASURES_SUPPORTS_CLASSIFICATION.get(name, True)
144
145def supports_regression(name):
146    return MEASURES_SUPPORTS_REGRESSION.get(name, False)
147
148def handles_continuous(name):
149    return MEASURES_HANDLES_CONTINUOUS.get(name, False)
150
151def measure_parameters(measure):
152    return [MethodParameter(**args) for args in MEASURE_PARAMS.get(measure, [])]
153
154def param_attr_name(measure, param):
155    """ Name of the OWRank widget's member where the parameter is stored.
156    """
157    return "param_" + measure.__name__ + "_" + param.name
158       
159class OWRank(OWWidget):
160    settingsList =  ["nDecimals", "nIntervals", "sortBy", "nSelected", "selectMethod", "autoApply", "showDistributions", "distColorRgb"]
161
162    def __init__(self,parent=None, signalManager = None):
163        OWWidget.__init__(self, parent, signalManager, "Rank")
164
165        self.inputs = [("Examples", ExampleTable, self.setData)]
166        self.outputs = [("Reduced Example Table", ExampleTable, Default + Single)]
167
168        self.nDecimals = 3
169        self.nIntervals = 4
170        self.sortBy = 2
171        self.selectMethod = 2
172        self.nSelected = 5
173        self.autoApply = True
174        self.showDistributions = 1
175        self.distColorRgb = (220,220,220, 255)
176        self.distColor = QColor(*self.distColorRgb)
177        self.minmax = {}
178        self.selectedMeasures = dict(MEASURES_DEFAULT_SELECTED)
179        self.data = None
180       
181#        self.measure_parameters = AttributeDict()
182#        self.measure_parameters = {}
183       
184        self.methodParamAttrs = []
185        for _, _, m in MEASURES:
186            params = measure_parameters(m) or []
187            for p in params:
188                setattr(self, param_attr_name(m, p), p.default)
189                self.methodParamAttrs.append(param_attr_name(m, p))
190        self.settingsList = self.settingsList + self.methodParamAttrs
191       
192        self.loadSettings() 
193
194        labelWidth = 80
195       
196        self.discMeasures = [name for name, short, _ in MEASURES \
197                             if supports_classification(name)]
198       
199        self.contMeasures = [name for name, short, _ in MEASURES \
200                             if supports_regression(name)]
201       
202        self.discMeasuresShort = [short for name, short, _ in MEASURES \
203                                  if supports_classification(name)]
204       
205        self.contMeasuresShort = [short for name, short, _ in MEASURES \
206                                  if supports_regression(name)]
207       
208        self.discEstimators = [measure for name, _, measure in MEASURES \
209                               if supports_classification(name)]
210       
211        self.contEstimators = [measure for name, _, measure in MEASURES \
212                               if supports_regression(name)]
213       
214        self.discHandlesContinuous = map(handles_continuous, self.discMeasures)
215        self.contHandlesContinuous = map(handles_continuous, self.contMeasures)
216
217        # The stacked layout for Classification/Regression measures
218#        self.stackedWidget = OWGUI.widgetBox(self.controlArea, margin=0,
219#                                             addSpace=True)
220       
221        self.stackedLayout = QStackedLayout()
222        self.stackedLayout.setContentsMargins(0, 0, 0, 0)
223        self.stackedWidget = OWGUI.widgetBox(self.controlArea, margin=0,
224                                             orientation=self.stackedLayout,
225                                             addSpace=True)
226#        self.stackedWidget.layout().addLayout(self.stackedLayout)
227        # Discrete class scoring
228        discreteBox = OWGUI.widgetBox(self.stackedWidget, "Scoring",
229                                      addSpace=False,
230                                      addToLayout=False)
231        self.stackedLayout.addWidget(discreteBox)
232       
233        # Continuous class scoring
234        continuousBox = OWGUI.widgetBox(self.stackedWidget, "Scoring",
235                                        addSpace=False,
236                                        addToLayout=False)
237        self.stackedLayout.addWidget(continuousBox)
238       
239        def measure_control(container, name, measure):
240            """ Construct UI control for measure.
241            """
242            params = measure_parameters(measure)
243            if params:
244                hbox = OWGUI.widgetBox(container, orientation = "horizontal")
245                OWGUI.checkBox(hbox, self.selectedMeasures, name, name,
246                               callback=partial(self.measuresSelectionChanged, name),
247                               tooltip="Enable " + name)
248                smallWidget = OWGUI.SmallWidgetLabel(hbox, pixmap=1, box=name + " Parameters",
249                                                     tooltip="Show " + name + "Parameters")
250                for param in params:
251                    OWGUI.spin(smallWidget.widget, self, param_attr_name(measure, param),
252                               param.range[0], param.range[-1],
253                               label=param.display_name, 
254                               tooltip=param.doc,
255                               callback=partial(self.measureParamChanged, name, param),
256                               callbackOnReturn=True)
257               
258                OWGUI.button(smallWidget.widget, self, "Load defaults",
259                             callback=partial(self.loadMeasureDefaults, name))
260            else:
261                OWGUI.checkBox(container, self.selectedMeasures, name, name,
262                               callback=partial(self.measuresSelectionChanged, name),
263                               tooltip="Enable " + name)
264       
265        for name, short_name, measure in MEASURES:
266            if supports_classification(name):
267                measure_control(discreteBox, name, measure)
268                   
269            if supports_regression(name):
270                measure_control(continuousBox, name, measure)
271       
272       
273        OWGUI.comboBox(discreteBox, self, "sortBy", label = "Sort by"+"  ",
274                       items = ["No Sorting", "Attribute Name", "Number of Values"] + \
275                               [name for name in self.discMeasures],
276                       orientation=0, valueType = int,
277                       callback=self.sortingChanged)
278       
279        OWGUI.comboBox(continuousBox, self, "sortBy", label = "Sort by"+"  ",
280                       items = ["No Sorting", "Attribute Name", "Number of Values"] + \
281                               [name for name in self.contMeasures],
282                       orientation=0, valueType = int,
283                       callback=self.sortingChanged)
284
285        box = OWGUI.widgetBox(self.controlArea, "Discretization",
286                              addSpace=True)
287        OWGUI.spin(box, self, "nIntervals", 2, 20,
288                   label="Intervals: ",
289                   orientation=0,
290                   tooltip="Disctetization for measures which cannot score continuous attributes.",
291                   callback=self.discretizationChanged,
292                   callbackOnReturn=True)
293
294        box = OWGUI.widgetBox(self.controlArea, "Precision", addSpace=True)
295        OWGUI.spin(box, self, "nDecimals", 1, 6, label="No. of decimals: ",
296                   orientation=0, callback=self.decimalsChanged)
297
298        box = OWGUI.widgetBox(self.controlArea, "Score bars",
299                              orientation="horizontal", addSpace=True)
300        self.cbShowDistributions = OWGUI.checkBox(box, self, "showDistributions",
301                                    'Enable', callback = self.cbShowDistributions)
302#        colBox = OWGUI.indentedBox(box, orientation = "horizontal")
303        OWGUI.rubber(box)
304        box = OWGUI.widgetBox(box, orientation="horizontal")
305        wl = OWGUI.widgetLabel(box, "Color: ")
306        OWGUI.separator(box)
307        self.colButton = OWGUI.toolButton(box, self, callback=self.changeColor, width=20, height=20, debuggingEnabled = 0)
308        self.cbShowDistributions.disables.extend([wl, self.colButton])
309        self.cbShowDistributions.makeConsistent()
310#        OWGUI.rubber(box)
311
312       
313        selMethBox = OWGUI.widgetBox(self.controlArea, "Select attributes", addSpace=True)
314        self.clearButton = OWGUI.button(selMethBox, self, "Clear", callback=self.clearSelection)
315        self.clearButton.setDisabled(True)
316       
317        buttonGrid = QGridLayout()
318        selMethRadio = OWGUI.radioButtonsInBox(selMethBox, self, "selectMethod", [], callback=self.selectMethodChanged)
319        b1 = OWGUI.appendRadioButton(selMethRadio, self, "selectMethod", "All", insertInto=selMethRadio, callback=self.selectMethodChanged, addToLayout=False)
320        b2 = OWGUI.appendRadioButton(selMethRadio, self, "selectMethod", "Manual", insertInto=selMethRadio, callback=self.selectMethodChanged, addToLayout=False)
321        b3 = OWGUI.appendRadioButton(selMethRadio, self, "selectMethod", "Best ranked", insertInto=selMethRadio, callback=self.selectMethodChanged, addToLayout=False)
322#        brBox = OWGUI.widgetBox(selMethBox, orientation="horizontal", margin=0)
323#        OWGUI.appendRadioButton(selMethRadio, self, "selectMethod", "Best ranked", insertInto=brBox, callback=self.selectMethodChanged)
324        spin = OWGUI.spin(OWGUI.widgetBox(selMethRadio, addToLayout=False), self, "nSelected", 1, 100, orientation=0, callback=self.nSelectedChanged)
325        buttonGrid.addWidget(b1, 0, 0)
326        buttonGrid.addWidget(b2, 1, 0)
327        buttonGrid.addWidget(b3, 2, 0)
328        buttonGrid.addWidget(spin, 2, 1)
329        selMethRadio.layout().addLayout(buttonGrid)
330        OWGUI.separator(selMethBox)
331
332        applyButton = OWGUI.button(selMethBox, self, "Commit", callback = self.apply, default=True)
333        autoApplyCB = OWGUI.checkBox(selMethBox, self, "autoApply", "Commit automatically")
334        OWGUI.setStopper(self, applyButton, autoApplyCB, "dataChanged", self.apply)
335
336        OWGUI.rubber(self.controlArea)
337       
338        # Discrete and continuous table views are stacked
339        self.ranksViewStack = QStackedLayout()
340        self.mainArea.layout().addLayout(self.ranksViewStack)
341       
342        self.discRanksView = QTableView()
343        self.ranksViewStack.addWidget(self.discRanksView)
344        self.discRanksView.setSelectionBehavior(QTableView.SelectRows)
345        self.discRanksView.setSelectionMode(QTableView.MultiSelection)
346        self.discRanksView.setSortingEnabled(True)
347#        self.discRanksView.horizontalHeader().restoreState(self.discRanksHeaderState)
348       
349        self.discRanksModel = QStandardItemModel(self)
350        self.discRanksModel.setHorizontalHeaderLabels(["Attribute", "#"] + self.discMeasuresShort)
351        self.discRanksProxyModel = MySortProxyModel(self)
352        self.discRanksProxyModel.setSourceModel(self.discRanksModel)
353        self.discRanksView.setModel(self.discRanksProxyModel)
354#        self.discRanksView.verticalHeader().setResizeMode(QHeaderView.ResizeToContents)
355        self.discRanksView.setColumnWidth(1, 20)
356        self.discRanksView.sortByColumn(2, Qt.DescendingOrder)
357        self.connect(self.discRanksView.selectionModel(),
358                     SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
359                     self.onSelectionChanged)
360        self.connect(self.discRanksView,
361                     SIGNAL("pressed(const QModelIndex &)"),
362                     self.onSelectItem)
363        self.connect(self.discRanksView.horizontalHeader(),
364                     SIGNAL("sectionClicked(int)"),
365                     self.headerClick)
366       
367        self.contRanksView = QTableView()
368        self.ranksViewStack.addWidget(self.contRanksView)
369        self.contRanksView.setSelectionBehavior(QTableView.SelectRows)
370        self.contRanksView.setSelectionMode(QTableView.MultiSelection)
371        self.contRanksView.setSortingEnabled(True)
372#        self.contRanksView.setItemDelegate(OWGUI.ColoredBarItemDelegate())
373#        self.contRanksView.horizontalHeader().restoreState(self.contRanksHeaderState)
374       
375        self.contRanksModel = QStandardItemModel(self)
376        self.contRanksModel.setHorizontalHeaderLabels(["Attribute", "#"] + self.contMeasuresShort)
377        self.contRanksProxyModel = MySortProxyModel(self)
378        self.contRanksProxyModel.setSourceModel(self.contRanksModel)
379        self.contRanksView.setModel(self.contRanksProxyModel)
380#        self.contRanksView.verticalHeader().setResizeMode(QHeaderView.ResizeToContents)
381        self.discRanksView.setColumnWidth(1, 20)
382        self.contRanksView.sortByColumn(2, Qt.DescendingOrder)
383        self.connect(self.contRanksView.selectionModel(),
384                     SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
385                     self.onSelectionChanged)
386        self.connect(self.contRanksView,
387                     SIGNAL("pressed(const QModelIndex &)"),
388                     self.onSelectItem)
389        self.connect(self.contRanksView.horizontalHeader(),
390                     SIGNAL("sectionClicked(int)"),
391                     self.headerClick)
392       
393        # Switch the current view to Discrete
394        self.switchRanksMode(0)
395        self.resetInternals()
396        self.updateDelegates()
397        self.updateVisibleScoreColumns()
398
399#        self.connect(self.table.horizontalHeader(), SIGNAL("sectionClicked(int)"), self.headerClick)
400       
401        self.resize(690,500)
402        self.updateColor()
403       
404        self.measure_scores = table((len(self.measures), 0), None)
405
406    def switchRanksMode(self, index):
407        """ Switch between discrete/continuous mode
408        """
409        self.ranksViewStack.setCurrentIndex(index)
410        self.stackedLayout.setCurrentIndex(index)
411       
412        if index == 0:
413            self.ranksView = self.discRanksView
414            self.ranksModel = self.discRanksModel
415            self.ranksProxyModel = self.discRanksProxyModel
416            self.measures = self.discMeasures
417            self.handlesContinuous = self.discHandlesContinuous
418            self.estimators = self.discEstimators
419        else:
420            self.ranksView = self.contRanksView
421            self.ranksModel = self.contRanksModel
422            self.ranksProxyModel = self.contRanksProxyModel
423            self.measures = self.contMeasures
424            self.handlesContinuous = self.contHandlesContinuous
425            self.estimators = self.contEstimators
426           
427        self.updateVisibleScoreColumns()
428           
429    def setData(self, data):
430        self.error(0)
431        self.resetInternals()
432        self.data = self.isDataWithClass(data) and data or None
433        if self.data:
434            attrs = self.data.domain.attributes
435            self.usefulAttributes = filter(lambda x:x.varType in [orange.VarTypes.Discrete, orange.VarTypes.Continuous],
436                                           attrs)
437            if is_class_continuous(self.data):
438                self.switchRanksMode(1)
439            elif is_class_discrete(self.data):
440                self.switchRanksMode(0)
441            else: # String or other.
442                self.error(0, "Cannot handle class variable type")
443           
444#            self.ranksView.setSortingEnabled(False)
445            self.ranksModel.setRowCount(len(attrs))
446            for i, a in enumerate(attrs):
447                if isinstance(a, orange.EnumVariable):
448                    v = len(a.values)
449                else:
450                    v = "C"
451                item = PyStandardItem()
452                item.setData(QVariant(v), Qt.DisplayRole)
453                self.ranksModel.setItem(i, 1, item)
454                item = PyStandardItem(a.name)
455                item.setData(QVariant(i), OWGUI.SortOrderRole)
456                self.ranksModel.setItem(i, 0, item)
457               
458            self.ranksView.resizeColumnToContents(1)
459           
460            self.measure_scores = table((len(self.measures),
461                                         len(attrs)), None)
462            self.updateScores()
463            if is_class_discrete(self.data):
464                self.setLogORTitle()
465            self.ranksView.setSortingEnabled(self.sortBy > 0)
466           
467        self.applyIf()
468
469    def updateScores(self, measuresMask=None):
470        """ Update the current computed measures. If measuresMask is given
471        it must be an list of bool values indicating what measures should be
472        computed.
473       
474        """ 
475        if not self.data:
476            return
477       
478        estimators = self.estimators
479        measures = self.measures
480        handlesContinous = self.handlesContinuous
481        self.warning(range(max(len(self.discEstimators), len(self.contEstimators))))
482       
483        if measuresMask is None:
484            # Update all selected measures
485            measuresMask = [self.selectedMeasures.get(m) for m in measures]
486       
487        for measure_index, (est, meas, mask) in enumerate(zip(
488                estimators, measures, measuresMask)):
489            if not mask:
490                continue
491            handles = MEASURES_HANDLES_CONTINUOUS.get(meas, False)
492            params = measure_parameters(est)
493            estimator = est()
494            if params:
495                for p in params:
496                    setattr(estimator, p.name,
497                            getattr(self, param_attr_name(est, p)))
498                   
499            if not handles:
500                data = self.getDiscretizedData()
501                attr_map = data.attrDict
502                data = self.data
503            else:
504                attr_map, data = {}, self.data
505            attr_scores = []
506            for i, attr in enumerate(data.domain.attributes):
507                attr = attr_map.get(attr, attr)
508                s = None
509                if attr is not None:
510                    try:
511                        s = estimator(attr, data)
512                    except Exception, ex:
513                        self.warning(measure_index, "Error evaluating %r: %r" % (meas, str(ex)))
514                        # TODO: store exception message (for widget info or item tooltip)
515                    if meas == "Log Odds Ratio" and s is not None:
516                        if s == -999999:
517                            attr = u"-\u221E"
518                        elif s == 999999:
519                            attr = u"\u221E"
520                        else:
521                            attr = attr.values[1]
522                        s = ("%%.%df" % self.nDecimals + " (%s)") % (s, attr)
523                attr_scores.append(s)
524            self.measure_scores[measure_index] = attr_scores
525       
526        self.updateRankModel(measuresMask)
527        self.ranksProxyModel.invalidate()
528       
529        if self.selectMethod in [0, 2]:
530            self.autoSelection()
531   
532    def updateRankModel(self, measuresMask=None):
533        """ Update the rankModel.
534        """
535        values = []
536        for i, scores in enumerate(self.measure_scores):
537            values_one = []
538            for j, s in enumerate(scores):
539                if isinstance(s, float):
540                    values_one.append(s)
541                else:
542                    values_one.append(None)
543                item = self.ranksModel.item(j, i + 2)
544                if not item:
545                    item = PyStandardItem()
546                    self.ranksModel.setItem(j ,i + 2, item)
547                item.setData(QVariant(s), Qt.DisplayRole)
548            values.append(values_one)
549       
550        for i, vals in enumerate(values):
551            valid_vals = [v for v in vals if v is not None]
552            if valid_vals:
553                vmin, vmax = min(valid_vals), max(valid_vals)
554                for j, v in enumerate(vals):
555                    if v is not None:
556                        # Set the bar ratio role for i-th measure.
557                        ratio = float((v - vmin) / ((vmax - vmin) or 1))
558                        if self.showDistributions:
559                            self.ranksModel.item(j, i + 2).setData(QVariant(ratio), OWGUI.BarRatioRole)
560                        else:
561                            self.ranksModel.item(j, i + 2).setData(QVariant(), OWGUI.BarRatioRole)
562                       
563        self.ranksView.resizeColumnsToContents()
564        self.ranksView.setColumnWidth(1, 20)
565        self.ranksView.resizeRowsToContents()
566           
567    def cbShowDistributions(self):
568        # This should be handled by the delegates only (must always set the BarRatioRole
569        self.updateRankModel()
570        # Need to update the selection
571        self.autoSelection()
572
573    def changeColor(self):
574        color = QColorDialog.getColor(self.distColor, self)
575        if color.isValid():
576            self.distColorRgb = color.getRgb()
577            self.updateColor()
578
579    def updateColor(self):
580        self.distColor = QColor(*self.distColorRgb)
581        w = self.colButton.width()-8
582        h = self.colButton.height()-8
583        pixmap = QPixmap(w, h)
584        painter = QPainter()
585        painter.begin(pixmap)
586        painter.fillRect(0,0,w,h, QBrush(self.distColor))
587        painter.end()
588        self.colButton.setIcon(QIcon(pixmap))
589        self.updateDelegates()
590
591    def resetInternals(self):
592        self.data = None
593        self.discretizedData = None
594        self.attributeOrder = []
595        self.selected = []
596        self.measured = {}
597        self.usefulAttributes = []
598        self.dataChanged = False
599        self.lastSentAttrs = None
600        self.ranksModel.setRowCount(0)
601
602    def onSelectionChanged(self, *args):
603        """ Called when the ranks view selection changes.
604        """
605        selected = self.selectedAttrs()
606        self.clearButton.setEnabled(bool(selected))
607        self.applyIf()
608       
609    def onSelectItem(self, index):
610        """ Called when the user selects/unselects an item in the table view.
611        """
612        self.selectMethod = 1 # Manual
613        self.clearButton.setEnabled(bool(self.selectedAttrs()))
614        self.applyIf()
615
616    def clearSelection(self):
617        self.ranksView.selectionModel().clear()
618
619    def selectMethodChanged(self):
620        if self.selectMethod in [0, 2]:
621            self.autoSelection()
622
623    def nSelectedChanged(self):
624        self.selectMethod = 2
625        self.selectMethodChanged()
626
627    def getDiscretizedData(self):
628        if not self.discretizedData:
629            discretizer = orange.EquiNDiscretization(numberOfIntervals=self.nIntervals)
630            contAttrs = filter(lambda attr: attr.varType == orange.VarTypes.Continuous, self.data.domain.attributes)
631            at = []
632            attrDict = {}
633            for attri in contAttrs:
634                try:
635                    nattr = discretizer(attri, self.data)
636                    at.append(nattr)
637                    attrDict[attri] = nattr
638                except:
639                    pass
640            self.discretizedData = self.data.select(orange.Domain(at, self.data.domain.classVar))
641            self.discretizedData.setattr("attrDict", attrDict)
642        return self.discretizedData
643       
644    def discretizationChanged(self):
645        self.discretizedData = None
646        self.updateScores([not b for b in self.handlesContinuous])
647        self.autoSelection()
648       
649    def measureParamChanged(self, name, param=None):
650        index = self.measures.index(name)
651        measure = self.estimators[index]
652        mask = [i == index for i, _ in enumerate(self.measures)]
653        self.updateScores(mask)
654   
655    def loadMeasureDefaults(self, name):
656        index = self.measures.index(name)
657        measure = self.estimators[index]
658        params = measure_parameters(measure)
659        for i, p in enumerate(params):
660            setattr(self, param_attr_name(measure, p), p.default)
661        self.measureParamChanged(name)
662       
663    def autoSelection(self):
664        selModel = self.ranksView.selectionModel()
665        rowCount = self.ranksModel.rowCount()
666        columnCount = self.ranksModel.columnCount()
667        model = self.ranksProxyModel
668        if self.selectMethod == 0:
669           
670            selection = QItemSelection(model.index(0, 0),
671                                       model.index(rowCount - 1,
672                                       columnCount -1))
673            selModel.select(selection, QItemSelectionModel.ClearAndSelect)
674        if self.selectMethod == 2:
675            nSelected = min(self.nSelected, rowCount)
676            selection = QItemSelection(model.index(0, 0),
677                                       model.index(nSelected - 1,
678                                       columnCount - 1))
679            selModel.select(selection, QItemSelectionModel.ClearAndSelect)
680
681    def headerClick(self, index):
682        self.sortBy = index + 1
683        if not self.ranksView.isSortingEnabled():
684            # The sorting is disabled ("No sorting|" selected by user)
685            self.sortingChanged()
686           
687        if index > 1 and self.selectMethod == 2:
688            # Reselect the top ranked attributes
689            self.autoSelection()
690        self.sortBy = index + 1
691        return
692
693    def sortingChanged(self):
694        """ Sorting was changed by user (through the Sort By combo box.)
695        """
696        self.updateSorting()
697        self.autoSelection()
698       
699    def updateSorting(self):
700        """ Update the sorting of the model/view.
701        """
702        self.ranksProxyModel.invalidate()
703        if self.sortBy == 0:
704            self.ranksProxyModel.setSortRole(OWGUI.SortOrderRole)
705            self.ranksProxyModel.sort(0, Qt.DescendingOrder)
706            self.ranksView.setSortingEnabled(False)
707           
708        else:
709            self.ranksProxyModel.setSortRole(Qt.DisplayRole)
710            self.ranksView.sortByColumn(self.sortBy - 1, Qt.DescendingOrder)
711            self.ranksView.setSortingEnabled(True)
712
713    def setLogORTitle(self):
714        var =self.data.domain.classVar   
715        if len(var.values) == 2:
716            title = "log OR (for %r)" % var.values[1][:10]
717        else:
718            title = "log OR"
719        if "Log Odds Ratio" in self.discEstimators:
720            index = self.discMeasures.index("Log Odds Ratio")
721            item = PyStandardItem(title)
722            self.ranksModel.setHorizontalHeaderItem(index + 2, item)
723
724    def measuresSelectionChanged(self, name=None):
725        """ Measure selection has changed. Update column visibility.
726        """
727        if name is None:
728            # Update all scores
729            measuresMask = None
730        else:
731            # Update scores for shown column if they are not yet computed.
732            shown = self.selectedMeasures.get(name, False)
733            index = self.measures.index(name)
734            if all(s is None for s in self.measure_scores[index]) and shown:
735                measuresMask = [n == name for n in self.measures]
736            else:
737                measuresMask = [False] * len(self.measures)
738        self.updateScores(measuresMask)
739       
740        self.updateVisibleScoreColumns()
741           
742    def updateVisibleScoreColumns(self):
743        """ Update the visible columns of the scores view.
744        """
745        for i, measure in enumerate(self.measures):
746            shown = self.selectedMeasures.get(measure)
747            self.ranksView.setColumnHidden(i + 2, not shown)
748
749    def sortByColumn(self, col):
750        if col < 2:
751            self.sortBy = 1 + col
752        else:
753            self.sortBy = 3 + self.selectedMeasures[col-2]
754        self.sortingChanged()
755
756    def decimalsChanged(self):
757        self.updateDelegates()
758        self.ranksView.resizeColumnsToContents()
759       
760    def updateDelegates(self):
761        self.contRanksView.setItemDelegate(OWGUI.ColoredBarItemDelegate(self,
762                            decimals=self.nDecimals,
763                            color=self.distColor))
764        self.discRanksView.setItemDelegate(OWGUI.ColoredBarItemDelegate(self,
765                            decimals=self.nDecimals,
766                            color=self.distColor))
767       
768    def sendReport(self):
769        self.reportData(self.data)
770        self.reportRaw(OWReport.reportTable(self.ranksView))
771
772    def applyIf(self):
773        if self.autoApply:
774            self.apply()
775        else:
776            self.dataChanged = True
777
778    def apply(self):
779        selected = self.selectedAttrs()
780        if not self.data or not selected:
781            self.send("Reduced Example Table", None)
782        else:
783            domain = orange.Domain(selected, self.data.domain.classVar)
784            domain.addmetas(self.data.domain.getmetas())
785            data = orange.ExampleTable(domain, self.data)
786            self.send("Reduced Example Table", data)
787        self.dataChanged = False
788       
789    def selectedAttrs(self):
790        if self.data:
791            inds = self.ranksView.selectionModel().selectedRows(0)
792            source = self.ranksProxyModel.mapToSource
793            inds = map(source, inds)
794            inds = [ind.row() for ind in inds]
795            return [self.data.domain.attributes[i] for i in inds]
796        else:
797            return []   
798
799
800class PyStandardItem(QStandardItem):
801    """ A StandardItem subclass for python objects.
802    """
803    def __init__(self, *args):
804        QStandardItem.__init__(self, *args)
805        self.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
806       
807    def __lt__(self, other):
808        my = self.data(Qt.DisplayRole).toPyObject()
809        other = other.data(Qt.DisplayRole).toPyObject()
810        if my is None:
811            return True
812        return my < other
813
814class MySortProxyModel(QSortFilterProxyModel):
815    def headerData(self, section, orientation, role):
816        """ Don't map headers.
817        """
818        source = self.sourceModel()
819        return source.headerData(section, orientation, role)
820   
821    def lessThan(self, left, right):
822        role = self.sortRole()
823        left = left.data(role).toPyObject()
824        right = right.data(role).toPyObject()
825        return left < right
826
827if __name__=="__main__":
828    a=QApplication(sys.argv)
829    ow=OWRank()
830    ow.setData(orange.ExampleTable("wine.tab"))
831    ow.setData(orange.ExampleTable("zoo.tab"))
832    ow.setData(orange.ExampleTable("servo.tab"))
833    ow.setData(orange.ExampleTable("iris.tab"))
834#    ow.setData(orange.ExampleTable("auto-mpg.tab"))
835    ow.show()
836    a.exec_()
837    ow.saveSettings()
838
Note: See TracBrowser for help on using the repository browser.