📊 Matplotlib & Seaborn · Débutant

Visualisation de données financières

⏱ 35 minutes🐍 Python 3.11📊 Matplotlib 3.9🎨 Seaborn 0.13

La visualisation est l'art de transformer des données brutes en insights compréhensibles. Ce tutoriel produit des graphiques professionnels pour des données financières — le type de charts qu'on retrouve dans des rapports d'analyse et des dashboards.

Setup et données

📖 Terme : Figure et Axes

Définition : En Matplotlib, une figure est la fenêtre globale qui contient tout. Les axes (ou subplots) sont les zones de graphique individuelles à l'intérieur de la figure. Vous pouvez avoir plusieurs axes dans une figure.

But : Organiser hiérarchiquement les graphiques — une figure contient un ou plusieurs axes.

Pourquoi ici : Comprendre la distinction figure/axes est crucial pour créer des layouts complexes. fig, (ax1, ax2) = plt.subplots(1, 2) crée une figure avec 2 axes côte à côte (1 ligne, 2 colonnes).

📖 Terme : Subplot

Définition : Un subplot (ou sous-graphique) est une zone de graphique individuelle dans une figure. plt.subplots(2, 1) crée une grille 2×1 (2 lignes, 1 colonne) avec 2 axes.

But : Créer des layouts multiples pour comparer plusieurs visualisations côte à côte ou en pile.

Pourquoi ici : Les subplots permettent de montrer plusieurs perspectives de données dans une seule figure — prix + volume, distribution + boxplot, etc. C'est plus informatif qu'un seul graphique.

setup.py
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import seaborn as sns
import pandas as pd
import numpy as np

# ── Style global ──
plt.style.use('seaborn-v0_8-darkgrid')
sns.set_theme(style='darkgrid', palette='husl')

COLORS = {
    'primary': '#2196F3',
    'success': '#4CAF50',
    'danger': '#F44336',
    'warning': '#FF9800',
    'dark': '#212121'
}

# ── Générer des données financières réalistes ──
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=252, freq='B')  # Jours ouvrés

# Simuler des prix avec mouvement brownien géométrique
returns = np.random.normal(0.0003, 0.015, 252)
prices = 100 * np.exp(np.cumsum(returns))

df = pd.DataFrame({
    'date': dates,
    'close': prices,
    'open': prices * (1 + np.random.normal(0, 0.003, 252)),
    'volume': np.random.randint(500000, 3000000, 252),
    'revenue': np.random.randint(50000, 200000, 252)
})
df['returns'] = df['close'].pct_change()
df['MA20'] = df['close'].rolling(20).mean()
df['MA50'] = df['close'].rolling(50).mean()
df = df.set_index('date')
📖 Terme : Mouvement brownien géométrique

Définition : Modèle mathématique des prix des actifs financiers. Les prix changent proportionnellement à eux-mêmes, et les rendements (log-retours) sont distribués normalement. Formule : prix = 100 * exp(cumsum(rendements)).

But : Simuler des prix d'actifs réalistes avec une tendance aléatoire et volatilité réaliste.

Pourquoi ici : C'est le modèle standard utilisé en finance (modèle Black-Scholes). Plus réaliste qu'une simple marche aléatoire — les prix ne peuvent pas être négatifs, et la volatilité augmente avec le prix (propriété du GBM).

chart_prix.py
# ── Figure avec 2 sous-graphiques (prix + volume) ──
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 8),
                                  gridspec_kw={'height_ratios': [3, 1]},
                                  sharex=True)
fig.suptitle('Analyse de cours — 2023', fontsize=16, fontweight='bold', y=0.98)

# ── Prix avec zones colorées (positif/négatif) ──
ax1.plot(df.index, df['close'], color=COLORS['primary'], linewidth=1.5, label='Prix', zorder=3)
ax1.plot(df.index, df['MA20'], color=COLORS['warning'], linewidth=1.2, label='MA20', linestyle='--')
ax1.plot(df.index, df['MA50'], color=COLORS['danger'], linewidth=1.2, label='MA50', linestyle='-.')

# Zone entre MA20 et MA50 (signal de croisement)
ax1.fill_between(df.index, df['MA20'], df['MA50'],
                  where=(df['MA20'] >= df['MA50']),
                  alpha=0.15, color=COLORS['success'], label='Tendance haussière')
ax1.fill_between(df.index, df['MA20'], df['MA50'],
                  where=(df['MA20'] < df['MA50']),
                  alpha=0.15, color=COLORS['danger'], label='Tendance baissière')

ax1.set_ylabel('Prix ($)', fontsize=12)
ax1.legend(loc='upper left')
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'${x:.0f}'))

# ── Volume en barres colorées ──
colors = [COLORS['success'] if r >= 0 else COLORS['danger'] for r in df['returns']]
ax2.bar(df.index, df['volume'] / 1e6, color=colors, alpha=0.7, width=0.8)
ax2.set_ylabel('Volume (M)', fontsize=10)

# Format des dates sur l'axe X
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))
ax2.xaxis.set_major_locator(mdates.MonthLocator())
plt.xticks(rotation=45)

plt.tight_layout()
plt.savefig('chart_financier.png', dpi=150, bbox_inches='tight')
plt.show()
Ce bloc crée une figure 2×1 avec deux sous-graphiques : prix (3x plus grand) + volume (1x). sharex=True aligne les axes X. fill_between colorise la zone entre deux lignes selon une condition : vert si MA20 > MA50 (tendance haussière), rouge sinon. Les barres de volume sont également colorées selon le rendement quotidien (vert si positif, rouge si négatif).

2. Distribution des rendements avec Seaborn

distribution.py
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# ── Histogramme + KDE ──
returns_clean = df['returns'].dropna() * 100
sns.histplot(returns_clean, bins=40, kde=True, ax=axes[0],
             color=COLORS['primary'], alpha=0.7)

# Lignes de référence statistiques
mean_r = returns_clean.mean()
std_r = returns_clean.std()
axes[0].axvline(mean_r, color='red', linestyle='--', label=f'Moyenne: {mean_r:.3f}%')
axes[0].axvline(mean_r - 2*std_r, color='orange', linestyle=':', label=f'VaR 95%: {mean_r-2*std_r:.2f}%')
axes[0].set_title('Distribution des rendements quotidiens', fontsize=13)
axes[0].set_xlabel('Rendement (%)')
axes[0].legend()

# ── Boxplot mensuel ──
df_monthly = df['returns'].dropna() * 100
monthly_data = df_monthly.groupby(df_monthly.index.month).apply(list)
months = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun',
          'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc']

box_data = [monthly_data[m] for m in sorted(monthly_data.index)]
bp = axes[1].boxplot(box_data, patch_artist=True, labels=months[:len(box_data)])

for patch, median in zip(bp['boxes'], bp['medians']):
    median_val = median.get_ydata()[0]
    patch.set_facecolor(COLORS['success'] if median_val >= 0 else COLORS['danger'])
    patch.set_alpha(0.7)

axes[1].axhline(y=0, color='black', linestyle='-', linewidth=0.8)
axes[1].set_title('Rendements mensuels — Boxplot', fontsize=13)
axes[1].set_ylabel('Rendement (%)')

plt.tight_layout()
plt.savefig('distribution_rendements.png', dpi=150, bbox_inches='tight')
plt.show()
📖 Terme : KDE (Kernel Density Estimate)

Définition : Technique d'estimation non-paramétrique de la densité de probabilité. Au lieu de barres fixes (histogramme), KDE lisse la distribution en utilisant des noyaux (kernels) Gaussiens autour de chaque point.

But : Visualiser la distribution lisse des données sans bruit d'histogramme.

Pourquoi ici : KDE vs Histogramme : l'histogramme montre les "bacs" (bins) discrets — le résultat dépend du nombre de bins. KDE est smooth et indépendant des bins — meilleur pour voir la vraie distribution.

La KDE curve (courbe lisse) montre la vraie distribution des rendements. Les lignes verticales de référence affichent la moyenne (rouge) et la Value-at-Risk 95% (orange) — importantes en finance pour quantifier le risque. Le boxplot en bas montre les rendements mois par mois : boîte = interquartile range (IQR), ligne = médiane, whiskers = outliers.
📖 Terme : Boxplot

Définition : Graphique qui résume la distribution d'une variable continue. La boîte s'étend du Q1 (25e percentile) au Q3 (75e percentile), avec une ligne au milieu (médiane/Q2). Les "whiskers" s'étendent à Q1-1.5×IQR et Q3+1.5×IQR, au-delà desquels les points sont outliers.

But : Visualiser la tendance centrale, la dispersion et les outliers d'une distribution.

Pourquoi ici : Boxplot est compact et comparatif — idéal pour montrer plusieurs distributions côte à côte (ici : par mois). L'IQR 50% montre rapidement où sont 50% des données.

3. Heatmap de corrélation

heatmap.py
# Simuler un portefeuille multi-actifs
assets = ['AAPL', 'GOOG', 'MSFT', 'TSLA', 'AMZN', 'META', 'NVDA', 'BTC']
portfolio = pd.DataFrame(
    np.random.randn(252, 8) @ np.linalg.cholesky(
        np.clip(np.random.randn(8, 8), -1, 1).T @ np.random.randn(8, 8) / 8 + np.eye(8) * 0.5,
    ).T,
    columns=assets
)

corr = portfolio.corr()

fig, ax = plt.subplots(figsize=(10, 8))
mask = np.triu(np.ones_like(corr, dtype=bool))  # Masquer le triangle supérieur
sns.heatmap(corr,
            mask=mask,
            annot=True, fmt='.2f',
            cmap='RdYlGn',  # Rouge (corrélation négative) → vert (positive)
            vmin=-1, vmax=1, center=0,
            square=True,
            linewidths=0.5,
            ax=ax)
ax.set_title('Matrice de corrélation du portefeuille', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.savefig('correlation_heatmap.png', dpi=150, bbox_inches='tight')
plt.show()
📖 Terme : Heatmap

Définition : Graphique qui affiche une matrice 2D avec des couleurs représentant les valeurs. Les couleurs vont généralement du bleu (faible) au rouge (élevé) ou du rouge (négatif) au vert (positif).

But : Visualiser des patterns et des magnitudes dans des données tabulaires 2D — idéal pour les matrices de corrélation, les tableaux croisés, etc.

Pourquoi ici : Une matrice de corrélation 8×8 est difficile à lire en chiffres bruts. La heatmap "RdYlGn" montre immédiatement : rouge = corrélation négative (assets inversement liés), vert = corrélation positive (assets liés).

La heatmap affiche la matrice de corrélation symétrique. Le masque élimine le triangle supérieur (données redondantes). Les annotations (fmt='.2f') affichent les valeurs exactes. Le colormap RdYlGn (Red-Yellow-Green) avec center=0 rend intuitif : rouge = corrélation négative (diversification), vert = corrélation positive (risque concentré).
Pour les rapports PDF ou présentations, exportez avec dpi=300 pour une qualité d'impression (sharpe, lisible même en zoom). Pour le web et le email, dpi=72 suffit et génère des fichiers plus légers. L'option bbox_inches='tight' élimine les marges blanches autour du graphique.