Repository: matplotlib/matplotlib

Issue:
[ENH]: Show parameter names in error message for mismatched array sizes in bar()
### Problem

I lost way too much time figuring out why some plotting code stopped working when I switched from vertical to horizontal bars. I eventually traced the problem down to this minimal repro that produces a very unhelpful error message from within numpy which it isn't even using directly:

```python
from matplotlib import pyplot as plt
arg_k = ["foo", "bar" "baz"]
arg_n = [1,2,3]
plt.barh(arg_k, arg_n)
plt.show()
```

```
ValueError: shape mismatch: objects cannot be broadcast to a single shape.  Mismatch is between arg 2 with shape (3,) and arg 3 
with shape (2,).
```

The problem is of course the missing comma in the `arg_k` array. In this simplified example the backtrace *does* eventually point at `plt.barh()`, but if the labels were set with `axis.set_yticklabels()` way before the plot call there is no useful context at all.

### Proposed solution

This seems like something that could be detected already in the `barh()` and other functions and reported immediately (with the appropriate argument names!) instead of requiring users to divine the actual problem from a long backtrace into 3rd party dependencies.


Discussion:
There's two topics here:

- `barh()` is only a thin wrapper for `bar(x=left, height=height, orientation="horizontal", ...)`. It leaves all the logic to `bar()` and just reassings names. This makes giving meaningful parameter names for `barh()` difficult. Side-note: We wouldn't introduce this function again nowadays (`bar(... orientation="horizontal")` is enough). Therefore, I'm reluctant to jump extra hoops to make names work here.
- `bar()` detects the mismatch as part of coersion of all parameters
  https://github.com/matplotlib/matplotlib/blob/9cc62f15a9e1f31cefc833c27a03d4c26f96dde2/lib/matplotlib/axes/_axes.py#L2567-L2569
  one could consider catching that and reraising a more specific message.


> but if the labels were set with `axis.set_ylabels()` way before the plot call there is no useful context at all.

Why would you do that? / If you do that we don't know of any relationship between the axis labels and the bars and thus cannot detect a possible inconsistency.
> one could consider catching that and reraising a more specific message

That sounds like a sensible approach then, yes

> Why would you do that?

Sorry, that should have been `axis.set_yticklabels()`. Anyway, the reason is grouped bars. In the previous (vertical) iteration of the plot I needed to aggressively group bars under the same label (like, 10 per label) and
```python
labels = ["foo", "bar", "baz"]
xticks = np.arange(len(labels))
axis.set_xticks(xticks)
axis.set_xticklabels(labels)

axis.bar(xticks-width/2, ...)
axis.bar(xticks, ...)
axis.bar(xticks+width/2, ...)
```
was the first solution I found for doing that. And when moving to the horizontal version I tried what seemed to be the most minimal code changes so I simply switched `axis.set_xticklabels()` to `axis.set_yticklabels()` and updated the label list, accidentally missing a comma in the process. (The new iteration still groups bars but now it was instead 10 groups with 2 bars each instead of 2 groups with 10 bars each)
@jansol you may be interested in #28560
**Good first issue:**

- put a try-except ValueError around 
  https://github.com/matplotlib/matplotlib/blob/9cc62f15a9e1f31cefc833c27a03d4c26f96dde2/lib/matplotlib/axes/_axes.py#L2567-L2569
- check wether it is a shape error "ValueError: shape mismatch: objects cannot be broadcast to a single shape."
- if so, replace the `arg N` parts in the message by the appropriate parameter names
- re-raise the exception

- Add a test that the message with appropriate parameter names is thrown
### Good first issue - notes for new contributors

This issue is suited to new contributors because it does not require understanding of the
Matplotlib internals. To get started, please see our [contributing
guide](https://matplotlib.org/stable/devel/index).

**We do not assign issues**. Check the *Development* section in the sidebar for linked pull
requests (PRs). If there are none, feel free to start working on it. If there is an open PR, please
collaborate on the work by reviewing it rather than duplicating it in a competing PR.

If something is unclear, please reach out on any of our [communication
channels](https://matplotlib.org/stable/devel/contributing.html#get-connected).
Hi @timhoffm can I work on it?
Go for it.
Hi @timhoffm after looking into the code I made the changes in the code as suggested by you, 

```
try:
            x, height, width, y, linewidth, hatch = np.broadcast_arrays(
                np.atleast_1d(x), height, width, y, linewidth, hatch
            )
except ValueError as e:
            arg_map = {
                "arg 0": "'x'",
                "arg 1": "'height'",
                "arg 2": "'width'",
                "arg 3": "'y'",
                "arg 4": "'linewidth'",
                "arg 5": "'hatch'"
            }
            error_message = str(e)
            for arg, name in arg_map.items():
                error_message = error_message.replace(arg, name)
            if "shape mismatch" in error_message:
                raise ValueError(
                    f"{error_message}"
                ) from e

```
and here is the error after modification,

```
File "/home/kaustubh/opensource/matplotlib/matplotlib2/matplotlib/lib/matplotlib/axes/_axes.py", line 2593, in bar
    raise ValueError(
ValueError: shape mismatch: objects cannot be broadcast to a single shape.  Mismatch is between 'width' with shape (3,) and 'y' with shape (2,).

```

Does this align with the expected behavior Please let me know?
Almost. You have to raise the original message again if it is not a "shape mismatch".

A note on way of working: Typically, you should not post solutions as code to issues. This should only be done if there's still a discussion going on and fleshing out all the details in a PR is not worthwhile. Since this topic here is quite clear, and you want to make a PR anyway, I encourage you to do the PR right away. It's simpler to review code in a PR than to comment on a code block in the discussion (for example the last lines should be simplified to `raise ValueError(error_message) from e` (no f-string, no spreading over three lines needed, but that's difficult to communicate here).
Okay, I will make a PR a very soon, thank you.



Patch:
diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index bb50664bab1d..e1ee1eb70370 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -2574,10 +2574,27 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
             height = self._convert_dx(height, y0, y, self.convert_yunits)
             if yerr is not None:
                 yerr = self._convert_dx(yerr, y0, y, self.convert_yunits)
-
-        x, height, width, y, linewidth, hatch = np.broadcast_arrays(
-            # Make args iterable too.
-            np.atleast_1d(x), height, width, y, linewidth, hatch)
+        try:
+            x, height, width, y, linewidth, hatch = np.broadcast_arrays(
+                # Make args iterable too.
+                np.atleast_1d(x), height, width, y, linewidth, hatch
+            )
+        except ValueError as e:
+            arg_map = {
+                "arg 0": "'x'",
+                "arg 1": "'height'",
+                "arg 2": "'width'",
+                "arg 3": "'y'",
+                "arg 4": "'linewidth'",
+                "arg 5": "'hatch'"
+            }
+            error_message = str(e)
+            for arg, name in arg_map.items():
+                error_message = error_message.replace(arg, name)
+            if error_message != str(e):
+                raise ValueError(error_message) from e
+            else:
+                raise
 
         # Now that units have been converted, set the tick locations.
         if orientation == 'vertical':


Test patch:
diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py
index 97abc750b9ef..62bdef8b35e3 100644
--- a/lib/matplotlib/tests/test_axes.py
+++ b/lib/matplotlib/tests/test_axes.py
@@ -9635,3 +9635,13 @@ def test_axes_set_position_external_bbox_unchanged(fig_test, fig_ref):
     ax_test.set_position([0.25, 0.25, 0.5, 0.5])
     assert (bbox.x0, bbox.y0, bbox.width, bbox.height) == (0.0, 0.0, 1.0, 1.0)
     ax_ref = fig_ref.add_axes([0.25, 0.25, 0.5, 0.5])
+
+
+def test_bar_shape_mismatch():
+    x = ["foo", "bar"]
+    height = [1, 2, 3]
+    error_message = (
+        r"Mismatch is between 'x' with shape \(2,\) and 'height' with shape \(3,\)"
+    )
+    with pytest.raises(ValueError, match=error_message):
+        plt.bar(x, height)