Pythonでmode(最頻値)を算出する最良の方法
Pythonでたまにmode(最頻値)を算出したくなるのですが、いつもどう算出するか迷います。算出方法を整理して、処理時間も計測してみました。
5つの方法
代表的な方法は以下でしょうか。思いついたものを試してみました。
- statistics.mode
- scipy.stats.mode
- numpy.unique
- pandas.DataFrame.mode or pandas.Series.mode
- collections.Counter
最良の方法(結論)
どれでも良い。が私が至った結論です。強いて言うならpd.DataFrame
を使っていたらそのままpandas
を使って、そうでないなら scipy
かstatistics
を使う。になると思います。状況に応じて好きなのを使えばいいと思います。
以下の視点で違いを考察しました。
# | 視点 | 差異 |
---|---|---|
1 | 最頻値が複数ある場合の挙動 | あり |
2 | 関数一発か | あり |
3 | 処理速度(最頻値のうち、最小の値を求める場合) | ほぼなし |
1. 最頻値が複数ある場合の挙動の差
特に注意しなければ行けないのが、mode(最頻値)が複数ある場合の挙動です。例えば、[1, 2, 2, 3, 3, 4]
の最頻値は[2, 3]
です。ライブラリや関数によって、以下のように挙動に違いがあるみたいです。statistics
はPythonのバージョンによって挙動が異なるので注意が必要です。
挙動 | 方法 |
---|---|
最頻値を複数返す | pandas, statistics(Python3 3.8) |
最初に出現した値を返す | statistics(Python3 3.8), collections |
最小の値を返す | scipy |
例外を返す | statistics(Python3 < 3.8のみ) |
自分で処理 | numpy |
2. 関数一発かの差
numpy
とcollections
は自分で処理を書く必要があります。自由度は高いですが、特別理由がない限りは自分で書く必要もないでしょう。あと最頻値が複数あるケースで、一つに絞り場合は結局max
だかmin
だか自分で処理する必要はあります。
方法 | |
---|---|
関数一発 | scipy, pandas, statistics(Python3 3.8) |
自分で処理 | numpy, collections |
3. 処理速度の差
結果は「scipy
と numpy
が早い」というのと、驚いたのが「pandas
もそれなりに早い」という点です。個人的にはmodeに処理速度を求めることがそんなにないと思いますので、気ににしなくて良いかなと。pd.DataFrame
やpd.Sereis
を使っている場合はmode
メソッドをそのまま使えばいいと思いますが、modeを算出するためだけにわざわざpd.Series
とかにキャストするのは個人的に違和感があります。
求め方
メモとして、それぞれの求め方を羅列していきます。
設定
入力する値は以下です。ヒストグラムを見ると、最頻値が[2, 3, 4]
であることが分かります。
import matplotlib.pyplot as plt x = [1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 6] # has two modes: [2, 3, 4] # Plot histogram fig, ax = plt.subplots() ax.hist(x, bins=np.arange(8) - 0.5, ec='white') ax.set(xlabel='values', ylabel='Frequency', title='Distribution of x')
1.statistics.mode
標準ライブラリのみで算出することができます。Python3 < 3.8では複数の最頻値があった場合に例外を吐きますので、version3.8+を使うのがおすすめです。
# 最頻値のうち、最初に出現した値を返す@version3.8+ import statistics statistics.mode(x) # 2
3.8以上では、mode
関数は最頻値があった場合、出現した最初のものを返す仕様です。複数最頻値を求めたい場合はmodemulti
関数を使いましょう。
If there are multiple modes with the same frequency, returns the first one encountered in the data.
# 複数の最頻値を返したい場合はmodemulti()を使う statistics.multimode(x) # [2, 3, 4]
例えば、複数の最頻値の中から最小値のみを求めたい場合
# statisticsで複数の最頻値のうち最小の値のみを返す min(statistics.multimode(x))
2. scipy.stats.mode
scipy.mode
は、値と頻度に分けて出力を抽出できます。scipyのmode
関数は最頻値があった場合、最小のものを返す仕様です。
If there is more than one such value, only the smallest is returned.
# scipyで最頻値を算出 import scipy mode, count = scipy.stats.mode(x) # ModeResult(mode=array([2]), count=array([3])) mode[0] # 2
1つしか最頻値返さないくせにちょっと仰々しい印象を持ちました。
3.numpy.unique
numpy
には最頻値を求める関数はなさそうですが、あえて求めるなら以下でしょうか。
# numpyで最頻値を求める import numpy as np uniqs, counts = np.unique(x, return_counts=True) uniqs[counts == np.amax(counts)] # [2, 3, 4]
他のライブラリをインポートする気分じゃないときは良いかもしれません。
複数の最頻値の中から最小値のみを求めたい場合はもちろん以下のようになります
# numpyで複数の最頻値のうち最小の値のみを返す uniqs[counts == np.amax(counts)].min() # 2
4. pandas.Series.mode
pandas
で最頻値を求める場合は以下です。複数の最頻値が返ります。
# pandasで最頻値を求める import pandas as pd pd.Series(x, name='values').mode() # 0 2 # 1 3 # 2 4 # dtype: int64
処理速度を計測すると意外なことに、キャストを含めてもpandas.Series.mode
が処理が早かったです。
複数の最頻値の中から最小値のみを求めたい場合は以下のようになります
# pandasで複数の最頻値のうち最小の値のみを返す pd.Series(x, name='values').mode().min() # 2
5. collections.Counter
最後にcollections.Counter
です。複数の最頻値のうち最初の値を返す場合は以下のようになります
from collections import Counter # collections.Counter複数の最頻値のうち最小の値のみを返す Counter(x).most_common()[0][0] # 2
ただ複数の最頻値をすべて返したい場合は、あまりCounter
を使う利点がないように思いましたので割愛します。
処理速度の比較
処理速度を比較してみました。最頻値を求める際、あまり処理速度をきにすることはないので、個人的には参考程度に思っています。
フェアな比較かどうか微妙なところですが、仕様は以下です。
- 最頻値の最初のものを一つ求める
- 入力の1次元配列(リスト)の長さを変えて計測
- 1条件ごと50回ずつ計測
コードはarticle_mode.py · GitHubこちらにあります。
結果は以下のようになりました。これだけ見るとstatistics
, collections
は入力の長さが大きくなると比較的遅くなるように見えますが、何秒もかかる訳ではないので大差無いと思います。
標準誤差をいれると以下です。
どれでも良い。という結論を出すために長々検証しました。分析としてはコスパが悪くて悪い見本ですが、お役に立てればこれ幸い。日々精進
Python:venv + pyenvでの環境構築
一つのOS上で複数バージョンのPythonを切り替えるためのツール、venv
+pyenv
のメモです。2020年の今、Pythonバージョン3.3以上しか使うことないだろうということで、個人的に venv
+pyenv
の組み合わせがシンプルで良いかと思います。本投稿では venv
+pyenv
の役割、導入手順および使い方を整理します。余裕があればpoetry
についても触れたいと思います。
前置きが長くなっていますので、venv
+ pyenv
での仮想環境構築だけ見たい方は導入と使い方、もしくは最後だけ見ればいいかもしれません
実行環境: macOS Mojave
モチベーションと用途の整理
まずこれらのツールを導入する際の2つのモチベーションを整理します。たぶん仮想環境
という言葉がこの両者を含んでしまっているのが、混乱しやすい原因の一つと思います。
1.パッケージ環境の切り替え
一般にプロジェクトによって必要な外部パッケージ(ライブラリ)は異なります。例えば、画像系ではOpenCVを使うかもしれませんが、画像以外のプロジェクトではなかなか使わないでしょう。不必要なパッケージが多くインストールされている環境は好ましくないため、プロジェクトごとに必要最小限のパッケージがインストールされた環境を用意することになります。このプロジェクトごとのパッケージ環境を切り替えるためのツールの代表がvenv
です。
2.Pythonバージョンの切り替え
もうひとつのモチベーションとして、一つのOS上でPythonのバージョンを切り替えて使いたいことがあります。特に以前はPython2系を使っているプロジェクトも多かったため、Python3系とPython2系を切り替えて使う必要がしばしばありました(Googleも2系だったし)。それ以外でも一般に開発を行っていると細かいバージョンの切り替えが必要になります。このPythonインタプリタの切り替えを行うツールの代表がpyenv
です。
venvとpyenvの用途
使い手から見たvenv
とpyenv
の主な役割を整理すると、以下のようになります1。表にもあるように、これらの機能を提供するツールは他にも存在します。
ツール | 用途 | その他のツール |
---|---|---|
venv | プロジェクトごとに異なるパッケージをインストールしたいとき | virtualenv ,pyenv-virtualenv , conda |
pyenv | プロエジェクトごと異なるPythonのバージョンを使いたいとき | virtualenv , conda |
なぜvenv + pyenvか
どのツールを選ぶかは、主に次の要素に依存する気がします。
- 使いたいPythonのバージョン
- 使っているOS
- 好み
Python3.3以上では、パッケージ環境(仮想環境)の切り替えとしてvenv
が標準で使えます。Pythonのバージョンを一つしか使ってない方はこのvenv
を使えば十分です。たとえばPythonはOSにインストールされた3.6を使って、プロジェクトによってパッケージ群だけ切り替えるケースがこれに当たります。開発しているわけでは無ければ、ほぼこれで事足りると思います。
同様に複数のPythonバージョンを切り替えたくなった場合でも、Python3.3以上のバージョンの切り替えであれば、venv
に加えてPythonインタプリタ切り替えツールとして pyenv
を使えば良いと思います。私はこのケースに該当します。
一方でバージョン3.3未満のPythonを使う場合はvenv
が使えないので、pyenv
+ pyenv-virtualenv
を使うのが良いでしょう。pyenv-virtualenv
はpyenv
のプラグインで、仮想環境の切り替え機能を提供します。
結局、ニーズに合わせて下表のようにまとまる気がします。
Python3.3以上のみ使う | Python3.3未満も使う | |
---|---|---|
パッケージ環境のみ切り替えたい | venv |
virtualenv (?しらん) |
パッケージ環境とPythonバージョンを切替えたい | pyenv +venv |
pyenv +pyenv-virtualenv |
一昔前は、pyenv
がWindowsに対応していなかったことと、データ分析界隈でAnaconda
がよく使われていたこともあり、Windowsではconda
によるパッケージやインタプリタ切り替えを行っていました。現在はpyenv-win
というのがあるらしく状況が変わっているようです。私も以前WindowsではAnaconda
を使っていましたが、環境が管理しにくい印象があって現在は使わなくなりました(というかWindowを使っていない)
導入と使い方
venv
+ pyenv
で十分である。という勝手な結論を前提にして、それらの導入方法と使い方を簡単に説明します。
venvの導入
Python3.3以上ではvenv
は標準ライブラリに含まれているので、導入の必要はありません。
venvの使い方
仮想環境の一般的な利用手順は次のようになります
- プロジェクトごとに仮想環境を作成
- 仮想環境に入る
- 環境構築や開発を行う
- ライブラリのインストール、環境変数設定など
- コーディング、実行
- 仮想環境を抜ける
1.プロジェクトごとに仮想環境を作成
作成する前に、念のためシステムにインストールされているPythonのバージョンを確認しましょう。
# pythonのバージョン確認 $ python -V >>> Python 3.8.5
ここでバージョンが3.3以上ならば問題ないのですが、それ未満だとvenv
は使えないので3.3以上をインストールする必要があります。インストール方法は"python3 インストール"とかでググってみてください。
バージョン3.3以上でしたら、pythonからvenv
モジュールを実行して仮想環境を作成できます。このとき同時に仮想環境の情報を保存するためのディレクトリパスを指定します。
# .venv/というディレクトリに新しい仮想環境を構築 $ python -m venv .venv
ここでは.venv
というディレクトリ名を指定しているので、上記コマンドを実行すると.venv/
がカレントディレクトリに作成されます。
2.仮想環境に入る
上で指定した.venv/
内にあるactivate
コマンドを使って仮想環境に入ります。仮想環境に入るとプロンプトの前に(.venv)
という表示が現れるので、環境に入ったことが分かります。
# 仮想環境に入る $ source .venv/bin/activate # (.env)という表示がプロンプトの前に現れる (.venv)$
現在の環境は作成したばかりのまっさらな仮想環境なので、外部パッケージは何もインストールされていません。ためにしnumpy
を呼び出してみるとエラーが出ることが分かります。
# pythonのインタラクティブモードに入る (.venv)$ python Python 3.8.5 (default, Aug 22 2020, 07:29:50) [Clang 10.0.1 (clang-1001.0.46.4)] on darwin Type "help", "copyright", "credits" or "license" for more information. >> # numpy をインポートしてみるとエラーが出る >>> import numpy as np Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'numpy'
3.環境構築や開発を行う
このまっさらな環境に必要なパッケージをインストールし、コードを書いて、実行していきます。
パッケージのインストール
パッケージのインストールはpip
でできます。仮想環境に入った状態でインストールしたパッケージは仮想環境内のみで利用可能です。外の環境に影響を及ぼしません。
# numpyとjupyterをインストール (.venv)$pip install numpy jupyter
Pythonの実行
仮想環境に入った状態で、pythonを実行すれば良いです。ファイルを指定したりして、普通に実行してください。
# いつもどおりpythonを実行すれば良い (.venv)$ python example.py
Jupyter notebookを使う
仮想環境に入った状態でjupyter notebookを立ち上げれば、仮想環境下でnotebookを走らせることができます。
(.venv)$ jupyter notebook
Hydrogenで使う(kernelspecをインストール)
仮想環境に入った状態でkernelspecをインストールすれば、AtomのHydrogenや他のJupyter notebookからこの仮想環境を選択できるようになります。
(.venv)$python -m ipykernel install --user --name kernel_name
4.仮想環境を抜ける
deactivate
コマンドで仮想環境から抜けられます。deactivate
とだけ打てばOKです。プロンプトの前の表示こが普段どおりに戻れば、環境から抜けられています。
# 仮想環境に入る (.venv)$ deactivate # (.env)という表示がプロンプトが消える $
pyenvの導入
つぎはPythonバージョンの切り替えツールpyenv
です。pyenv
は環境変数PATH
を必要に応じて変更することでPythonインタプリタを意図通りに切り替えてくれます。pyenvのインストールはpyenvのgithubページの手順に沿って行えば簡単にできます。
ここではGithub経由、かつbashを使っている環境でのでインストール例を本家ページから引用します。環境が異なる方は本家ページを見てください。
# pyenvのインストール。clone先は$HOME/.pyenvがおすすめ git clone https://github.com/pyenv/pyenv.git ~/.pyenv # 環境変数の設定 echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile # PATH挿入と自動補完の設定。 eval "$(pyenv init -)"がきちんと.bash_profileに記載されたか確認しよう echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.bash_profile # シェルの再起動 exec "$SHELL"
pyenvで複数のPythonのバージョンをインストール
pyenv
を使って任意のPythonバージョンをインストールしてみましょう。pyenv install --list
で利用可能なPythonのバージョン一覧を確認できます。anaconda
やminiconda
もインストールできるのが面白いですね。
# pyenvで利用可能なPythonバージョン一覧を確認 $ pyenv install --list Available versions: 2.1.3 2.2.3 2.3.7 2.4.0 ... 3.8.3 3.8.4 3.8.5 3.9.0b5 3.9-dev 3.10-dev activepython-2.7.14 activepython-3.5.4 activepython-3.6.0 anaconda-1.4.0 anaconda-1.5.0 anaconda-1.5.1 ...
ここでは3.8.3
をインストールしてみたいと思います。インストールにはちょっと時間がかかります。
# Python3.8.3をインストール $ pyenv install 3.8.3
インストール完了後、pyenv versions
とすれば、現在の環境にインストールされているPythonインタプリタ一覧が確認できます。
# インストールされているPython一覧を確認する $ pyenv versions * system (set by /Users/daisuke/.pyenv/version) 3.8.3
上の例では、system
と記載されているものと3.8.3
がインストールされているのが確認できます。 *
マークがついているのが現在設定されているPythonのバージョンです。system
というのは元からシステムにインストールされているpython
コマンドを表します。
pyenvで複数のPythonのバージョンを切り替え
pyenv
でのバージョンの切り替え方法は、いくつかあるのですが、ここでは現在のシェルで一時的にバージョンを切り替える方法pyenv shell
を説明します。
特定のバージョン(例えば、3.8.3
)に切り替えたい場合は、pyenv shell 3.8.3
で可能です。元のバージョンに戻りたいときは、shellを終了するか、同じコマンドでsystem
を指定すれば良いかと思います。
# バージョン3.8.3に切り替える $ pyenv shell 3.8.3 # 現在のバージョンを確認する $ pyenv version 3.8.3 (set by PYENV_VERSION environment variable) # バージョン3.8.3から元にもどす $ pyenv shell system
venv + pyenvで任意のバージョンの仮想環境を構築する
今回これを書きたかっただけなのですが、前置きが長かった。。。。
venv
+pyenv
を使って、任意のバージョン仮想環境(パッケージ環境)を構築したいときは以下のようにします。単純に構築したいPythonのバージョンに切り替えて、仮想環境を作成するだけです。
# pyenvでPythonバージョンを切り替える $ pyenv shell 3.8.3 # venvで新しい仮想環境を構築 $ python -m venv .venv
これにより、指定のPythonバージョンの仮想環境が.venv/
に構築されます。仮想環境に入って実際に確認すると意図通りのPythonバージョンに設定されていることが分かります。
# 仮想環境に入る $ source .venv/bin/activate # 仮想環境内のPythonのバージョン確認 (.venv)$ python -V Python 3.8.3
ちなみに仮想環境内のPythonとpyenv shell
した直後のPythonは同じバージョン(3.8.3)ですが、2つのコマンドの実態は別物になります。
下記のように仮想環境内では.venv/
の中にインストールされたPythonインタプリタを参照しています。
# 仮想環境のPythonは.venv/内のpythonを見にいっている (.evn)$ which python /Users/messefor/prj/blog200822/.venv/bin/python
一方でpyenv shell
による切り替え直後はpyenv
によってPATHに挿入されたPythonインタプリタを参照しています。shims
というのがpyenv
によって挿入された参照先になります
$ pyenv shell 3.8.3 $ which python /Users/messefor/.pyenv/shims/python
久々にPythonの環境構築の話題を書いたら長くなりました。。。。次はpoetry
についてまとめたいと思います。日々精進
参考文献
時系列分析:ARモデルの性質と定常性(1/2)
ARモデルはシンプルな時系列モデルですが、自己共分散や自己相関はMAモデルほど簡素な表現はなく、Yule-Walker方程式のような連立方程式を解いていくのが一般的のようです。本投稿では2回に分けてARモデルの期待値、自己相関および定常性などの性質を整理したいと思います。
ARモデル
ARモデルは現在の値が、過去の自分自身の状態を引きずっている(記憶している)という構造を持つモデルです。世の中の現象ってだいたい過去の状態を引きずっていそうです。 ARモデルではこの過去の記憶を、過去の値の線形和で表現します。p次のARモデルは以下のように定式化されます。
ここでは定数項でベースのレベルを決めます(直流成分)。中心化をしてを前提とすることも多いです。はホワイトノイズで、は、以前には含まれていない時点で新しく加わった情報でイノベーションとも呼ばれています。
AR(1)の例
いくつかAR過程の具体例を見てみます。ここではとし、の値をいくつか変えて生成し、その原系列とACFをプロットします。生成に使ったPythonスクリプトはw3_1.py · GitHubに置いています。
の場合(ランダムウォーク ):
1次のAR過程のうちかつホワイトノイズに同分布を仮定するとランダムウォークと呼ばれる有名な過程になります。
生成したランダムウォークの推移とACFは次のようになりました。
の場合:
の場合:
同じAR(1)過程でものとる値によって印象は随分違います。 の例が発散していることからも明らかなようにAR過程はの値によって定常性も異なります。
期待値
AR過程の期待値を計算してみます。
AR(1)の場合:
1次のARモデルの期待値をとると
ここで定常を仮定とすると期待値は時刻に依存しないため、となります。と置くと次のようになります。
AR(p)の場合:
p次の場合も同様に期待値をとって
定常を仮定して、と置くと次のようになります。
また上式より直流成分のとき、定常なARモデルの期待値は常に0になることが分かります。
自己分散・共分散
自己分散・共分散は少し工夫が必要になります。まずは愚直に計算してみましょう。
AR(1)の場合:
分散:
AR(1)モデルの分散を計算します。
定常を仮定して、と置くと以下のようになります。
自己共分散:
との共分散をとります。
2行目への展開は、共分散を分配しています。3行目への展開は、定数や独立な変数との共分散は0であることを利用しています。分散・共分散の計算は以下を参考にしてください。
ここではラグの自己共分散なので、またとのラグはなので同様にをと置くと
という自己共分散の関係式が現れます。の自己共分散は式を使って次のように表せます。
Yule-Walker方程式
さらに式をで割ると、自己相関でも同様の関係が成り立つことが分かります。
式はYule-Walker方程式と呼ばれているものです。
AR過程の自己相関がが従うAR過程と同一の係数をもつ差分方程式に従うことを示す
引用:経済・ファイナンスデータの計量時系列分析- 沖本 竜義
式の関係性を繰り返し適用すると、ラグの自己相関はを使って次のように書けます。
ラグ0の自己相関なので
このようにAR(1)の自己相関は非常にシンプルな形で表現できることが分かります。またのとき自己相関はが大きくなるにつれて減衰することが分かります。
AR(2)の場合:
AR(1)のケースでは素直に分散・自己共分散を算出できましたが、同じことをAR(2)でも試してみます。
分散:
AR(2)モデルの分散を計算します。
定常を仮定するとといえるので
このようにとの関係式が出てきました。つまり分散を知るには、ラグ1の自己共分散が必要になります。
自己共分散:
ラグ1の自己共分散を計算します。
式にを代入して整理すると、
ということで分散が求まりました。さらには式にを代入して
となります。同様に以降も求めることができます。 このようにAR過程ではあるラグでの自己共分散を求める際、他のラグの自己共分散の値を使って連立方程式を解く必要があります。これらの操作は次のYule-Walker方程式を使って統一的かつ簡潔に表現できます。
次回、Yule-Walker方程式を用いたAR(p)の自己相関関数や定常性について整理します。
まとめ
- ARモデルは過去の状態の線形和使って現在の値を表現します
- ARモデルの定常性は係数によって決まります
- AR(1)の期待値、分散、自己相関係数はそれぞれ次のようになります
- AR過程の自己相関はが従うAR過程と同一の係数をもつ差分方程式に従います(Yule-Walker方程式)
- ARモデルの分散や自己相関は上記の関係性を使うことで求めることができます
AR(2)の共分散の式展開が合わずに時間を無駄に使いました。とほほ。中年の残りすくない時間を。。。日々精進
参考文献
時系列分析:MAモデルの反転可能性
自己相関構造は時系列モデル選択の重要な基準になります。しかしながらMAモデルでは同じ自己相関構造を持つモデル(パラメタ)が複数存在するため、その中からMAモデルを一つ選ぶ基準が必要です。この基準として反転可能性の議論がでてきます。本投稿ではMAモデルの反転可能性について確認します。 時系列分析において反転可能性は重要な性質の一つで、ARモデルの収束性判定でも用いられます。
MAモデルの選択
自己相関構造は時系列モデル選択の目安となりますが、MAモデルでは同じ期待値や自己相関構造を持つパラメタは複数存在します。以下の2つのMA(1)モデルそれぞれの自己相関構造を確認してみましょう。
の期待値と自己共分散を定義どおりに計算すると
ホワイトノイズの性質から時点が一致する自己共分散以外は0になるので、式はラグによって場合分けされ
これより自己相関は
なお一般にMA(q)モデルの自己相関は次のようになります。これが分かっていれば、上記はすぐに算出できます。
MA(q)モデルの自己相関の考え方については以下を参考にしてください。
同様にの期待値、自己相関を計算すると
2つのモデルの期待値、自己相関は全く同じになります。沖本本によると
同一の期待値と自己相関構造をもつMA(q)過程は一般的に個あることが知られている
とのことなので、自己相関構造からMAモデルを一つ選ぶ基準が必要です。
反転可能性(invertiblity)
この基準として登場するのが反転可能性(invertiblity)です。MAモデルは条件によって、以下のようにAR()で表すことができます。これを反転と呼んでいます。
の形を見ると分かりやすいですが、AR()に反転させた形でみるとがと、過去のの線形和の差になっています。これは現在ののうち、過去のを使っても表現できない部分が である、と解釈できます。反転可能なMA過程は、解釈が容易になるなど良い性質を持っています。
MAモデルの反転のイメージ
なぜMAモデルがAR()で表せるのでしょうか。MA(1)モデルの例で確認します。ここでは等比数列の部分和を用いて導出してみます。まずMA(1)モデルをラグオペレータ(後退オペレータ)の多項式を用いて表します。
ラグオペレータについては以下の記事を参考にしてください。
ここではラグオペレータの多項式です。ここでもしの逆演算を定義できるなら次のように撹乱項をとラグオペレータで表現できます。
実際には単位円上の複素数と考えて良いようで、は次のようになります。
ここで高校数学の等比数列の部分和から以下の関係性を使います。
をにとばした場合、式はのとき収束し次のようになります
この関係性を使って式を展開してみます、式で、と考えて、のとき
さらにこれを用いて、式を展開すると
が成り立ちます。これで式と同じ形になりました。
ここでの条件はと、 が単位円上の複素数であることを考えるとのため、最終的にが満たすべき条件となります。
MA過程の反転可能条件
MA(1)の反転を行う過程では、という条件が必要ということがわかりました。一般にMA(q)過程の反転可能性の条件はMA特性方程式の根を使って設定されます。MA特性方程式とは、ラグオペレータ多項式のラグオペレータを複素数と置き換え、多項式を0とした方程式で次の形をとります。
MA過程の判定可能性条件:
MA特性方程式の根がすべて単位円の外に存在する(の解の絶対値が1より大きい)場合、そのMA過程は判定可能
この反転可能性の制約を入れることで、期待値と自己相関構造からMAモデルは一つに定まります。
MA(1)の反転可能性
先程のMA(1)の例で反転可能性条件を確認してみます。 MA(1)モデルのMA特性方程式は、式のラグオペレータ多項式を考えて
この方程式の根はの一つ 。反転可能になるためにはとなればいいので
よってが条件となります。先程のの条件とも一致します。
まとめ
- MAモデルは同じ自己相関構造をもつ複数のモデルが存在します
- 反転可能性の制約を入れることで、自己相関構造からMAモデルは一つに定まります
- MA(q)の反転可能性条件は以下のMA特性方程式のすべての根が単位円の外側にあることです()
MAモデルをを入力とするシステムとして捉える感じが出てきました。制御や信号処理とつながるのは興味深いですね。いつかすべて理解出来る日まで日々精進
時系列分析:ホワイトノイズとiid
ホワイトノイズとiid(independent and identically distributed:独立同分布)を混同してしまう事があります。本投稿ではこの2つを整理します。普段の分析ではホワイトノイズとiidをそこまで厳密に区別するケースは少ないと思います。理論を追っていて、前提条件として同一分布を意識するときぐらいでしょうか
ホワイトノイズとiidの違い(まとめ)
共通点:
ホワイトノイズとiidはどちらも確率変数の列がそれぞれ互いに独立です。なので自己共分散は0となります。
違い:
- ホワイトノイズはノイズの性質なので平均が0。一方で、iidは独立同分布といっているだけなので、平均値は必ずしも0とは限らない
- ホワイトノイズは独立でさえあればいいけど、一方でiidの系列は独立かつ同分布の必要がある
要素を全部まとめると次の表のようになります(意味合い重複した部分あり)
よく使う表記 | 平均 | 自己共分散 | 独立 | 同分布 | 定常性 | |
---|---|---|---|---|---|---|
ホワイトノイズ | 0 | 必要あり | 必要なし | 弱定常 | ||
iid | 必要あり | 必要あり | 強定常 |
iidのように同一分布に従うことは強い仮定なので、それに比べて同一分布を仮定しないホワイトノイズは仮定が弱いです。また定常性という視点では、ホワイトノイズは弱定常、iidは強定常になります。
ホワイトノイズの定義
沖本本の定義をそのまま引用するとホワイトノイズの定義は以下のようになっています。ホワイトノイズは平均値0、分散、自己相関(共分散)が0です。
すべての時点 において
iidの定義
同様にiidの定義は以下です
各時点のデータが互いに独立でかつ同一の分布に従う系列
引用:経済・ファイナンスデータの計量時系列分析 - 沖本 竜義 p11
分布が同じで独立な系列であることを要求しているだけなので、そもそもガウシアンのように期待値と分散のみで決定される分布かどうかも分かりません。
一般的な使い分け
我々がシミュレーションなどで誤差項を生成する場合、平均値0の正規分布に従うノイズを発生させることが多いと思います。これらのノイズは独立なのでホワイトノイズですが、同一の分布に従うためiidでもあります。いわゆるガウシアンホワイトノイズと言われるもので、iidになります。
各モデル誤差項の設定
沖本本やWikipediaを参考に各モデルの誤差項の仮定を確認してみたいと思います。結論から言うと、MAモデル、ARモデル、ARMAモデルともに撹乱項はホワイトノイズで、iidを仮定していません。ランダムウォークだけなぜかiidを仮定しているみたいです。
撹乱項の仮定 | |
---|---|
MAモデル | ホワイトノイズ |
ARモデル | ホワイトノイズ |
ARMAモデル | ホワイトノイズ |
ランダムウォーク | iid |
MAモデル
沖本本のMA(q)過程の定義では、MA(q)過程はホワイトノイズの線形和になっています。iidを仮定していません
引用:経済・ファイナンスデータの計量時系列分析 - 沖本 竜義 p24
Wikipediaでも同様にをwhite noise error terms
と記載しています。
ARモデル
ARモデルの撹乱項もホワイトノイズです。iidを仮定していません
引用:経済・ファイナンスデータの計量時系列分析 - 沖本 竜義 p26
Wikipediaでも同様にをwhite noise
と記載しています。
ARMAモデル
ARMAモデルの撹乱項もホワイトノイズです。iidを仮定していません
引用:経済・ファイナンスデータの計量時系列分析 - 沖本 竜義 p34
ランダムウォーク
ランダムウォークでは撹乱項にiidを仮定しています。
引用:経済・ファイナンスデータの計量時系列分析 - 沖本 竜義 p106
ランダムウォークをAR(1)過程と考えるならホワイトノイズでも良さそうなものですが、このiidの仮定が重要なのでしょうか。Wikipediaでも独立かつ同分布な Rd 値確率変数族
と書いてあります。
なぜランダムウォークだけiidを仮定しているのか分かってません。どなたかご存知でしたら教えて下さい。日々精進
参考文献
時系列分析:後退オペレータについて
時系列の勉強をしていると、後退オペレータ(Backshift Operator)というのが出てきます。このオペレータを使って時系列モデルを表現することで、簡潔で見通しの良い議論が可能になるようです。本投稿ではこれについて考察したいと思います。なお後退オペレータは、様々な呼び名があるようで、シフトオペレータ、時間シフトオペレータ、Backshift Operator、Backwardshift Operator など異なる記述で書かれています。
後退オペレータの定義
英語版Wikiでは次のように説明してあります
時系列分析において後退オペレータは時系列の要素に作用し、前の要素を生成する
In time series analysis, the lag operator (L) or backshift operator (B) operates on an element of a time series to produce the previous element.
つまり時系列のある時点の値を一時点前の に戻すような操作になります。
次のような時系列があったとすると
後退オペレータは以下のように定義されます。
ある時点の要素に後退オペレータを乗ずることで、一時点前の要素を得ることができます。
同様に二つ前の要素は
のようにアクセスできます。一般に時点前の要素は後退オペレータを用いて次のようにアクセスできます。
後退オペレータの多項式表現
後退オペレータを用いて時系列モデル(確率過程)を簡潔に表現するケースは多く見受けられます。その際に次のように後退オペレータの多項式を使って統一的にモデルを表現します。まずこのことを頭に入れて次の具体例を眺めると良いと思います。
後退オペレータの使いどころ
それでは具体例を見ていきましょう。以下の例はCourseraのここに記載している内容ほぼそのまんまです。
1. ランダムウォーク
まず以下のランダムウォークを考えます。
where
ランダムウォークを後退オペレータを用いて表すと
となります。
さらに後退オペレータの多項式を使って次のようになります。
where
ランダムウォーク自体がシンプルなので、さほど後退オペレータのありがたみは感じません。
2. MA(q)モデル
MA(q)モデルを見てみましょう。 ランダムウォークと同じですがMA過程なのでに対して後退オペレータを適用します
where
というのは簡潔ですね。
3. AR(p)モデル
またAR(p)過程は、次のようになります
where
というのも簡潔ですね。
4. ARMAモデル
上記のMA(q)とAR(p)を踏まえてARMAモデルも考えてみます。
where
ARMAモデルはのように大変スッキリした形で記述できることがわかります。ARモデルとMAモデルの表現も統一感があります。
5. 差分作用素
差分作用素は後退オペレータでも記述できます
1階差の場合:
2階差の場合:
一般に
後退オペレータは何なのか
ところで後退オペレータの実態は何なのでしょうか。ある関数に乗ずることで、をシフトさせるような作用素といえば、複素数が思い浮かびます。厳密ではないですが、後退オペレータは複素数そのものと考えて問題なさそうな気がします。
たとえば関数 があるとして、フーリエ級数に展開できるとすると
等間隔でサンプリングされたすると
これをにしたい場合、 を乗じてあげれば、なんかよさそうです。
なので、この場合で問題ないかなと思うのですが、どなたかご存知でしたら教えて下さい。
追記: 関数解析学の分野でシフト作用素として一般化されているものでした。折を見て、整理し直したいと思います。日々精進
参考文献
- Lag operator | WIKIPEDIA
- 経済・データの計量時系列分析 (統計ライブラリー) – 沖本 竜義
- Practical Time Series Analysis | Coursera
- Appendix B COMPLEX VARIABLES, THE SPECTRUM, AND LAG OPERATOR
- 時系列解析入門 – 北川 源四郎
はてなブログで日々数式を含んだ記事を書く
はてなブログを復活して2週間。はてなブログでTeXを使って数式を書くのが大変で当初ずっとイライラしていた。現時点での私の対応策を書きたいと思う。
まずQiitaと比べて圧倒的に面倒な点は次:
1の対策. Typoraで下書き
[1]に関しては、Typora — a markdown editor, markdown reader.を使うことで、markdown + TeX をリアルタイムにて下書きを確認している。私はTeXもそんなに慣れているわけではないのでリアルタイムにレンダリングしてくれないと苦しい
Typoraで下書きをすれば、TeXもリアルタイムに確認できる
TeXはmathpixで
ちなみにTeXコードも複雑な式はMathpix Snipを使って、手書きノートの写真から直接TeXコードに変換している。
たとえばこれが、
mathpix で読み込むとこうなる。
もちろん完璧ではないが、精度も十分。めちゃ便利。なおこれはpngで出力したもの。
2の対策. スクリプトで置換
当初、はてなブログでのTeXエスケープの方法を探り探りやっていたが、結局以下の記事に記載してあることが全てだった。
上記ブログに設置してあるフォームから変換できるのだが、作業の流れ上Typoraで書いた.mdファイルをそのまま一気に変換したかったので、とりいそぎ自前でPythonでスクリプトを書いた。
実行部分
path_art = 'article.md' path_art_cnvd = 'article_new.md' # 変換対象のテキストファイルの読み込み with open(path_art, 'r') as f: artile_strings = f.read() # 変換 rht = ConvertHatenaTex() artile_strings_new = rht.replace(artile_strings) # 変換後のファイルを保存 with open('article_new.md', 'w') as f: f.write(artile_strings_new)
やっていることは、$$~$$
と$~$
で囲まれたブロックを見つけてきて、愚直に必要部分を置換しているだけ。
全体
"""Convertace article with TEX syntax with Hatena Blog Syntax """ import os import re from typing import List, Callable from copy import deepcopy def replace_m(x: str, rep_map: List[dict]) -> str: for pattern, repl in rep_map: x = x.replace(pattern, repl) return x def add_tags(x: str, tag_pre: str, tag_post: str) -> str: """Concatenate string and tags""" return tag_pre + x + tag_post class ConvertHatenaTex: """Convert TEX syntax to Hatena Blog TEX Syntax. """ def __init__(self): self.pattern_block = r'\$\$([\S\s]*?)\$\$' self.pattern_inline = r'\$([a-zA-Z0-9!-/:-@¥[-`{-~ ]+)\$' self.rep_map_block = [('[', r'\['), (']', r'\]'),] self.rep_map_inline = [('[', r'\\['), (']', r'\\]'), ('_', r'\_'), ('^', '^ '), (r'\{', r'\\{') ,(r'\}', r'\\}')] tag_pre_b = '<div align="center">\n[tex:\displaystyle{\n\n' tag_post_b = '\n}]\n</div>\n' # tag_pre_b = '<pre>\n[tex:\n\n' # tag_post_b = '\n]\n</pre>\n' self.tags_block = (tag_pre_b, tag_post_b) tag_pre_i = '[tex:\displaystyle{' tag_post_i = '}]' # tag_pre_i = '[tex:' # tag_post_i = ']' self.tags_inline = (tag_pre_i, tag_post_i) def replace(self, artile_strings: str) -> str: x = deepcopy(artile_strings) x = self._replace(x, self.pattern_block, self.proc_in_blocks) x = self._replace(x, self.pattern_inline, self.proc_in_lines) return x def proc_in_blocks(self, x: str) -> str: """Process replacing lines in blocks""" x = replace_m(x, self.rep_map_block) x = add_tags(x, *self.tags_block) return x def proc_in_lines(self, x: str) -> str: """Process replacing in lines""" x = replace_m(x, self.rep_map_inline) x = add_tags(x, *self.tags_inline) return x def _replace(self, artile_strings: str, pattern: str, rep_func: Callable[[str], str]) -> str: """Extract block/inline parts and replace""" prog = re.compile(pattern) results = prog.finditer(artile_strings) replace_strs = [] n_init = 0 for mg in results: str_chunk = mg.group(1) str_chunk = rep_func(str_chunk) n_start, n_end = mg.span() str_pre = artile_strings[n_init:n_start] replace_strs.append(str_pre) replace_strs.append(str_chunk) n_init = n_end replace_strs.append(artile_strings[n_end:]) artile_strings_new = ''.join(replace_strs) return artile_strings_new if __name__ == '__main__': path_art = 'article.md' path_art_cnvd = 'article_new.md' # Load a article to be replaced with open(path_art, 'r') as f: artile_strings = f.read() rht = ConvertHatenaTex() artile_strings_new = rht.replace(artile_strings) # Save the replaced article with open(path_art_cnvd, 'w') as f: f.write(artile_strings_new)
人生残り短い中年に残された時間は少ない。日々のアウトプットにあまり時間を使いすぎるのは精神衛生上良くない。
オススメの方法があったら教えてください。