Repository: pandas-dev/pandas
Pull request: TST: DataFrame.hist() does not get along with matplotlib.pyplot.tight_layout()
 - [ ] closes #9351
 - [ ] tests added / passed
 - [ ] passes ``git diff upstream/master | flake8 --diff``
 - [ ] whatsnew entry


Problem statement:
DataFrame.hist() does not get along with matplotlib.pyplot.tight_layout()
When `tight_layout()` is called after `DataFrame.hist()` it raises `AttributeError: 'NoneType' object has no attribute 'is_bbox` if any of subplots is empty (number of charts with histograms is smaller than number os subplots).

I.e. the following code:

```
pd.DataFrame({'a':np.random.randn(100),
              'b':np.random.randn(100),
              }).hist()
plt.tight_layout()
```

works fine, but this code:

```
pd.DataFrame({'a':np.random.randn(100),
              'b':np.random.randn(100),
              'c':np.random.randn(100),
              }).hist()
plt.tight_layout()
```

raises `AttributeError`.

My versions:
pandas: 0.15.2
numpy: 1.9.1
scipy: 0.14.1
IPython: 2.3.1
matplotlib: 1.4.2

Hints from issue discussion:
Ditto here, I can't seem to figure out what's going on.

Would either of you want to dig into what's causing the problem?

It seems that

``` python
    129         tight_bbox_raw = union([ax.get_tightbbox(renderer) for ax in subplots])
```

in `matplotlib.tight_layout.auto_adjust_subplotpars` returns None when it's expected to return a `BBox`.

Actually, just before the error is raised, `subplots` is a list of one `AxesSubplot` instance for which `get_visible()` returns `False`. `ax.get_tightbbox()` returns `None` for invisible axes and `auto_adjust_subplotpars()` pass this `None` to `TransformedBbox()`, which expects a real `Bbox` and raises the exception.

Looks like a matplotlib issue to me, or maybe pandas should not be creating that invisible `Axes`.

A quick workaround would be to add something like this:

```
if tight_bbox_raw is None:
    continue
```

But it might have side effects.

+1

As of `matplotlib 2.0` this now works as expected.
@ResidentMario can you add a test to confirm this behavior (that skips if mpl < 2.0)?

Commit message: Add unit test for #9351

Patch to review:
diff --git a/pandas/tests/plotting/test_hist_method.py b/pandas/tests/plotting/test_hist_method.py
index 4f64f66bd3c4d..54538ba358812 100644
--- a/pandas/tests/plotting/test_hist_method.py
+++ b/pandas/tests/plotting/test_hist_method.py
@@ -238,6 +238,20 @@ def test_hist_layout(self):
         with tm.assertRaises(ValueError):
             df.hist(layout=(-1, -1))
 
+    @slow
+    def test_tight_layout(self):
+        tm._skip_if_mpl_1()
+
+        df = DataFrame(randn(100, 2))
+        df.plot.hist()
+
+        try:
+            self.plt.tight_layout()
+        except AttributeError:
+            raise
+
+        tm.close()
+
 
 @tm.mplskip
 class TestDataFrameGroupByPlots(TestPlotBase):
diff --git a/pandas/util/testing.py b/pandas/util/testing.py
index 1bd539469dbe3..cf0ddf184dcf6 100644
--- a/pandas/util/testing.py
+++ b/pandas/util/testing.py
@@ -286,6 +286,14 @@ def _skip_if_mpl_1_5():
         pytest.skip("matplotlib 1.5")
 
 
+def _skip_if_mpl_1():
+    import matplotlib
+    v = matplotlib.__version__
+    if v < LooseVersion('2.0.0') or v[0] == '0' or v[0] == '1':
+        import pytest
+        pytest.skip("matplotlib 1.5")
+
+
 def _skip_if_no_scipy():
     try:
         import scipy.stats  # noqa


Code review comments:
diff_hunk: @@ -238,6 +238,20 @@ def test_hist_layout(self):
         with tm.assertRaises(ValueError):
             df.hist(layout=(-1, -1))
 
+    @slow
+    def test_tight_layout(self):
+        tm._skip_if_mpl_1()
+
+        df = DataFrame(randn(100, 2))
+        df.plot.hist()
+
+        try:
+            self.plt.tight_layout()
path: pandas/tests/plotting/test_hist_method.py
text: @user1:
you don't need the try/except (if it works it won't raise)

add a 1-line comment & the issue number as a comment

diff_hunk: @@ -286,6 +286,14 @@ def _skip_if_mpl_1_5():
         pytest.skip("matplotlib 1.5")
 
 
+def _skip_if_mpl_1():
+    import matplotlib
path: pandas/util/testing.py
text: @user1:
actually you don't need this, instead use:

``
if self.mpl_ge_2_0_0:
``

as these are defined in the base class (you can look in ``pandas.tools.plotting``

@author:
`self.mpl_get_2_0_0` I assume? Edit: nvm, I see it is indeed `ge`. What does that stand for?

@user1:
no take this function out

diff_hunk: @@ -286,6 +286,14 @@ def _skip_if_mpl_1_5():
         pytest.skip("matplotlib 1.5")
 
 
+def _skip_if_mpl_1():
+    import matplotlib
+    v = matplotlib.__version__
+    if v < LooseVersion('2.0.0') or v[0] == '0' or v[0] == '1':
+        import pytest
+        pytest.skip("matplotlib 1.5")
path: pandas/util/testing.py
text: @user1:
matpotlib < 2.0.0