The solution is to place the matplotlib Axes objects returned by pd.DataFrame.hist() into a figure with the desired layout. Unfortunately, placing new Axes objects into an existing Figure is a bit involved.
GridSpec layout
Creating the layout you want is not too complicated using nested matplotlib.gridspec.GridSpecs (see here for an example). Something like this.
import matplotlib.gridspec as gs
num_outer_columns = 2
num_outer_rows = 2
num_inner_columns = 2
num_inner_rows = 3
outer_layout = gs.GridSpec(num_outer_rows, num_outer_columns)
inner_layout = []
for row_num in range(num_outer_rows):
inner_layout.append([])
for col_num in range(num_outer_columns):
inner_layout[row_num].append(
gs.GridSpecFromSubplotSpec(
num_inner_rows,
num_inner_columns,
outer_layout[row_num, col_num]
)
)
You can create subplots within this grid using ax = plt.subplot(inner_layout[outer_row_num][outer_col_num][inner_row_num, inner_col_num]) and store the properly positioned axs for later.
Copying Axes into an existing Figure
Your df.hist() calls will produce something like this:
In [1]: dframe.hist(column=x, by=y)
Out[1]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x1189be160>,
<matplotlib.axes._subplots.AxesSubplot object at 0x118e3eb50>],
[<matplotlib.axes._subplots.AxesSubplot object at 0x118e74d60>,
<matplotlib.axes._subplots.AxesSubplot object at 0x118ea8340>],
[<matplotlib.axes._subplots.AxesSubplot object at 0x118e76d62>,
<matplotlib.axes._subplots.AxesSubplot object at 0x118ea9350>]],
dtype=object)
Now you just have to replace the ax objects positioned earlier using your inner_layout with the AxesSubplot objects returned above. Unfortunately there isn't a convenient ax.from_axes(other_ax) method to do this, so you'll have to copy the Axes returned by df.hist() over manually by following this answer.