Repository: pandas-dev/pandas

Issue:
BUG: boxplot does not set limits of axis
`DataFrame.boxplot` fails to properly set the limits of the y-axis is this example:

``` python
df = pd.DataFrame({'X': [1, 0, 1, 0],
                   'Y': [1, 0, 1, 0],
                   'Z': [1, 0, 1, 0],
                   #'M': [1, 0, 1, 0],
                   'B': list("XXYY")}, 
                  index=list("UUVV"))

print df
"""
   B  X  Y  Z
U  X  1  1  1
U  X  0  0  0
V  Y  1  1  1
V  Y  0  0  0
"""

df.boxplot(by='B')
```

I would expect the y-axis to go from 0 to 1, not -0.06 to 0.06.
If I uncomment the `M` it works.

I use pandas 0.12.0 and matplotlib 1.3.1

![Uploading boxplot.png . . .]()



Discussion:
Attaching the image did not work as I expected. The image is here:
http://nbviewer.ipython.org/7475835

Note that it also works when you comment both `M` and `Z` eg (so you only have two columns). So it seems it has something to do with the fourth empty ax.

EDIT: It's not really a pandas bug (but could maybe solved in pandas), but it also happens if you do it yourself manually in matplotlib. Due to the shared axes, there goes something wrong with the limits when there is an empty subplot. 

It seems this should be fixed in matplotlib master: https://github.com/matplotlib/matplotlib/issues/2356 and https://github.com/matplotlib/matplotlib/pull/2357

I can confirm that this is fixed in matplotlib master.

is this still a bug? or just in matplotlib? anything to do?

It's a bug in matplotlib, which it is fixed in matplotlib master, and not a real pandas bug. But it would be possible to workaround it in pandas (as it produces a wrong boxplot by only using pandas code). But I do not know how easy and/or necessary this would be.

ok....prob by the time 0.14 comes out, will just close this


Resolving patch:
diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py
--- a/pandas/tools/plotting.py
+++ b/pandas/tools/plotting.py
@@ -3174,7 +3174,14 @@ def _subplots(naxes=None, sharex=False, sharey=False, squeeze=True,
     # Note off-by-one counting because add_subplot uses the MATLAB 1-based
     # convention.
     for i in range(1, nplots):
-        ax = fig.add_subplot(nrows, ncols, i + 1, **subplot_kw)
+        kwds = subplot_kw.copy()
+        # Set sharex and sharey to None for blank/dummy axes, these can
+        # interfere with proper axis limits on the visible axes if
+        # they share axes e.g. issue #7528
+        if i >= naxes:
+            kwds['sharex'] = None
+            kwds['sharey'] = None
+        ax = fig.add_subplot(nrows, ncols, i + 1, **kwds)
         axarr[i] = ax
 
     if nplots > 1:


Test patch:
diff --git a/pandas/tests/test_graphics.py b/pandas/tests/test_graphics.py
--- a/pandas/tests/test_graphics.py
+++ b/pandas/tests/test_graphics.py
@@ -1830,6 +1830,34 @@ def test_boxplot_return_type_legacy(self):
             result = df.boxplot(return_type='both')
         self._check_box_return_type(result, 'both')
 
+    @slow
+    def test_boxplot_axis_limits(self):
+
+        def _check_ax_limits(col, ax):
+            y_min, y_max = ax.get_ylim()
+            self.assertLessEqual(y_min, col.min())
+            self.assertGreaterEqual(y_max, col.max())
+
+        df = self.hist_df.copy()
+        df['age'] = np.random.randint(1, 20, df.shape[0])
+        # One full row
+        height_ax, weight_ax = df.boxplot(['height', 'weight'], by='category')
+        _check_ax_limits(df['height'], height_ax)
+        _check_ax_limits(df['weight'], weight_ax)
+        self.assertEqual(weight_ax._sharey, height_ax)
+
+        # Two rows, one partial
+        p = df.boxplot(['height', 'weight', 'age'], by='category')
+        height_ax, weight_ax, age_ax = p[0, 0], p[0, 1], p[1, 0]
+        dummy_ax = p[1, 1]
+        _check_ax_limits(df['height'], height_ax)
+        _check_ax_limits(df['weight'], weight_ax)
+        _check_ax_limits(df['age'], age_ax)
+        self.assertEqual(weight_ax._sharey, height_ax)
+        self.assertEqual(age_ax._sharey, height_ax)
+        self.assertIsNone(dummy_ax._sharey)
+
+
     @slow
     def test_kde_df(self):
         tm._skip_if_no_scipy()