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.
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).
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.
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')
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).
# ── 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()
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).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()
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.
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.
# 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()
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).
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.