Subplots & styling
One chart answers one question. A review deck usually needs three or four related charts side by side, in a consistent style, exported at the right size. That is what subplots and styling are for.
Many panels in one figure
plt.subplots(nrows, ncols) returns the Figure and an array of Axes.
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].hist(df["age"], bins=20, color="#4C78A8", edgecolor="white")
axes[0].set_title("Age distribution")
axes[0].set_xlabel("Age (years)")
counts = df["city"].value_counts().head(6)
axes[1].bar(counts.index, counts.values, color="#F58518")
axes[1].set_title("Customers by city")
axes[1].tick_params(axis="x", rotation=45)
fig.suptitle("Customer profile — Q2 FY26", fontsize=14)
fig.tight_layout()
fig.savefig("profile.png", dpi=200, bbox_inches="tight")
With more than one row, axes is 2-D: use axes[0, 1], or loop over axes.flat when you build one panel per category.
fig, axes = plt.subplots(2, 3, figsize=(12, 6), sharey=True)
for ax, (city, part) in zip(axes.flat, df.groupby("city")):
ax.plot(part["month"], part["sales"])
ax.set_title(city)
for ax in axes.flat[len(df["city"].unique()):]:
ax.set_visible(False) # hide leftover empty panels
sharey=True forces one common y scale, which is what makes small multiples comparable. Without it, a city selling ₹2 lakh and one selling ₹40 lakh look identical.
Styling once, not chart by chart
plt.style.use("seaborn-v0_8-whitegrid")
plt.rcParams.update({
"figure.dpi": 110,
"font.size": 11,
"axes.titlesize": 13,
"axes.spines.top": False,
"axes.spines.right": False,
})
plt.style.use() picks a whole look; rcParams overrides individual defaults for every chart in the session. Keep this block in one module so every analyst in the team produces charts that match. Per-chart tweaks still work:
ax.spines[["top", "right"]].set_visible(False)
ax.legend(title="Store", frameon=False, loc="upper left")
ax.set_ylim(0, None) # bars and areas must start at zero
Saving for different destinations
| Destination | Call | Why |
|---|---|---|
| Slide or Word report | savefig("x.png", dpi=200, bbox_inches="tight") |
Sharp raster, no clipped labels |
| Website | savefig("x.svg") |
Vector, stays crisp at any zoom |
| Print / journal | savefig("x.pdf") |
Vector with embedded fonts |
| Dark slide | savefig("x.png", transparent=True) |
No white box around the chart |
In a loop, close figures with plt.close(fig) so memory does not creep up when you export 200 store charts.
Key takeaways
fig, axes = plt.subplots(r, c)first; then treat each Axes as its own little chart.sharex/shareymake panels comparable;fig.suptitlegives the group one headline.- Set style globally with
rcParams, not by repeating colour arguments everywhere. - Always
bbox_inches="tight"when saving, and save beforeplt.show().
Practice
Take a sales CSV with columns month, store, sales, footfall. Set a house style with rcParams, then build a 2x2 figure: chain trend, sales by store, footfall histogram, and footfall vs sales scatter. Share the y-axis where it makes sense, add a suptitle, and export the figure as both PNG (200 dpi) and SVG.