Pythonでマルコフチェーンを用い、天気の遷移確率を求めよう
本記事の目的
ベイズ推定記事のための前記事第二弾。前回はモンテカルロ法を用いて円周率を導出した。
このモンテカルロ法と本記事のマルコフチェーンを組み合わせた手法をMCMC法(マルコフ連鎖モンテカルロ法)といい、ベイズ推定を行う際によく用いられている。なので、ベイズ推定を学ぶ前にマルコフチェーンについても学ぶ必要があると思い、記事にまとめた。マルコフチェーンとは
表面だけ説明していく。マルコフチェーンとは、事後データが前データの中でも直前のデータのみに依存するモデルを指す。数学的に書くなら、時刻t+1のデータ
理論:天気の遷移確率の導出
パッと思い浮かんだのが、「前日の天気が今日の天気にどのくらい関係しているか」だったので、これを行っていく。こちらのサイトを頼らせていただいた。
天気は簡潔にするため、0:晴、1:曇、2:雨の3通りとし、1日目の天気を配列の0番目、2日目の天気を配列の1番目、・・・、としていって遷移確率を求める。また、遷移確率と難しく書いているが(前日の天気,今日の天気)の各確率、つまり(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)の9通りの確率を求めているに過ぎない。
実践:天気の遷移確率の導出
早速、コードを記す。
import numpy as np
import itertools
weather=np.array([1, 1, 2, 1, 0, 1, 2, 0, 1, 1, 0, 2, 0, 0, 2, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 2, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 2, 1,
0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 2, 1, 1, 1, 1,
0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1,
1, 2, 1, 0, 0, 1, 0, 1, 2, 2, 1, 0, 0, 1, 1, 1, 0, 0, 1,
1, 1, 1, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 1, 2, 1, 1, 1, 1,
1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 2, 0, 1, 2, 1, 0, 2, 1,
0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 2, 2, 2, 0, 0, 2, 1, 1, 2,
1, 1, 0, 1, 0, 0, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 0, 2, 1,
0, 0, 0, 0, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
2, 2, 0, 0, 1, 0, 0, 2, 0, 1, 0, 2, 1, 0, 2, 0, 0, 2, 2,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 2, 1,
0, 2, 1, 0, 2, 2, 2, 2, 0, 1, 1, 1, 1, 1, 0, 0, 2, 1, 1,
1, 2, 1, 1, 1, 1, 1, 2, 0, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2,
2, 2, 1, 1, 1, 0, 1, 0, 0, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1,
1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 2, 1, 0, 2,
1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 0, 0, 0, 2, 0, 1,
0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0])
def trans_prob(data):
len_data=len(np.unique(data))
zero = np.zeros((len_data,len_data))
for i in range(len(data)-1):
j=i+1
for before_we, today_we in itertools.product(range(len_data), range(len_data)):
if data[i] == before_we and data[j] == today_we:
zero[before_we][today_we] += 1
sum = np.sum(zero, axis=1).reshape((len_data,1))
prob = zero / sum
return prob
print(trans_prob(weather))
-
weather=np.array~で何をやっているか
理論編でも述べたように、適当に0:晴、1:曇、2:雨として配列に入れている。各自、適当に変えて試してみるのもよい。
-
len_data=len(np.unique(data))で何をやっているか
np.unique()
で重複を削除した要素配列を取得している。今回だと[0,1,2]が得られる。これにlenを付けてあげることで、要素数3が得られる。
-
zero = np.zeros((len_data,len_data))で何をやっているか
基本形はnumpy.zeros(shape, dtype=float, order='C')
であるが第二引数と第三引数は大体省略されている。第一引数のshapeは配列の行列数を示しているが、2×3のゼロ行列を作るときはnp.zeros((2,3))
のようにshape自体を()で囲むことに注意。
-
j=i+1で何をやっているか
iは前日の天気を表す要素の配列番号、jは当日の天気を表す要素の配列番号としているので、i+1としている。
-
for before_we, today_we in itertools.product(range(len_data), range(len_data))で何をやっているか
ここが本記事の重要ポイント。itertools.product
は直積を示している。range(len_data)
は[0,1,2]を表しており、数式にすると
となる。理論編で述べた遷移確率の各要素ですね。これをfor文で回し、if data[i] == before_we and data[j] == today_we
で適当に決めた天気配列weatherを(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)に割り振っている。
-
sum = np.sum(zero, axis=1).reshape((len_data,1))で何をやっているか
上記はカウント数となっているので、確率にするためには全体数で割る必要がある。前日の天気は必ず0:晴、1:曇、2:雨のどれかになるとし、横方向(同じ行)の要素を合計するnp.sum(zero, axis=1)
を用いて遷移確率を求めている。
結果
結果は次のようになると思う。
[[0.51515152 0.34848485 0.13636364]
[0.31360947 0.55029586 0.13609467]
[0.21428571 0.51785714 0.26785714]]
分かりにくいが、行番号が前日の天気、列番号が当日の天気を表しているため、(0,1):晴→曇の遷移確率は0.34848485、(2,0):雨→晴の遷移確率は0.21428571などの結果が分かる。もっと見やすくしたい方は、ヒートマップなどを用いてみてください。本記事で伝えたかったのは、直前データから現データの確率を求める手法をマルコフチェーンといい、前記事で述べたモンテカルロ法と組み合わせて、ベイズ推定を行っていること。
おまけ:47都道府県の天気遷移確率の導出
上記では、適当にナンバリングしたデータで天気の遷移確率を求めたが、次は気象庁の実データでやってみたいと思う。ここではWEBスクレイピングを用いて、47都道府県の先月の天気遷移確率を導出してみた。ただ、WEBスクレイピングに関しては、やり方次第でサーバーへの負荷を高め、業務妨害になるなど、色々危険なので、プログラミングコードは載せず、出力画面と自分の躓きメモを記すだけにする。自分用ノートみたいなものなので、興味がある方だけ見てもらえたらと。では、出力画面をどうぞ。
出力画面(静岡県)
出力画面(和歌山県)
これらのデータは気象庁「過去の気象データ検索」を加工して作成している。いつも通りの雑なフロントに加えて、計算日の先月データということで、約30個のデータしかなかったため、遷移確率が0となっているデータがあるが、仕方ない。WEBスクレイピングを試せたので良し。
また、今回は、こちらのサイトを参考にして、作成している。
以下、自分用メモを記す。
-
HTMLとCSSについて
後々出てくるHTMLとCSSについてまとめておく。Web情報はHTMLとCSSでほとんど構成されている。簡潔にまとめるとHTMLは文章構造、このHTMLで表す文章の色や文字サイズなどの装飾機能をCSSが担う。Google Chromeを用いた実際のコードの確認方法は次の通り。まず、適当なウェブサイト上で右クリックすると「検証」というボタンがあると思う。これをクリックするとウィンドウが現れ、上の方にそのウェブサイトのHTMLコードが確認できると思う。また、そのHTMLコード上で右クリックし、Copy→Copy selectorでHTMLのCSSコードが確認できる。
<a>HTMLの例です</a>
*{color:red}
-
WEBスクレイピングのライブラリについて
スクレイピングはデータの取得→抽出というのが一連の流れである。スクレイピングの代表的なライブラリにはRequests、Beautiful Soup、Seleniumがあるが、Requestsは主にデータ取得、Beautiful Soupは主にデータ抽出、Seleniumはデータ取得とデータ抽出共にできるが、遅いというデメリットがある。そのため、RequestsとBeautiful Soupを用いるのが一般的である。こちらのサイトを参考にさせていただいた。
-
ライブラリBeautiful Soupの使い方について
Beautiful Soup()
の第一引数はライブラリrequestsで取得した解析対象のHTML、第二引数は解析機の種類を指す。解析機には追加ライブラリ不要の"html.parser"や高速に解析できる"lxml"など様々ある。これらの情報に関しても上記のサイトを参考にするとよい。
-
.select_oneの引数について
上記で解析するサイトと解析機の種類を決めたら、次にそのサイトのどこを解析するか細かく設定していく。その設定の仕方の一つが.select_one(CSSセレクタ)
である。CSSセレクタの調べ方は上記の「HTMLとCSSについて」を参考にしてほしい。
-
月の日数の取得方法について
calendar.monthrange(年,月)
を用いる。これを行うと、月初日の曜日(月曜が0、日曜が6)と、月の日数の情報がタプルで取得できる。なので、月の日数のみが欲しいときは最後に[1]を付ける。
calendar.monthrange(2022, 8)[1]
最後までお読みいただきありがとうございました。お疲れ様でした。
Discussion