cols = ['Column {}'.format(col) for col in range(1, 4)]
rows = ['Row {}'.format(row) for row in ['A', 'B', 'C', 'D']]
fig, axes = plt.subplots(nrows=4, ncols=3, figsize=(12, 8))
plt.setp(axes.flat, xlabel='X-label', ylabel='Y-label')
pad = 5
for ax, col in zip(axes[0], cols):
    ax.annotate(col, xy=(0.5, 1), xytext=(0, pad), xycoords='axes fraction',
        textcoords='offset points', size='large', ha='center', va='baseline')
for ax, row in zip(axes[:, (0)], rows):
    ax.annotate(row, xy=(0, 0.5), xytext=(-ax.yaxis.labelpad - pad, 0),
        xycoords=ax.yaxis.label, textcoords='offset points', size='large',
        ha='right', va='center')
fig.tight_layout()
fig.subplots_adjust(left=0.15, top=0.95)
plt.show()
Row and column headers in matplotlib's subplots