はじめに
時系列データを見ていると、
なんとなく周期性がありそう
同じような波形が繰り返している気がする
でも、何時間ごと・何ステップごとに似ているのかを定量的に見たい
という場面があります。
たとえば、気温、アクセス数、センサー値、実験装置のログ、細胞や生体信号、株価のような時系列データでは、「時間的な繰り返し」や「ゆっくり変化するパターン」を見たくなることがあります。
このようなときに使える基本的な道具の1つが自己相関です。
自己相関は、ざっくり言うと、
時系列データを少しずらして、元のデータとどれくらい似ているかを見る方法
です。
この記事では、ダミーデータをPythonで作りながら、自己相関の考え方を説明します。
この記事でやること
この記事では、以下の流れで自己相関を理解します。
- 24時間周期を持つダミー時系列を作る
- 「時間をずらして比べる」とはどういうことかを見る
- 自己相関関数をPythonで計算する
- 自己相関のピークから周期を推定する
- 周期性のあるデータとランダムなデータを比較する
最終的には、次のようなことが分かります。
自己相関を見ると、「どれくらい時間をずらすと自分自身と似ているか」が分かる。
周期的なデータでは、周期に対応するlagで自己相関が大きくなる。
自己相関とは何か
自己相関の「自己」とは、データ自身のことです。
普通の相関係数では、2つの変数 $x$ と $y$ の関係を見ます。
たとえば、
- 気温とアイスの売上
- 身長と体重
- 入力値と出力値
のように、異なる2つのデータの関係を見ます。
一方、自己相関では、1つの時系列データを時間方向にずらして、自分自身と比べます。
たとえば、時系列データを
$$
x_0, x_1, x_2, x_3, \cdots
$$
とします。
これを1ステップずらすと、
$$
x_1, x_2, x_3, x_4, \cdots
$$
になります。
元のデータと、1ステップずらしたデータを比べると、
1ステップ後の値は、今の値とどれくらい似ているか?
が分かります。
同じように、24ステップずらして比べると、
24ステップ後の値は、今の値とどれくらい似ているか?
が分かります。
この「どれくらいずらすか」を lag と呼びます。
lagとは何か
lag は、日本語では「遅れ」や「ずれ」と訳されます。
時系列データにおける lag は、
何ステップずらして比べるか
を表します。
たとえば、1時間ごとに測定されたデータなら、
- lag = 1 は、1時間ずらして比べる
- lag = 6 は、6時間ずらして比べる
- lag = 24 は、24時間ずらして比べる
という意味になります。
もしデータに24時間周期があるなら、lag = 24 のときに、元のデータとよく似るはずです。
自己相関の式
この記事では、次の形の自己相関を使います。
$$
r(k)=
\frac{
\sum_{t=0}^{N-k-1}(x_t-\bar{x})(x_{t+k}-\bar{x})
}{
\sum_{t=0}^{N-1}(x_t-\bar{x})^2
}
$$
ここで、
- $x_t$ は時刻 $t$ の値
- $\bar{x}$ は平均値
- $k$ は lag
- $r(k)$ は lag $k$ における自己相関
- $N$ はデータ数
です。
少し式は長いですが、やっていることはシンプルです。
平均を引いたデータと、lagだけずらしたデータが、どれくらい同じ向きに動くかを見る
ということです。
自己相関の値は、おおよそ次のように読みます。
| 自己相関の値 | 意味 |
|---|---|
| $1$ に近い | かなり似ている |
| $0$ に近い | あまり関係がない |
| $-1$ に近い | 逆向きに似ている |
lag = 0 のときは、データをまったくずらしていないので、自分自身と完全に一致します。したがって、正規化した自己相関では
$$
r(0)=1
$$
になります。
Google Colabで実行するコード
このコードでは、24時間周期を持つダミーデータを作り、自己相関を計算します。
import os
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import find_peaks
# ---------------------------------------------
# 0. 保存先
# ---------------------------------------------
os.makedirs("fig", exist_ok=True)
# ---------------------------------------------
# 1. ダミーデータを作る
# ---------------------------------------------
rng = np.random.default_rng(42)
samples_per_day = 24
n_days = 14
true_period = 24 # 24サンプル = 24時間周期、という想定
t = np.arange(samples_per_day * n_days)
# 24時間周期のきれいな波
clean = np.sin(2 * np.pi * t / true_period)
# ノイズを加えて、観測データらしくする
noise = rng.normal(0, 0.45, size=len(t))
y = clean + noise
# 比較用:周期性を持たないランダムデータ
random_y = rng.normal(0, 1.0, size=len(t))
# ---------------------------------------------
# 2. 自己相関関数を定義する
# ---------------------------------------------
def autocorr(x, max_lag=None):
"""
平均を引いたあと、lag=0で1になるように正規化した自己相関を計算する。
"""
x = np.asarray(x, dtype=float)
x = x - np.mean(x)
if max_lag is None:
max_lag = len(x) - 1
denom = np.sum(x * x)
acf = np.array([
np.sum(x[:len(x)-lag] * x[lag:]) / denom
for lag in range(max_lag + 1)
])
return acf
max_lag = 7 * 24
acf = autocorr(y, max_lag=max_lag)
acf_random = autocorr(random_y, max_lag=max_lag)
lags = np.arange(max_lag + 1)
# ---------------------------------------------
# 3. 図1: ダミー時系列
# ---------------------------------------------
plt.figure(figsize=(10, 4))
plt.plot(t, y, label="noisy observation")
plt.plot(t, clean, linestyle="--", label="hidden clean rhythm")
plt.title("Dummy time series with a 24-hour rhythm")
plt.xlabel("time [hour]")
plt.ylabel("value [a.u.]")
plt.legend()
plt.tight_layout()
plt.savefig("fig/fig1_time_series.png", dpi=160)
plt.show()
# ---------------------------------------------
# 4. 図2: ずらしたデータ同士の散布図
# ---------------------------------------------
test_lags = [6, 12, 24]
fig, axes = plt.subplots(1, 3, figsize=(12, 4), sharex=True, sharey=True)
for ax, lag in zip(axes, test_lags):
x_now = y[:-lag]
x_later = y[lag:]
r = np.corrcoef(x_now, x_later)[0, 1]
ax.scatter(x_now, x_later, s=12, alpha=0.7)
ax.axhline(0, linestyle="--", linewidth=1)
ax.axvline(0, linestyle="--", linewidth=1)
ax.set_title(f"lag = {lag} h\ncorr = {r:.2f}")
ax.set_xlabel("$x_t$")
axes[0].set_ylabel("$x_{t+lag}$")
fig.suptitle("Compare the signal with its lagged copy")
plt.tight_layout()
plt.savefig("fig/fig2_lagged_scatter.png", dpi=160)
plt.show()
# ---------------------------------------------
# 5. 図3: 自己相関
# ---------------------------------------------
plt.figure(figsize=(10, 4))
plt.plot(lags, acf)
plt.axhline(0, linestyle="--", linewidth=1)
for p in [24, 48, 72, 96, 120, 144]:
plt.axvline(p, linestyle=":", linewidth=1)
plt.title("Autocorrelation function")
plt.xlabel("lag [hour]")
plt.ylabel("autocorrelation")
plt.tight_layout()
plt.savefig("fig/fig3_autocorrelation.png", dpi=160)
plt.show()
# ---------------------------------------------
# 6. 図4: 周期信号とランダム信号の比較
# ---------------------------------------------
plt.figure(figsize=(10, 4))
plt.plot(lags, acf, label="periodic + noise")
plt.plot(lags, acf_random, label="random noise")
plt.axhline(0, linestyle="--", linewidth=1)
plt.title("Autocorrelation: periodic signal vs random noise")
plt.xlabel("lag [hour]")
plt.ylabel("autocorrelation")
plt.legend()
plt.tight_layout()
plt.savefig("fig/fig4_periodic_vs_random.png", dpi=160)
plt.show()
# ---------------------------------------------
# 7. 自己相関のピークから周期候補を探す
# ---------------------------------------------
peaks, props = find_peaks(acf, height=0.25, distance=12)
peak_lags = peaks[peaks > 0]
print("自己相関のピーク候補 [hour]:", peak_lags[:10])
if len(peak_lags) > 0:
estimated_period = peak_lags[0]
print(f"最初のピークから推定した周期: {estimated_period} hours")
print(f"今回のダミーデータで設定した真の周期: {true_period} hours")
else:
print("明確なピークは見つかりませんでした。")
実行結果の例
上のコードを実行すると、最後に次のような結果が表示されます。
自己相関のピーク候補 [hour]: [ 24 48 72 96 120 144]
最初のピークから推定した周期: 24 hours
今回のダミーデータで設定した真の周期: 24 hours
今回のダミーデータでは、もともと24時間周期を設定していました。
自己相関のピーク候補として、
$$
24, 48, 72, 96, 120, 144
$$
が出てきています。
これは、
24時間ごとに、時系列が自分自身と似ている
ということを表しています。
図1:24時間周期を持つダミー時系列
図1では、24時間周期を持つきれいな波に、ランダムなノイズを加えた時系列を表示しています。
破線はノイズを加える前の「隠れたきれいな周期成分」です。実際のデータ解析では、このようなきれいな成分は直接は見えません。
実際に観測されるのは、ノイズを含んだ青い線のようなデータです。
この図だけを見ても、
なんとなく周期がありそう
とは感じられます。
しかし、図を眺めるだけでは、周期を定量的に判断しにくいです。
そこで自己相関を使います。
図2:時間をずらして自分自身と比べる
図2では、元の時系列 $x_t$ と、lagだけずらした時系列 $x_{t+lag}$ を散布図にしています。
今回は、
- lag = 6時間
- lag = 12時間
- lag = 24時間
で比較しています。
実行例では、おおよそ次のような相関になります。
| lag | 相関の例 | 解釈 |
|---|---|---|
| 6時間 | 約 $0$ | あまり似ていない |
| 12時間 | 約 $-0.7$ | 逆向きに似ている |
| 24時間 | 約 $0.7$ | よく似ている |
24時間周期のサイン波を考えると、12時間ずらすと山と谷が入れ替わります。
そのため、lag = 12 では負の相関が出ます。
一方、24時間ずらすと、山は山に、谷は谷に対応しやすくなります。
そのため、lag = 24 では正の相関が大きくなります。
これが自己相関の基本的な見方です。
図3:自己相関関数を見る
図3は、横軸に lag、縦軸に自己相関をとったグラフです。
これを自己相関関数と呼びます。
英語では autocorrelation function と呼ばれ、略して ACF と書かれることもあります。
この図では、lag = 24, 48, 72, ... のあたりで山が出ています。
これは、
24時間ごとに、元の時系列と似た形が現れている
ということを意味します。
lag = 0 の自己相関は必ず1です。
なぜなら、lag = 0 では、データをまったくずらしていないからです。
つまり、自分自身と自分自身を比べているので、完全に一致します。
自己相関の山が少しずつ小さくなる理由
図3を見ると、24時間、48時間、72時間とピークが繰り返し出ています。
ただし、ピークの高さは少しずつ小さくなっています。
これは、今回の自己相関の計算では、lagが大きくなるほど比較に使えるデータ点が少なくなるためです。
たとえば、データ数が100個あるとします。
lag = 1 なら、かなり多くの点を比較できます。
しかし、lag = 80 では、比較できる点はかなり少なくなります。
そのため、大きなlagでは自己相関の推定が不安定になりやすいです。
実データを見るときも、
大きすぎるlagの自己相関は、データ点数が少なくなるため慎重に見る
という意識が大切です。
図4:周期信号とランダム信号を比べる
図4では、周期性を持つデータと、ランダムなノイズだけのデータの自己相関を比較しています。
周期性を持つデータでは、自己相関に波のような構造が見えます。
一方、ランダムなデータでは、lag = 0 を除くと、自己相関はおおむね0付近をふらふらします。
この違いから、自己相関は、
時系列の中に繰り返し構造があるかどうか
を見るための基本的な道具として使えることが分かります。
自己相関で周期を探す考え方
自己相関を使って周期を探すときの基本的な考え方はシンプルです。
- いろいろなlagで自己相関を計算する
- lag = 0 以外で、自己相関が大きくなる場所を探す
- 最初に大きなピークが出るlagを周期の候補とする
今回の例では、lag = 24 で最初の大きなピークが出ました。
したがって、
$$
\text{推定された周期} = 24 \text{ 時間}
$$
と考えることができます。
もちろん、実データではノイズ、外れ値、トレンド、データ数の少なさなどの影響があります。
そのため、自己相関だけで機械的に周期を決めるのではなく、時系列のグラフやドメイン知識と合わせて判断することが重要です。
自己相関を見るときの注意点
自己相関は便利ですが、万能ではありません。
初心者が特に注意したい点をまとめます。
1. トレンドがあると自己相関が大きく見えることがある
時系列が全体として右肩上がり、または右肩下がりになっている場合、周期性がなくても自己相関が高く見えることがあります。
たとえば、単調に増加するデータでは、少しずらしても値の大小関係が似ているため、自己相関が高くなりやすいです。
このような場合は、トレンドを除去してから自己相関を見ることがあります。
2. lagが大きいところは不安定になりやすい
lagが大きくなるほど、比較に使えるデータ点が少なくなります。
そのため、大きなlagで見えるピークは、偶然の影響を受けやすくなります。
3. 周期が複数ある場合は解釈が難しくなる
実データでは、1つの周期だけでなく、複数の周期が混ざっていることがあります。
たとえば、
- 1日周期
- 1週間周期
- 装置由来の周期
- 測定条件の変化
などが重なると、自己相関の形も複雑になります。
4. 欠損値がある場合は前処理が必要
今回の例では、データに欠損値がないと仮定しました。
しかし、実際の時系列データでは、欠損値が含まれることがあります。
欠損値がある場合は、補間、除外、専用の手法などを検討する必要があります。
自己相関とフーリエ変換の違い
周期性を見る方法としては、自己相関のほかにフーリエ変換もよく使われます。
ざっくり言うと、
- 自己相関:時間をずらして、自分自身との似ている度合いを見る
- フーリエ変換:時系列を周波数成分に分解する
という違いがあります。
自己相関は、
何ステップずらすと似ているか
を直感的に見やすい方法です。
一方、フーリエ変換は、
どの周波数成分がどれくらい含まれるか
を見るのに向いています。
どちらが優れているというより、目的に応じて使い分けるとよいです。
まとめ
この記事では、自己相関を使って時系列の周期性を見る方法を、ダミーデータで確認しました。
ポイントは以下です。
- 自己相関は、時系列をずらして自分自身と比べる方法
- lag は「何ステップずらすか」を表す
- 周期性があるデータでは、周期に対応するlagで自己相関が大きくなる
- 24時間周期のダミーデータでは、lag = 24, 48, 72, ... にピークが出た
- ランダムなデータでは、lag = 0 以外の自己相関はおおむね0付近になる
- 実データでは、トレンド、ノイズ、欠損値、大きすぎるlagに注意する
自己相関は、時系列解析の入り口としてとても分かりやすい道具です。
まずは「時系列をずらして、自分自身と比べる」という直感を持っておくと、自己相関関数のグラフが読みやすくなります。
おまけ:自分で試すと理解が深まる変更
余裕があれば、コードの一部を変えて試してみてください。
1. ノイズを大きくする
noise = rng.normal(0, 1.0, size=len(t))
ノイズを大きくすると、自己相関のピークが見えにくくなります。
2. 周期を12時間に変える
true_period = 12
この場合、自己相関のピークは lag = 12, 24, 36, ... のあたりに出るはずです。
3. データ期間を短くする
n_days = 3
データ期間を短くすると、自己相関の推定が不安定になります。
周期性があるはずなのに、ピークが見えにくくなることがあります。
4. ランダムデータの自己相関を見る
今回のコードでは、周期性のないランダムデータも比較しています。
ランダムデータでは、lag = 0 以外の自己相関が0付近になることを確認できます。
これを見ると、
周期性があるデータの自己相関は、ランダムデータとはかなり違う
ことが分かります。
おわりに
時系列解析では、いきなり高度なモデルを使う前に、まずデータの形をよく見ることが重要です。
自己相関は、そのための基本的で強力な道具です。
「このデータは、どれくらい時間をずらすと自分自身と似ているのか?」
この問いを持つだけで、時系列データの見方が少し変わります。



