Skip to content

Commit

Permalink
Box plot: Move 'order by importance' checkbox to groups
Browse files Browse the repository at this point in the history
  • Loading branch information
janezd committed Oct 1, 2019
1 parent 7e33526 commit 3e97b35
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 78 deletions.
86 changes: 35 additions & 51 deletions Orange/widgets/visualize/owboxplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ def __init__(self):
self.scene_min_x = self.scene_max_x = self.scene_width = 0
self.label_width = 0

self.attrs = VariableListModel()
self.attrs = DomainModel(
separators=False, valid_types=DomainModel.PRIMITIVE)
view = gui.listView(
self.controlArea, self, "attribute", box="Variable",
model=self.attrs, callback=self.attr_changed)
Expand All @@ -212,19 +213,16 @@ def __init__(self):
# set the minimal height (see the penultimate paragraph of
# http://doc.qt.io/qt-4.8/qabstractscrollarea.html#addScrollBarWidget)
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)
self.group_vars = VariableListModel(placeholder="None")
view = gui.listView(
self.controlArea, self, "group_var", box="Subgroups",
model=self.group_vars, callback=self.grouping_changed)
gui.separator(view.box, 6, 6)
self.cb_order = gui.checkBox(
gui.checkBox(
view.box, self, "order_by_importance",
"Order by relevance",
tooltip="Order by 𝜒² or ANOVA over the subgroups",
callback=self.apply_sorting)
self.group_vars = DomainModel(
placeholder="None", separators=False,
valid_types=Orange.data.DiscreteVariable)
self.group_view = view = gui.listView(
self.controlArea, self, "group_var", box="Subgroups",
model=self.group_vars, callback=self.grouping_changed)
view.setEnabled(False)
view.setMinimumSize(QSize(30, 30))
# See the comment above
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)
Expand Down Expand Up @@ -289,11 +287,11 @@ def eventFilter(self, obj, event):

return super().eventFilter(obj, event)

def reset_attrs(self, domain):
self.attrs[:] = [
def reset_groups(self, domain):
self.group_vars[:] = [None] + [
var for var in chain(
domain.class_vars, domain.metas, domain.attributes)
if var.is_primitive()]
if var.is_discrete]

# noinspection PyTypeChecker
@Inputs.data
Expand All @@ -309,41 +307,30 @@ def set_data(self, dataset):
self.attribute = None
if dataset:
domain = dataset.domain
self.group_vars.set_domain(domain)
self.group_view.setEnabled(len(self.group_vars) > 1)
self.reset_attrs(domain)
self.select_default_variables(domain)
self.attrs.set_domain(domain)
self.reset_groups(domain)
self.attribute = self.attrs[0]
self.openContext(self.dataset)
self.grouping_changed()
self.attr_changed()
else:
self.reset_all_data()
self.commit()

def select_default_variables(self, domain):
# visualize first non-class variable, group by class (if present)
if len(self.attrs) > len(domain.class_vars):
self.attribute = self.attrs[len(domain.class_vars)]
elif self.attrs:
self.attribute = self.attrs[0]

if domain.class_var and domain.class_var.is_discrete:
self.group_var = domain.class_var
else:
self.group_var = None # Reset to trigger selection via callback

def apply_sorting(self):
def compute_score(attr):
if attr is group_var:
def compute_stat(group):
if group is attr:
return 3
if group is None:
return -1
if attr.is_continuous:
# One-way ANOVA
col = data.get_column_view(attr)[0].astype(float)
groups = (col[group_col == i] for i in range(n_groups))
group_col = data.get_column_view(group)[0].astype(int)
groups = (attr_col[group_col == i]
for i in range(len(group.values)))
groups = (col[~np.isnan(col)] for col in groups)
groups = [group for group in groups if len(group)]
p = f_oneway(*groups)[1] if len(groups) > 1 else 2
else:
p = self._chi_square(group_var, attr)[1]
p = self._chi_square(group, attr)[1]
if math.isnan(p):
return 2
return p
Expand All @@ -352,17 +339,13 @@ def compute_score(attr):
if data is None:
return
domain = data.domain
attribute = self.attribute
group_var = self.group_var
if self.order_by_importance and group_var is not None:
n_groups = len(group_var.values)
group_col = data.get_column_view(group_var)[0] if \
domain.has_continuous_attributes(
include_class=True, include_metas=True) else None
self.attrs.sort(key=compute_score)
attr = self.attribute
if self.order_by_importance:
if attr.is_continuous:
attr_col = data.get_column_view(attr)[0].astype(float)
self.group_vars.sort(key=compute_stat)
else:
self.reset_attrs(domain)
self.attribute = attribute
self.reset_groups(domain)

def _chi_square(self, group_var, attr):
# Chi-square with the given distribution into groups
Expand All @@ -379,16 +362,13 @@ def _chi_square(self, group_var, attr):
def reset_all_data(self):
self.clear_scene()
self.stat_test = ""
self.attrs.clear()
self.group_vars.set_domain(None)
self.group_view.setEnabled(False)
self.attrs.set_domain(None)
self.group_vars[:] = [None]
self.is_continuous = False
self.update_display_box()

def grouping_changed(self):
self.cb_order.setEnabled(self.group_var is not None)
self.apply_sorting()
self.attr_changed()
self.update_graph()

def select_box_items(self):
temp_cond = self.conditions.copy()
Expand All @@ -398,6 +378,10 @@ def select_box_items(self):
[c.conditions for c in temp_cond])

def attr_changed(self):
self.apply_sorting()
self.update_graph()

def update_graph(self):
self.compute_box_data()
self.update_display_box()
self.layout_changed()
Expand Down
47 changes: 20 additions & 27 deletions Orange/widgets/visualize/tests/test_owboxplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ def test_input_data_missings_disc_no_group_var(self):
data.X[:, 1] = np.nan
data.domain.attributes[1].values = []
self.send_signal("Data", data)
self.widget.controls.order_by_importance.setChecked(True)
self._select_list_items(self.widget.controls.attribute)
self._select_list_items(self.widget.controls.group_var)

Expand All @@ -102,51 +101,46 @@ def test_attribute_combinations(self):

def test_apply_sorting(self):
controls = self.widget.controls
group_list = controls.group_var
attr_list = self.widget.attrs
order_check = controls.order_by_importance
attributes = self.widget.attrs
groups = self.widget.group_vars

def select_group(i):
group_selection = group_list.selectionModel()
group_selection.setCurrentIndex(
group_list.model().index(i),
group_selection.ClearAndSelect)
def select_attr(i):
attr_selection = controls.attribute.selectionModel()
attr_selection.setCurrentIndex(
attr_list.index(i),
attr_selection.ClearAndSelect)

data = self.titanic
self.send_signal("Data", data)

select_group(0)
self.assertFalse(order_check.isEnabled())
select_group(2) # First attribute
self.assertTrue(order_check.isEnabled())
select_attr(1) # First attribute

order_check.setChecked(False)
self.assertEqual(tuple(attributes),
data.domain.class_vars + data.domain.attributes)
self.assertEqual(
tuple(groups),
(None, ) + data.domain.class_vars + data.domain.attributes)
order_check.setChecked(True)
self.assertEqual([x.name for x in attributes],
self.assertIsNone(groups[0])
self.assertEqual([x.name for x in groups[1:]],
['sex', 'survived', 'age', 'status'])
select_group(1) # Class
self.assertEqual([x.name for x in attributes],
select_attr(0) # Class
self.assertIsNone(groups[0])
print(self.widget.group_var)
self.assertEqual([x.name for x in groups[1:]],
['sex', 'status', 'age', 'survived'])

data = self.heart
self.send_signal("Data", data)
select_group(1) # Class
order_check.setChecked(True)
self.assertEqual([x.name for x in attributes],
select_attr(0) # Class
self.assertIsNone(groups[0])
self.assertEqual([x.name for x in groups[1:]],
['thal',
'chest pain',
'major vessels colored',
'ST by exercise',
'max HR',
'exerc ind ang',
'slope peak exc ST',
'gender',
'age',
'rest ECG',
'rest SBP',
'cholesterol',
'fasting blood sugar > 120',
'diameter narrowing'])

Expand All @@ -170,7 +164,6 @@ def test_continuous_metas(self):
domain = Domain([], domain.class_var, metas)
data = Table.from_table(domain, self.iris)
self.send_signal(self.widget.Inputs.data, data)
self.widget.controls.order_by_importance.setChecked(True)

def test_label_overlap(self):
self.send_signal(self.widget.Inputs.data, self.heart)
Expand Down

0 comments on commit 3e97b35

Please sign in to comment.