Skip to content

Commit d16638e

Browse files
authored
Merge pull request #1746 from janezd/vizrank-single-column
[ENH] Vizrank indicators and filters
2 parents 1c2c278 + bf1c6e7 commit d16638e

File tree

4 files changed

+57
-16
lines changed

4 files changed

+57
-16
lines changed

Orange/widgets/visualize/owmosaic.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ def compute_score(self, state):
240240
dof = reduce(mul, (len(attr.values) - 1 for attr in attrlist))
241241
return distributions.chi2.sf(ss, dof)
242242

243+
def bar_length(self, score):
244+
return 1 if score == 0 else -log(score, 10) / 50
245+
243246
def on_selection_changed(self, selected, deselected):
244247
attrs = selected.indexes()[0].data(self._AttrRole)
245248
self.selectionChanged.emit(attrs + (None, ) * (4 - len(attrs)))

Orange/widgets/visualize/owscatterplot.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,14 @@ def compute_score(self, state):
7474
knn = NearestNeighbors(n_neighbors=n_neighbors).fit(X)
7575
ind = knn.kneighbors(return_distance=False)
7676
if self.master.data.domain.has_discrete_class:
77-
return -np.sum(Y[ind] == Y.reshape(-1, 1))
77+
return -np.sum(Y[ind] == Y.reshape(-1, 1)) / n_neighbors / len(Y)
7878
else:
7979
return -r2_score(Y, np.mean(Y[ind], axis=1)) * \
8080
(len(Y) / len(self.master.data))
8181

82+
def bar_length(self, score):
83+
return max(0, -score)
84+
8285
def score_heuristic(self):
8386
X = self.master.graph.jittered_data.T
8487
Y = self.master.data.Y

Orange/widgets/visualize/owsieve.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from itertools import chain
2+
import math
23

34
import numpy as np
45
from scipy.stats.distributions import chi2
@@ -50,7 +51,11 @@ def initialize(self):
5051
self.attrs = self.master.attrs
5152

5253
def compute_score(self, state):
53-
return ChiSqStats(self.master.discrete_data, *state).p
54+
p = ChiSqStats(self.master.discrete_data, *state).p
55+
return 2 if np.isnan(p) else p
56+
57+
def bar_length(self, score):
58+
return min(1, -math.log(score, 10) / 50) if 0 < score <= 1 else 0
5459

5560

5661
class OWSieveDiagram(OWWidget):

Orange/widgets/visualize/utils/__init__.py

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
from bisect import bisect_left
66
from operator import attrgetter
77

8-
from AnyQt.QtCore import Qt, QSize, pyqtSignal as Signal
8+
from AnyQt.QtCore import Qt, QSize, pyqtSignal as Signal, QSortFilterProxyModel
99
from AnyQt.QtGui import QStandardItemModel, QStandardItem, QColor, QBrush, QPen
1010
from AnyQt.QtWidgets import (
1111
QTableView, QGraphicsTextItem, QGraphicsRectItem, QGraphicsView, QDialog,
12-
QVBoxLayout
12+
QVBoxLayout, QLineEdit
1313
)
1414
from Orange.data import Variable
1515
from Orange.widgets import gui
16-
from Orange.widgets.gui import HorizontalGridDelegate
16+
from Orange.widgets.gui import HorizontalGridDelegate, TableBarItem
1717
from Orange.widgets.utils.messages import WidgetMessagesMixin
1818
from Orange.widgets.utils.progressbar import ProgressBarMixin
1919
from Orange.widgets.widget import Msg
@@ -38,6 +38,7 @@ class VizRankDialog(QDialog, ProgressBarMixin, WidgetMessagesMixin):
3838
- `on_selection_changed` that handles event triggered when the user selects
3939
a table row. The method should emit signal
4040
`VizRankDialog.selectionChanged(object)`.
41+
- `bar_length` returns the length of the bar corresponding to the score.
4142
4243
The class provides a table and a button. A widget constructs a single
4344
instance of this dialog in its `__init__`, like (in Sieve) by using a
@@ -95,13 +96,25 @@ def __init__(self, master):
9596
self.saved_progress = 0
9697
self.scores = []
9798

99+
self.filter = QLineEdit()
100+
self.filter.setPlaceholderText("Filter ...")
101+
self.filter.textChanged.connect(self.filter_changed)
102+
self.layout().addWidget(self.filter)
103+
# Remove focus from line edit
104+
self.setFocus(Qt.ActiveWindowFocusReason)
105+
98106
self.rank_model = QStandardItemModel(self)
107+
self.model_proxy = QSortFilterProxyModel(self)
108+
self.model_proxy.setSourceModel(self.rank_model)
99109
self.rank_table = view = QTableView(
100110
selectionBehavior=QTableView.SelectRows,
101111
selectionMode=QTableView.SingleSelection,
102112
showGrid=False)
103-
view.setItemDelegate(HorizontalGridDelegate())
104-
view.setModel(self.rank_model)
113+
if self._has_bars:
114+
view.setItemDelegate(TableBarItem())
115+
else:
116+
view.setItemDelegate(HorizontalGridDelegate())
117+
view.setModel(self.model_proxy)
105118
view.selectionModel().selectionChanged.connect(
106119
self.on_selection_changed)
107120
view.horizontalHeader().setStretchLastSection(True)
@@ -111,6 +124,10 @@ def __init__(self, master):
111124
self.button = gui.button(
112125
self, self, "Start", callback=self.toggle, default=True)
113126

127+
@property
128+
def _has_bars(self):
129+
return type(self).bar_length is not VizRankDialog.bar_length
130+
114131
@classmethod
115132
def add_vizrank(cls, widget, master, button_label, set_attr_callback):
116133
"""
@@ -177,6 +194,9 @@ def initialize(self):
177194
self.button.setText("Start")
178195
self.button.setEnabled(self.check_preconditions())
179196

197+
def filter_changed(self, text):
198+
self.model_proxy.setFilterFixedString(text)
199+
180200
def stop_and_reset(self, reset_method=None):
181201
if self.keep_running:
182202
self.scheduled_call = reset_method or self.initialize
@@ -234,6 +254,12 @@ def compute_score(self, state):
234254
"""
235255
raise NotImplementedError
236256

257+
def bar_length(self, score):
258+
"""Compute the bar length (between 0 and 1) corresponding to the score.
259+
Return `None` if the score cannot be normalized.
260+
"""
261+
return None
262+
237263
def row_for_state(self, score, state):
238264
"""
239265
Abstract method that return the items that are inserted into the table.
@@ -264,8 +290,12 @@ def run(self):
264290
score = self.compute_score(state)
265291
if score is not None:
266292
pos = bisect_left(self.scores, score)
267-
self.rank_model.insertRow(
268-
pos, self.row_for_state(score, state))
293+
row_items = self.row_for_state(score, state)
294+
if self._has_bars:
295+
bar = self.bar_length(score)
296+
if bar is not None:
297+
row_items[0].setData(bar, gui.TableBarItem.BarRole)
298+
self.rank_model.insertRow(pos, row_items)
269299
self.scores.insert(pos, score)
270300
progress.advance()
271301
self._select_first_if_none()
@@ -370,7 +400,10 @@ def check_preconditions(self):
370400
return can_rank
371401

372402
def on_selection_changed(self, selected, deselected):
373-
attrs = [selected.indexes()[i].data(self._AttrRole) for i in (0, 1)]
403+
selection = selected.indexes()
404+
if not selection:
405+
return
406+
attrs = selected.indexes()[0].data(self._AttrRole)
374407
self.selectionChanged.emit(attrs)
375408

376409
def state_count(self):
@@ -385,13 +418,10 @@ def iterate_states(self, initial_state):
385418
sj = 0
386419

387420
def row_for_state(self, score, state):
388-
items = []
389421
attrs = sorted((self.attrs[x] for x in state), key=attrgetter("name"))
390-
for attr in attrs:
391-
item = QStandardItem(attr.name)
392-
item.setData(attr, self._AttrRole)
393-
items.append(item)
394-
return items
422+
item = QStandardItem(", ".join(a.name for a in attrs))
423+
item.setData(attrs, self._AttrRole)
424+
return [item]
395425

396426

397427
class CanvasText(QGraphicsTextItem):

0 commit comments

Comments
 (0)