Skip to content

[Bug]: ax.bar raises for all-nan data on matplotlib 3.6.1 — Task 268 — matplotlib__matplotlib-24149 #34

Open

Description

@rowan-stein

[Bug]: ax.bar raises for all-nan data on matplotlib 3.6.1

Bug summary

ax.bar raises an exception in 3.6.1 when passed only NaN data. This breaks seaborn's histogram function (which draws and then removes a "phantom" bar to trip the color cycle).

Code for reproduction

import numpy as np
import matplotlib.pyplot as plt
f, ax = plt.subplots()
ax.bar([np.nan], [np.nan])

Actual outcome

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Cell In [1], line 4
      2 import matplotlib.pyplot as plt
      3 f, ax = plt.subplots()
----> 4 ax.bar([np.nan], [np.nan])[0].get_x()

File ~/miniconda/envs/py310/lib/python3.10/site-packages/matplotlib/__init__.py:1423, in _preprocess_data.<locals>.inner(ax, data, *args, **kwargs)
   1420 @functools.wraps(func)
   1421 def inner(ax, *args, data=None, **kwargs):
   1422     if data is None:
-> 1423         return func(ax, *map(sanitize_sequence, args), **kwargs)
   1425     bound = new_sig.bind(ax, *args, **kwargs)
   1426     auto_label = (bound.arguments.get(label_namer)
                   or bound.kwargs.get(label_namer))

File ~/miniconda/envs/py310/lib/python3.10/site-packages/matplotlib/axes/_axes.py:2373, in Axes.bar(self, x, height, width, bottom, align, **kwargs)
   2371 x0 = x
   2372 x = np.asarray(self.convert_xunits(x))
-> 2373 width = self._convert_dx(width, x0, x, self.convert_xunits)
   2374 if xerr is not None:
   2375     xerr = self._convert_dx(xerr, x0, x, self.convert_xunits)

File ~/miniconda/envs/py310/lib/python3.10/site-packages/matplotlib/axes/_axes.py:2182, in Axes._convert_dx(dx, x0, xconv, convert)
   2170 try:
   2171     # attempt to add the width to x0; this works for
   2172     # datetime+timedelta, for instance
   (...)
   2179     # removes the units from unit packages like `pint` that
   2180     # wrap numpy arrays.
   2181     try:
-> 2182         x0 = cbook._safe_first_finite(x0)
   2183     except (TypeError, IndexError, KeyError):
   2184         pass

File ~/miniconda/envs/py310/lib/python3.10/site-packages/matplotlib/cbook/__init__.py:1749, in _safe_first_finite(obj, skip_nonfinite)
   1746     raise RuntimeError("matplotlib does not "
   1747                        "support generators as input")
   1748 else:
-> 1749     return next(val for val in obj if safe_isfinite(val))

StopIteration: 

Expected outcome

On 3.6.0 this returns a BarCollection with one Rectangle, having nan for x and height.

Additional information

  • Likely related to FIX: show bars when the first location is nan matplotlib/matplotlib#23751
  • Root cause: cbook._safe_first_finite raises StopIteration when no finite value exists (all-NaN). In Axes._convert_dx, StopIteration is not handled for both the x0 and xconv paths, so the exception propagates.
  • Observed asymmetry:
    • ax.bar([np.nan], [0]) raises (due to NaN x)
    • ax.bar([0], [np.nan]) works

Research specification (from Emerson Gray)

  • Fix scope: lib/matplotlib/axes/_axes.py, method Axes._convert_dx.
  • Behavior change: For both calls to cbook._safe_first_finite (on x0 and xconv), catch StopIteration and fall back to cbook.safe_first_element so that NaN-only inputs do not raise.
  • Maintain existing exception handling for (TypeError, IndexError, KeyError) and outer handler (ValueError, TypeError, AttributeError).
  • Expected post-fix behavior:
    • ax.bar([np.nan], [np.nan]) returns a BarContainer with one Rectangle; its x and height are NaN (consistent with 3.6.0).
    • ax.bar([np.nan], [0]) returns without raising; x is NaN, height 0.
    • ax.bar([0], [np.nan]) continues to work.
    • No change to autoscaling semantics.

Test plan

Add regression tests to confirm:

  1. ax.bar([np.nan], [np.nan]) does not raise and returns a container with one Rectangle (optionally assert NaN x/height).
  2. ax.bar([np.nan], [0]) does not raise (optionally assert NaN x, zero height).
  3. ax.bar([0], [np.nan]) continues to work (optionally assert height is NaN).

Environment

  • Reproducible on Matplotlib 3.6.1 (Python 3.10, NumPy 1.22/1.23). Newer versions may not reproduce the regression.

Task reference: Task 268 — matplotlib__matplotlib-24149

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions