Skip to content

Commit aa30c1f

Browse files
authored
Merge pull request #2066 from jerneju/indexerror-owcorrespondence
[FIX] owcorrespondence: Handle variables with one value
2 parents 1f72515 + 98e2839 commit aa30c1f

File tree

2 files changed

+54
-13
lines changed

2 files changed

+54
-13
lines changed

Orange/widgets/unsupervised/owcorrespondence.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from AnyQt.QtWidgets import QListView, QApplication
66
from AnyQt.QtGui import QBrush, QColor, QPainter
7-
from AnyQt.QtCore import Qt, QEvent, QItemSelectionModel, QItemSelection
7+
from AnyQt.QtCore import QEvent, QItemSelectionModel, QItemSelection
88

99
import pyqtgraph as pg
1010
import Orange.data
@@ -14,7 +14,6 @@
1414
from Orange.widgets.utils import itemmodels, colorpalette
1515

1616
from Orange.widgets.visualize.owscatterplotgraph import ScatterPlotItem
17-
from Orange.widgets.io import FileFormat
1817

1918

2019
class ScatterPlotItem(pg.ScatterPlotItem):
@@ -140,6 +139,12 @@ def _p_axes(self):
140139
def _var_changed(self):
141140
self.selected_var_indices = sorted(
142141
ind.row() for ind in self.varview.selectionModel().selectedRows())
142+
rfs = self.update_XY()
143+
if rfs is not None:
144+
if self.component_x >= rfs:
145+
self.component_x = rfs-1
146+
if self.component_y >= rfs:
147+
self.component_y = rfs-1
143148
self._invalidate()
144149

145150
def _component_changed(self):
@@ -160,6 +165,15 @@ def customEvent(self, event):
160165
return super().customEvent(event)
161166

162167
def _update_CA(self):
168+
self.update_XY()
169+
self.component_x, self.component_y = self.component_x, self.component_y
170+
171+
self._setup_plot()
172+
self._update_info()
173+
174+
def update_XY(self):
175+
self.axis_x_cb.clear()
176+
self.axis_y_cb.clear()
163177
ca_vars = self.selected_vars()
164178
if len(ca_vars) == 0:
165179
return
@@ -171,26 +185,24 @@ def _update_CA(self):
171185
ctable = contingency.get_contingency(self.data, *ca_vars[::-1])
172186

173187
self.ca = correspondence(ctable, )
188+
rfs = self.ca.row_factors.shape[1]
174189
axes = ["{}".format(i + 1)
175-
for i in range(self.ca.row_factors.shape[1])]
176-
self.axis_x_cb.clear()
190+
for i in range(rfs)]
177191
self.axis_x_cb.addItems(axes)
178-
self.axis_y_cb.clear()
179192
self.axis_y_cb.addItems(axes)
180-
self.component_x, self.component_y = self.component_x, self.component_y
181-
182-
self._setup_plot()
183-
self._update_info()
193+
return rfs
184194

185195
def _setup_plot(self):
186196
self.plot.clear()
187-
188197
points = self.ca
189198
variables = self.selected_vars()
190199
colors = colorpalette.ColorPaletteGenerator(len(variables))
191200

192201
p_axes = self._p_axes()
193202

203+
if points == None:
204+
return
205+
194206
if len(variables) == 2:
195207
row_points = self.ca.row_factors[:, p_axes]
196208
col_points = self.ca.col_factors[:, p_axes]
@@ -220,7 +232,10 @@ def _setup_plot(self):
220232
item.setPos(point[0], point[1])
221233

222234
inertia = self.ca.inertia_of_axis()
223-
inertia = 100 * inertia / numpy.sum(inertia)
235+
if numpy.sum(inertia) == 0:
236+
inertia = 100 * inertia
237+
else:
238+
inertia = 100 * inertia / numpy.sum(inertia)
224239

225240
ax = self.plot.getAxis("bottom")
226241
ax.setLabel("Component {} ({:.1f}%)"
@@ -236,7 +251,10 @@ def _update_info(self):
236251
fmt = ("Axis 1: {:.2f}\n"
237252
"Axis 2: {:.2f}")
238253
inertia = self.ca.inertia_of_axis()
239-
inertia = 100 * inertia / numpy.sum(inertia)
254+
if numpy.sum(inertia) == 0:
255+
inertia = 100 * inertia
256+
else:
257+
inertia = 100 * inertia / numpy.sum(inertia)
240258

241259
ax1, ax2 = self._p_axes()
242260
self.infotext.setText(fmt.format(inertia[ax1], inertia[ax2]))
@@ -314,6 +332,7 @@ def correspondence(A):
314332
E = row_sum * col_sum
315333

316334
D_r, D_c = row_sum.ravel() ** -1, col_sum.ravel() ** -1
335+
D_r, D_c = numpy.nan_to_num(D_r), numpy.nan_to_num(D_c)
317336

318337
def gsvd(M, Wu, Wv):
319338
assert len(M.shape) == 2

Orange/widgets/unsupervised/tests/test_owcorrespondence.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Test methods with long descriptive names can omit docstrings
22
# pylint: disable=missing-docstring
3-
from Orange.data import Table
3+
from Orange.data import Table, Domain, DiscreteVariable, ContinuousVariable
44
from Orange.widgets.tests.base import WidgetTest
55
from Orange.widgets.unsupervised.owcorrespondence \
66
import OWCorrespondenceAnalysis
@@ -15,3 +15,25 @@ def test_no_data(self):
1515
self.send_signal("Data", Table(Table("iris").domain))
1616
self.assertTrue(self.widget.Error.empty_data.is_shown())
1717
self.assertIsNone(self.widget.data)
18+
19+
def test_data_values_in_column(self):
20+
"""
21+
Check that the widget does not crash when:
22+
1) Domain has a two or more discrete variables but less than in a table
23+
2) There is at least one NaN value in a column.
24+
GH-2066
25+
"""
26+
table = Table(
27+
Domain(
28+
[ContinuousVariable("a"),
29+
DiscreteVariable("b", values=["t", "f"]),
30+
DiscreteVariable("c", values=["y", "n"]),
31+
DiscreteVariable("d", values=["k", "l", "z"])]
32+
),
33+
list(zip(
34+
[42.48, 16.84, 15.23, 23.8],
35+
["t", "t", "", "f"],
36+
"yyyy",
37+
"klkk"
38+
)))
39+
self.send_signal("Data", table)

0 commit comments

Comments
 (0)