0%
Matplotlib: Creating Professional Visualizations in Python

Matplotlib: Creating Professional Visualizations in Python

A complete guide to Matplotlib for creating line plots, scatter plots, bar charts, and histograms in Python with the object-oriented interface, subplots, and export.

I

InSkillCoach

· min

Matplotlib: Creating Professional Visualizations in Python

Matplotlib is the long-standing, essential visualization library of the Python ecosystem. It lets you produce all the classic chart types (line plots, scatter plots, bar charts, histograms) with total control over every element of the figure, and export the result in formats suited to both the web and print. This guide covers the anatomy of a figure, the recommended object-oriented interface, the main chart types, and the customization techniques that make the difference between a rough draft of a chart and a professional visualization.

Anatomy of a Figure: Figure and Axes

Understanding two objects is enough to unlock the entire library:

  • The Figure is the overall container: the “sheet of paper” that holds everything else (size, resolution, background).
  • The Axes represent a plotting area: a coordinate system with its ticks, labels, and curves. A figure can contain one or more Axes.

Inside an Axes, you’ll find the x and y axes (xaxis, yaxis), the ticks, the labels, the title, the legend, and the artists (lines, points, bars).

import matplotlib.pyplot as plt
import numpy as np

# Une figure contenant un seul Axes
fig, ax = plt.subplots(figsize=(8, 5))

print(type(fig))   # <class 'matplotlib.figure.Figure'>
print(type(ax))    # <class 'matplotlib.axes._axes.Axes'>

pyplot Interface vs. Object-Oriented Interface

Matplotlib offers two coding styles:

x = np.linspace(0, 2 * np.pi, 100)

# Style pyplot (à la MATLAB) : un état global implicite
plt.plot(x, np.sin(x))
plt.title("Sinus")
plt.xlabel("x")
plt.show()

# Style orienté objet : on manipule explicitement fig et ax
fig, ax = plt.subplots()
ax.plot(x, np.sin(x))
ax.set_title("Sinus")
ax.set_xlabel("x")
plt.show()

Recommendation: adopt the object-oriented interface. The pyplot style is fine for a quick one-line plot, but as soon as a figure contains several subplots or is built inside a function, pyplot’s global state becomes a source of bugs. The object-oriented interface makes it explicit which Axes receives which command, and it is the style used in the modern official documentation. All the examples in this guide use it.

Line Plots with plot

x = np.linspace(0, 10, 200)

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, np.sin(x), label="sin(x)", color="tab:blue", linewidth=2)
ax.plot(x, np.cos(x), label="cos(x)", color="tab:orange",
        linestyle="--", linewidth=2)
ax.plot(x[::20], np.sin(x[::20]), "o", color="tab:blue",
        markersize=6, label="points mesurés")

ax.set_title("Fonctions trigonométriques")
ax.set_xlabel("x (radians)")
ax.set_ylabel("Amplitude")
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()

Common line styles: "-" (solid), "--" (dashed), ":" (dotted), "-." (dash-dot). Markers: "o" (circle), "s" (square), "^" (triangle), "+", "x".

Scatter Plots with scatter

scatter lets you vary the size and color of each point, which encodes up to four variables in a single chart.

rng = np.random.default_rng(42)
x = rng.random(80)
y = 2 * x + rng.normal(0, 0.2, 80)
tailles = rng.integers(20, 200, 80)
couleurs = y - 2 * x                     # écart au modèle

fig, ax = plt.subplots(figsize=(8, 5))
nuage = ax.scatter(x, y, s=tailles, c=couleurs, cmap="viridis",
                   alpha=0.7, edgecolors="black", linewidths=0.5)

fig.colorbar(nuage, ax=ax, label="Résidu")
ax.set_title("Nuage de points avec taille et couleur variables")
ax.set_xlabel("x")
ax.set_ylabel("y")
plt.show()

Bar Charts

langages = ["Python", "SQL", "JavaScript", "Rust"]
popularite = [48, 35, 30, 12]

fig, ax = plt.subplots(figsize=(8, 5))
barres = ax.bar(langages, popularite, color="tab:blue", edgecolor="black")

# Étiqueter chaque barre avec sa valeur
ax.bar_label(barres, fmt="%d %%")

ax.set_title("Popularité des langages (exemple)")
ax.set_ylabel("Part des répondants (%)")
ax.set_ylim(0, 55)
plt.show()

# Barres horizontales : plus lisibles quand les catégories ont des noms longs
fig, ax = plt.subplots(figsize=(8, 4))
ax.barh(langages, popularite, color="tab:green")
ax.set_xlabel("Part des répondants (%)")
plt.show()

For grouped bars, shift the positions with a numeric offset:

positions = np.arange(len(langages))
largeur = 0.35
annee_1 = [45, 33, 32, 8]
annee_2 = [48, 35, 30, 12]

fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(positions - largeur / 2, annee_1, largeur, label="2025")
ax.bar(positions + largeur / 2, annee_2, largeur, label="2026")
ax.set_xticks(positions)
ax.set_xticklabels(langages)
ax.legend()
plt.show()

Histograms

A histogram visualizes the distribution of a continuous variable.

rng = np.random.default_rng(0)
donnees = rng.normal(loc=170, scale=8, size=1000)   # tailles simulées

fig, ax = plt.subplots(figsize=(8, 5))
valeurs, bords, patches = ax.hist(donnees, bins=30, color="tab:blue",
                                  edgecolor="white", alpha=0.85)

ax.axvline(donnees.mean(), color="red", linestyle="--",
           label=f"Moyenne = {donnees.mean():.1f} cm")
ax.set_title("Distribution des tailles")
ax.set_xlabel("Taille (cm)")
ax.set_ylabel("Effectif")
ax.legend()
plt.show()

# density=True normalise pour comparer à une densité de probabilité
# bins accepte un entier, une liste de bords, ou "auto"

Pie Charts

Use them sparingly (beyond 5 categories, prefer horizontal bars):

parts = [45, 25, 20, 10]
etiquettes = ["Cloud", "Data", "Web", "Autres"]

fig, ax = plt.subplots(figsize=(6, 6))
ax.pie(parts, labels=etiquettes, autopct="%1.1f%%",
       startangle=90, explode=(0.05, 0, 0, 0),
       colors=["tab:blue", "tab:orange", "tab:green", "tab:gray"])
ax.set_title("Répartition du chiffre d'affaires")
plt.show()
# autopct affiche les pourcentages, explode détache la première part

Subplots and Chart Grids

plt.subplots(rows, columns) creates a grid of Axes in a single call.

x = np.linspace(0, 2 * np.pi, 100)

fig, axes = plt.subplots(2, 2, figsize=(10, 8))
# axes est un tableau NumPy 2D d'objets Axes

axes[0, 0].plot(x, np.sin(x))
axes[0, 0].set_title("sin(x)")

axes[0, 1].plot(x, np.cos(x), color="tab:orange")
axes[0, 1].set_title("cos(x)")

axes[1, 0].plot(x, np.sin(2 * x), color="tab:green")
axes[1, 0].set_title("sin(2x)")

axes[1, 1].plot(x, np.exp(-x) * np.sin(5 * x), color="tab:red")
axes[1, 1].set_title("Oscillation amortie")

fig.suptitle("Grille de quatre graphiques", fontsize=14)
fig.tight_layout()      # ajuste les espacements pour éviter les chevauchements
plt.show()

# Axes partagés : pratique pour comparer des séries
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(8, 6))

For irregular layouts (one wide chart on top, two small ones below), explore fig.add_gridspec or plt.subplot_mosaic.

Customization: Titles, Legends, Colors, Styles

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, np.sin(x), label="signal")

# Titres et étiquettes avec contrôle typographique
ax.set_title("Titre principal", fontsize=14, fontweight="bold")
ax.set_xlabel("Temps (s)", fontsize=11)
ax.set_ylabel("Amplitude (V)", fontsize=11)

# Légende : position et habillage
ax.legend(loc="upper right", frameon=True, shadow=False)

# Limites et graduations
ax.set_xlim(0, 2 * np.pi)
ax.set_xticks([0, np.pi, 2 * np.pi])
ax.set_xticklabels(["0", "π", "2π"])

# Grille discrète
ax.grid(True, linestyle=":", alpha=0.5)

# Supprimer les bordures superflues (style épuré)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
plt.show()

Style sheets let you change the overall appearance in a single line:

print(plt.style.available[:5])
# ['Solarize_Light2', '_classic_test_patch', '_mpl-gallery', '_mpl-gallery-nogrid', 'bmh']

plt.style.use("seaborn-v0_8-whitegrid")   # style sobre adapté aux rapports

# Ou localement, sans modifier l'état global :
with plt.style.context("ggplot"):
    fig, ax = plt.subplots()
    ax.plot(x, np.sin(x))

For colors, prefer the named tab: palette (tab:blue, tab:orange, …): it is designed to be distinct and readable, including, to a large extent, for people with color blindness.

Annotations

x = np.linspace(0, 10, 200)
y = np.exp(-x / 3) * np.sin(2 * x)

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, y)

# Texte simple positionné en coordonnées des données
ax.text(6, 0.4, "Zone amortie", fontsize=11, color="gray")

# Annotation avec flèche vers un point précis
i_max = y.argmax()
ax.annotate(f"Maximum ({x[i_max]:.2f}, {y[i_max]:.2f})",
            xy=(x[i_max], y[i_max]),          # pointe de la flèche
            xytext=(3, 0.8),                  # position du texte
            arrowprops=dict(arrowstyle="->", color="black"))

# Lignes de repère
ax.axhline(0, color="black", linewidth=0.8)
ax.axvline(x[i_max], color="red", linestyle=":", alpha=0.6)
plt.show()

Logarithmic Scales

Essential whenever the data spans several orders of magnitude.

n = np.logspace(1, 6, 50)          # de 10 à 1 000 000
temps_lineaire = n * 1e-6
temps_quadratique = n ** 2 * 1e-9

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(n, temps_lineaire, label="O(n)")
ax.plot(n, temps_quadratique, label="O(n²)")

ax.set_xscale("log")
ax.set_yscale("log")
ax.set_xlabel("Taille de l'entrée n")
ax.set_ylabel("Temps (s)")
ax.set_title("Complexités comparées (échelle log-log)")
ax.legend()
ax.grid(True, which="both", alpha=0.3)   # grille sur graduations majeures et mineures
plt.show()

# Raccourcis : ax.semilogy(...), ax.semilogx(...), ax.loglog(...)

Saving with savefig

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, np.sin(x))

# PNG haute résolution pour le web ou les slides
fig.savefig("graphique.png", dpi=200, bbox_inches="tight")

# SVG ou PDF : vectoriels, parfaits pour l'impression et les articles
fig.savefig("graphique.svg", bbox_inches="tight")
fig.savefig("graphique.pdf", bbox_inches="tight")

# Fond transparent pour intégration dans des slides
fig.savefig("graphique_transparent.png", dpi=200, transparent=True)

Two parameters to remember: dpi controls the resolution of bitmap formats (150 to 300 for a sharp result), and bbox_inches="tight" removes excess white margins. Call savefig before plt.show(), because show may clear the figure in some environments.

Integration with Pandas

DataFrames expose a .plot method that delegates to Matplotlib and returns an Axes object, which you can then customize as usual.

import pandas as pd

df = pd.DataFrame({
    "mois": pd.date_range("2026-01-01", periods=6, freq="MS"),
    "ventes": [120, 135, 150, 145, 170, 190],
    "couts": [100, 110, 118, 120, 130, 140],
}).set_index("mois")

# Le DataFrame trace directement sur un Axes existant
fig, ax = plt.subplots(figsize=(8, 5))
df.plot(ax=ax, marker="o")

ax.set_title("Ventes et coûts mensuels")
ax.set_ylabel("k€")
ax.legend(["Ventes", "Coûts"])
plt.show()

# Autres raccourcis : df.plot.bar(), df.plot.hist(), df.plot.scatter(x=..., y=...)

A Word on Seaborn

Seaborn is a statistics-oriented layer on top of Matplotlib: in a single line, it produces charts that would take dozens of lines of pure Matplotlib (box plots by category, correlation matrices, regressions with confidence intervals).

import seaborn as sns

tips = sns.load_dataset("tips")

fig, ax = plt.subplots(figsize=(8, 5))
sns.boxplot(data=tips, x="day", y="total_bill", hue="sex", ax=ax)
ax.set_title("Distribution des additions par jour")
plt.show()

Since Seaborn returns Matplotlib objects, everything you’ve learned in this guide (Axes, titles, savefig) still applies. In practice, many analysts use Seaborn for quick exploration and pure Matplotlib for final, finely controlled figures.

Best Practices

  • Use the object-oriented interface (fig, ax = plt.subplots()) everywhere: it is explicit, composable, and avoids global-state bugs.
  • Label everything: a chart with no title, no units on the axes, and no legend is unusable outside the context where it was created.
  • Call fig.tight_layout() (or create the figure with layout="constrained") to avoid overlapping text.
  • Choose the right chart type: line plot for a trend over time, bars for categories, histogram for a distribution, scatter plot for a relationship.
  • Export to a vector format (SVG, PDF) for print and documents, and to PNG with dpi>=150 for the web.
  • Limit visual clutter: remove unnecessary borders, soften the grid with alpha, and reserve bright colors for the important information.
  • Set the random seed in examples and demos to get reproducible figures.

Conclusion

Matplotlib is built on a simple model, the Figure containing Axes, which the object-oriented interface lets you manipulate precisely. With the handful of chart types covered here (line plots, scatter plots, bars, histograms), subplots, and the customization tools, you cover nearly all data analysis visualization needs. Native integration with Pandas smooths the path from data to chart, and Seaborn speeds up statistical exploration. To go further, the official Matplotlib gallery is a valuable resource: every figure comes with its complete source code.

InSkillCoach

About InSkillCoach

Training and technology expert

Coach specializing in advanced technologies and AI, backed by GNeurone Inc.

Certifications:

  • AWS Certified Solutions Architect – Professional
  • Google Cloud certifications
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
1.4k
56

Comments

Comments are powered by GitHub Discussions

Sign in with GitHub to join the discussion

Link copied!