Skip to content

Commit ed69cd5

Browse files
authored
Merge pull request #1980 from VesnaT/nomogram_const
OWNomogram: Handle data with constant features
2 parents 45307ad + e1ee491 commit ed69cd5

File tree

2 files changed

+47
-10
lines changed

2 files changed

+47
-10
lines changed

Orange/widgets/visualize/ownomogram.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ def _get_tooltip_label_value(self):
238238
start = float(self.tooltip_labels[0])
239239
stop = float(self.tooltip_labels[-1])
240240
delta = (self.tooltip_values[-1] - self.tooltip_values[0])
241+
if not delta:
242+
return np.nan
241243
return start + self.value * (stop - start) / delta
242244

243245

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

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

269272
def mousePressEvent(self, event):
@@ -463,7 +466,8 @@ def __init__(self, name, data_extremes, values, scale, name_offset, offset,
463466
coef):
464467
self.name = name.toPlainText()
465468
self.diff = (data_extremes[1] - data_extremes[0]) * coef
466-
k = (data_extremes[1] - data_extremes[0]) / (values[-1] - values[0])
469+
diff_ = np.nan_to_num(values[-1] - values[0])
470+
k = (data_extremes[1] - data_extremes[0]) / diff_ if diff_ else 0
467471
labels = [str(np.round(v * k + data_extremes[0], 1)) for v in values]
468472
super().__init__(name, values, scale, name_offset, offset, labels)
469473
self.dot.tooltip_labels = labels
@@ -525,7 +529,8 @@ def __init__(self, name, data_extremes, values, scale, name_offset, offset,
525529

526530
# ticks
527531
for value in values:
528-
k = (value - values[0]) / (values[-1] - values[0])
532+
diff_ = np.nan_to_num(values[-1] - values[0])
533+
k = (value - values[0]) / diff_ if diff_ else 0
529534
y_tick = (y_stop - y_start) * k + y_start - self.tick_height / 2
530535
x_tick = value * scale - self.tick_width / 2 + offset
531536
tick = QGraphicsRectItem(
@@ -887,7 +892,8 @@ def update_scene(self):
887892
minimums = [min(p) for p in points]
888893
if self.align == OWNomogram.ALIGN_LEFT:
889894
points = [p - m for m, p in zip(minimums, points)]
890-
d = 100 / max(max(abs(p)) for p in points)
895+
max_ = np.nan_to_num(max(max(abs(p)) for p in points))
896+
d = 100 / max_ if max_ else 1
891897
if self.scale == OWNomogram.POINT_SCALE:
892898
points = [p * d for p in points]
893899

@@ -934,7 +940,8 @@ def create_main_nomogram(self, name_items, points, max_width, point_text,
934940
max_p = max(max(p) for p in points)
935941
values = self.get_ruler_values(min_p, max_p, max_width)
936942
min_p, max_p = min(values), max(values)
937-
scale_x = max_width / (max_p - min_p)
943+
diff_ = np.nan_to_num(max_p - min_p)
944+
scale_x = max_width / diff_ if diff_ else max_width
938945

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

9941001
values = self.get_ruler_values(min_sum, max_sum, max_width)
9951002
min_sum, max_sum = min(values), max(values)
996-
scale_x = max_width / (max_sum - min_sum)
1003+
diff_ = np.nan_to_num(max_sum - min_sum)
1004+
scale_x = max_width / diff_ if diff_ else max_width
9971005
cls_var, cls_index = self.domain.class_var, self.target_class_index
9981006
nomogram_footer = NomogramItem()
9991007

@@ -1105,7 +1113,7 @@ def reconstruct_domain(original, preprocessed):
11051113
def get_ruler_values(start, stop, max_width, round_to_nearest=True):
11061114
if max_width == 0:
11071115
return [0]
1108-
diff = (stop - start) / max_width
1116+
diff = np.nan_to_num((stop - start) / max_width)
11091117
if diff <= 0:
11101118
return [0]
11111119
decimals = int(np.floor(np.log10(diff)))
@@ -1127,6 +1135,8 @@ def get_ruler_values(start, stop, max_width, round_to_nearest=True):
11271135

11281136
@staticmethod
11291137
def get_points_from_coeffs(current_value, coefficients, possible_values):
1138+
if any(np.isnan(possible_values)):
1139+
return 0
11301140
indices = np.argsort(possible_values)
11311141
sorted_values = possible_values[indices]
11321142
sorted_coefficients = coefficients[indices]

Orange/widgets/visualize/tests/test_ownomogram.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
# Test methods with long descriptive names can omit docstrings
22
# pylint: disable=missing-docstring
3-
from Orange.data import Table
4-
from Orange.classification import (NaiveBayesLearner, LogisticRegressionLearner,
5-
MajorityLearner)
3+
import numpy as np
4+
5+
from Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable
6+
from Orange.classification import (
7+
NaiveBayesLearner, LogisticRegressionLearner, MajorityLearner
8+
)
69
from Orange.widgets.tests.base import WidgetTest
710
from Orange.widgets.visualize.ownomogram import (
811
OWNomogram, DiscreteFeatureItem, ContinuousFeatureItem, ProbabilitiesDotItem
@@ -114,6 +117,30 @@ def test_nomogram_with_instance_lr(self):
114117
["sex", "status", "age"],
115118
["sex", "status", "age"]])
116119

120+
def test_constant_feature_disc(self):
121+
"""Check nomogram for data with constant discrete feature"""
122+
domain = Domain([DiscreteVariable("d1", ("a", "c")),
123+
DiscreteVariable("d2", ("b",))],
124+
DiscreteVariable("cls", ("e", "d")))
125+
X = np.array([[0, 0], [1, 0], [0, 0], [1, 0]])
126+
data = Table(domain, X, np.array([0, 1, 1, 0]))
127+
cls = NaiveBayesLearner()(data)
128+
self._test_helper(cls, [50, 50])
129+
cls = LogisticRegressionLearner()(data)
130+
self._test_helper(cls, [50, 50])
131+
132+
def test_constant_feature_cont(self):
133+
"""Check nomogram for data with constant continuous feature"""
134+
domain = Domain([DiscreteVariable("d", ("a", "b")),
135+
ContinuousVariable("c")],
136+
DiscreteVariable("cls", ("c", "d")))
137+
X = np.array([[0, 0], [1, 0], [0, 0], [1, 0]])
138+
data = Table(domain, X, np.array([0, 1, 1, 0]))
139+
cls = NaiveBayesLearner()(data)
140+
self._test_helper(cls, [50, 50])
141+
cls = LogisticRegressionLearner()(data)
142+
self._test_helper(cls, [50, 50])
143+
117144
def _test_helper(self, cls, values):
118145
self.send_signal("Classifier", cls)
119146

0 commit comments

Comments
 (0)