技術資料

目次 

目的 

産業連関表を活用した為替変動による波及効果の分析, 金融経済データとの相関分析, また3Dグラフによるサプライチェーン構造の可視化を行う.

使用するファイル全部 

フォルダ名用途ファイル名ファイルの場所
kawasehendouflaskを用いたシステムの記述7to12_Yfinance.ipynbkawasehendou
kabuka用途7to12_JPX401.ipynbkabuka
sankakuka_2javascriptや画像のファイルが入っているsankakuka_2020.pysankakuka_2
3Dgraph3Dグラフを作成するときのjavascriptのファイル3Dgraph_kihon.ipynb3Dgraph
3Dgraphjavascriptで読み込む用のjsonファイルapp_0114.py3Dgraph
soukanグラフのボタンを作成する用の画像soukan_7to12_map_yfinance.ipynbsoukan

バージョンについては上で示した通りである. seleniumではクリックなどのユーザーアクションを模倣することで深いところにあるデータも収集することができる. スクレイピングをする部分のコードはこれ↓

#ref(): File not found: "scraping.py" at page "戸田さん卒論_backup"

#ref(): File not found: "スクレイピング流れ.PNG.jpg" at page "戸田さん卒論_backup"

今回は日本銀行時系列統計データ検索サイトを例に説明する. 日本銀行時系列統計データ検索サイトではクリックアクションを繰り返すことで金利や物価指数など金融時系列データを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])

データ収集と前処理 

産業連関表の収集 

産業連関表は以下の総務省のリンク先からダウンロードする
https://www.soumu.go.jp/toukei_toukatsu/data/io/2020/io20_00001.html

総務省 産業連関表


スクレイピングによるデータ収集 

 #ここからスタート
 #日本経済新聞のページからティッカーのみを取得する.
 from selenium import webdriver
 from selenium.webdriver.chrome.service import Service
 from selenium.webdriver.common.by import By
 from selenium.webdriver.support.ui import WebDriverWait
 from selenium.webdriver.support import expected_conditions as EC
 from webdriver_manager.chrome import ChromeDriverManager
 from bs4 import BeautifulSoup
 import time
 # Chromeドライバーのセットアップ
 service = Service("C:/Users/ta_na/OneDrive/デスクトップ/chromedriver-win64    (1)/chromedriver-win64/chromedriver.exe")
 driver = webdriver.Chrome(service=service)
 # URLを指定
 url = "https://www.nikkei.com/markets/kabu/nidxprice/?StockIndex=N500"
 driver.get(url)
 # ページが完全に読み込まれるのを待つ
 time.sleep(3)
 # 繰り返し回数を指定
 repeat_count = 10  # 例として3回繰り返す
 # ティッカー情報を格納するリスト
 tickers = []
 # 繰り返し処理
 for i in range(repeat_count):
     print(f"Page {i+1} のティッカーを取得中...")
     # BeautifulSoupを使ってページのHTMLを解析
     soup = BeautifulSoup(driver.page_source, 'html.parser')
     # ティッカー情報を格納
     for row in soup.find_all('tr'):  # テーブル行を取得
         columns = row.find_all('td')  # 行内の列を取得
         if len(columns) > 1:  # 必要な列が存在する場合のみ処理
             ticker = columns[0].text.strip()  # ティッカー情報を取得
             tickers.append(ticker)
     # 次のページに移動するためのクリックアクション
     try:
         # XPathを使って次ページボタンをクリック
         next_button = driver.find_element(By.XPATH, "//*[@id='CONTENTS_MARROW']/div[2]/div/div[2]/div/ul/li[7]/a")  # ボタンのXPathを修正
         next_button.click()  # クリックアクション
         print(f"ページ {i+1} の次のページへ移動")
     except Exception as e:
         print(f"クリックアクションでエラーが発生しました: {e}")
         break  # 次のページに移動できない場合、ループを終了

     # 次のページの読み込みを待機
     time.sleep(3)
 # 結果を出力
 print("取得したティッカー一覧:")
 print(tickers)
 # 必要に応じてファイルに保存
 with open("nikkei500_tickers_all.txt", "w") as file:
     for ticker in tickers:
         file.write(f"{ticker}\n")
 # ブラウザを閉じる
 driver.quit()


Pythonのライブラリであるyfinanceを用いてデータの収集を行う

 import yfinance as yf
 import pandas as pd
 import os
 output_dir = 'C:/Users/ta_na/OneDrive/デスクトップ/kabuka/kabuka/csv/out_data'
 # 業種ごとのデータを格納する辞書 
 industry_stock_data = {}
 industry_averages = {}
 # 1業種ずつ処理
 for col, tickers in df_t1.items():  # df_tickers1 は辞書なので items() でキーと値を取得
     tickers = [ticker for ticker in tickers if ticker is not None]  # Noneを除いたティッカーのリスト
     ticker_close_prices = []  # その業種のティッカーに対応するClose価格を一時的に格納するリスト
     for ticker in tickers:
         if isinstance(ticker, str):  # tickerが文字列である場合
             ticker_symbol = ticker + ".T"  # ティッカー番号に .T を追加して日本株式形式にする
             print(f"取得するティッカー: {ticker_symbol}")
             try:
                 # yfinanceを使って指定期間の日足データを取得
                 data = yf.download(ticker_symbol, interval="1d", start="2024-07-01", end="2024-12-01", progress=False)
                 # データが取得できた場合、Closeの値だけを格納
                 if not data.empty:
                     ticker_close_prices.append(data['Close'])
                     print(f"{ticker_symbol} の日足株価データのCloseのみ取得しました。")
                 else:
                     print(f"{ticker_symbol} のデータが見つかりませんでした。")
             except Exception as e:
                 print(f"{ticker_symbol} の取得中にエラーが発生しました: {e}")
         else:
             print(f"無効なティッカー: {ticker}(スキップされました)")
     # データを統合して業種ごとのデータフレームを作成
     if ticker_close_prices:
         industry_df = pd.concat(ticker_close_prices, axis=1)
         # 列数とティッカー数が一致しない場合、自動調整
         if len(industry_df.columns) != len(tickers):
             print(f"警告: 列数の不一致。業種: {col} のティッカー数 {len(tickers)} と列数 {len(industry_df.columns)} が一致しません。")
             # 一致しない場合、列数に合わせてティッカーリストを調整
             tickers = tickers[:len(industry_df.columns)]
         # 列名にティッカー番号を設定
         industry_df.columns = tickers
         industry_df['Date'] = industry_df.index  # 日付を追加
         industry_df.set_index('Date', inplace=True)  # 'Date' をインデックスとして設定
         # NaNを削除(各ティッカーに対してNaNがある場合)
         industry_df = industry_df.dropna(axis=1, how='any')  # NaNを含む列を削除
         # 業種ごとの一日ごとの平均株価を計算
         industry_df['Average'] = industry_df.mean(axis=1)
         # 業種ごとの平均株価データを industry_averages に格納
         industry_averages[col] = industry_df['Average']
         # 必要に応じてCSVファイルとして保存
         file_path = os.path.join(output_dir, f"{col}_stock_data_with_average.csv")
         industry_df.to_csv(file_path)
         print(f"{col} のデータを '{file_path}' に保存しました。")
     else:
         print(f"{col} 業種のデータが存在しませんでした。")
 # 最終的な業種別平均株価データフレームを作成
 df_heikin = pd.DataFrame(industry_averages)
 # 結果の確認
 print("業種別平均株価データフレーム:")
 print(df_heikin)
 # 必要に応じてCSVとして保存
 df_heikin.to_csv(os.path.join(output_dir, 
 "industry_average_stock_prices.csv"), encoding="utf-8-sig")
 import yfinance as yf
 import pandas as pd

為替のデータを取得する

 # USD/JPYの為替データを取得
 usd_jpy = yf.download("JPY=X", start="2024-07-01", end="2024-12-01", progress=False)
 # 終値を取得
 usd_jpy_close = usd_jpy.iloc[:, 3]
 usd_jpy_close = usd_jpy.reset_index()

データの前処理 

分析するためにはデータの前処理を行わなければ正しい結果は得られない. 今回は前処理の手法として正規化を行う. 正規化の手法として,VAR-LiNGAMではMin-Max法によるデータの正規化,RF5に用いるデータの正規化手法としては説明変数にMax法による正規化,目的変数にはZ scoreによる正規化を行う.

VAR-LiNGAMへの適用 

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(): File not found: "ingakekka.PNG" at page "戸田さん卒論_backup"

得られた因果性は上のような形式で保存される. fromには影響を与えている要素,toには影響を与えられている要素,valueには因果性の大きさが格納されている. 一行ずつ因果の向きと大きさが格納されている

数法則発見法(RF5法)によるモデル化 

数法則発見法の1つであるRF5を用いて経済変数間のモデル化を行う. RF5のプログラムは以下に貼っておく.

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

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

RFを実行する際はDriveのRF_real_experiment100.pyを実行すればよい. 実行する際は実際に用いるデータを置き換える必要がある. その際には169行目のpd.read_csv('データのパス(csv形式)')に置き換える. データの形式の例を以下に示す.

#ref(): File not found: "data.png.jpg" at page "戸田さん卒論_backup"

データの内容としては右端の列に目的変数,それ以外の列に説明変数のデータを入れる. 結果はtheta.csvに出力され,それをRF5の定式化に当てはめることで構築されたモデルを復元することができる. 以下にtheta.csvの中身を示す.

#ref(): File not found: "theta.jpg" at page "戸田さん卒論_backup"

RF5の定式化は以下に示す.

#ref(): File not found: "RF_teishikika.png" at page "戸田さん卒論_backup"

θは(w_0,w_j,w_jk)かなっている. このθの中身がtheta.csvには格納され,これを用いて復元を行う.

3Dグラフの作成 

産業連関表の三角化を行う.

import numpy as np
import pandas as pd
from pulp import LpProblem, LpVariable, LpBinary, lpSum, LpMaximize, value
import networkx as nx
import matplotlib.pyplot as plt
# 入力データの読み込み
def read_input_data(file_path):
    # CSVファイルを読み込む
    df_yunyu = pd.read_csv(file_path)
   
    # セクター数を取得
    #NSec = int(df.iloc[0, 1])
    NSec = 37
   
    # A行列を取得(2行目以降を数値行列として読み込む)
    #A = df.iloc[2:, 2:].replace({',': ''}, regex=True).values.astype(float)
    A = df_yunyu.replace({',': ''}, regex=True).values.astype(float)
    return NSec, A
# 最適化問題の定義と解決
def solve_triangulation(NSec, A):
    problem = LpProblem("Triangulation", LpMaximize)
   
    # セクターの範囲
    SSec = range(NSec)
   
    # 決定変数
    X = [[LpVariable(f"X_{i}_{j}", cat=LpBinary) for j in SSec] for i in SSec]
   
    # 制約条件
    for i in SSec:
        for j in SSec:
            if i < j:
                for k in SSec:
                    if j < k:
                        problem += X[i][j] + X[j][k] - X[i][k] >= 0, 
f"Transitivity1_{i}_{j}_{k}"
                        problem += X[i][j] + X[j][k] - X[i][k] <= 1, 
f"Transitivity2_{i}_{j}_{k}"
   
    # 目的関数
    LowSum = lpSum(
        (A[i, j] - A[j, i]) * X[i][j] + A[j, i]
        for i in SSec for j in SSec if i < j
    )
    problem += LowSum
    
    # 問題を解く
    problem.solve()
    
    # 結果を取り出す
    Xo = np.zeros((NSec, NSec))
    Rank = np.zeros(NSec)
    for i in SSec:
        for j in SSec:
            if i < j:
                Xo[i, j] = value(X[i][j])
            elif i > j:
                Xo[i, j] = 1 - value(X[j][i])
        Xo[i, i] = 1
        Rank[i] = sum(Xo[i, :])
   
    return Xo, Rank
# 結果(rank)をファイルに出力
def write_output_data(file_path, Rank):
    df = pd.DataFrame({"Sector": np.arange(1, len(Rank) + 1), "Rank": Rank})
    # インデックスをリセットして1からの連番にする
    df.reset_index(drop=True, inplace=True)  # 現在のインデックスをリセット
    df.index = df.index + 1        # インデックスを1から始める

    # 産業名リスト
    industry_names = [
        "農林漁業", "鉱業", "飲食料品", "繊維製品", "パルプ・紙・木製品", "化学製品", "石油・石炭製品", 
       "プラスチック・ゴム製品", "窯業・土石製品", "鉄鋼", "非鉄金属", "金属製品", "はん用機械", 
       "生産用機械", "業務用機械", "電子部品", "電気機械", "情報通信機器", "輸送機械", "その他の製造工業製品", 
       "建設", "電力・ガス・熱供給", "水道", "廃棄物処理", "商業", "金融・保険", "不動産", "運輸・郵便", 
       "情報通信", "公務", "教育・研究", "医療・福祉", "他に分類されない会員制団体", "対事業所サービス", 
       "対個人サービス", "事務用品", "分類不明"
    ]
    # インデックスを産業名に置き換える
    df.index = industry_names

    df.to_csv(file_path, encoding="utf-8-sig")
    # Rank 列を昇順でソート
    df_rank_sorted = df.sort_values(by="Rank", ascending=True)
    df_rank_sorted.to_csv("C:/Users/ta_na/OneDrive/デスクトップ/s ankakuka_2/sankakuka_2/csv/out_data/Japan2020_37_rank_sort_kihon_tonyukeisu.csv", encoding="utf-8-sig")  # index=False でインデックスを保存しない
# 結果(行列X)をファイルに出力
def write_output_matrix(file_path, matrix):
    # 行列をデータフレームに変換して保存
    df = pd.DataFrame(matrix)
    df.to_csv(file_path, index=False, header=False)
# メイン処理
if __name__ == "__main__":
    # 入力ファイルと出力ファイル
    IN_FILE = "C:/Users/ta_na/OneDrive/デスクトップ/kawasehendou/kawasehendou/csv/coefficient_matrix_kihon.csv"
   OUT_FILE_RANK = "C:/Users/ta_na/OneDrive/デスクトップ/sankakuka_2/sankakuka_2/csv/out_data/Japan2020_37_rank_kihon_tonyukeisu.csv"
   OUT_FILE_X = "C:/Users/ta_na/OneDrive/デスクトップ/sankakuka_2/sankakuka_2/csv/out_data/Japan2020_37_X_kihon_tonyukeisu.csv"
   # データの読み込み
   NSec, A = read_input_data(IN_FILE)
   
   # 最適化の実行
   Xo, Rank = solve_triangulation(NSec, A)
   
   # 結果を保存
   write_output_data(OUT_FILE_RANK, Rank)
   write_output_matrix(OUT_FILE_X, Xo)
   print(f"Results saved to {OUT_FILE_RANK}")
   print(f"Results saved to {OUT_FILE_X}")

jsonファイルの作成 

3D Force-Directed Graphに産業部門間の関係の情報を送るためにjsonファイルを作成する.
産業部門間の関係が格納されたcsvファイルをjsonファイルに変換する

 import numpy as np
 import pandas as pd
 X = pd.read_csv("C:/Users/ta_na/OneDrive/デスクトップ/sankakuka_2/sankakuka_2/csv/out_data/Japan2020_37_X_kihon_tonyukeisu.csv", header=None)
 X
 import numpy as np
 import pandas as pd
 coefficient_matrix_kihon = pd.read_csv("C:/Users/ta_na/OneDrive/デスクトップ/kawasehendou/kawasehendou/csv/coefficient_matrix_kihon.csv")
 coefficient_matrix_kihon
 # X の 1 の部分に対応する coefficient_matrix_kihon の値を取り出し、
 # X の 0 の部分は 0 のままにする
 coefficient_matrix_kihon_2 = X * coefficient_matrix_kihon
 # 出力結果
 print(coefficient_matrix_kihon_2)
      
 # 新しいデータフレームを作成
 rows = []
 for i, row_label in enumerate(coefficient_matrix_kihon_2.index):
     for j, col_label in enumerate(coefficient_matrix_kihon_2.columns):
         if i != j and coefficient_matrix_kihon_2.iloc[i, j] != 0:  # 対角要素をスキップ
             rows.append({'from': row_label, 'to': col_label, 'effect': coefficient_matrix_kihon_2.iloc[i, j]})
 # 新しいデータフレームを作成
 result_df = pd.DataFrame(rows)
 print(result_df)
 # CSVファイルに保存
 result_df.to_csv('csv/out_data/from_to_kihon.csv', index=False, encoding='utf-8-sig')
 # 'effect'列の値を100倍
 result_df['effect'] = result_df['effect'] * 100
 #全部
 import pandas as pd
 import csv
 import json
 # effectを削除
 df_effect=result_df.drop(["effect"],axis=1)
 #fromとtoの中の要素を一つのリストにする
 count_id = df_effect.stack().drop_duplicates().tolist()
 # links の形式にデータを変換する
 links = []
 for index, row in result_df.iterrows():
     links.append({
         "source": row['from'],
         "target": row['to'],
         "value": row['effect']
     })
 # データを指定された形式に変換
 nodes = [{"id": item, "group": 1} for item in count_id]
 # JSON形式に変換
 result = {"nodes": nodes, "links": links}
 # JSONファイルに保存
 with open("static/json/output_toda_kihon_zenbu.json", "w") as f:
     json.dump(result, f, indent=2)

作成するjsonファイルの形式(output_toda_kihon_zenbu.json)

{
   "nodes": [
       {
           "id": "\u8fb2\u6797\u6f01\u696d",
           "group": 1
       },
       {
           "id": "\u98f2\u98df\u6599\u54c1",
           "group": 1
       },
~~~~~~~~~~省略~~~~~~~~~~
       {
           "id": "\u60c5\u5831\u901a\u4fe1",
           "group": 1
       },
       {
           "id": "\u5bfe\u4e8b\u696d\u6240\u30b5\u30fc\u30d3\u30b9",
           "group": 1
       }
   ],
   "links": [
       {
           "source": "\u8fb2\u6797\u6f01\u696d",
           "target": "\u98f2\u98df\u6599\u54c1",
           "value": 19.05628066765477
       },
       {
           "source": "\u8fb2\u6797\u6f01\u696d",
           "target": "\u6559\u80b2\u30fb\u7814\u7a76",
           "value": 0.13824726106617
       },
~~~~~~~~~~省略~~~~~~~~~~
       {
           "source": "\u5206\u985e\u4e0d\u660e",
           "target": "\u4ed6\u306b\u5206\u985e\u3055\u308c\u306a\u3044\u4f1a\u54e1\u5236\u56e3\u4f53",
           "value": 1.2702003066174
       },
       {
           "source": "\u5206\u985e\u4e0d\u660e",
           "target": "\u5bfe\u500b\u4eba\u30b5\u30fc\u30d3\u30b9",
           "value": 0.43188001994264
       }
   ]
}

3Dグラフ 

3Dグラフの描画にはThree.jsのモジュール”3D Force-Directed Graph”を使う.
参考にしたサイト👉https://vasturiano.github.io/3d-force-graph/
javascriptの買い方はサイトを参考にすれば様々な変更が可能.
⚠モジュールのインポート方法はサイトのものでは行えなかったため独自で行った.

<!DOCTYPE html>
<html lang="en">
<head>
	 <meta charset="utf-8">
	 <title>3D Graph with Rankings</title>
	 <style>
		 body {
		 	 margin: 0;
			 padding: 0;
			 display: flex;
			 flex-direction: column;
			 height: 100vh;
		 }
		 #container {
			 display: flex;
			 flex: 1;
			 width: 100%;
		 }
		 #three {
			 flex: 1;
			 background-color: aliceblue;
			 height: 100%;
		 }
		 #info {
			 position: fixed;  /* fixedに変更して、スクロールしても位置が固定されるようにする */
			 top: 10px;
			 right: 10px;
			 background-color: #fff;
			 padding: 10px;
			 border: 1px solid #ccc;
			 border-radius: 5px;
			 width: 300px;
			 max-height: 900px;
			 overflow-y: auto;
			 z-index: 10;
		 }
		 #rankTable {
			 position: fixed; /* 左上に固定表示 */
			 top: 10px;
			 left: 10px;
			 background-color: #fff;
			 padding: 10px;
			 border: 1px solid #ccc;
			 border-radius: 5px;
			 width: 300px;
			 max-height: 900px;
			 overflow-y: auto;
			 z-index: 10;
			 font-size: 12px;
		 }
		 /* レスポンシブ対応 */
		 @media screen and (max-width: 768px) {
			 #container {
				 flex-direction: column;
			 }
			 #rankTable {
				 width: 100%;
			 }
			 #three {
				 width: 100%;
				 height: 400px;
			 }
		 }
	 </style>
</head>
<body>
	 <!-- 左上にランク表 -->
	 <div id="rankTable">
		 <table>
			 <thead>
				 <tr>
					 <th>産業部門名</th>
					 <th>順列</th>
				 </tr>
			 </thead>
			 <tbody>
				 <tr><td>生産用機械</td><td>1</td></tr>
				 <tr><td>医療・福祉</td><td>2</td></tr>
				 <tr><td>教育・研究</td><td>3</td></tr>
				 <tr><td>対個人サービス</td><td>4</td></tr>
				 <tr><td>飲食料品</td><td>5</td></tr>
				 <tr><td>農林漁業</td><td>6</td></tr>
				 <tr><td>水道</td><td>7</td></tr>
				 <tr><td>他に分類されない会員制団体</td><td>8</td></tr>
				 <tr><td>分類不明</td><td>9</td></tr>
				 <tr><td>公務</td><td>10</td></tr>
				 <tr><td>輸送機械</td><td>11</td></tr>
				 <tr><td>情報通信機器</td><td>12</td></tr>
				 <tr><td>事務用品</td><td>13</td></tr>
				 <tr><td>業務用機械</td><td>14</td></tr>
				 <tr><td>建設</td><td>15</td></tr>
				 <tr><td>はん用機械</td><td>16</td></tr>
				 <tr><td>電気機械</td><td>17</td></tr>
				 <tr><td>電子部品</td><td>18</td></tr>
				 <tr><td>窯業・土石製品</td><td>19</td></tr>
				 <tr><td>金属製品</td><td>20</td></tr>
				 <tr><td>鉄鋼</td><td>21</td></tr>
				 <tr><td>非鉄金属</td><td>22</td></tr>
				 <tr><td>廃棄物処理</td><td>23</td></tr>
				 <tr><td>繊維製品</td><td>24</td></tr>
				 <tr><td>その他の製造工業製品</td><td>25</td></tr>
				 <tr><td>パルプ・紙・木製品</td><td>26</td></tr>
				 <tr><td>プラスチック・ゴム製品</td><td>27</td></tr>
				 <tr><td>化学製品</td><td>28</td></tr>
				 <tr><td>電力・ガス・熱供給</td><td>29</td></tr>
				 <tr><td>石油・石炭製品</td><td>30</td></tr>
				 <tr><td>鉱業</td><td>31</td></tr>
				 <tr><td>商業</td><td>32</td></tr>
				 <tr><td>運輸・郵便</td><td>33</td></tr>
				 <tr><td>不動産</td><td>34</td></tr>
				 <tr><td>金融・保険</td><td>35</td></tr>
				 <tr><td>情報通信</td><td>36</td></tr>
				 <tr><td>対事業所サービス</td><td>37</td></tr>
			 </tbody>
		 </table>
	 </div>
	 <!-- 真ん中に3Dグラフ -->
	 <div id="three"></div>
	 <!-- 右上にノード情報 -->
	 <div id="info">
	 	 <!-- ノードの情報がここに表示されます -->
	 </div>
	 <!-- JavaScript ライブラリの読み込み -->
	 <script type="module" 
src="https://unpkg.com/three@0.158.0/build/three.js" defer></script>
	 <script type="module" src="https://unpkg.com/3d-force-graph@1.73.0/dist/3d-force-graph.min.js" defer></script>
	 <script type="module" src="https://unpkg.com/three-spritetext@1.8.1/dist/three-spritetext.min.js" defer></script>
	 <script src="./static/main2_0114.js" charset="utf-8" defer></script>
</body>
</html>


 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/json/output_toda_kihon_zenbu.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; 
         // // 特定のノード名の色を変更
         // if (node.id === '農林漁業') {
         //     sprite.color = 'red'; // 特定ノードのテキスト色を赤に変更
         // } else {
         //     sprite.color = node.color; // 他のノードは元の色を使用
         // }
         sprite.textHeight = 7.5;
         return sprite;
       
     })
   
     .linkThreeObject(link => {
         // extend link with text sprite
         const sprite = new SpriteText(link.value.toFixed(2)); // 小数点1桁にフォーマット
         sprite.color = 'blue';
         sprite.textHeight = 4.0;
         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 => {
         // ノードクリック時に入出力リンクを表示
         const outgoingLinks = Graph.graphData().links.filter(link => link.source.id === node.id);
         const incomingLinks = Graph.graphData().links.filter(link => link.target.id === node.id);
         // 左上に情報を表示
         const displayArea = document.getElementById("info");
         displayArea.innerHTML = `<h3>ノード: ${node.id}</h3>`;
        
         // 出ているリンク
         //displayArea.innerHTML += `<h4>Outgoing Links</h4>`;
         displayArea.innerHTML += `<h4>販売先</h4>`;
         outgoingLinks.forEach(link => {
             displayArea.innerHTML += `<p>${link.target.id} (Value: ${link.value.toFixed(2)})</p>`;
         });
         // 向かってくるリンク
         //displayArea.innerHTML += `<h4>Incoming Links</h4>`;
         displayArea.innerHTML += `<h4>購入先</h4>`;
         incomingLinks.forEach(link => {
             displayArea.innerHTML += `<p>${link.source.id} (Value: ${link.value.toFixed(2)})</p>`;
         });
     })
     .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(() => 'red') // リンクの色を黒に設定
     .linkWidth(1)
     .backgroundColor("#f8f8f8");  
 // ノード間の力学的レイアウトを調整
 Graph
     .d3Force("link").distance(300) // リンクの長さを100に設定(デフォルトは約30-50)
     .d3Force("charge").strength(300); // ノード間の反発力を調整(値を小さくすると密集する)
 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;
     });
 }

    


実際の出力結果

#ref(): File not found: "3d.jpg" at page "戸田さん卒論_backup"


その他 

ロード画面 

スクレイピングなどの処理を行っているとき画面がそのままだと,ちゃんとプログラムが動いているのかわからないのでロード画面を作った.

<div id="loading">
    <p>作成中...</p>
</div>

<script>
    window.addEventListener('DOMContentLoaded', function(){
        // ページの表示状態が変わったときに実行
        document.addEventListener("visibilitychange", function() {
        console.log(document.visibilityState);
        document.getElementById('loading').style.display = 'none';
        });
    });
    function performSearch() {
        var loadingScreen = document.getElementById('loading');
 
        // ロード画面を表示
        loadingScreen.style.display = 'flex';
    }
</script>

ブラウザバック 

ブラウザバックをしたときにフォームなどのユーザからの入力が保持されていないとエラーが起こるため,それを回避している.

3Dグラフによる可視化 

改善版としてグラフをよりアトラクティブにするシステムを作成した. 改善前ではノードを引っ張ることしかできていなかったが,クリックしドラッグを行うことで様々な角度からネットワークを確認できるようにした.

その際には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(): File not found: "2.png" at page "戸田さん卒論_backup"


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS