A chart can communicate in one glance what a spreadsheet takes paragraphs to say. This chapter covers Matplotlib for precise, customizable plots and Seaborn for attractive statistical charts. The goal is not just to produce pictures, but to produce pictures that honestly and clearly tell the story of the data.
Matplotlib is the oldest and most flexible plotting library in Python. Its core idea is a figure (the whole canvas) containing one or more axes (the individual plots drawn on it).
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot([1, 2, 3, 4], [2, 4, 1, 5])
ax.set_title("A Simple Line")
ax.set_xlabel("Index")
ax.set_ylabel("Value")
plt.show()
import numpy as np
x = np.linspace(0, 10, 100)
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
axes[0, 0].plot(x, np.sin(x), color="purple") # line
axes[0, 0].set_title("Line")
axes[0, 1].bar(["A", "B", "C"], [3, 7, 5]) # bar
axes[0, 1].set_title("Bar")
rng = np.random.default_rng(42)
axes[1, 0].scatter(rng.normal(size=50), rng.normal(size=50)) # scatter
axes[1, 0].set_title("Scatter")
axes[1, 1].hist(rng.normal(size=500), bins=20) # histogram
axes[1, 1].set_title("Histogram")
plt.tight_layout()
plt.show()
fig, ax = plt.subplots()
ax.plot(x, np.sin(x), label="sin")
ax.plot(x, np.cos(x), label="cos")
ax.legend()
ax.set_xlabel("Angle (radians)")
ax.set_ylabel("Value")
ax.set_title("Sine and Cosine")
fig.savefig("wave.png", dpi=150, bbox_inches="tight")
Seaborn works directly with Pandas DataFrames and adds themes and high-level statistical plots. It imports as sns and sits on top of Matplotlib, so every Matplotlib trick still works.
import seaborn as sns
import pandas as pd
sns.set_theme(style="whitegrid")
data = pd.DataFrame({
"group": ["a"] * 50 + ["b"] * 50,
"value": list(range(50)) + list(range(50, 100))
})
histplot (the modern replacement for the older distplot) draws a histogram and can overlay a kernel density estimate.
sns.histplot(data["value"], bins=20, kde=True)
plt.show()
sns.boxplot(data=data, x="group", y="value")
plt.title("Values by Group")
plt.show()
sns.catplot(data=data, x="group", y="value", kind="box")
sns.countplot(x="group", data=data)
plt.show()
import numpy as np
wide = pd.DataFrame({
"a": np.random.default_rng(1).normal(size=100),
"b": np.random.default_rng(2).normal(size=100),
"c": np.random.default_rng(3).normal(size=100),
})
sns.pairplot(wide)
plt.show()
corr = wide.corr()
sns.heatmap(corr, annot=True, cmap="coolwarm")
plt.title("Correlation Matrix")
plt.show()
plt.tight_layout() so titles and labels never overlap between subplots.plt.subplots(figsize=(...)).ax.annotate or a simple ax.text rather than making readers hunt for it.dpi=150 or higher so charts stay sharp when embedded.Build a small end-to-end example that exercises everything above. We use the classic "tips" dataset that ships with Seaborn so the numbers are real.
tips = sns.load_dataset("tips")
print(tips.head())
fig, ax = plt.subplots(figsize=(8, 5))
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="day", ax=ax)
ax.set_title("Tip vs Total Bill by Day")
fig.savefig("tips_scatter.png", dpi=150)
plt.show()
sns.boxplot(data=tips, x="day", y="total_bill")
plt.show()
tips dataset, create a single figure with two subplots side by side: on the left a histogram of total_bill with 25 bins and a KDE overlay, and on the right a heatmap of the correlation between the numeric columns total_bill, tip, and size. Give every subplot a clear title and axis labels, then save the figure to tips_analysis.png. Bonus: add a caption under the figure stating, in one sentence, the relationship the heatmap reveals.