K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

相互相関

05.1.4

相互相関

最終更新 2020-03-11 読了時間 5 分
まとめ
相互相関(cross-correlation)は、2 本の時系列の片方を 1 期ずつずらしながら相関を計算し、「どれだけずらしたときに最も似るか」を調べる手法です。このページでは FRED から鉄・鋼の生産者物価指数 WPU101 と鉄鉱石の生産者物価指数 PCU2122121221 を取り、海運会社 Genco Shipping(GNK)の月次株価と numpy.correlate(mode="full") で相互相関を計算します。相関が最大になる位置 corr.argmax() から系列長を引いた値がラグで、0 なら同時に動き、正なら株価が物価指数に遅れて動くと読めます。計算の前に ax.twinx() で 2 本の線を単位の違う 2 軸に重ね、目で見た印象と数値のラグを突き合わせます。

直感 #

鉄鉱石や鋼材の値段が上がると、それを運ぶばら積み船の需要が増え、海運会社 GNK の株価も上がりそうです。問題は「同じ月か、数か月遅れか」です。相互相関は片方の系列を 1 か月、2 か月とずらして重ね、重なりの良さ(積和)を全部のずらし幅について計算します。積和が最大になったずらし幅がラグで、ラグが 3 なら「物価指数が動いてから 3 か月後に株価が動く」と解釈できます。平均を引いてから計算するのは、両方の系列が常に正の値だと「ずらさなくても全部正×正で積和が大きい」という見かけの相関が出てしまうためです。

FRED からデータを取り出す準備 #

生産者物価指数は FRED から取得します。関数 get_fred_data() は FRED データベース のページと同じもので、系列 ID を渡すと日付索引の DataFrame を返します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import japanize_matplotlib
import numpy as np
import os
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from full_fred.fred import Fred

# FRED_API_KEY = os.getenv('FRED_API_KEY')
fred = Fred()
print(f"FRED APIキーが環境変数に設定されている:{fred.env_api_key_found()}")

def get_fred_data(name, start="2013-01-01", end=""):
    df = fred.get_series_df(name)[["date", "value"]].copy()
    df["date"] = pd.to_datetime(df["date"])
    df["value"] = pd.to_numeric(df["value"], errors="coerce")
    df = df.set_index("date")

    if end == "":
        df = df.loc[f"{start}":]
    else:
        df = df.loc[f"{start}":f"{end}"]

    return df
FRED APIキーが環境変数に設定されている:True

鉄・鋼の生産者物価指数(WPU101) #

ソース: https://fred.stlouisfed.org/series/WPU101

Producer Price Index by Commodity: Metals and Metal Products: Iron and Steel は鉄及び鋼の生産者物価指数(月次)です。2018 年 1 月から 2022 年 5 月までの 53 か月分を取得します。

1
2
df_WPU101 = get_fred_data("WPU101", start="2018-01-01", end="2022-05-01")
df_WPU101.head(5)

value
date
2018-01-01217.6
2018-02-01222.1
2018-03-01227.5
2018-04-01234.1
2018-05-01239.0
1
2
sns.set(rc={"figure.figsize": (15, 8)})
sns.lineplot(data=df_WPU101, x="date", y="value")
<AxesSubplot:xlabel='date', ylabel='value'>

Producer Price Index by Commodity: Metals and Metal Products… figure

GNK の株価を月次に揃える #

Genco Shipping & Trading Limited ($GNK) はばら積み船を運航する海運会社です。Yahoo Finance から CSV で保存した日次株価を読み、物価指数と比べられるように月次平均にして月初日に揃えます。

1
2
3
4
5
6
df_GNK = pd.read_csv("./GNK.csv")
df_GNK["Date"] = pd.to_datetime(df_GNK["Date"])
df_GNK = df_GNK.set_index("Date").resample("M").mean()
df_GNK.index = df_GNK.index + pd.DateOffset(1)  # 日付を月初に調整する
df_GNK = df_GNK.loc["2018-01-01":"2022-05-01"]
df_GNK.head()

OpenHighLowCloseAdj CloseVolume
Date
2018-01-0112.557513.435512.137512.765011.157512603175.0
2018-02-0113.972014.542013.131013.760012.027212510600.0
2018-03-0112.992513.852512.652013.430011.738769357050.0
2018-04-0115.010015.692514.402514.757512.899099796075.0
2018-05-0115.528016.353014.962015.958013.948420631500.0

物価指数(200〜400)と株価(10〜25 ドル)は桁が違うので、左右に別の縦軸を持つ 2 軸グラフで重ねます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
japanize_matplotlib.japanize()

fig = plt.figure()
ax1 = fig.add_subplot(111)
sns.lineplot(data=df_WPU101, x="date", y="value", label="鉄・鋼の生産者物価指数")
plt.legend(loc="lower right", fontsize="20")

ax2 = ax1.twinx()
sns.lineplot(data=df_GNK, x="Date", y="Close", label="GNK", color="r")
plt.legend(loc="upper left", fontsize="20")

GNKの価格推移 figure

実行結果の読み方 #

青(物価指数)と赤(GNK)はどちらも 2020 年前半に底を打ち、2021 年に大きく上昇しています。目で見ると赤(GNK)がやや先に上がり始めたようにも、同時にも見えます。この「どちらが先か」を数値で決めるのが次の相互相関です。

相互相関を求める #

相互相関関数は、ふたつの信号、配列(ベクトル)の類似性を確認するために使われる。関数の配列の結果がすべて1であれば相関があり、すべてゼロであれば無相関であり、すべて −1 であれば負の相関がある。しばしば、相関と略されることがあり、相関係数と似ているために混同することがある。 (出典:Wikipedia)

numpy.correlate(a, v, "full") は、v を左端から右端まで 1 つずつずらしながら a との積和を計算し、長さ len(a) + len(v) - 1 の配列を返します。中央(添字 len(v) - 1)がずらし 0 に対応するので、最大値の位置から中央を引けばラグになります。

ドキュメント: numpy.correlate

1
2
3
4
5
6
7
8
df_GNK["Close_norm"] = df_GNK["Close"] - df_GNK["Close"].mean()
df_WPU101["value_norm"] = df_WPU101["value"] - df_WPU101["value"].mean()

corr = np.correlate(df_GNK["Close_norm"], df_WPU101["value_norm"], "full")
delay = corr.argmax() - (len(df_WPU101["value_norm"]) - 1)
print("ラグ: " + str(delay))

plt.plot(corr)
ラグ: 0

[<matplotlib.lines.Line2D at 0x13e252170>]

[] figure

実行結果の読み方 #

ラグは 0 で、相互相関の図は中央(横軸 52)を頂点とする 1 つの山になっています。鉄・鋼の物価指数と GNK の株価は月次で見ると同じ月に動いており、「物価指数を見てから株を買う」という時間差は月単位では存在しないことになります。

鉄鉱石の生産者物価指数(PCU2122121221)で同じことを試す #

ソース:https://fred.stlouisfed.org/series/PCU2122121221

Producer Price Index by Industry: Iron Ore Mining は鉄鉱石の生産者物価指数です。鋼材より上流の原料なので、GNK の株価に対して先行するかを同じ手順で確かめます。

1
2
df_PCU2122121221 = get_fred_data("PCU2122121221", start="2018-01-01", end="2022-05-01")
df_PCU2122121221.head(5)

value
date
2018-01-01121.0
2018-02-01122.5
2018-03-01122.5
2018-04-01133.8
2018-05-01138.7
1
2
3
4
5
6
7
8
fig = plt.figure()
ax1 = fig.add_subplot(111)
sns.lineplot(data=df_PCU2122121221, x="date", y="value", label="鉄鉱石の生産者物価指数")
plt.legend(loc="lower right", fontsize="20")

ax2 = ax1.twinx()
sns.lineplot(data=df_GNK, x="Date", y="Close", label="GNK", color="r")
plt.legend(loc="upper left", fontsize="20")

Producer Price Index by Industry: Iron Ore Mining figure

1
2
3
4
5
6
7
8
9
df_PCU2122121221["value_norm"] = (
    df_PCU2122121221["value"] - df_PCU2122121221["value"].mean()
)

corr = np.correlate(df_GNK["Close_norm"], df_PCU2122121221["value_norm"], "full")
delay = corr.argmax() - (len(df_PCU2122121221["value_norm"]) - 1)
print("ラグ: " + str(delay))

plt.plot(corr)
ラグ: 0

[<matplotlib.lines.Line2D at 0x13e373fa0>]

[] figure

使いどころと落とし穴 #

np.correlate は正規化しないので、返ってくる値は相関係数(−1〜1)ではなく積和です。山の位置(ラグ)は読めますが高さを別のペアと比べることはできず、相関係数の形で欲しいなら両系列を標準偏差でも割るか、pandas の Series.corr を shift(k) しながら繰り返す方法に替えます。株価のように右肩上がりのトレンドを持つ系列は、トレンドだけで相互相関の山が広がります。diff() や pct_change() で変化率にしてから計算するほうが「動きの連動」を正しく捉えられます。月次に落とすと 53 点しかないので、ラグ 1 の差は 1 点のずれで決まることになり、日次や週次で点数を増やしてから確認するのが安全です。

まとめ #

相互相関は 2 本の系列を 1 期ずつずらしながら積和を取り、最大になるずらし幅をラグとして読む手法です。実装は np.correlate(a - a.mean(), b - b.mean(), "full") と argmax() - (len(b) - 1) の 2 行で、前提として resample("M") と .loc[start:end] で頻度と期間を揃えておきます。鉄・鋼と鉄鉱石のどちらの物価指数も GNK の株価とラグ 0 で連動しており、月次では先行関係は見つかりませんでした。トレンドを除いた変化率や、より細かい頻度で試すのが次の一手です。