Repository: pandas-dev/pandas

Issue:
The layout of dataframe histograms should be customizable
Right now, the layout of the subplots for a dataframe's histogram is out of the control of the user, it picks the (very sane) default of trying to balance out the rows and columns.

This is great, but sometimes I want the histograms to all be in a row, or all in a column, or some combination for better presentation.



Discussion:

Resolving patch:
diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py
--- a/pandas/tools/plotting.py
+++ b/pandas/tools/plotting.py
@@ -1890,7 +1890,7 @@ def plot_group(group, ax):
 
 def hist_frame(data, column=None, by=None, grid=True, xlabelsize=None,
                xrot=None, ylabelsize=None, yrot=None, ax=None, sharex=False,
-               sharey=False, figsize=None, **kwds):
+               sharey=False, figsize=None, layout=None, **kwds):
     """
     Draw Histogram the DataFrame's series using matplotlib / pylab.
 
@@ -1916,6 +1916,7 @@ def hist_frame(data, column=None, by=None, grid=True, xlabelsize=None,
     sharey : bool, if True, the Y axis will be shared amongst all subplots.
     figsize : tuple
         The size of the figure to create in inches by default
+    layout: (optional) a tuple (rows, columns) for the layout of the histograms
     kwds : other plotting keyword arguments
         To be passed to hist function
     """
@@ -1943,12 +1944,21 @@ def hist_frame(data, column=None, by=None, grid=True, xlabelsize=None,
 
     import matplotlib.pyplot as plt
     n = len(data.columns)
-    rows, cols = 1, 1
-    while rows * cols < n:
-        if cols > rows:
-            rows += 1
-        else:
-            cols += 1
+
+    if layout is not None:
+        if not isinstance(layout, (tuple, list)) or len(layout) != 2:
+            raise ValueError('Layout must be a tuple of (rows, columns)')
+
+        rows, cols = layout
+        if rows * cols < n:
+            raise ValueError('Layout of %sx%s is incompatible with %s columns' % (rows, cols, n))
+    else:
+        rows, cols = 1, 1
+        while rows * cols < n:
+            if cols > rows:
+                rows += 1
+            else:
+                cols += 1
     fig, axes = _subplots(nrows=rows, ncols=cols, ax=ax, squeeze=False,
                           sharex=sharex, sharey=sharey, figsize=figsize)
 


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
@@ -611,6 +611,31 @@ def test_hist(self):
         # propagate attr exception from matplotlib.Axes.hist
         self.assertRaises(AttributeError, ser.hist, foo='bar')
 
+    @slow
+    def test_hist_layout(self):
+        import matplotlib.pyplot as plt
+        plt.close('all')
+        df = DataFrame(np.random.randn(100, 4))
+
+        layout_to_expected_size = (
+            {'layout': None, 'expected_size': (2, 2)},  # default is 2x2
+            {'layout': (2, 2), 'expected_size': (2, 2)},
+            {'layout': (4, 1), 'expected_size': (4, 1)},
+            {'layout': (1, 4), 'expected_size': (1, 4)},
+            {'layout': (3, 3), 'expected_size': (3, 3)},
+        )
+
+        for layout_test in layout_to_expected_size:
+            ax = df.hist(layout=layout_test['layout'])
+            self.assert_(len(ax) == layout_test['expected_size'][0])
+            self.assert_(len(ax[0]) == layout_test['expected_size'][1])
+
+        # layout too small for all 4 plots
+        self.assertRaises(ValueError, df.hist, layout=(1, 1))
+
+        # invalid format for layout
+        self.assertRaises(ValueError, df.hist, layout=(1,))
+
     @slow
     def test_scatter(self):
         _skip_if_no_scipy()