「分散投資はリスクを下げる」とよく言われますが、その効き目は 銘柄間の相関 に左右されます。本記事では、複数銘柄の日次リターンから相関行列を計算し、seaborn のヒートマップで全体像を確認するまでを Python で整理します。
目次
- なぜ相関を見るのか
- 必要なライブラリ
- サンプルデータの準備
- 相関行列の計算
- ヒートマップで可視化
- 上三角だけを描く
- クラスタリングで並び替える
- 期間で変わる相関
なぜ相関を見るのか
ポートフォリオの分散 は、構成銘柄の分散と相関で決まります。等加重で 2 銘柄を持つ場合の例で書くと、
が小さい(あるいは負)ほど、ポートフォリオの分散は小さくなります。逆に なら、銘柄を増やしてもリスクは下がりません。「同じセクターばかりに分散しても効果が薄い」という直感は、この式から読み取れます。
必要なライブラリ
pip install pandas numpy matplotlib seabornseaborn は matplotlib の上に乗った可視化ライブラリです。ヒートマップが 1 行で書けるため、相関行列の可視化に向きます。
サンプルデータの準備
5 銘柄の日次対数リターンを、相関のある多変量正規分布から生成します(実データを使う場合は J-Quants の終値から対数リターンに直したものを使います)。
import numpy as npimport pandas as pd
rng = np.random.default_rng(seed=33)n_days = 750tickers = ["7203", "9984", "8306", "6758", "4063"]
# 真の相関を仮定(対角は 1)true_corr = np.array([ [1.00, 0.30, 0.45, 0.50, 0.35], [0.30, 1.00, 0.20, 0.40, 0.25], [0.45, 0.20, 1.00, 0.30, 0.30], [0.50, 0.40, 0.30, 1.00, 0.40], [0.35, 0.25, 0.30, 0.40, 1.00],])sigma = np.array([0.018, 0.022, 0.016, 0.020, 0.017]) # 日次ボラcov = true_corr * np.outer(sigma, sigma)
returns = pd.DataFrame( rng.multivariate_normal(mean=np.zeros(5), cov=cov, size=n_days), index=pd.date_range("2023-01-04", periods=n_days, freq="B"), columns=tickers,)print(returns.head().round(4))実データを使う場合は、ロング形式 DataFrame から pivot でワイド形式に変換します。
# wide = long_df.pivot(index="Date", columns="Code", values="log_return")相関行列の計算
pandas の .corr() 一発で相関行列が出ます。
corr = returns.corr()print(corr.round(2))- 対角は常に 1
- 値域は
- 欠損(NaN)は自動的に除外される(ペアワイズ)
NaN を含むデータで計算する場合、銘柄 A と B の相関は両方の値が揃っている日だけで取られます。期間が銘柄ごとに違うと結果が安定しないので、returns.dropna() で全銘柄のデータが揃う期間に絞るほうが安全です。
ヒートマップで可視化
seaborn の heatmap は対称行列を描くのに向いています。
import matplotlib.pyplot as pltimport seaborn as sns
fig, ax = plt.subplots(figsize=(7, 6))sns.heatmap( corr, annot=True, fmt=".2f", cmap="coolwarm", vmin=-1, vmax=1, center=0, square=True, cbar_kws={"shrink": 0.8}, ax=ax,)ax.set_title("Daily return correlation")plt.tight_layout()plt.savefig("corr_heatmap.png", dpi=120)plt.close(fig)
引数のポイントは次の通りです。
annot=True: 各セルに数値を書き込むcmap="coolwarm": 0 を中心に青↔赤のグラデーションvmin=-1, vmax=1, center=0: 値域を固定し、視覚的に比較しやすくする
上三角だけを描く
対称行列なので、下三角(または上三角)だけ表示すると見やすくなります。
mask = np.triu(np.ones_like(corr, dtype=bool), k=1)
fig, ax = plt.subplots(figsize=(7, 6))sns.heatmap( corr, annot=True, fmt=".2f", cmap="coolwarm", vmin=-1, vmax=1, center=0, square=True, mask=mask, cbar_kws={"shrink": 0.8}, ax=ax,)ax.set_title("Daily return correlation (lower triangle)")plt.tight_layout()plt.savefig("corr_heatmap_tri.png", dpi=120)plt.close(fig)np.triu(..., k=1) は対角を残して上三角を True にする mask を作り、heatmap の mask 引数で対応するセルが非表示になります。
クラスタリングで並び替える
似た動きをする銘柄を近くに並べると、グループが見えやすくなります。sns.clustermap を使うと、階層的クラスタリングで自動的に並び替えてくれます。
g = sns.clustermap( corr, annot=True, fmt=".2f", cmap="coolwarm", vmin=-1, vmax=1, center=0, figsize=(7, 7), cbar_kws={"shrink": 0.8},)g.fig.suptitle("Clustered correlation", y=1.02)g.savefig("corr_clustermap.png", dpi=120)セクター別に並ぶ傾向があれば、「銘柄の業種に沿った相関構造」が読み取れます。
期間で変わる相関
相関は期間によって動きます。市場ストレス時には多くの銘柄の相関が上昇し、平時の分散効果が崩れる場面があります。ローリング相関で確認できます。
WINDOW = 60pair = ("7203", "6758")
rolling_corr = returns[pair[0]].rolling(WINDOW).corr(returns[pair[1]])
fig, ax = plt.subplots(figsize=(9, 4))ax.plot(rolling_corr.index, rolling_corr, color="tab:blue")ax.axhline(0, color="black", linestyle="--", linewidth=0.8)ax.set_title(f"Rolling correlation: {pair[0]} vs {pair[1]} (window={WINDOW})")ax.set_ylabel("Correlation")ax.set_xlabel("Date")ax.grid(alpha=0.3)plt.tight_layout()plt.savefig("rolling_corr.png", dpi=120)plt.close(fig)注意点
- 相関 因果: 相関が高くても、片方がもう片方を動かしているとは限りません
- ピアソン相関の前提: 線形関係を仮定する。極端な外れ値や非線形な依存に弱い
- 異なる売買時間: 国をまたいだ銘柄を比較する場合、市場が開いている時間が違うと相関が低めに出ます(時差リスク)
- 生存者バイアス: 上場廃止になった銘柄を含めずに相関を計算すると、相関構造が現実より楽観的になります(#10-7「バックテストの落とし穴(俯瞰)」)
生成AI へのプロンプト例
ロング形式 DataFrame から「リターン取得 → 相関 → ヒートマップ保存」までを 1 関数にまとめたい場合の例です。
入力 DataFrame に次の列があります:- Date (datetime64)- Code (str)- log_return (float)
次の処理を行う関数 plot_correlation(df, out_path) を書いてください。
仕様:- Date と Code でピボットしてワイド形式にする- 全銘柄でデータが揃う期間に dropna する- 相関行列を計算する- seaborn の heatmap で出力(annot=True, cmap="coolwarm", center=0, vmin=-1, vmax=1)- out_path に PNG として保存する- 戻り値は計算した相関行列(DataFrame)
要件:- pandas 2.2 / matplotlib / seaborn 系- docstring を日本語で書くまとめ
- 分散投資の効き目は「銘柄間の相関」で決まる
- pandas の
.corr()で相関行列、seaborn のheatmapで可視化が 1 行 - 上三角マスク、クラスタリングを使うと構造が見やすい
- 相関は期間で変わる(特に市場ストレス時に上昇しやすい)
- 相関が低いペアを混ぜることがリスク低減の出発点