Skip to content

[Bug]: Axes.grouped_bar() with non-string orientation (e.g., NumPy array) raises ambiguous truth-value error instead of clean ValueError #30706

Description

@ilakkmanoharan

Bug summary

Passing a non-string value (like a NumPy array) to orientation in Axes.grouped_bar() triggers a misleading “ambiguous truth value” error from _api.check_in_list. The function should instead raise a clear ValueError stating the value is not valid.

Code for reproduction

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.grouped_bar([[1, 2, 3], [3, 2, 1]], orientation=np.array([1, 2, 3]))

Actual outcome

Axes.grouped_bar() fails inside _api.check_in_list() because NumPy arrays cannot be evaluated in a boolean context.
Instead of raising a clear validation error, it produces a misleading NumPy truth-value error, causing the test to fail.

Image

Expected outcome

After applying the fix, invalid inputs (including NumPy arrays) are handled gracefully, and all tests expecting a clear ValueError now pass.

Additional information

  1. Conditions under which this bug happens:

The bug occurs whenever the orientation parameter passed to Axes.grouped_bar() is not a string, such as:
orientation = np.array([1, 2, 3]) # NumPy array
orientation = 1 # integer
orientation = None # NoneType

These types trigger an internal truth-value evaluation inside _api.check_in_list, which expects a scalar comparable value (like a string).

  1. Edge cases affected:

NumPy arrays (np.array([...]))
Non-string types (integers, floats, None, lists, etc.)
Any custom object that doesn’t implement eq safely with strings

  1. Behavior in earlier versions:

This issue has likely existed since the introduction of Axes.grouped_bar() (Matplotlib 3.11, provisional API).
Other similar Matplotlib APIs (e.g., barh, stem, etc.) already validate string-type enums, so they are not affected.

  1. Root cause (why this happens):

The function directly calls:

_api.check_in_list(["vertical", "horizontal"], orientation=orientation)

When orientation is a NumPy array, the expression val not in values inside check_in_list() performs elementwise comparison, returning an array of booleans.

Python then attempts to interpret that array as a single truth value, which triggers NumPy’s error:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all().

  1. Proposed fix (confirmed working):

Add an early type guard before calling _api.check_in_list():

if not isinstance(orientation, str):
raise ValueError(f"{orientation!r} is not a valid value for orientation")
_api.check_in_list(["vertical", "horizontal"], orientation=orientation)

-- This ensures non-string inputs are rejected immediately and consistently,
-- prevents the ambiguous truth-value error, and
-- aligns grouped_bar() with Matplotlib’s standard API validation behavior.

Operating system

MacOS

Matplotlib Version

3.11.0.dev1446+g319295e28.d20251030

Matplotlib Backend

macosx

Python version

Python 3.13.7

Jupyter version

No response

Installation

pip

Activity

rcomer commented on Oct 30, 2025

@rcomer
Member

Integers and None should not have a problem

In [4]: check_in_list(['foo', 'bar'], wibble=None)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[4], line 1
----> 1 check_in_list(['foo', 'bar'], wibble=None)

File ~/miniforge3/envs/notebook-jul25/lib/python3.13/site-packages/matplotlib/_api/__init__.py:130, in check_in_list(values, _print_supported_values, **kwargs)
    128 if _print_supported_values:
    129     msg += f"; supported values are {', '.join(map(repr, values))}"
--> 130 raise ValueError(msg)

ValueError: None is not a valid value for wibble; supported values are 'foo', 'bar'

In [5]: check_in_list(['foo', 'bar'], wibble=1)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[5], line 1
----> 1 check_in_list(['foo', 'bar'], wibble=1)

File ~/miniforge3/envs/notebook-jul25/lib/python3.13/site-packages/matplotlib/_api/__init__.py:130, in check_in_list(values, _print_supported_values, **kwargs)
    128 if _print_supported_values:
    129     msg += f"; supported values are {', '.join(map(repr, values))}"
--> 130 raise ValueError(msg)

ValueError: 1 is not a valid value for wibble; supported values are 'foo', 'bar'

I don't think a numpy array would ever work in check_in_list. So if we want to do something about this, maybe there should be a type check in check_in_list itself.

timhoffm commented on Oct 31, 2025

@timhoffm
Member

Fundamentally, one could even consider why this raises at all for numpy arrays and whether numpy should care for this. Conceptually, I would have expected that np.array([1, 2]) in ["a", "b"] would just give False. That it’s not is somewhere in the implementation details between list.__contains__ and numpy.


Edit: The issue is that a in some_list essentially does

for elem in some_list:
    if elem == a:
        return True
return False

and == comparisons on numpy arrays don't work this way. So no, numpy can't fix this, and python list won't special case for objects that don't support == comparison to arbitrary other objects.

TL;DR: We have to handle this in _check_in_list.

timhoffm commented on Oct 31, 2025

@timhoffm
Member

@ilakkmanoharan a question on the organizational level: Have you used AI to generate the bug report? If so, how?

The background is that the content seems overly verbose and factually wrong in some details. I would like to understand how people write bug reports nowadays so that we can optimize our contribution guidelines and issue templates.

rcomer commented on Nov 1, 2025

@rcomer
Member

TL;DR: We have to handle this in _check_in_list.

OTOH, we have been using check_in_list for a long time in many places and so far no user has reported getting a confusing message because they accidentally passed a numpy array where they shouldn’t. If a failure mode is only discovered by an LLM agent that is actively looking for them, do we need to worry about it?

added 2 commits that reference this issue on Nov 1, 2025
c7e5066
90c089d

timhoffm commented on Nov 1, 2025

@timhoffm
Member

@rcomer I agree this is quite an edge case, which could be left alone. OTOH the fix is quite simple and does not incur relevant runtime overhead, so we can equally well handle this. I've done a small PR in #30714.

rcomer commented on Nov 1, 2025

@rcomer
Member

@timhoffm I think there may be a missing "not" in your most recent comment 👀

timhoffm commented on Nov 1, 2025

@timhoffm
Member

Right. Corrected. 😇

added a commit that references this issue on Nov 2, 2025
419eb3e
added this to the v3.10.8 milestone on Nov 3, 2025
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

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions