MA過程の反転可能性

今日もまた沖本本からMA過程の話題です。

本題に入る前にAR過程の復習ですが、このブログでも紹介した通り、
AR過程は定常になるとは限らず、各種の性質については、定常AR過程なら、という前提がついていました。
参考:自己回帰過程の定義と定常になる条件、定常自己回帰過程の性質
その一方で、MA過程については常に定常でなのでとても扱いやすいように見えます。
しかし、MA過程には同一の期待値と、同一の自己相関構造を持つ異なるMA過程が複数存在するという問題があります。

一般に、同一の期待値と自己相関構造を持つMA(q)過程は$2^q$個存在するらしいです。
本に、MA(1)の例が載っているので、このブログではMA(4)あたりに対して、この$2^4=16$個がそうだ!って例を見つけて描きたかったのですが、
挫折したので本に載ってるMA(1)に対する例を紹介します。

それは
$$
\begin{eqnarray}
y_t = \varepsilon_t +\theta\varepsilon_{t-1} , \varepsilon_t \sim W.N.(\sigma^2)
\end{eqnarray}
$$
と、
$$
\begin{eqnarray}
y_t = \tilde{\varepsilon}_t+\frac{1}{\theta}\tilde{\varepsilon}_{t-1}, \tilde{\varepsilon}_t \sim W.N.(\theta^2\sigma^2)
\end{eqnarray}
$$
です。
これの期待値や自己相関を 移動平均過程の定義と性質 で紹介した式に沿って計算すると、一致することを確認できます。

この時、$2^q$個のMA(q)過程の中からどれを採用するかの基準が必要になりますが、
そのために一つの基準になるのがMA過程の反転可能性(invertibility)です。

MA(q)過程が反転可能(invertible)とは、そのMA(q)過程をAR($\infty$)過程に書きなおせることです。

例えば、$|\theta|<1$であれば、 $$ y_t = \varepsilon_t + \theta\varepsilon_{t-1} $$ は $$ y_t = -\sum_{k=1}^{\infty}(-\theta)^ky_{t-k}+\varepsilon_t $$ と書き直せます。 このMA(1)の例も沖本先生の本からです。 (MA(2)の例を紹介しようと計算を頑張っていたのですが思ったより複雑になったので本の例をそのまま。) 一般のMA(q)過程の反転可能性については、AR過程の定常条件とよく似ています。 次のMA特性方程式を調べることで確認できます。 $$ 1+\theta_1z+\theta_2z^2+\cdots+\theta_qz^q=0. $$ このMA特性方程式の全ての解の絶対値が1より大きければMA過程は反転可能になります。 この時、撹乱項$\varepsilon_t$は、$y_t$を過去の値から予測した時の予測誤差と解釈できます。 昔、別の本で移動平均過程を勉強した時、いきなり移動平均もでるは誤差項による回帰だと書かれていて全く理解できなったのですが、 ようやく理解できました。 沖本先生の本だと、反転可能なものの唯一性等の証明が載っていない(そして自分で証明しようともしたが上手くいっていない)ので、 何かの折に他の文献にも当たってみようと思います。

pandasで数値データを区間ごとに区切って数える

今日偶然知って便利だった関数を紹介します。

まずやりたいこと。
配列でもDatafarameの列でも、数値データを区間(bins)ごとに区切って数えたい場面は多々あると思います。
例えば、
[455,133,666,111,645,236]
みたいなデータを、
100以上,200未満が2個、200以上,300未満が1個、、、といった具合です。
ヒストグラム書くためにやる操作ですね。

これまで僕がこれをやる時100単位でやるのであれば、
各データを100で割り算して小数点以下を切り捨てて、再度100倍するといった面倒なことをやってました。

これを pandas.cut という関数を使うと、非常に容易に行えます。
ドキュメントはこちら。
pandas.cut

早速これを使ってみましょう。
次のコードは、乱数で生成した10個の数値を、100ごとに区切って数えるものです。
結果に出てくる “[300, 400)”といった出力は、文字列に見えますが、Intervalというオブジェクトです。
right=False は指定しないと、n以上m未満ではなく、nより大きいm以下、って区切り方になります。
引数のbinsには区切りたい点のリストを渡していますが、arrange関数で生成して渡しています。


import pandas as pd
import numpy as np  # ダミーデータ生成用

# 300〜999 の整数を10個生成
data = np.random.randint(300, 1000, 10)
# 100 ごとに区切る。
category = pd.cut(data, bins=np.arange(300, 1100, 100), right=False)

print("データとそれが所属する区間")
for d, c in zip(data, category):
    print(d, "・・・", c)

print("\n区間ごとのデータ件数")
print(category.value_counts())

# 以下出力
データとそれが所属する区間
309 ・・・ [300, 400)
305 ・・・ [300, 400)
874 ・・・ [800, 900)
953 ・・・ [900, 1000)
727 ・・・ [700, 800)
950 ・・・ [900, 1000)
384 ・・・ [300, 400)
789 ・・・ [700, 800)
486 ・・・ [400, 500)
501 ・・・ [500, 600)

区間ごとのデータ件数
[300, 400)     3
[400, 500)     1
[500, 600)     1
[600, 700)     0
[700, 800)     2
[800, 900)     1
[900, 1000)    2
dtype: int64

ARMA過程

今回も沖本先生の経済・ファイナンスデータの計量時系列分析を元にした記事です。
最近、AR過程やMA過程について色々と紹介していますが、これら両方を含む過程を考えることができます。
それを自己回帰移動平均(ARMA)過程と言います。
(autoregressive moving average process)

(p,q)次ARMA過程(ARMA(p,q)過程)は、次の式で定義されます.

$$
y_t = c + \phi_1y_{t-1}+\cdots+\phi_py_{t-p}+\varepsilon_t+\theta_1\varepsilon_{t-1}+\cdots+\theta_q\varepsilon_{t-q}\\
\varepsilon_t \sim W.N.(\sigma^2)
$$
ARMA過程の性質は、AR過程の性質とMA過程の性質の強い方が残ります。
例えば定常性については、AR過程部分が定常であれば定常になります。
(結果だけ見れば、MA過程が定常過程なので、それに定常過程を足しても定常というだけの話に見えますが、
正確な証明にはもう少し細かな議論を要します)

AR過程部分の定常性については、以前の記事でも紹介したAR特性方程式を使います。

ARMA(p,q)過程が定常の時、過程の期待値は、AR過程部分の期待値と等しくなります。
$$
\mu = E(y_t) = \frac{c}{1-\phi_1-\phi_2-\cdots-\phi_p}
$$

また、$q+1$次以降の自己共分散と自己相関もAR部分とおなじ、次のユール・ウォーカー方程式に従います。
$$
\begin{eqnarray}
\gamma_k & = \phi_1\gamma_{k-1}+\phi_2\gamma_{k-2}+\cdots+\phi_p\gamma_{k-p}, k\geq q+1\\
\rho_k & = \phi_1\rho_{k-1}+\phi_2\rho_{k-2}+\cdots+\phi_p\rho_{k-p}, k\geq q+1
\end{eqnarray}
$$
$q$次までについては、MA項の影響もあり具体的な表記が難しくなります。

また、定常ARMA過程の自己相関は指数的に減衰します。これもAR部分の性質ですね。
(MA過程の部分の自己相関はq+1次以降消えます。)

2019年第1四半期によく読まれた記事

一昨日でこのブログを開設してから3ヶ月が経過しました。
まだ毎日数人ですが、このブログに訪れてくれる方も現れています。

せっかくGoogle アナリティクスも導入していますので、
このあたりで、これまでによく読まれた記事を紹介したいと思います。
他のブログではよくベスト10とか紹介されていますが、そこまで記事数も訪問数もないのでベスト5で。

早速ですが結果はこちらになります。なお、自分のアクセスは除外しています。

  1. Mac(Mojave) に pip で mecab-python3をインストールする時にはまった
  2. macにgraphvizをインストールする
  3. WP Mail SMTPを設定してLightsailのwordpressからメール送信できるようにする
  4. pythonで編集距離(レーベンシュタイン距離)を求める
  5. Prestoで1ヶ月後の時刻を求める時に気をつけること

トップ2が環境構築の話、3位がWordpressの話となり、全然データサイエンティストのブログぽくない結果になりました。
そもそもこのブログ開設当初の記事がブログ構築の話ばかりだったのでしかたがないですね。

最近書いている時系列解析の話などはほぼ読まれていないようです。
ただ、今後も当分は今のペースで記事を増やしていこうと思っています。
もともと過去に検証したこととか、手元にメモしたものを一箇所に集約することも目的だったので、
これまで通り基本的な内容の記事が中心になる予定です。

ユール・ウォーカー方程式から求めた自己相関をサンプルから計算した自己相関と比較する

前回の記事で定常AR過程の自己相関係数を解析的に算出しました。
また、さらに以前の記事で、同じAR(3)過程に従う系列をpythonで生成しています。
このAR(3)過程のサンプルから、自己相関係数を計算したら、それは本当にユール・ウォーカー方程式から求めた値と一致するのかなというのが今回のテーマです。
結論を先に言っておくと非常に長い系列サンプルを用意しないと無理です。

では早速やってみましょう。
今回もサンプルはこれ。(適当に作った系列ですが、このブログでは高頻度で登場します。)

$$
y_t=1+\frac{11}{15}y_{t-1}-\frac{1}{3}y_{t-2}+\frac{1}{15}y_{t-3}+\varepsilon_t , \varepsilon_t \sim iid N(0,\sigma^2)
$$
ユール・ウォーカー方程式から求まる自己相関$\rho_k$の最初の8項は次のようになります。
$$
\begin{eqnarray}
\rho_0 &=& 1.0 \\
\rho_1 &=& 0.5555555555555556 \\
\rho_2 &=& 0.1111111111111111 \\
\rho_3 &=& -0.037037037037037035 \\
\rho_4 &=& -0.027160493827160487 \\
\rho_5 &=& -0.0001646090534979357 \\
\rho_6 &=& 0.006463648834019207 \\
\rho_7 &=& 0.0029841792409693643
\end{eqnarray}
$$

あとは、早速乱数で{y_t}を生成して、pandasの関数で自己相関を算出し、
上の値と比べてみましょう。

まず、100項算出してやってみたのが次のコードです。
注意点として、今回は系列の最初の方のデータを系列の期待値+撹乱項で生成しました。
また、撹乱項の標準偏差を$0.1$にしています。(つまり分散は$0.01$)。
これまで、$1$を使っていましたが、期待値が$1.875$の系列に対する撹乱項としてはちょっと分散が大きすぎましたので。

コードと出力はこちら。


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

phi_1 = 11/15
phi_2 = -1/3
phi_3 = 1/15

# 過程の期待値
Ey = 1/(1-phi_1-phi_2-phi_3)

# 項数
T = 100

# 過程の生成
y = np.zeros(T)
# 初期値 (過程の期待値+乱数)
y[0] = Ey + np.random.normal(loc=0, scale=0.1)
y[1] = Ey + np.random.normal(loc=0, scale=0.1)
y[2] = Ey + np.random.normal(loc=0, scale=0.1)
for i in range(3, T):
    y[i] = 1 + phi_1*y[i-1] + phi_2*y[i-2] + phi_3*y[i-3] +\
                np.random.normal(loc=0, scale=0.1)

# 自己相関の算出
series = pd.Series(y)
series_autocorr = [series.autocorr(i) for i in range(8)]


# rho_values に、前回の記事で算出した自己相関が代入されている想定
# 可視化
fig = plt.figure(figsize=(12, 6))
ax = fig.add_subplot(1, 1, 1, title='T=100')
ax.bar(np.arange(len(rho_values))-0.3, rho_values,  width=0.3, label='真の自己相関')
ax.bar(range(len(series_autocorr)), series_autocorr, width=0.3, label='サンプルから計算した自己相関')
ax.legend()
plt.show()

ラグが3以上になると全然ダメですね。

Tの値を 1000,10000 と増やして順に実行したのがこちらです。

徐々に近づいてきたようにみます。

自己相関はpandasで結構手軽に計算できるので、
過去にも、時系列データをみたらとりあえず計算して眺めたりしていたのですが、
データ数がかなり大きくないとなかなか真の値に近いものにならないことがわかりました。
そもそも現実データには大量のノイズが入るので、ほんの数年分の
月次データ6ヶ月ラグとかみてもほとんどあてにならなさそうですね。

定常自己回帰過程AR(3)の自己相関係数を具体的に求めてみる

自己回帰過程の定義と定常になる条件、定常自己回帰過程の性質
という記事で、定常自己回帰過程の性質を紹介しました。
その中に、逐次的に自己相関係数を算出する次の公式があります。

$$
\rho_k = \phi_1\rho_{k-1}+\phi_2\rho_{k-2}+\cdots+\phi_p\rho_{k-p}, k\geq1
$$
これをユール・ウォーカー方程式と言います。
こいつを使って、具体的に自己相関過程の自己相関係数を求めてみようというのが今回の記事です。

$p=1$や$p=2$の例は本に載っているので、$p=3$でやってみます。
$$
\rho_k = \phi_1\rho_{k-1}+\phi_2\rho_{k-2}+\phi_p\rho_{k-3}, k\geq1
$$
また、例として扱う定常AR(3)過程は、以前の記事でグラフをプロットした、こちらです。
$$y_t=1+\frac{11}{15}y_{t-1}-\frac{1}{3}y_{t-2}+\frac{1}{15}y_{t-3}+\varepsilon_t$$

$\phi_1,\phi_2,\phi_3$の値がわかるので、あとは、$\rho_0,\rho_1,\rho_2$さえわかれば残りの$\rho_k$は順番に計算できます。

これは、次の4つの関係式を使って計算できます。最初の2つは、ユール・ウォーカー方程式に$k=1,2$を代入したもの、
残り2つは自己相関係数の性質です。

$$
\rho_1 = \phi_1\rho_0 + \phi_2\rho_{-1} + \phi_3\rho_{-2}\\
\rho_2 = \phi_1\rho_1 + \phi_2\rho_0 + \phi_3\rho_{-1}\\
\rho_0 = 1\\
\rho_{k} = \rho_{-k}
$$
これを整理すると、
$$
\rho_1 = \phi_1 + \phi_2\rho_1 + \phi_3\rho_2\\
\rho_2 = \phi_1\rho_1 + \phi_2 + \phi_3\rho_1\\
$$
となり、さらに
$$
(1-\phi_2)\rho_1 – \phi_3\rho_2= \phi_1\\
(\phi_1+\phi_3)\rho_1 -\rho_2 =-\phi_2\\
$$
と変形できます。
あとは$\phi_k$の値を代入して、
$$
\frac{4}{3}\rho_1 – \frac{1}{15}\rho_2= \frac{11}{15}\\
\frac{4}{5}\rho_1 -\rho_2 =\frac{1}{3}\\
$$
となり、これを解くと次の二つが得られます。
$$\rho_1=\frac{5}{9},\rho_2=\frac19$$

あとは順番に代入するだけなので、pythonにやってもらいましょう。


import numpy as np
phi_1 = 11/15
phi_2 = -1/3
phi_3 = 1/15
rho_values = np.zeros(6)
rho_values[0] = 1
rho_values[1] = 5/9
rho_values[2] = 1/9
for i in range(3, 6):
    rho_values[i] = phi_1*rho_values[i-1]\
                                + phi_2*rho_values[i-2]\
                                + phi_3*rho_values[i-3]
for rho in rho_values:
    print(rho)

# 以下出力
1.0
0.5555555555555556
0.1111111111111111
-0.037037037037037035
-0.027160493827160487
-0.0001646090534979357

これで計算できました。
自己相関が指数関数的に減衰している様子も確認できます。

pythonでアルファベットの大文字小文字変換

自然言語処理系の機械学習を行うときやテキストマイニングをやるとき、
前処理としてアルファベットの大文字小文字を統一することがよくあります。
(失われる情報もあるのでしない方が良いという主張も見たことがありますが、僕は大抵の場合小文字に統一します。)

これはpythonの文字列が持っている
str.lower()str.upper()
を使うことで実現できます。
pythonの組み込み型のドキュメントを見ると乗っています。


text = "Hello World!"
print(text.lower())  # hello world!
print(text.upper())  # HELLO WORLD!

これだけだと、記事にしなかったと思うのですが、ドキュメントを読んでいると他にも関数が準備されていることがわかりました。


text = "Hello World!"
# 大文字と小文字を入れ替える
print(text.swapcase())  # hELLO wORLD!

text = "HELLO world!"
# 各単語の1文字目を大文字に、残りを小文字に変換する
print(text.title())  # Hello World!

text = "HELLO world!"
# 最初の文字を大文字に、残りを小文字に変換する
print(text.capitalize())  # Hello world!

正直、使う場面を思いつかないのですが面白いですね。
英語ネイティブな人たちが使うのでしょうか。

pythonで文字と文字コードの相互変換

pythonで文字を文字コードに変えたり、文字コード(整数)をそれが表す文字に変換したりする方法のメモです。
これらはそれぞれ、 ord と chr という組み込み関数で実現できます。

ドキュメントはこちら。
組み込み関数

これらをアスキーコードへの変換やアスキーコードから文字への変換だと説明しているサイトもあるようですが、
ドキュメントに書かれている通りUnicodeに対応しています。
(もしかしたらpython2系の時代はアスキー文字だけだったのかな?)

組み込み関数なので何もインポートせずに利用可能です。
ただし、ordは文字のみを受けつけ、文字列を渡すとエラーになるので注意してください。

単純なサンプル。


print(ord("a"))  # 97
print(ord("あ"))   # 12354
print(chr(97))   # a
print(chr(12354))   # あ

numpyの線形代数モジュールで連立一次方程式を解く

numpyで作成できる配列によく似たオブジェクトのarrayですが、配列同様にネストして多次元のarrayを作成できます。
(日頃から意識せず普通に使ってますね。)
特に2次元のarrayについては、行列として扱うことが可能です。

そしてnumpyには、numpy.linalgという線形代数のモジュールがあり、
行列式や固有値、逆行列の計算などができます。

ドキュメントはこちら。
Linear algebra (numpy.linalg)

今回は基本的な例として次の連立一次方程式を解いてみましょう。
$$
\begin{eqnarray}
&x_0-4&x_1+2&x_2&=7\\
9&x_0-5&x_1+2&x_2&=-2\\
3&x_0-10&x_1+5&x_2&=3
\end{eqnarray}
$$

これは
$$
A = \left[ \begin{matrix}
1&-4&2\\
9&-5&2\\
3&-10&5
\end{matrix}\right]
$$
$$
b = \left[ \begin{matrix}
7\\
-2\\
-3
\end{matrix}\right]
$$
とおいて、 Aに逆行列が存在すれば、(つまりAの行列式が0でなければ)$A^{-1}b$を計算することで解けます。

それではやってみましょう。
行列式はlinalg.det(A)
逆行列はlinalg.inv(A)で算出できます。
行列の式はnp.dot(A,B)A.dot(B)か、A@Bなどの書き方があります。


# 行列の定義
A = np.array(
        [
            [1,  -4, 2],
            [9,  -5, 2],
            [3, -10, 5]
        ]
    )
b = np.array([7, -2, 3]).T

# Aの行列式の確認 (実装の都合でわずかに誤差が出ます。)
print("dat(A)=", np.linalg.det(A))

# 方程式の解
print("[x_0, x_1, x_2]=", np.linalg.inv(A)@b)

# 以下出力
dat(A)= 0.9999999999999893
[x_0, x_1, x_2]= [ -29. -223. -428.]

これで、連立一次方程式が解けました。
Aの行列式が微妙に1にならないのは実装されていているアルゴリズムの都合のようです。

Sympyで方程式を解く

一つ前の自己回帰過程のサンプルを算出する記事のコードの中で、
こっそりとSympyというのを使っていました。

これはpythonで数式処理を行うためのライブラリです。
簡単な使い方などは公式のチュートリアルのイントロを見ていただくとわかります。
SymPy Tutorial » Introduction

これで記事を終えてしまうとあんまりなので、今回は方程式を解く方法紹介します。
シンプルに
$$x^2-5x+6=0$$
を$x$について解いて $x=2,3$という解を得るコードです。


import sympy
# 記号として使いたい文字をsymbols関数で定義
x = sympy.symbols('x')
# 多項式の定義
f = x**2 - 5*x + 6
print(f)  # 出力 : x**2 - 5*x + 6
# solveに渡すことで、 f = 0 という方程式を xについて解いてくれます。
sympy.solve(f, x)  # 出力 [2, 3]

多項式の展開や因数分解、微積分など、結構色々なことができるライブラリなので便利です。
このブログでも順次各機能を紹介したいと思います。