Repository: matplotlib/matplotlib
Pull request: FIX: bar mixed units, allow ValueError as well
## PR Summary

Closes #13186

Follow up to #12903 where we try and do math first for `bar` (x+dx) before units conversion.  Pandas throws a `ValueError` for a previously working case.  This adds that to the allowable `excepts`...

## PR Checklist

- [ ] Has Pytest style unit tests
- [ ] Code is [Flake 8](http://flake8.pycqa.org/en/latest/) compliant
- [ ] New features are documented, with examples if plot related
- [ ] Documentation is sphinx and numpydoc compliant
- [ ] Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
- [ ] Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

<!--
Thank you so much for your PR!  To help us review your contribution, please
consider the following points:

- A development guide is available at https://matplotlib.org/devdocs/devel/index.html.

- Help with git and github is available at
  https://matplotlib.org/devel/gitwash/development_workflow.html.

- Do not create the PR out of master, but out of a separate branch.

- The PR title should summarize the changes, for example "Raise ValueError on
  non-numeric input to set_xlim".  Avoid non-descriptive titles such as
  "Addresses issue #8576".

- The summary should provide at least 1-2 sentences describing the pull request
  in detail (Why is this change required?  What problem does it solve?) and
  link to any relevant issues.

- If you are contributing fixes to docstrings, please pay attention to
  http://matplotlib.org/devel/documenting_mpl.html#formatting.  In particular,
  note the difference between using single backquotes, double backquotes, and
  asterisks in the markup.

We understand that PRs can sometimes be overwhelming, especially as the
reviews start coming in.  Please let us know if the reviews are unclear or
the recommended next step seems overly demanding, if you would like help in
addressing a reviewer's comments, or if you have been waiting too long to hear
back on your PR.
-->

Problem statement:
ax.bar throws when x axis is pandas datetime
<!--To help us understand and resolve your issue, please fill out the form to the best of your ability.-->
<!--You can feel free to delete the sections that do not apply.-->

### Bug report

**Bug summary**

ax.bar throws error when x-axis is datetime, behaviour has changed between current master (throws error) and version 3.0.2 (no error)

**Code for reproduction**

```python
import pandas as pd
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(20,10));
ax = fig.gca();

df = pd.DataFrame({'year':[2018,2018,2018],'month':[1,1,1], 'day':[1,2,3], 'value':[1,2,3]})
df['date'] = pd.to_datetime(df[['year', 'month', 'day']])

ax.bar(df['date'], df['value'], width=10,align='center')
```

**Actual outcome**

```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-12-500999fd6ea9> in <module>
      8 df['date'] = pd.to_datetime(df[['year', 'month', 'day']])
      9 
---> 10 ax.bar(df['date'], df['value'], width=10,align='center')

~/matplotlib/lib/matplotlib/__init__.py in inner(ax, data, *args, **kwargs)
   1515     def inner(ax, *args, data=None, **kwargs):
   1516         if data is None:
-> 1517             return func(ax, *map(sanitize_sequence, args), **kwargs)
   1518 
   1519         bound = new_sig.bind(ax, *args, **kwargs)

~/matplotlib/lib/matplotlib/axes/_axes.py in bar(self, x, height, width, bottom, align, **kwargs)
   2230             x0 = x
   2231             x = np.asarray(self.convert_xunits(x))
-> 2232             width = self._convert_dx(width, x0, x, self.convert_xunits)
   2233             if xerr is not None:
   2234                 xerr = self._convert_dx(xerr, x0, x, self.convert_xunits)

~/matplotlib/lib/matplotlib/axes/_axes.py in _convert_dx(dx, x0, xconv, convert)
   2055                 dx = [dx]
   2056                 delist = True
-> 2057             dx = [convert(x0 + ddx) - x for ddx in dx]
   2058             if delist:
   2059                 dx = dx[0]

~/matplotlib/lib/matplotlib/axes/_axes.py in <listcomp>(.0)
   2055                 dx = [dx]
   2056                 delist = True
-> 2057             dx = [convert(x0 + ddx) - x for ddx in dx]
   2058             if delist:
   2059                 dx = dx[0]

pandas/_libs/tslibs/timestamps.pyx in pandas._libs.tslibs.timestamps._Timestamp.__add__()

ValueError: Cannot add integral value to Timestamp without freq.
```

**Expected outcome**

<!--A description of the expected outcome from the code snippet-->
<!--If this used to work in an earlier version of Matplotlib, please note the version it used to work on-->

**Matplotlib version**
built matplotlib from source

Hints from issue discussion:
That this worked before seems fortuitous, but not consistent with other dates.  What units are 10 supposed to be in this example?   I’m not clear on what pandas has for a timedelta, but that’s what we would expect for a date using other Datetimes with bar.  

Open to the idea that this behaviour still needs to be changed. I got talked into the current implementation, but maybe it’s wrong.   
Crossref #12903
? Are you sure that worked v3.0.2 w/o an error? It fails for me back to v2.2.3 (which is where I quit, because I would have to recompile)

```
Traceback (most recent call last):
  File "testPandas.py", line 10, in <module>
    ax.bar(df['date'], df['value'], width=10,align='center')
  File "/Users/jklymak/matplotlib/lib/matplotlib/__init__.py", line 1810, in inner
    return func(ax, *args, **kwargs)
  File "/Users/jklymak/matplotlib/lib/matplotlib/axes/_axes.py", line 2277, in bar
    left = x - width / 2
TypeError: ufunc subtract cannot use operands with types dtype('<M8[ns]') and dtype('float64')
```
@jklymak actually that specific example doesn't work on 3.0.2 - what did work then but fails now is slightly more complicated - I was grouping by my date column. I guess because the tpye of the grouped column is a `pandas.core.indexes.datetimes.DatetimeIndex` and perhaps that supports addition with a unit-less width?

```
import pandas as pd
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(20,10));
ax = fig.gca();

df = pd.DataFrame({'year':[2018,2018,2018],'month':[1,2,3], 'day':[1,2,3], 'value':[1,3,2]})
df['date'] = pd.to_datetime(df[['year', 'month', 'day']])
monthly = df[['date','value']].groupby(['date']).sum()

dates = monthly.index
colours = ['lightslategray','lightsteelblue','lightslategray']
forecast = monthly['value']
baseline = monthly['value']
ax.bar(dates, forecast, color = colours, width=10,align='center')
ax.plot(dates, baseline, color = 'orange',lw=4)
```
Given that the error is `ValueError: Cannot add integral value to Timestamp without freq.` I think pandas should *really* change that to a `TypeError`, but, see #13187 for a quick fix that seems to work.  
I suspect you really should be passing something more meaningful in as the width there, but since pandas allows it, we shouldn't break it.  
Yeah i agree - it's confusing that it appears to work in one case but not the other. Thanks
Lets leave open until we get our fix in.  #13187 will automatically close it...

Commit message: FIX: bar mixed units, allow ValueError as well
FIX: make exception in bar for convert(x+dx) more liberal

Patch to review:
diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index 1830ef5309fe..e4c3b0fa0237 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -2057,9 +2057,9 @@ def _convert_dx(dx, x0, xconv, convert):
             dx = [convert(x0 + ddx) - x for ddx in dx]
             if delist:
                 dx = dx[0]
-        except (TypeError, AttributeError) as e:
-            # but doesn't work for 'string' + float, so just
-            # see if the converter works on the float.
+        except Exception:
+            # if the above fails (for any reason) just fallback to what
+            # we do by default and convert dx by iteslf.
             dx = convert(dx)
         return dx
 


Code review comments:
diff_hunk: @@ -2057,7 +2057,7 @@ def _convert_dx(dx, x0, xconv, convert):
             dx = [convert(x0 + ddx) - x for ddx in dx]
             if delist:
                 dx = dx[0]
-        except (TypeError, AttributeError) as e:
+        except Exception:
path: lib/matplotlib/axes/_axes.py
text: @user1:
Doesn't that catch everything?

@user2:
In principle I'm ok with `except Exception:`, but for *targetted* code. That's a pretty big `try` block to be catching with `Exception`--a lot of unexpected behavior could happen in those lines. Is it possible to just wrap the try around `dx = [convert(x0 + ddx) - x for ddx in dx]`?