Skip to content

Commit 50cc5ec

Browse files
janezdastaric
authored andcommitted
Sieve: Switch to DomainModel, which also fixes bug with vizrank if the domain includes meta attributes
1 parent 7b5f2be commit 50cc5ec

File tree

3 files changed

+51
-42
lines changed

3 files changed

+51
-42
lines changed

Orange/widgets/utils/itemmodels.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,9 @@ def clear(self):
456456
def __len__(self):
457457
return len(self._list)
458458

459+
def __contains__(self, value):
460+
return value in self._list
461+
459462
def __iter__(self):
460463
return iter(self._list)
461464

Orange/widgets/visualize/owsieve.py

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from itertools import chain
21
import math
32

43
import numpy as np
@@ -8,17 +7,17 @@
87
from AnyQt.QtGui import QColor, QPen, QBrush
98
from AnyQt.QtWidgets import QGraphicsScene, QGraphicsLineItem, QSizePolicy
109

11-
from Orange.data import Table, filter
10+
from Orange.data import Table, filter, Variable
1211
from Orange.data.sql.table import SqlTable, LARGE_TABLE, DEFAULT_SAMPLE_TIME
1312
from Orange.preprocess import Discretize
1413
from Orange.preprocess.discretize import EqualFreq
1514
from Orange.statistics.contingency import get_contingency
16-
from Orange.widgets import gui, widget
15+
from Orange.widgets import gui, widget, settings
1716
from Orange.widgets.settings import DomainContextHandler, ContextSetting
1817
from Orange.widgets.utils import to_html as to_html
1918
from Orange.widgets.utils.annotated_data import (create_annotated_table,
2019
ANNOTATED_DATA_SIGNAL_NAME)
21-
from Orange.widgets.utils.itemmodels import VariableListModel
20+
from Orange.widgets.utils.itemmodels import DomainModel
2221
from Orange.widgets.visualize.utils import (
2322
CanvasText, CanvasRectangle, ViewWithPress, VizRankDialogAttrPair)
2423
from Orange.widgets.widget import OWWidget, Default, AttributeList
@@ -51,7 +50,8 @@ def initialize(self):
5150
self.attrs = self.master.attrs
5251

5352
def compute_score(self, state):
54-
p = ChiSqStats(self.master.discrete_data, *state).p
53+
p = ChiSqStats(self.master.discrete_data,
54+
*(self.attrs[i].name for i in state)).p
5555
return 2 if np.isnan(p) else p
5656

5757
def bar_length(self, score):
@@ -74,9 +74,10 @@ class OWSieveDiagram(OWWidget):
7474

7575
want_control_area = False
7676

77+
settings_version = 1
7778
settingsHandler = DomainContextHandler()
78-
attrX = ContextSetting("", exclude_metas=False)
79-
attrY = ContextSetting("", exclude_metas=False)
79+
attr_x = ContextSetting(None, exclude_metas=False)
80+
attr_y = ContextSetting(None, exclude_metas=False)
8081
selection = ContextSetting(set())
8182

8283
def __init__(self):
@@ -90,16 +91,15 @@ def __init__(self):
9091
self.selection = set()
9192

9293
self.attr_box = gui.hBox(self.mainArea)
93-
model = VariableListModel()
94-
model.wrap(self.attrs)
94+
self.domain_model = DomainModel(valid_types=DomainModel.PRIMITIVE)
9595
combo_args = dict(
9696
widget=self.attr_box, master=self, contentsLength=12,
9797
callback=self.update_attr, sendSelectedValue=True, valueType=str,
98-
model=model)
98+
model=self.domain_model)
9999
fixed_size = (QSizePolicy.Fixed, QSizePolicy.Fixed)
100-
self.attrXCombo = gui.comboBox(value="attrX", **combo_args)
100+
gui.comboBox(value="attr_x", **combo_args)
101101
gui.widgetLabel(self.attr_box, "\u2715", sizePolicy=fixed_size)
102-
self.attrYCombo = gui.comboBox(value="attrY", **combo_args)
102+
gui.comboBox(value="attr_y", **combo_args)
103103
self.vizrank, self.vizrank_button = SieveRank.add_vizrank(
104104
self.attr_box, self, "Score Combinations", self.set_attr)
105105
self.vizrank_button.setSizePolicy(*fixed_size)
@@ -126,10 +126,17 @@ def showEvent(self, event):
126126
super().showEvent(event)
127127
self.update_graph()
128128

129+
@classmethod
130+
def migrate_context(cls, context, version):
131+
if not version:
132+
settings.rename_setting(context, "attrX", "attr_x")
133+
settings.rename_setting(context, "attrY", "attr_y")
134+
settings.migrate_str_to_variable(context)
135+
129136
def set_data(self, data):
130137
"""
131138
Discretize continuous attributes, and put all attributes and discrete
132-
metas into self.attrs, which is used as a model for combos.
139+
metas into self.attrs.
133140
134141
Select the first two attributes unless context overrides this.
135142
Method `resolve_shown_attributes` is called to use the attributes from
@@ -150,24 +157,22 @@ def set_data(self, data):
150157
self.selection = set()
151158
if self.data is None:
152159
self.attrs[:] = []
160+
self.domain_model.set_domain(None)
153161
else:
162+
self.domain_model.set_domain(data.domain)
154163
if any(attr.is_continuous for attr in data.domain):
155164
discretizer = Discretize(
156165
method=EqualFreq(n=4),
157166
discretize_classes=True, discretize_metas=True)
158167
self.discrete_data = discretizer(data)
159168
else:
160169
self.discrete_data = self.data
161-
self.attrs[:] = [
162-
var for var in chain(
163-
self.discrete_data.domain,
164-
(var for var in self.data.domain.metas if var.is_discrete))
165-
]
170+
self.attrs = [x for x in self.domain_model if isinstance(x, Variable)]
166171
if self.attrs:
167-
self.attrX = self.attrs[0].name
168-
self.attrY = self.attrs[len(self.attrs) > 1].name
172+
self.attr_x = self.attrs[0]
173+
self.attr_y = self.attrs[len(self.attrs) > 1]
169174
else:
170-
self.attrX = self.attrY = None
175+
self.attr_x = self.attr_y = None
171176
self.areas = []
172177
self.selection = set()
173178
self.openContext(self.data)
@@ -181,7 +186,7 @@ def set_data(self, data):
181186
len(self.data.domain.attributes) > 1)
182187

183188
def set_attr(self, attr_x, attr_y):
184-
self.attrX, self.attrY = attr_x.name, attr_y.name
189+
self.attr_x, self.attr_y = attr_x, attr_y
185190
self.update_attr()
186191

187192
def update_attr(self):
@@ -213,15 +218,15 @@ def resolve_shown_attributes(self):
213218
self.attr_box.setEnabled(True)
214219
if not self.input_features: # None or empty
215220
return
216-
features = [f for f in self.input_features if f in self.attrs]
221+
features = [f for f in self.input_features if f in self.domain_model]
217222
if not features:
218223
self.warning(
219224
"Features from the input signal are not present in the data")
220225
return
221-
old_attrs = self.attrX, self.attrY
222-
self.attrX, self.attrY = [f.name for f in (features * 2)[:2]]
226+
old_attrs = self.attr_x, self.attr_y
227+
self.attr_x, self.attr_y = [f for f in (features * 2)[:2]]
223228
self.attr_box.setEnabled(False)
224-
if (self.attrX, self.attrY) != old_attrs:
229+
if (self.attr_x, self.attr_y) != old_attrs:
225230
self.selection = set()
226231
self.update_graph()
227232

@@ -264,8 +269,8 @@ def update_selection(self):
264269
val_x, val_y = area.value_pair
265270
filts.append(
266271
filter.Values([
267-
filter.FilterDiscrete(self.attrX, [val_x]),
268-
filter.FilterDiscrete(self.attrY, [val_y])
272+
filter.FilterDiscrete(self.attr_x.name, [val_x]),
273+
filter.FilterDiscrete(self.attr_y.name, [val_y])
269274
]))
270275
else:
271276
width = 1
@@ -356,22 +361,22 @@ def make_tooltip():
356361
"""Create the tooltip. The function uses local variables from
357362
the enclosing scope."""
358363
# pylint: disable=undefined-loop-variable
359-
def _oper(attr_name, txt):
360-
if self.data.domain[attr_name] is ddomain[attr_name]:
364+
def _oper(attr, txt):
365+
if self.data.domain[attr.name] is ddomain[attr.name]:
361366
return "="
362367
return " " if txt[0] in "<≥" else " in "
363368

364369
return (
365-
"<b>{attrX}{xeq}{xval_name}</b>: {obs_x}/{n} ({p_x:.0f} %)".
366-
format(attrX=to_html(attr_x),
370+
"<b>{attr_x}{xeq}{xval_name}</b>: {obs_x}/{n} ({p_x:.0f} %)".
371+
format(attr_x=to_html(attr_x.name),
367372
xeq=_oper(attr_x, xval_name),
368373
xval_name=to_html(xval_name),
369374
obs_x=fmt(chi.probs_x[x] * n),
370375
n=int(n),
371376
p_x=100 * chi.probs_x[x]) +
372377
"<br/>" +
373-
"<b>{attrY}{yeq}{yval_name}</b>: {obs_y}/{n} ({p_y:.0f} %)".
374-
format(attrY=to_html(attr_y),
378+
"<b>{attr_y}{yeq}{yval_name}</b>: {obs_y}/{n} ({p_y:.0f} %)".
379+
format(attr_y=to_html(attr_y.name),
375380
yeq=_oper(attr_y, yval_name),
376381
yval_name=to_html(yval_name),
377382
obs_y=fmt(chi.probs_y[y] * n),
@@ -389,19 +394,19 @@ def _oper(attr_name, txt):
389394
for item in self.canvas.items():
390395
self.canvas.removeItem(item)
391396
if self.data is None or len(self.data) == 0 or \
392-
self.attrX is None or self.attrY is None:
397+
self.attr_x is None or self.attr_y is None:
393398
return
394399

395400
ddomain = self.discrete_data.domain
396-
attr_x, attr_y = self.attrX, self.attrY
397-
disc_x, disc_y = ddomain[attr_x], ddomain[attr_y]
401+
attr_x, attr_y = self.attr_x, self.attr_y
402+
disc_x, disc_y = ddomain[attr_x.name], ddomain[attr_y.name]
398403
view = self.canvasView
399404

400-
chi = ChiSqStats(self.discrete_data, attr_x, attr_y)
405+
chi = ChiSqStats(self.discrete_data, disc_x, disc_y)
401406
n = chi.n
402407
max_ylabel_w = max((width(val) for val in disc_y.values), default=0)
403408
max_ylabel_w = min(max_ylabel_w, 200)
404-
x_off = width(attr_x) + max_ylabel_w
409+
x_off = width(attr_x.name) + max_ylabel_w
405410
y_off = 15
406411
square_size = min(view.width() - x_off - 35, view.height() - y_off - 50)
407412
square_size = max(square_size, 10)
@@ -443,9 +448,9 @@ def _oper(attr_name, txt):
443448
curr_x += width
444449

445450
bottom = y_off + square_size + max_xlabel_h
446-
text(attr_y, 0, y_off + square_size / 2,
451+
text(attr_y.name, 0, y_off + square_size / 2,
447452
Qt.AlignLeft | Qt.AlignVCenter, bold=True, vertical=True)
448-
text(attr_x, x_off + square_size / 2, bottom,
453+
text(attr_x.name, x_off + square_size / 2, bottom,
449454
Qt.AlignHCenter | Qt.AlignTop, bold=True)
450455
xl = text("χ²={:.2f}, p={:.3f}".format(chi.chisq, chi.p),
451456
0, bottom)
@@ -454,7 +459,7 @@ def _oper(attr_name, txt):
454459

455460
def get_widget_name_extension(self):
456461
if self.data is not None:
457-
return "{} vs {}".format(self.attrX, self.attrY)
462+
return "{} vs {}".format(self.attr_x.name, self.attr_y.name)
458463

459464
def send_report(self):
460465
self.report_plot()

Orange/widgets/visualize/tests/test_owsieve.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def setUp(self):
2020
self.widget = self.create_widget(OWSieveDiagram)
2121

2222
def _select_data(self):
23+
self.widget.attr_x, self.widget.attr_y = self.data.domain[:2]
2324
area = self.widget.areas[0]
2425
self.widget.select_area(area, QMouseEvent(
2526
QEvent.MouseButtonPress, QPoint(), Qt.LeftButton,

0 commit comments

Comments
 (0)