From cfb27b348139f635325734ff4509e6d67a4b6e54 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 26 Aug 2022 17:47:02 -0400 Subject: [PATCH 1/2] FIX: show bars when the first location is nan Due to the way we handle units on the bar width having an invalid value in the first position of the x bar (of y of barh) would effectively poison all of the widths making all of the bars invisible. This also renames the cbook function _safe_first_non_none function -> _safe_first_finite and adjusts the behavior to also drop nans closes #23687 --- lib/matplotlib/axes/_axes.py | 14 +++++++------- lib/matplotlib/cbook/__init__.py | 19 +++++++++++++++---- lib/matplotlib/dates.py | 2 +- lib/matplotlib/tests/test_axes.py | 24 ++++++++++++++++++++++++ lib/matplotlib/tests/test_cbook.py | 6 +++--- lib/matplotlib/units.py | 2 +- 6 files changed, 51 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index cb1d4c989c23..205b594e7588 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2179,12 +2179,12 @@ def _convert_dx(dx, x0, xconv, convert): # removes the units from unit packages like `pint` that # wrap numpy arrays. try: - x0 = cbook._safe_first_non_none(x0) + x0 = cbook._safe_first_finite(x0) except (TypeError, IndexError, KeyError): pass try: - x = cbook._safe_first_non_none(xconv) + x = cbook._safe_first_finite(xconv) except (TypeError, IndexError, KeyError): x = xconv @@ -2801,11 +2801,11 @@ def broken_barh(self, xranges, yrange, **kwargs): """ # process the unit information if len(xranges): - xdata = cbook._safe_first_non_none(xranges) + xdata = cbook._safe_first_finite(xranges) else: xdata = None if len(yrange): - ydata = cbook._safe_first_non_none(yrange) + ydata = cbook._safe_first_finite(yrange) else: ydata = None self._process_unit_info( @@ -3447,10 +3447,10 @@ def _upcast_err(err): # safe_first_element because getitem is index-first not # location first on pandas objects so err[0] almost always # fails. - isinstance(cbook._safe_first_non_none(err), np.ndarray) + isinstance(cbook._safe_first_finite(err), np.ndarray) ): # Get the type of the first element - atype = type(cbook._safe_first_non_none(err)) + atype = type(cbook._safe_first_finite(err)) # Promote the outer container to match the inner container if atype is np.ndarray: # Converts using np.asarray, because data cannot @@ -4313,7 +4313,7 @@ def _parse_scatter_color_args(c, edgecolors, kwargs, xsize, c_is_string_or_strings = ( isinstance(c, str) or (np.iterable(c) and len(c) > 0 - and isinstance(cbook._safe_first_non_none(c), str))) + and isinstance(cbook._safe_first_finite(c), str))) def invalid_shape_exception(csize, xsize): return ValueError( diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 17d1cad3a753..3c9a48a4dd7c 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1703,10 +1703,10 @@ def safe_first_element(obj): This is an type-independent way of obtaining the first element, supporting both index access and the iterator protocol. """ - return _safe_first_non_none(obj, skip_none=False) + return _safe_first_finite(obj, skip_nonfinite=False) -def _safe_first_non_none(obj, skip_none=True): +def _safe_first_finite(obj, *, skip_nonfinite=True): """ Return the first non-None element in *obj*. This is a method for internal use. @@ -1715,7 +1715,14 @@ def _safe_first_non_none(obj, skip_none=True): supporting both index access and the iterator protocol. The first non-None element will be obtained when skip_none is True. """ - if skip_none is False: + def safe_isfinite(val): + try: + return np.isfinite(val) if np.isscalar(val) else True + except TypeError: + # This is something that numpy can not make heads or tails + # of, assume "finite" + return True + if skip_nonfinite is False: if isinstance(obj, collections.abc.Iterator): # needed to accept `array.flat` as input. # np.flatiter reports as an instance of collections.Iterator @@ -1730,12 +1737,16 @@ def _safe_first_non_none(obj, skip_none=True): "as input") return next(iter(obj)) elif isinstance(obj, np.flatiter): + # TODO do the finite filtering on this return obj[0] elif isinstance(obj, collections.abc.Iterator): raise RuntimeError("matplotlib does not " "support generators as input") else: - return next(val for val in obj if val is not None) + return next( + val for val in obj + if val is not None and safe_isfinite(val) + ) def sanitize_sequence(data): diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 6dfc972f704d..dc01ffded2ec 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -1908,7 +1908,7 @@ def default_units(x, axis): x = x.ravel() try: - x = cbook._safe_first_non_none(x) + x = cbook._safe_first_finite(x) except (TypeError, StopIteration): pass diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index a230af2ac1e0..628f9542aa42 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8109,3 +8109,27 @@ def test_get_xticklabel(): for ind in range(10): assert ax.get_xticklabels()[ind].get_text() == f'{ind}' assert ax.get_yticklabels()[ind].get_text() == f'{ind}' + + +def test_bar_leading_nan(): + + barx = np.arange(3, dtype=float) + barheights = np.array([0.5, 1.5, 2.0]) + barstarts = np.array([0.77]*3) + + barx[0] = np.NaN + + fig, ax = plt.subplots() + + bars = ax.bar(barx, barheights, bottom=barstarts) + + hbars = ax.barh(barx, barheights, left=barstarts) + + for bar_set in (bars, hbars): + # the first bar should have a nan in the location + nanful, *rest = bar_set + assert (~np.isfinite(nanful.xy)).any() + assert np.isfinite(nanful.get_width()) + for b in rest: + assert np.isfinite(b.xy).all() + assert np.isfinite(b.get_width()) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 70f2c2499418..26748d1a5798 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -602,7 +602,7 @@ def test_flatiter(): it = x.flat assert 0 == next(it) assert 1 == next(it) - ret = cbook._safe_first_non_none(it) + ret = cbook._safe_first_finite(it) assert ret == 0 assert 0 == next(it) @@ -758,7 +758,7 @@ def test_contiguous_regions(): def test_safe_first_element_pandas_series(pd): # deliberately create a pandas series with index not starting from 0 s = pd.Series(range(5), index=range(10, 15)) - actual = cbook._safe_first_non_none(s) + actual = cbook._safe_first_finite(s) assert actual == 0 @@ -893,5 +893,5 @@ def test_format_approx(): def test_safe_first_element_with_none(): datetime_lst = [date.today() + timedelta(days=i) for i in range(10)] datetime_lst[0] = None - actual = cbook._safe_first_non_none(datetime_lst) + actual = cbook._safe_first_finite(datetime_lst) assert actual is not None and actual == datetime_lst[1] diff --git a/lib/matplotlib/units.py b/lib/matplotlib/units.py index cf973034d341..2bcfcaf2eb12 100644 --- a/lib/matplotlib/units.py +++ b/lib/matplotlib/units.py @@ -197,7 +197,7 @@ def get_converter(self, x): except KeyError: pass try: # If cache lookup fails, look up based on first element... - first = cbook._safe_first_non_none(x) + first = cbook._safe_first_finite(x) except (TypeError, StopIteration): pass else: From bbf0cd2610eb253fdfee73276e0a57fb8a28851b Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 13 Sep 2022 16:47:57 -0400 Subject: [PATCH 2/2] MNT: shorten logic + docstring --- lib/matplotlib/cbook/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 3c9a48a4dd7c..171c7a6fff1f 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1708,7 +1708,8 @@ def safe_first_element(obj): def _safe_first_finite(obj, *, skip_nonfinite=True): """ - Return the first non-None element in *obj*. + Return the first non-None (and optionally finite) element in *obj*. + This is a method for internal use. This is an type-independent way of obtaining the first non-None element, @@ -1716,6 +1717,8 @@ def _safe_first_finite(obj, *, skip_nonfinite=True): The first non-None element will be obtained when skip_none is True. """ def safe_isfinite(val): + if val is None: + return False try: return np.isfinite(val) if np.isscalar(val) else True except TypeError: @@ -1743,10 +1746,7 @@ def safe_isfinite(val): raise RuntimeError("matplotlib does not " "support generators as input") else: - return next( - val for val in obj - if val is not None and safe_isfinite(val) - ) + return next(val for val in obj if safe_isfinite(val)) def sanitize_sequence(data): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy