Skip to content

Commit 6b54dcb

Browse files
committed
FIX: fixed compare showing all cells white and 0 as maximum diff when all the cells with differences are 0 in the first array (fixes #288)
1 parent a18972d commit 6b54dcb

File tree

4 files changed

+85
-52
lines changed

4 files changed

+85
-52
lines changed

doc/source/changes/version_0_35.rst.inc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,7 @@ Fixes
117117
* fixed :py:obj:`compare()` colors when the only difference is nans on either
118118
side.
119119
120+
* fixed :py:obj:`compare()` colors and max difference label when the only
121+
differences are for rows where the value is 0 in the first array.
122+
120123
* fixed single column plot in viewer when ticks are not strings

larray_editor/comparator.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from qtpy.QtWidgets import (QWidget, QVBoxLayout, QListWidget, QSplitter, QHBoxLayout,
66
QLabel, QCheckBox, QLineEdit, QComboBox, QMessageBox)
77

8-
from larray_editor.utils import replace_inf, _
8+
from larray_editor.utils import _
99
from larray_editor.arraywidget import ArrayEditorWidget
1010
from larray_editor.editor import AbstractEditorWindow, DISPLAY_IN_GRID
1111

@@ -112,7 +112,8 @@ def _get_atol_rtol(self):
112112
self.tolerance_line_edit.setText('')
113113
tol = 0
114114
QMessageBox.critical(self, "Error", str(e))
115-
return (tol, 0) if self.tolerance_combobox.currentText() == "absolute" else (0, tol)
115+
is_absolute = self.tolerance_combobox.currentText() == "absolute"
116+
return (tol, 0) if is_absolute else (0, tol)
116117

117118
# override keyPressEvent to prevent pressing Enter after changing the tolerance value
118119
# in associated QLineEdit to close the parent dialog box
@@ -160,31 +161,69 @@ def _update_from_combined_array(self):
160161

161162
atol, rtol = self._get_atol_rtol()
162163
try:
163-
self._diff_below_tolerance = self._combined_array.eq(self._array0, rtol=rtol, atol=atol, nans_equal=self.nans_equal)
164+
# eq does not take atol and rtol into account
165+
eq = self._combined_array.eq(self._array0,
166+
nans_equal=self.nans_equal)
167+
isclose = self._combined_array.eq(self._array0,
168+
rtol=rtol, atol=atol,
169+
nans_equal=self.nans_equal)
164170
except TypeError:
165-
self._diff_below_tolerance = self._combined_array == self._array0
171+
# object arrays
172+
eq = self._combined_array == self._array0
173+
isclose = eq
174+
self._diff_below_tolerance = isclose
166175

167176
try:
168177
with np.errstate(divide='ignore', invalid='ignore'):
169178
diff = self._combined_array - self._array0
170179
reldiff = diff / self._array0
171-
180+
# make reldiff 0 where the values are the same than array0 even for
181+
# special values (0, nan, inf, -inf)
182+
# at this point reldiff can still contain nan and infs
183+
reldiff = la.where(eq, 0, reldiff)
184+
185+
# 1) compute maxabsreldiff for the label
186+
# this should NOT exclude nans or infs
187+
relmin = reldiff.min(skipna=False)
188+
relmax = reldiff.max(skipna=False)
189+
maxabsreldiff = max(abs(relmin), abs(relmax))
190+
191+
# 2) compute bg_value
172192
# replace -inf by min(reldiff), +inf by max(reldiff)
173-
finite_reldiff, finite_relmin, finite_relmax = replace_inf(reldiff)
174-
maxabsreldiff = max(abs(finite_relmin), abs(finite_relmax))
175-
176-
# We need a separate version for bg and the label, so that when we modify atol/rtol, the background
177-
# color is updated but not the maxreldiff label
178-
# this is necessary for nan, inf and -inf (because inf - inf = nan, not 0)
179-
# this is more precise than divnot0, it only ignore 0 / 0, not x / 0
180-
reldiff_for_bg = la.where(self._diff_below_tolerance, 0, finite_reldiff)
181-
maxabsreldiff_for_bg = max(abs(np.nanmin(reldiff_for_bg)), abs(np.nanmax(reldiff_for_bg)))
193+
reldiff_for_bg = reldiff.copy()
194+
isneginf = reldiff == -np.inf
195+
isposinf = reldiff == np.inf
196+
isinf = isneginf | isposinf
197+
198+
# given the way reldiff is constructed, it cannot contain only infs
199+
# (because inf/inf is nan) it can contain only infs and nans though,
200+
# in which case finite_relXXX will be nan, so unless the array
201+
# is empty, finite_relXXX should never be inf
202+
finite_relmin = np.nanmin(reldiff, where=~isinf, initial=np.inf)
203+
finite_relmax = np.nanmax(reldiff, where=~isinf, initial=-np.inf)
204+
# special case when reldiff contains only 0 and infs (to avoid
205+
# coloring the inf cells white in that case)
206+
if finite_relmin == 0 and finite_relmax == 0 and isinf.any():
207+
finite_relmin = -1
208+
finite_relmax = 1
209+
reldiff_for_bg[isneginf] = finite_relmin
210+
reldiff_for_bg[isposinf] = finite_relmax
211+
212+
# make sure that "acceptable" differences show as white
213+
reldiff_for_bg = la.where(isclose, 0, reldiff_for_bg)
214+
215+
# We need a separate version for bg and the label, so that when we
216+
# modify atol/rtol, the background color is updated but not the
217+
# maxreldiff label
218+
maxabsreldiff_for_bg = max(abs(np.nanmin(reldiff_for_bg)),
219+
abs(np.nanmax(reldiff_for_bg)))
182220
if maxabsreldiff_for_bg:
183221
# scale reldiff to range 0-1 with 0.5 for reldiff = 0
184222
self._bg_value = (reldiff_for_bg / maxabsreldiff_for_bg) / 2 + 0.5
185223
# if the only differences are nans on either side
186-
elif not self._diff_below_tolerance.all():
187-
# use white (0.5) everywhere except where reldiff is nan, so that nans are grey
224+
elif not isclose.all():
225+
# use white (0.5) everywhere except where reldiff is nan, so
226+
# that nans are grey
188227
self._bg_value = reldiff_for_bg + 0.5
189228
else:
190229
# do NOT use full_like as we don't want to inherit array dtype

larray_editor/tests/test_api_larray.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,14 +274,39 @@ def test_plot_returning_ax_and_using_show():
274274
# arr1['M'] = la.nan
275275
# compare(arr1, arr2, nans_equal=False, title='with nans on left side')
276276
# arr2['M'] = la.nan
277-
# compare(arr1, arr2, nans_equal=False, title='with nans on both sides')
277+
# compare(arr1, arr2, title='with nans on both sides (nans_equal=True)')
278+
# compare(arr1, arr2, nans_equal=False, title='with nans on both sides (nans_equal=False)')
278279
# arr1['M'] = arr1_m
279280
# compare(arr1, arr2, nans_equal=False, title='with nans on right side')
280281
#
281282
# arr1 = la.ndtest((3, 3))
282283
# arr2 = 2 * arr1
283284
# arr3 = la.where(arr1 % 2 == 0, arr1, -arr1)
284-
# compare(arr1, arr2, arr3, bg_gradient='blue-white-red')
285+
# compare(arr1, arr2, arr3, bg_gradient='blue-white-red', title='changed gradient')
286+
287+
# arr1 = la.ndtest(3).astype(np.float64)
288+
# arr2 = arr1.copy()
289+
# arr2['a0'] = 42
290+
# arr3 = arr2.copy()
291+
# arr3['a1'] = 4
292+
# arr4 = arr1.copy()
293+
# arr4['a1'] = la.inf
294+
# arr5 = arr1.copy()
295+
# arr5['a1'] = -la.inf
296+
# arr6 = arr1.copy()
297+
# arr6['a1'] = 1.00000000001
298+
# compare(arr1, arr2)
299+
# compare(arr2, arr1)
300+
# compare(arr1, arr1)
301+
# compare(arr1, arr3)
302+
# compare(arr3, arr1)
303+
# compare(arr1, arr4)
304+
# compare(arr4, arr1)
305+
# compare(arr1, arr5)
306+
# compare(arr5, arr1)
307+
# compare(arr5, arr5)
308+
# compare(arr1, arr6)
309+
# compare(arr6, arr1)
285310

286311
# test for arr.plot(show=True) which is the default
287312
# =================================================

larray_editor/utils.py

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -382,40 +382,6 @@ def show_figure(figure, title=None, parent=None):
382382
window.show()
383383

384384

385-
def replace_inf(value):
386-
"""Replace -inf/+inf in array with respectively min(array_without_inf)/max(array_without_inf).
387-
388-
It leaves nans intact.
389-
390-
Parameters
391-
----------
392-
value : np.ndarray or any compatible type
393-
Input array.
394-
395-
Returns
396-
-------
397-
(np.ndarray, float, float)
398-
array with infinite values replaced by the min and maximum respectively
399-
minimum finite value
400-
maximum finite value
401-
402-
Examples
403-
--------
404-
>>> replace_inf(np.array([-5, np.inf, 0, -np.inf, -4, np.nan, 5]))
405-
(array([ -5., 5., 0., -5., -4., nan, 5.]), -5.0, 5.0)
406-
"""
407-
value = value.copy()
408-
# replace -inf by min(value)
409-
isneginf = value == -np.inf
410-
minvalue = np.nanmin(value[~isneginf])
411-
value[isneginf] = minvalue
412-
# replace +inf by max(value)
413-
isposinf = value == np.inf
414-
maxvalue = np.nanmax(value[~isposinf])
415-
value[isposinf] = maxvalue
416-
return value, minvalue, maxvalue
417-
418-
419385
def scale_to_01range(value, vmin, vmax):
420386
"""Scale value to 0-1 range based on vmin and vmax.
421387

0 commit comments

Comments
 (0)