#author("2024-03-08T00:41:11+00:00","","")
#author("2024-03-08T00:41:33+00:00","","")
[[技術資料]]

金融変数と実体経済変数の因果探索と数法則発見法による波及経路のモデル化と可視化

*目次 [#e890b3d5]

#CONTENTS

*目的 [#x38aa8d3]
VAR-LiNGAMによる時系列を考慮した因果探索を行い,時系列を考慮した3D因果グラフの作成,また数法則発見法を用いた経済変数のモデル化を行うことで,経済変数間の影響を直感的に理解できるようなシステムの実装を目指す.
*使うモジュールのインストール [#u1248302]
***使用するモジュール [#ta19c6d2]
|モジュール|version|用途|
|pandas||データ収集やデータフレームへの格納などに用いる|
|LiNGAM||VAR-LiNGAMによる分析に用いる|
|selenium||データの収集,スクレイピングに用いる|
|pandas-market-calendars||土日祝日を知ることができる.データの時間足調整に用いる|
|flask||システムの実装に用いる|
|json||3Dグラフの作成,ページ遷移先にデータを与えるのに用いる|
|pyvis||3Dグラフの作成|
|webdriver_manager||スクレイピングに用いる|
モジュールのインストールはコマンドプロンプトでpip install モジュール名

バージョンまで指定する場合はコマンドプロンプトでpip install モジュール名==指定するバージョン
でインストールする
*データ収集と前処理 [#ob3ca958]

**スクレイピングによるデータ収集 [#v2c613db]
スクレイピングを用いてデータを収集する.
スクレイピングには「selenium」を用いる.
seleniumのバージョンについては上で示した通りである.
seleniumではクリックなどのユーザーアクションを模倣することで深いところにあるデータも収集することができる.
スクレイピングをする部分のコードはこれ↓
#ref(scraping.py,,スクレイピング用コード)
#ref(スクレイピング流れ.PNG.jpg)
今回は日本銀行時系列統計データ検索サイトを例に説明する.
日本銀行時系列統計データ検索サイトではクリックアクションを繰り返すことで金利や物価指数など金融時系列データをcsvファイル形式で取得することができる.

ダウンロードしたデータはcsv形式でダウンロードされる.

そのため,ダウンロードされたcsvファイルを一つのフォルダにまとめて保存しする必要がある.

また,ダウンロードした時点のファイル名もダウンロードされた時間,回数によって勝手に決められてしまうものもあるため,ファイル名を変更する必要がある.

そのためにまず,ファイルのダウンロード先を指定する必要がある
 dldir_path = Path('保存先のフォルダのパス')
 dldir_path.mkdir(exist_ok=True)
 download_dir = str(dldir_path.resolve())
 options.add_experimental_option('prefs', {'download.default_directory': download_dir})

また,ファイル名を変更するコードとして以下を追加する.
 filename_list=["変更後のファイル名(1)","変更後のファイル名(2)".....]
 list_of_files = glob.glob('保存先のフォルダのパス/*')
 latest_file = max(list_of_files, key=os.path.getctime)
 file_name = str(latest_file.split('\\')[1])
 print(latest_file)
 print()
 os.remove("csv/"+filename_list[i-1])
 os.rename(latest_file,"csv/"+filename_list[i-1])
**データの前処理 [#xbcf3848]
分析するためにはデータの前処理を行わなければ正しい結果は得られない.
今回は前処理の手法として正規化を行う.
正規化の手法として,VAR-LiNGAMではMin-Max法によるデータの正規化,RF5に用いるデータの正規化手法としては説明変数にMax法による正規化,目的変数にはZ scoreによる正規化を行う.
*VAR-LiNGAMへの適用 [#eb3cce83]
VAR-LiNGAMの分析ではPythonのモジュール「LiNGAM」のVAR-LiNGAMを用いる.
これはhttps://lingam.readthedocs.io/en/latest/tutorial/var.html を参考にすればよい
 path = "用いるデータのパス"
 df=pd.read_csv(path,parse_dates=True,index_col="Day",encoding="cp932")
 scaler = MinMaxScaler()
 normalized_df = pd.DataFrame(scaler.fit_transform(df), columns=df.columns)
 model = lingam.VARLiNGAM(lags=2, prune=True)
 model.fit(normalized_df)
上のコードではデータの読み取り,正規化を行い,それについてVAR-LiNGAMを実行している.
 labels = [f'{col}(t)' for i, col in enumerate(normalized_df.columns)]+[f'{col}(t-1)' for i,col in enumerate(df.columns)]
 make_dot(np.hstack(model.adjacency_matrices_),
         lower_limit=0.05,
         ignore_shape=True,
         labels=labels
         )
上のコードで2次元グラフを作成している.このコードは結果を出すうえで特に必要ないため,実行しなくても良いが必要になった場合にはgraphvizのインストールが必要になる.
graphvizのインストールについては以下のサイトを参考にした

https://www.kkaneko.jp/tools/win/graphviz.html

 model = lingam.VARLiNGAM()
 result = model.bootstrap(normalized_df,n_sampling=100)
これでLiNGAMのVAR-LiNGAMをモデルとして設定し,ブートストラップ法による分析を行っている.n_samplingの数でサンプリング回数を設定し,今回は100回になっているが,用いるデータのサンプル数を考慮し設定する必要がある.
ブートストラップ法では一般的に用いるデータのサンプル数よりも大きいものを設定することが望ましい.
 p_values = model.get_error_independence_p_values()
これで誤差変数間の独立性を分析する.
 cdc = result.get_causal_direction_counts(n_directions=8, min_causal_effect=0.3,
 split_by_causal_effect_sign=True)
このコードで因果関係の方向性を取得する.n_directionは上位いくつの因果方向を限定するかを設定するものであり,min_causal_effectでは係数が何以上の因果方向に限定するかを指定できる.
 print_causal_directions(cdc, 100, labels=labels)
でその結果を描画できる.
 dagc = result.get_directed_acyclic_graph_counts(n_dags=3, 
 min_causal_effect=0.2, split_by_causal_effect_sign=True)
これではDAGのランキングを取得できる.また,
 causal_effects = result.get_total_causal_effects(min_causal_effect=0.01)
 df = pd.DataFrame(causal_effects)

 df['from'] = df['from'].apply(lambda x : labels[x])
 df['to'] = df['to'].apply(lambda x : labels[x])
 df
これで合計因果関係を取得することができ,この結果を用いて3Dグラフの作成を行うため,pandasのto_csvを用いることでcsvファイルに保存しておく.
#ref(ingakekka.PNG)
得られた因果性は上のような形式で保存される.
fromには影響を与えている要素,toには影響を与えられている要素,valueには因果性の大きさが格納されている.
一行ずつ因果の向きと大きさが格納されている
*数法則発見法(RF5法)によるモデル化 [#tdd1a952]
数法則発見法の1つであるRF5を用いて経済変数間のモデル化を行う.
RF5のプログラムは以下に貼っておく.

RF5では何度も反復を行い学習することでモデルを作成するため,反復回数を設定する必要がある.
反復回数を設定する際にはwhile s < 500:の500の部分を反復したい回数に設定する.
この反復回数は従来研究では10000回や20000回が望ましいと述べられていたのでそれ以上の値に設定した方が良いだろう.

実行して得られたBICが低ければ低いほど良い結果をとりやすいのでBICが最も小さくなったものの結果を見て考察を行えばよい.

RFを実行する際はDriveのRF_real_experiment100.pyを実行すればよい.
実行する際は実際に用いるデータを置き換える必要がある.
その際には169行目のpd.read_csv('データのパス(csv形式)')に置き換える.
データの形式の例を以下に示す.
#ref(data.png.jpg)
データの内容としては右端の列に目的変数,それ以外の列に説明変数のデータを入れる.
結果はtheta.csvに出力され,それをRF5の定式化に当てはめることで構築されたモデルを復元することができる.
以下にtheta.csvの中身を示す.
#ref(theta.jpg)
RF5の定式化は以下に示す.
#ref(RF_teishikika.png)
θは(w_0,w_j,w_jk)かなっている.
このθの中身がtheta.csvには格納され,これを用いて復元を行う.
*因果3Dグラフの作成 [#s53cb794]
VAR-LiNGAMによって得られた結果から因果3Dグラフを作成する.
ここではVARLiNGAMで得られた合計因果関係を格納したcsvファイルを用いて3Dグラフを作成する.
csvファイルは以下の形式で保存されており,以下のプログラムでグラフが作成される.
 import pandas as pd
 import json
 from pyvis.network import Network
 def kyoki_word_network():
    got_net = Network(height="1000px", width="95%", bgcolor="#FFFFFF", font_color="black", notebook=True, directed=True)
    got_data = pd.read_csv("ultra_kekka.csv")[:2000]
    sources = got_data['from']
    targets = got_data['to']
    weights = got_data['effect']
    unique_nodes = set(sources) | set(targets)
    nodes = [{"id": node, "label": node, "title": node} for node in unique_nodes]
    edges = [{"from": src, "to": dst, "color": 'red' if w < 0 else 'blue', "label": str(round(w, 4)), "arrows": "to"} for src, dst, w in zip(sources, targets, weights)]
    nodes_json = json.dumps(nodes, ensure_ascii=False)
    edges_json = json.dumps(edges, ensure_ascii=False)
    html_code = """
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Your Network</title>
        <style>
            #mynetwork {
                height: 1000px;
                width: 95%;
                border: 1px solid lightgray;
            }
        </style>
    </head>
    <body>
        <div id="mynetwork"></div>
        <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
        <script>
            var container = document.getElementById("mynetwork");
            var nodes = new vis.DataSet(""" + nodes_json + """);
            var edges = new vis.DataSet(""" + edges_json + """);
            var data = {nodes: nodes, edges: edges};
            var options = {};
            var network = new vis.Network(container, data, options);
            // ノードのダブルクリック用のイベントリスナーを追加
            network.on("doubleClick", function (params) {
                if (params.nodes.length > 0) {
                    var nodeId = params.nodes[0];
                    var nodeLabel = nodes.get(nodeId).label;
                    // ページ遷移や他のダブルクリックされたノードに基づくアクションを実行
                    window.location.href = "page/" + nodeLabel;  // ページ遷移のURLを変更してください
                }
            });
        </script>
    </body>
    </html>
    """
    with open("your_network.html", "w", encoding="utf-8") as html_file:
        html_file.write(html_code)
        got_net.show_buttons(filter_=['physics'])
        return got_net
 kyoki_word_network()
jscode=""の部分でクリックアクションなどを設定している.
今回はダブルクリックしたときにページ遷移するようにした.
#ref(networka.PNG)
ここからノードをクリックすることで,ページ遷移を行う.
**3Dグラフによる可視化 [#u182dd73]
改善版としてグラフをよりアトラクティブにするシステムを作成した.
改善前ではノードを引っ張ることしかできていなかったが,クリックしドラッグを行うことで様々な角度からネットワークを確認できるようにした.

その際にはthree.jsによる可視化を行い,アプリケーションの作成を行った.

three.jsについては以下のサイトを参考にした.
https://github.com/vasturiano/3d-force-graph
以下に改善版のプログラムを示す.
 const highlightLinks = new Set();
 const highlightNodes = new Set();
 let hoverNode = null;

 const Graph = ForceGraph3D()
    (document.getElementById("three"))
    .jsonUrl('http://127.0.0.1:5000/static/output2.json')
    .nodeColor('#ffffff')
    .nodeLabel('id')
    .nodeRelSize(20)
    .nodeThreeObject(node => {
        const sprite = new SpriteText(node.id);
        sprite.material.depthWrite = false; // make sprite background transparent
        sprite.color = node.color;
        sprite.textHeight = 7.5;
        return sprite;
    })
    .linkThreeObject(link => {
        // extend link with text sprite
        const sprite = new SpriteText(`${link.value}`);
        sprite.color = 'blue';
        sprite.textHeight = 1.5;
        return sprite;
      })
      .linkPositionUpdate((sprite, { start, end }) => {
        const middlePos = Object.assign(...['x', 'y', 'z'].map(c => ({
          [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point
        })));

        // Position sprite
        Object.assign(sprite.position, middlePos);
      })    
    .onNodeClick(node => {
        var baseUrl = "page/" + node.id;
        window.location.href = baseUrl;  
    })

    .onNodeHover(node => {
        // ノードにホバーした時の処理
        hoverNode = node ? node.id : null;
        updateHoverText(); // テキストを更新する関数を呼び出す
    })
    .linkOpacity(0.25)
    .linkDirectionalArrowLength(3)
    .linkDirectionalArrowRelPos(1)
    .linkCurvature(0.01)
    .linkDirectionalParticleWidth(2)
    .linkDirectionalParticles("value")
    .linkDirectionalParticleSpeed(d => d.value * 0.01)
    .linkThreeObjectExtend(true)
    .linkColor(link=>{
        if (link.value > 0){
            return 'red'
        } else {
            return 'blue'
        }
    })
    .linkWidth(1)
    .backgroundColor("#f8f8f8");
 function updateHoverText() {
    // ホバー時のノードのテキストサイズを変更
    Graph.nodeThreeObject(node => {
        const sprite = new SpriteText(node.id);
        sprite.material.depthWrite = false;
        sprite.color = node.color;
        sprite.textHeight = hoverNode === node.id ? 15 : 7.5; // ホバー時にテキストサイズを変更
        return sprite;
    });
}
これを実行する際は改善前と同じくflaskフォルダ内のapp.pyを実行すればよい.
以下に実行結果を示す.
#ref(2.png)

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS