Skip to content

Commit

Permalink
Merge pull request #1980 from VesnaT/nomogram_const
Browse files Browse the repository at this point in the history
OWNomogram: Handle data with constant features
  • Loading branch information
lanzagar authored Jan 31, 2017
2 parents 45307ad + e1ee491 commit ed69cd5
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 10 deletions.
24 changes: 17 additions & 7 deletions Orange/widgets/visualize/ownomogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ def _get_tooltip_label_value(self):
start = float(self.tooltip_labels[0])
stop = float(self.tooltip_labels[-1])
delta = (self.tooltip_values[-1] - self.tooltip_values[0])
if not delta:
return np.nan
return start + self.value * (stop - start) / delta


Expand All @@ -263,7 +265,8 @@ def horizontal_line(self, line):

def move(self, x):
super().move(x)
k = (x - self._min_x) / (self._max_x - self._min_x)
diff_ = np.nan_to_num(self._max_x - self._min_x)
k = (x - self._min_x) / diff_ if diff_ else 0
self.setY(self._min_y - self._r / 2 + (self._max_y - self._min_y) * k)

def mousePressEvent(self, event):
Expand Down Expand Up @@ -463,7 +466,8 @@ def __init__(self, name, data_extremes, values, scale, name_offset, offset,
coef):
self.name = name.toPlainText()
self.diff = (data_extremes[1] - data_extremes[0]) * coef
k = (data_extremes[1] - data_extremes[0]) / (values[-1] - values[0])
diff_ = np.nan_to_num(values[-1] - values[0])
k = (data_extremes[1] - data_extremes[0]) / diff_ if diff_ else 0
labels = [str(np.round(v * k + data_extremes[0], 1)) for v in values]
super().__init__(name, values, scale, name_offset, offset, labels)
self.dot.tooltip_labels = labels
Expand Down Expand Up @@ -525,7 +529,8 @@ def __init__(self, name, data_extremes, values, scale, name_offset, offset,

# ticks
for value in values:
k = (value - values[0]) / (values[-1] - values[0])
diff_ = np.nan_to_num(values[-1] - values[0])
k = (value - values[0]) / diff_ if diff_ else 0
y_tick = (y_stop - y_start) * k + y_start - self.tick_height / 2
x_tick = value * scale - self.tick_width / 2 + offset
tick = QGraphicsRectItem(
Expand Down Expand Up @@ -887,7 +892,8 @@ def update_scene(self):
minimums = [min(p) for p in points]
if self.align == OWNomogram.ALIGN_LEFT:
points = [p - m for m, p in zip(minimums, points)]
d = 100 / max(max(abs(p)) for p in points)
max_ = np.nan_to_num(max(max(abs(p)) for p in points))
d = 100 / max_ if max_ else 1
if self.scale == OWNomogram.POINT_SCALE:
points = [p * d for p in points]

Expand Down Expand Up @@ -934,7 +940,8 @@ def create_main_nomogram(self, name_items, points, max_width, point_text,
max_p = max(max(p) for p in points)
values = self.get_ruler_values(min_p, max_p, max_width)
min_p, max_p = min(values), max(values)
scale_x = max_width / (max_p - min_p)
diff_ = np.nan_to_num(max_p - min_p)
scale_x = max_width / diff_ if diff_ else max_width

nomogram_header = NomogramItem()
point_item = RulerItem(point_text, values, scale_x, name_offset,
Expand Down Expand Up @@ -993,7 +1000,8 @@ def create_footer_nomogram(self, probs_text, d, minimums,

values = self.get_ruler_values(min_sum, max_sum, max_width)
min_sum, max_sum = min(values), max(values)
scale_x = max_width / (max_sum - min_sum)
diff_ = np.nan_to_num(max_sum - min_sum)
scale_x = max_width / diff_ if diff_ else max_width
cls_var, cls_index = self.domain.class_var, self.target_class_index
nomogram_footer = NomogramItem()

Expand Down Expand Up @@ -1105,7 +1113,7 @@ def reconstruct_domain(original, preprocessed):
def get_ruler_values(start, stop, max_width, round_to_nearest=True):
if max_width == 0:
return [0]
diff = (stop - start) / max_width
diff = np.nan_to_num((stop - start) / max_width)
if diff <= 0:
return [0]
decimals = int(np.floor(np.log10(diff)))
Expand All @@ -1127,6 +1135,8 @@ def get_ruler_values(start, stop, max_width, round_to_nearest=True):

@staticmethod
def get_points_from_coeffs(current_value, coefficients, possible_values):
if any(np.isnan(possible_values)):
return 0
indices = np.argsort(possible_values)
sorted_values = possible_values[indices]
sorted_coefficients = coefficients[indices]
Expand Down
33 changes: 30 additions & 3 deletions Orange/widgets/visualize/tests/test_ownomogram.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Test methods with long descriptive names can omit docstrings
# pylint: disable=missing-docstring
from Orange.data import Table
from Orange.classification import (NaiveBayesLearner, LogisticRegressionLearner,
MajorityLearner)
import numpy as np

from Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable
from Orange.classification import (
NaiveBayesLearner, LogisticRegressionLearner, MajorityLearner
)
from Orange.widgets.tests.base import WidgetTest
from Orange.widgets.visualize.ownomogram import (
OWNomogram, DiscreteFeatureItem, ContinuousFeatureItem, ProbabilitiesDotItem
Expand Down Expand Up @@ -114,6 +117,30 @@ def test_nomogram_with_instance_lr(self):
["sex", "status", "age"],
["sex", "status", "age"]])

def test_constant_feature_disc(self):
"""Check nomogram for data with constant discrete feature"""
domain = Domain([DiscreteVariable("d1", ("a", "c")),
DiscreteVariable("d2", ("b",))],
DiscreteVariable("cls", ("e", "d")))
X = np.array([[0, 0], [1, 0], [0, 0], [1, 0]])
data = Table(domain, X, np.array([0, 1, 1, 0]))
cls = NaiveBayesLearner()(data)
self._test_helper(cls, [50, 50])
cls = LogisticRegressionLearner()(data)
self._test_helper(cls, [50, 50])

def test_constant_feature_cont(self):
"""Check nomogram for data with constant continuous feature"""
domain = Domain([DiscreteVariable("d", ("a", "b")),
ContinuousVariable("c")],
DiscreteVariable("cls", ("c", "d")))
X = np.array([[0, 0], [1, 0], [0, 0], [1, 0]])
data = Table(domain, X, np.array([0, 1, 1, 0]))
cls = NaiveBayesLearner()(data)
self._test_helper(cls, [50, 50])
cls = LogisticRegressionLearner()(data)
self._test_helper(cls, [50, 50])

def _test_helper(self, cls, values):
self.send_signal("Classifier", cls)

Expand Down

0 comments on commit ed69cd5

Please sign in to comment.