#author("2025-03-05T01:12:06+01:00","","")
#author("2025-03-07T02:51:36+01:00","","")
[[由利恵]]

*目次 [#e890b3d5]

#CONTENTS

**目的 [#z78b3c7b]
近年,経営環境は大きく変化しており,いわゆるVUCA な時代を迎えている.企業が持続的な発展を図るためには,自社の核となる独自の強みを生かし,他者との差別化を図ることが極めて重要である.そんな中,IP ランドスケープが注目を集めている.本研究では,今日に至るまでの莫大な特許文章群を対象とした知見発見および探索を目的とする.
~
~
~
***使用するファイル全部 [#ta19c6d2]
|扱うデータ|用途|ファイル名|ファイルの場所|
|システムの内部処理|flaskを用いたシステムの記述|appli_2.py|application/practice|
|ドライバーのファイル|自分の環境に合わせたChromeDriverの保存|chromedriver.exe|application/practice|
|staticファイル|javascriptや画像のファイルが入っている|static|application/practice|
|↑の中身|3Dグラフを作成するときのjavascriptのファイル|main2.js|static|
|↑の中身|javascriptで読み込む用のjsonファイル|output.json|static|
|↑の中身|グラフのボタンを作成する用の画像|xy2.png/xyz2.png|static|
|テキストデータ|集めてきたテキストデータの一時保存|text_data.pickle|application/practice|
|ベクトル(数値)|2次元に圧縮したベクトル|vectors.pickle|application/practice|
|ベクトル(数値)|15次元に圧縮したベクトル|vectors_15.pickle|application/pracitce|
|シルエット係数|それぞれのクラス数におけるシルエット係数の値|shilhouette.pickle|application/practice|
|クラスタリング結果|クラスタリングの結果のデータ|df_umap.pkl|application/practice|
|simpson係数|simpson係数の値と単語の出現回数など|jaccard_coef.pkl|application/practice|
|ユーザー辞書|各クラスターのユーザー辞書の保存|user_dic_{classXX}.csv[XX=クラスターの番号(例.class03)]|application/practice|
|共起語ネットワーク|2dの共起語ネットワークのhtmlファイル|kyoki_100.html|application/practice|
~
*動かし方 [#ccda06db]
1.practiceの中のappli_2.pyを動かす.~
2.必要なモジュールをすべて入れる.(pip installなど)~
⚠umapとMeCabは少し名前が違うモジュールなので注意,そのほかはそのままの名前でインストールすればいいはず.~
-Sentence-BERTを動かすときに"fugashi"をインストールする必要がある可能性あり.
 pip install umap-learn
 
 pip install mecab-python3
2'.termextractはpipではインストールできないため別の入れ方をする.また,モジュールの中身を変更する必要もあり.~
-詳しくは> [[termextract>#qbb8ab79]]

3.すべてのインストールが完了したらlocalhost:5000にアクセス.
⚠必ずlocalhost:5000にアクセス!~
-詳しくは>[[3Dグラフ>#gd773348]]~

*スクレピング処理 [#w90988e1]

***ChromeDriverのインストール [#j661bc61]
まず、ChromeDriverをインストールする.自身のGoogleChromeのバージョンを確認し,それに合ったバージョンをインストールする(https://chromedriver.chromium.org/downloads).

わからなかったらここを見て👇~
👉https://zenn.dev/ryo427/articles/7ff77a86a2d86a

~
**1.データ取得 [#c9661663]
***seleniumのインストール [#j661bc61]
seleniumをインストールする.バージョン3でもよいが,プログラムの書き方が異なる.&br;
 <pythonのとき>
 pip install selenium
 <notebookのとき>
 !python -m pip install selenium

***必要なモジュールをインポートする. [#h00fd477]
 from selenium import webdriver
 from selenium.webdriver.common.by import By
 from selenium.webdriver.chrome.service import Service
***driverのオプションを設定する. [#b380d497]
 options = webdriver.ChromeOptions()
 options.add_argument('--headless')
 options.add_argument('--no-sandbox')
 options.add_argument('--disable-dev-shm-usage')
--headless ヘッドレスモード(バックグラウンドで起動)
--no-sandbox sandboxモードを解除する(クラッシュ回避)
--disable-dev-shm-usage パーティションが小さすぎることによる、クラッシュを回避する。
***chromedriverのパスを設定する. [#b380d497]
インストールしたchromedriver.exeの場所を指定する.
 driver_path = "chromedriver-win64/chromedriver.exe"
***driverを作成する. [#b380d497]
 driver1 = webdriver.Chrome(service=ChromeService(driver_path), options=options)
 driver1.implicitly_wait(10)
--implicitly_wait(10) 指定した時間要素が見つかるまで待機.

⚠seleniumのバージョンによってコードの書き方が異なる場合がある(今回はver=4.12.0
)

***urlの指定方法 [#uff6c3d3]
urlでユーザーからのキーワードと取得する年数を指定する.

 url_1 = (
     "https://patents.google.com/?q=("
     + str(keyword)
     + ")&before=priority:"
     + str(2000)
     + "1231&after=priority:"
     + str(2000)
     + "0101&sort=old"
 )
--str(keyword):ここにユーザーから取得したキーワードを入力する.~
--&before=priority:* + str(XXXX) + 1231&after=priority:str(XXXX) +0101&sort=old~
➡priorityがXXXX年の0101(一月一日)から1231(十二月三十一日)のものを指定する

***取得方法 [#c0fff484]
 def W1(url):
        driver1.get(url)
        try:
            results1 = driver1.find_element(
                By.XPATH, '//*[@id="count"]/div[1]/span[1]/span[3]'
            ).text.replace(",", "")
            if int(results1) <= 10:
                p_n_1 = 1
            else:
                p_n_1 = int(results1) // 10
        except Exception as e:
            print("error")
        for p1 in range(p_n_1 + 1):
            driver1.get(url + "&page=" + str(p1))
            link1 = driver1.find_elements(
                By.XPATH,
                '//*[@id="resultsContainer"]/section/search-result-item/article/state-modifier',
            )
            for i1 in range(len(link1)):
                try:
                    link_url1 = "https://patents.google.com/" + link1[i1].get_attribute(
                        "data-result"
                    )
                    patt1 = pat_text(link_url1)
                    patt1.get_soup()
                    # patt.get_title()
                    patt1.get_claims()
                    
                    d_save1 = []
                    d_save1.append(patt1.get_description())
                    d_save1.append(link1[i1].get_attribute("data-result"))
                    desc1.append(d_save1)
                except Exception as e:
                    print(e)
1.urlから中身を取得する.~
2.find_elementで検索結果の数を取得する.~
3.{p_n_1}にページ数を渡す.(何ページあるのかを直接取得できなかったため,要素数から割り出す)~

--検索結果に表示された特許の数{results}が10以下ならp_n_1に1をそれ以外なら10で割った結果の整数部分を渡す.~
---resultsはGoogle Patantsに表示される「About ****** results」の部分を取得している.~
➡(結果の数値が4桁1,000などの場合は「,」があるとうまく処理できないので,排除している.

4.各ページの中から特許番号を取得する.~

--for文でページ分回す~
---driver1.get(url + "&page=" + str(p1))この部分でページ数を指定~

5.find_elementで特許番号(例:patent/JP5965646B2/ja)の部分を取得する~
--link1にはそのページの取得した特許番号が含まれる要素がすべて含まれている.~
---.get_attribute("data-results")の部分は実際の「patent/JP5965646B2/ja」などを取り出している部分~

6.取得した番号をもとにhtmlのurlを作成し,関数(pat_text)に渡す.~
7.pat_textからの本文と特許番号をd_save1に渡す.~
👇実際の取得結果~
#ref(テキストフォーマット2.jpg)

~
~
**2.並列化 [#ad9614fa]
スクレイピングの高速化を試みた,今回はthreadsを用いて並列化を行う.&br;
 import threading

    thr1 = threading.Thread(target=W1, args=(url_1,))
    thr2 = threading.Thread(target=W2, args=(url_2,))
    thr3 = threading.Thread(target=W3, args=(url_3,))
    thr4 = threading.Thread(target=W4, args=(url_4,))
    thr5 = threading.Thread(target=W5, args=(url_5,))
    thr6 = threading.Thread(target=W6, args=(url_6,))
    ~~~~~省略~~~~~
            thr24まで
    ~~~~~~~~~~~~
threadを一年ごとに設定する.~
それを6年ずつ実行する~
要素が混在しないように一年ごととそれぞれのスレッドごとにdescを用意する.
    desc01 = []
    desc02 = []
    desc03 = []
    desc04 = []
    if int(year) == 24:
        #6年分
        desc1 = [] 
        desc2 = [] 
        desc3 = [] 
        desc4 = [] 
        desc5 = [] 
        desc6 = []
        
        #各スレッドのスタート
        thr1.start() 
        thr2.start() 
        thr3.start() 
        thr4.start() 
        thr5.start() 
        thr6.start()
        
        #各スレッドの処理が終了するまで待機
        thr1.join() 
        thr2.join() 
        thr3.join() 
        thr4.join() 
        thr5.join() 
        thr6.join()
        
        desc01 = desc1 + desc2 + desc3 + desc4 + desc5 + desc6
        
    if int(year) == 18 or int(year) == 24:
        ~~~~~省略~~~~~
            thr7からthr12まで
        ~~~~~~~~~~~~
        desc02 = desc1 + desc2 + desc3 + desc4 + desc5 + desc6
        
    if int(year) == 12 or int(year) == 18 or int(year) == 24:
        ~~~~~省略~~~~~
            thr13からthr18まで
        ~~~~~~~~~~~~
        desc03 = desc1 + desc2 + desc3 + desc4 + desc5 + desc6
    
    ~~~~~省略~~~~~
       thr19からthr24まで
    ~~~~~~~~~~~~
    desc04 = desc1 + desc2 + desc3 + desc4 + desc5 + desc6
-選択された年数によって動かす処理の数を変えている.

最後に各スレッドのdescを合わせる
    desc = desc01 + desc02 + desc03 + desc04


~
**3.保存の仕方と例外 [#c1eb3f9b]
ほかのルーティングでテキストデータを参照したい場合がある.~
csvに保存してもよいが,文字化けなどの可能性もあるため今回は
pickleモジュールを用いてpickle形式のファイルで保存する.~
-保存する場合
 with open('text_data.pickle', mode='wb') as fo:
     pickle.dump(desc,fo)
--保存される元のデータは{desc}
--保存先のpickleファイルは{text_data.pickle}

-呼びだしたい場合
 with open('text_data.pickle', mode='br') as fi:
     desc = pickle.load(fi)
--読み込むpickleファイルは{text_data.pickle}
--保存先の変数は{desc}

最後に,取得できたデータの要素数によって例外処理を追加する.~
要素数が0の場合に正しく動作しないことや,要素数が少なすぎることを考慮して,要素数が30未満の場合は,トップページ戻るようにしている.~

 desc_len = len(desc)
 if desc_len < 30:
     return redirect(url_for('start'))
-{desc}の要素数が30未満の時はredirectでstart(トップページ)に飛ぶようにしている.

*Sentence-BERT [#u506e563]
事前学習モデルは”sonoisa/sentence-bert-base-ja-mean-token”を用いる~
 model = SentenceBertJapanese("sonoisa/sentence-bert-base-ja-mean-tokens")
-モデルの指定はここで行う.~

SentenceBertJapaneseの中身はここを参照👇~
👉https://huggingface.co/sonoisa/sentence-bert-base-ja-mean-tokens
~
~

*UMAP [#d75a68f0]
UMAPでSentence-BERTから得られたベクトルを2次元と15次元に圧縮する.~
-15次元のベクトルは後述するクラスタリングなどに用いる.
-2次元のベクトルは散布図のプロットに用いる.

**UMAPのパラメータ [#w17b6077]
-n neighbors>
n_neighbors パラメータは,各データポイントの埋め込みにおいて考量する近隣
点の数を指定する.~
-min_dist>
min_distパラメータは,UMAP によって生成される低次元埋め込み空間内のデー
タ点間の最小距離を制御する.~
-n_components>
n_components パラメータは,UMAP によって生成される埋め込み次元の次元数
を指定する.~
-metric>
metricパラメータは,データ間の類似度や距離を算出するための手法を指定する
ことができる.~

実際の値
 sentence_vectors_umap_15 = umap.UMAP(n_components=15, 
                                      random_state=42, 
                                      n_neighbors = 25, 
                                      min_dist = 0.1,
                                      metric = 'cosine').fit_transform(sentence_vectors)
上記は15次元の場合,2次元にするときはn_componentsの値を2にする.~

ベクトル化されたデータもpickleを用いて保存しておく.~
 with open('vectors_15.pickle', mode='wb') as fo:
     pickle.dump(sentence_vectors_umap_15, fo)

 with open('vectors.pickle', mode='wb') as fo:
     pickle.dump(sentence_vectors_umap_2, fo)
~
*シルエット分析 [#e6771c98]
K-medoidsでクラスタリングを行うために最適なクラスター数を導出する.
シルエット分析はクラスタリング結果における凝縮度と乖離度をもとに最適なクラスター数を導出する.~
クラスター数が3から19まで(20以上だと多すぎるかな?)のシルエット係数を計算し係数が一番高くなったクラスター数を最適な数とする.~
-プログラム
 def show_silhouette(labels):
     cluster_labels = np.unique(labels)
     num_clusters = cluster_labels.shape[0]
     silhouette_vals = silhouette_samples(data, labels)  # シルエット係数の計算
     # 可視化
     y_ax_lower, y_ax_upper = 0, 0
     y_ticks = []
 
     for idx, cls in enumerate(cluster_labels):
         cls_silhouette_vals = silhouette_vals[labels==cls]
         cls_silhouette_vals.sort()
         y_ax_upper += len(cls_silhouette_vals)
         y_ticks.append((y_ax_lower + y_ax_upper) / 2.0)
         y_ax_lower += len(cls_silhouette_vals)
 
     silhouette_avg = np.mean(silhouette_vals)
     silhouette_df.append(silhouette_avg)

-実際の結果
#ref(シルエット.png)

この場合は一番シルエット係数が高い15を最適なクラスター数とする.
~
*クラスタリング [#cda78d69]
クラスタリングにはk-medoidsを用いる.~
k-meansではデータの外れ値が大きい場合,クラスタリングの結果が大雑把になってしまうことが稀にあるため,外れ値につよいk-medoidsを用いる.詳細は[[島崎]]に''託す''.~
-クラスタリングを行った結果はそれぞれのベクトルにクラスタ番号を対応付けて保存しておく.
 df_umap_2 = pd.DataFrame(data=sentence_vectors_umap_2, columns=['x', 'y'])
 df_umap_2["class"] = ["cluster"+str(x) for x in predicted_labels]
 df_umap_15 = pd.DataFrame(data=sentence_vectors_umap_15, columns=['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'a10', 'a11', 'a12', 'a13', 'a14', 'a15'])
 df_umap_15["class"] = ["cluster"+str(x) for x in predicted_labels]
 df_umap_2.to_pickle('df_umap.pkl')
~
**タイトルの提示 [#sd94bd78]
各データの重心とのユークリッド距離を計算する.~
 centers = kmeans_model.cluster_centers_
 df_umap_15["distance"] = np.nan
 
 for j in range(class_n):
     class_name = str("cluster" + str(j))
     d = df_umap_15[df_umap_15["class"] == class_name]
        
     for i in range(d.shape[0]):
         v = d.iloc[i, :]
         v = v[:-2]
 
         distances = np.subtract(v, centers[j])
  
         distances_squared = np.square(distances)
 
         distance1 = np.sqrt(np.sum(distances_squared))
 
         df_umap_15.at[d.index[i], "distance"] = distance1
-df_umap_15に新しくdistanceという列を追加する.
--最初に列を追加する理由は,後から入れると,最初はdistanceという列が存在しないが次から存在するため処理がめんどくさくなるから.(下に記述する部分で)
 df_umap_15["distance"] = np.nan

-dfの横列にはベクトルにプラスして「クラス番号」と「distance」が入っているため,vのサイズとcenters(中心の位置)のサイズが異なる.~
そこで,vのサイズをcentersと合わせるために後ろの2つの要素を除外する.~
 v = v[:-2]

-求められた距離を求めたデータに対応付けて"distance"に代入する.
 df_umap_15.at[d.index[i], "distance"] = distance1
~
それぞれのクラスについて先ほど求めたdistanceを用いてタイトルを生成する.
 for a in tqdm.tqdm(range(class_n)):
     vec_dis = df_umap_15[df_umap_15["class"] == "cluster" + str(a)]
     vec_dis_sorted = vec_dis.sort_values('distance')
     title_all = []
     # #ランダム
     # if text.shape[0] >= 10:
     #     random_n = 10
     # else:
     #     random_n = text.shape[0]
 
     # for i in tqdm.tqdm(random.sample(range(text.shape[0]), k=random_n)):
     for i in tqdm.tqdm(range(vec_dis_sorted.head(10).shape[0])):
         # target_text = text.iloc[i][0]
         target_text = pd.DataFrame(df[0]).loc[vec_dis_sorted.head(10).index[i]][0]
 
         tagged_text = get_mecab_tagged(target_text)
 
         terms = term_ext(tagged_text)
 
         compound = remove_single_words(terms)
         title_all.extend([termextract.core.modify_agglutinative_lang(cmp_noun) for cmp_noun, value in compound.most_common(3)])
         
     set1 = sorted([k for k, v in collections.Counter(title_all).items() if v >= 1], key=title_all.index)
     title.append("/".join(set1[0:3]))
+クラスに含まれる要素を取り出す.
+その要素を{distance}が小さい順に並べ替える.
+小さいものから10個取り出す.
➡それらが10個未満の場合はすべて取り出す.
+重要語を計算し,その中から重要度が高いものを取り出す.
 title_all.extend([termextract.core.modify_agglutinative_lang(cmp_noun) for cmp_noun, value in compound.most_common(3)])
 ↳それぞれの文章からトップ3を抜き出す.
 set1 = sorted([k for k, v in collections.Counter(title_all).items() if v >= 1], key=title_all.index)
 ↳それらを重要度が高い順に並べ替える.
 title.append("/".join(set1[0:3]))
 ↳最後にトップ3を抜き出す.

**散布図グラフの描画 [#qe5ab135]
散布図の描画は2次元で行う.~
また,散布図の下には各クラスターの内容を表示する.~
散布図にはデータのプロットと,データのクラスリングの結果を表示する.~
クラスタリング結果の見方は,データの色と点の形からクラスタ番号を参照する.~
その番号とクラスターの内容を照らし合わせる.~
 marker_styles = [
     ".",
     ",",
     "o",
     "v",
     "^",
     "<",
     ">",
     "1",
     "2",
     "3",
     "4",
     "8",
     "s",
     "p",
     "*",
     "h",
     "H",
     "+",
     "x",
     "D",
     "d",
 ]
-グラフに表示するときの点の形を事前に定義する.

**グラフの画像保存 [#h6adf354]
作成されたグラフは画像形式度保存してhtml上に出力する.
 buf: BytesIO = BytesIO()
 # figure.savefig(buf, format='png')
 figure.savefig(buf, bbox_inches="tight")
 with buf:
     data = buf.getvalue()
    
 img_data = base64.b64encode(data).decode("ascii")
 
 img_src: str = "data:image/png;base64," + img_data
-matplotlibで作成したグラフを画像形式で保存する.保存した画像はhtml側に送る

**htmlでの表示 [#xe6ab4e0]
 <div style="height: 50%;">
     <a href="{{ img_src }}" data-lightbox="group"><img src="{{ img_src }}" style="height: 100%;"/></a> 
 </div>
-上記のように表示する.~
~
-実際の結果
#ref(散布図.jpg)
~
*選択したクラスターとグラフの大きさ [#je205489]
**クラスターに含まれる特許 [#db28ac17]
選択したクラスターに含まれている特許の実際のGoogle Patentsのサイトに飛べるようにしている.~
スクレイピングの時に取得した特許番号の部分を使ってurlを作成している.~
詳しく知りたい場合はjinja2のfor文の書き方を調べるとよい.~
-select.html
 <h1>特許一覧</h1>
 {% for x in plat_index %}{%set plat_index_loop = loop %}
 {% for y in plat_index2 %}{%if loop.index==plat_index_loop.index %}
 <ul>
     <a href=https://patents.google.com/{{x}} target="_blank">{{y}}</a>
 </ul>
 {% endif %}
 {% endfor %}
 {% endfor %}

**グラフの大きさの選択 [#pee44f2c]
グラフの大きさを描画する共起関係の数をもとに設定する.

-select.html
 <form action="/graph" method="POST">
     <div class="flexbox">
         <div class="flex-item">
             <button type="submit" style="height: 250px;">
                 <img src="static/xyz2.png" alt="Button Image" style="height: 100%;">
             </button>
         </div>
         <div style="font-size:large" class="flex-item">3Dグラフ</div>
     
         <div class="yoko">
             <label>
                 <input type="radio" name="3g_size" class="check" value="1000" >小
             </label>
             <label>
                 <input type="radio" name="3g_size" class="check" value="2000" checked>中
             </label>
             <label>
                 <input type="radio" name="3g_size" class="check" value="3000">大
             </label>
         </div>
     </div>
 </form>

*分かち書き [#ldc6d820]
**termextract [#qbb8ab79]
専門用語や複合語などを抽出するためのモジュール
***モジュールの入れ方 [#q35d9ead]
以下のサイトからtermextractをダウンロードする.~
~
👉http://gensen.dl.itc.u-tokyo.ac.jp/pytermextract/~
ダウンロードしたら,ダウンロード先のファイル(termextract)のディレクトリで,コマンドプロンプトを起動する.~
コマンドプロンプトで,以下の操作を行う.~
 pip install .
~
***core.pyの変更 [#pd0b1db6]
既存のcore.pyを用いるとエラーが起こる場合があるため変更する.~
まず自身のパソコンのtermextractがインストールされているファイルに移動~
-保存場所の確認方法
 import termextract
 
 print(termextract.__path__)

このファイルの中のcore.pyを変更する.(今回はcore2.pyとして別のファイルを作成している)~
core2.pyにした時のモジュールの定義
 import termextract.core2
~
変更箇所
 from decimal import Decimal
 ~~~~~~~~~~~~~~~~~~~
 84| importance = Decimal(importance) ** (1 / (2 * Decimal(average_rate) * count))
 ~~~~~~~~~~~~~~~~~~~

エラーが起こる理由はおそらく重要度を計算するときに,計算する式の値の桁数が大きすぎるため
~
**Janomeの辞書登録 [#c7686f71]
termextractの出力結果をもとにJanomeの辞書の登録を行う.~
csv形式で与えることでユーザー辞書を登録することができる.~
termextactはjanomeを用いる元のmecabを用いるものがあるが,今回はmecabバージョンを使う.~
-termextractの定義部分~
 CHASEN_ARGS = r' -F "%m\t%f[7]\t%f[6]\t%F-[0,1,2,3]\t%f[4]\t%f[5]\n"'
 CHASEN_ARGS += r' -U "%m\t%m\t%m\t%F-[0,1,2,3]\t\t\n"'
 m = MeCab.Tagger(ipadic.MECAB_ARGS + CHASEN_ARGS)
 m.parse('')
 
 def get_mecab_tagged(text):
     node = m.parseToNode(text)
     buf = ''
     while node:
         if node.surface:
             buf += node.surface + '\t' + node.feature + '\n'
         node = node.next
     return buf
 
 def term_ext(tagged_text):
     frequency = termextract.mecab.cmp_noun_dict(tagged_text)
     lr = termextract.core2.score_lr(
         frequency,
         ignore_words=termextract.mecab.IGNORE_WORDS,
         lr_mode=1, average_rate=1)
     term_imp = termextract.core.term_importance(frequency, lr)
     return Counter(term_imp)
 
 def remove_single_words(terms):
     c = Counter()
     for cmp, value in terms.items():
         if len(cmp.split(' ')) != 1:
             c[termextract.core.modify_agglutinative_lang(cmp)] = value
     return c
-辞書作成部分
 for i in tqdm.tqdm(range(text.shape[0])):
     target_text = text.iloc[i][0]
     tagged_text = get_mecab_tagged(target_text)
 
     terms = term_ext(tagged_text)
 
     compound = remove_single_words(terms)
     for cmp_noun, value in compound.most_common(10):
         # print(termextract.core.modify_agglutinative_lang(cmp_noun), value, sep="\t")
         df_frequency.append(termextract.core.modify_agglutinative_lang(cmp_noun))
 
     app_list = [-1, -1, 1000, '名詞', '固有名詞', '*', '*', '*', '*']
     app_list2 =['*', '*']
 
     for i in range(len(df_frequency)):
         df_append=[]
         df_append.append(df_frequency[i])
         df_append.extend(app_list)
         df_append.append(df_frequency[i])
         df_append.extend(app_list2)
         df_csv_frequency.append(df_append)
 
 df_dictio=pd.DataFrame(df_csv_frequency)
 df_dictio.to_csv("user_dic_" + str(class_set) +".csv", sep=",",index=False,header=False,encoding='cp932', errors='ignore') 

-実際のcsvファイル
#ref(ユーザー辞書2.png)

**分かち書き処理 [#pe2962a7]
 sentences = []
 sentences_2 = []
 
 for i in tqdm.tqdm(range(text.shape[0])):
     target_texts = text.iloc[i]
     t = Tokenizer('user_dic_' + str(class_set) +'.csv', udic_enc='cp932')
     texts = target_texts.str.split('。')
     wakati_list = []
     for s in texts[0]:
         words = []
         for token in t.tokenize(s):
             s_token = token.part_of_speech.split(',')
             # 一般名詞、自立動詞(「し」等の1文字の動詞は除く)、自立形容詞を抽出
             if (s_token[0] == '名詞' and s_token[1] == '一般') \
                     or (s_token[0] == '形容詞' and s_token[1] == '自立')\
                     or (s_token[0] == '名詞' and s_token[1] == '固有名詞'):
                 words.append(token.surface)
         wakati_list.append(words)
     sentences.append(wakati_list)
     sentences_2.extend(wakati_list)
 
 # combination_sentences = []
 # for words in tqdm.tqdm(sentences_2):
 combination_sentences = [list(itertools.combinations(words, 2)) for words in sentences_2]
 combination_sentences = [[tuple(sorted(combi)) for combi in combinations] for combinations in combination_sentences]
 tmp = []
 for combinations in combination_sentences:
     tmp.extend(combinations)
 combination_sentences = tmp
 
 touroku_list = []
 
 for i in tqdm.tqdm(range(len(combination_sentences))):
     if (combination_sentences[i][0] in df_frequency) or (combination_sentences[i][1] in df_frequency):
         touroku_list.append(combination_sentences[i])

-df_frequency[]~
それぞれの文章から最大10個重要度が高い順にdf_frequencyに挿入していく.~
 for cmp_noun, value in compound.most_common(10):
     df_frequency.append(termextract.core.modify_agglutinative_lang(cmp_noun))

-combination_sentences~
分かち書きで抽出された単語同士の文章中での組み合わせを列挙する.~
例:[今日,私,学校,行った]➡[今日,私],[今日,学校],[今日,行った]…[学校,行った]~
 combination_sentences = [list(itertools.combinations(words, 2)) for words in sentences_2]
 combination_sentences = [[tuple(sorted(combi)) for combi in combinations] for combinations in combination_sentences]
 tmp = []
 for combinations in combination_sentences:
     tmp.extend(combinations)
 combination_sentences = tmp

-touroku_list[]~
分かち書きの結果をそのまま使うと,一般的な用語が多く含まれることが想定される.~
そのため,df_frequencyに登録されている重要語が高い用語があるものだけを取り出す.~
combination_sentencesの中に重要語が含まれていればそれをtouroku_listに挿入する.~
 for i in tqdm.tqdm(range(len(combination_sentences))):
     if (combination_sentences[i][0] in df_frequency) or (combination_sentences[i][1] in df_frequency):
     touroku_list.append(combination_sentences[i])

*共起語ネットワーク [#raa586e3]
**共起関係の導出 [#z9e7882e]
Jaccard係数,Dice係数,Simpson係数の計算を行う.(実際に使っているのはSimpson係数)~
それぞれの係数の値は{jaccard_coef}に格納されている.(変数名を変更するのが面倒くさかったため)
-Simpson係数の計算
 def make_overlap_coef_data(combination_sentences):
 
     combi_count = collections.Counter(combination_sentences)
 
     word_associates = []
     for key, value in combi_count.items():
         word_associates.append([key[0], key[1], value])
 
     word_associates = pd.DataFrame(word_associates, columns=['word1', 'word2', 'intersection_count'])
 
     words = []
     for combi in combination_sentences:
         words.extend(combi)
 
     word_count = collections.Counter(words)
     word_count = [[key, value] for key, value in word_count.items()]
     word_count = pd.DataFrame(word_count, columns=['word', 'count'])
 
     word_associates = pd.merge(
         word_associates,
         word_count.rename(columns={'word': 'word1'}),
         on='word1', how='left'
     ).rename(columns={'count': 'count1'}).merge(
         word_count.rename(columns={'word': 'word2'}),
         on='word2', how='left'
     ).rename(columns={'count': 'count2'}).assign(
         union_count=lambda x: np.minimum(x.count1,x.count2)
     ).assign(
         count_diff=lambda x: np.abs(x.count1 - x.count2)
     ).assign(jaccard_coef=lambda x: x.intersection_count / x.union_count).sort_values(
         ['jaccard_coef', 'intersection_count'], ascending=[False, False]
     )
--count_diff~
お互いの集合の要素差を求めている
--union_count~
count1とcount2の小さいほうを求める.
--jaccard_coef~
intersection_countをunion_countで割る

-Jaccard係数の計算
 ~~~~~~~~~~同文~~~~~~~~~~
 word_associates = pd.merge(
         word_associates,
         word_count.rename(columns={'word': 'word1'}),
         on='word1', how='left'
     ).rename(columns={'count': 'count1'}).merge(
         word_count.rename(columns={'word': 'word2'}),
         on='word2', how='left'
     ).rename(columns={'count': 'count2'}).assign(
         union_count=lambda x: x.count1 + x.count2 - x.intersection_count
     ).assign(jaccard_coef=lambda x: x.intersection_count / x.union_count).sort_values(
         ['jaccard_coef', 'intersection_count'], ascending=[False, False]
     )
--intersection_count~
要素の共通部分の数
--union_count~
count1とcount2の合計からintercsection_countを引くことで,集合の数を求めている.
--jaccard_coef~
intersection_countをunion_countで割る

-Dice係数の計算
 ~~~~~~~~~同文~~~~~~~~~~
 word_associates = pd.merge(
         word_associates,
         word_count.rename(columns={'word': 'word1'}),
         on='word1', how='left'
     ).rename(columns={'count': 'count1'}).merge(
         word_count.rename(columns={'word': 'word2'}),
         on='word2', how='left'
     ).rename(columns={'count': 'count2'}).assign(
         union_count=lambda x: x.count1 + x.count2
     ).assign(jaccard_coef=lambda x: 2 * x.intersection_count / x.union_count).sort_values(
         ['jaccard_coef', 'intersection_count'], ascending=[False, False]
     )

--union_ount~
count1とcount2の合計
--jaccard_coef
intersection_countの2倍をunion_countで割る.

実際の出力結果
#ref(関係.jpg)
-intersection_countにはword1とword2が同時に出てくる回数.
-count1はword1の出現回数
-count2はword2の出現回数
-union_countはcount1とcount2の小さいほうの数
-count_diffはcount1とcount2の要素数の差
-jaccard_coefはsimpson係数の値
⚠カラムの名前が混在しているので注意!

***しきい値の設定 [#ydce4187]
より良い結果を得るためにしきい値を設定する.
具体的には
+Simpson係数が1未満のもの
+お互いの要素差が5000未満のもの

 jaccard_coef_data = make_overlap_coef_data(touroku_list)
    
 simpson = jaccard_coef_data['count_diff']
    
 simpson2 = jaccard_coef_data['jaccard_coef']
 
 filt = (simpson < 5000) & (simpson2 < 1)
 jaccard_coef_data[filt].to_pickle('jaccard_coef.pkl')

**jsonファイルの作成 [#pe7fdb90]
3D Force-Directed Graphに共起関係の情報を送るためにjsonファイルを作成する.~
simpson係数の結果からjsonファイルに変換する
 jaccard_coef_data = pd.read_pickle('jaccard_coef.pkl')
 got_data = jaccard_coef_data.head(int(g_size))
 sources = got_data['word1']#count
 targets = got_data['word2']#first
 
 edge_data = zip(sources, targets)
 
 count_list_df = pd.DataFrame([{'first' : i[0], 'second' : i[1]} for i in edge_data])
 
 count_id = count_list_df.stack().drop_duplicates().tolist()
 
 word1 = got_data[['word1','count1']].rename(columns={ 'word1' : 'word' , 'count1' : 'count'})
 word2 = got_data[['word2','count2']].rename(columns={ 'word2' : 'word', 'count2' : 'count'})
 
 df_word_count = pd.concat([word1, word2]).drop_duplicates(subset='word')
 
 def create_json(nodes, links):
     json_data = {
         "nodes": nodes,
         "links": links
     }
     return json_data
 
 edge_data = zip(sources, targets)
 
 nodes = []
 
 for _, row in df_word_count.iterrows():
     node = {"id": row['word'], "group": 1}
     if row['count'] > 3000:
         node['group'] = 2
     nodes.append(node)
 
 links = [{"source": values[0], "target": values[1], "value": 1} for values in edge_data]
 json_data = create_json(nodes, links)
 
 with open('static/output.json', 'w', encoding='utf-8') as json_file:
     json.dump(json_data, json_file, ensure_ascii=False, indent=4)

作成するjsonファイルの形式(output.json)
 {
    "nodes": [
        {
            "id": "砂利",
            "group": 1
        },
        {
            "id": "実施形態",
            "group": 2
        },
 ~~~~~~~~~~省略~~~~~~~~~~
        {
            "id": "残雪",
            "group": 1
        },
        {
            "id": "上顎",
            "group": 1
        }
    ],
    "links": [
        {
            "source": "砂利",
            "target": "骨材",
            "value": 1
        },
        {
            "source": "咬合部",
            "target": "歯列",
            "value": 1
        },
 ~~~~~~~~~~省略~~~~~~~~~~
        {
            "source": "ヒドロキシ",
            "target": "紫外線吸収剤",
            "value": 1
        },
        {
            "source": "実施形態",
            "target": "符号",
            "value": 1
        }
    ]
 }

-"nodes"にはグラフに表示される単語の定義を行う.~
--"id"はノードの単語.~
--"group"は色分けなどをしたいときにノードのグループを指定する.~

-"links"には共起関係を記述する.~
--"source"は共起元の単語.~
--"target"は共起先の単語.~
--"value"は結ぶ線の大きさを変更するときなどに利用される.~

**3Dグラフ [#gd773348]
3Dグラフの描画にはThree.jsのモジュール”3D Force-Directed Graph”を使う.~
参考にしたサイト👉https://vasturiano.github.io/3d-force-graph/~
javascriptの買い方はサイトを参考にすれば様々な変更が可能.~
⚠モジュールのインポート方法はサイトのものでは行えなかったため独自で行った.
-graph.html
 <!DOCTYPE html>
 <html lang="en">
   <meta charset="utf-8">
   <style>body{margin: 0px; padding: 0px;}</style>
 </head>
 <body>
   <button type="button" onclick="history.back()">戻る</button>
   <div id="three" style="background-color: aliceblue;"></div>
   <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.js" charset="utf-8" defer></script>
 </body>
 </html>

--<script type="module" src="https://unpkg.com/three@0.158.0/build/three.js" defer></script>~
three.jsのインポート

--<script type="module" src="https://unpkg.com/3d-force-graph@1.73.0/dist/3d-force-graph.min.js" defer></script>~
3D Force-Directed Graphのモジュールのインポート

--<script type="module" src="https://unpkg.com/three-spritetext@1.8.1/dist/three-spritetext.min.js" defer></script>~
テキストをノードにするときに必要なモジュールのインポート

--<script src="./static/main2.js" charset="utf-8" defer></script>
プログラムに使うjavascriptのファイルの指定


-main2.js
 const highlightLinks = new Set();
 const highlightNodes = new Set();
 let hoverNode = null;
 
 const Graph = ForceGraph3D()
     (document.getElementById("three"))
 
    .jsonUrl('http://localhost:5000/static/output.json')
    .nodeLabel('id')
    .nodeRelSize(20)
    .nodeThreeObject(node => {
        const sprite = new SpriteText(node.id);
        const group = new SpriteText(node.group)
        // const sprite = new SpriteText(`${link.source} > ${link.target}`);
        sprite.material.depthWrite = false; // make sprite background transparent
        if (node.group == '2') {
            sprite.color = 'red';
        } else {
            sprite.color = 'black'
        }
        sprite.textHeight = 7.5;
        return sprite;
    })
    .onNodeClick(node => {
        const distance = 100;
        const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);
 
        const newPos = node.x || node.y || node.z
        ? { x:node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio}
        : { x: 0, y: 0, z: distance};
 
        Graph.cameraPosition(
            newPos,
            node,
            100
        )
    })
 
 // link
    .linkOpacity(0.25)
    .linkDirectionalArrowLength(3)
    .linkDirectionalArrowRelPos(1)
    .linkCurvature(0.01)
    .linkDirectionalParticleWidth(2)
    .linkDirectionalParticles("value")
    .linkDirectionalParticleSpeed(d => d.value * 0.01)
    .linkThreeObjectExtend(true)
    .linkColor(() => '#708090')
    .linkWidth(1)
 
    .backgroundColor("#f8f8f8");

-ここで共起関係を記述したjsonファイルを指定している.~
⚠ここのパスにlocalhost:5000を指定しているため,ローカルで動かすときはlocalhost:5000にアクセスしないとエラーが起こる.
 .jsonUrl('http://localhost:5000/static/output.json')
-実際に共起語の描画の処理を行っている.~
ノードの色の変更の処理もここで行う.~
 .nodeThreeObject
-初期設定だとグラフの回転軸が中央で固定になってしまうため,今回はクリックしたノードを中心に回転できるようにしている.
 .onNodeClick
-矢印の設定もろもろ
 .linkOpacity(0.25)
 .linkDirectionalArrowLength(3)
 .linkDirectionalArrowRelPos(1)
 .linkCurvature(0.01)
 .linkDirectionalParticleWidth(2)
 .linkDirectionalParticles("value")
 .linkDirectionalParticleSpeed(d => d.value * 0.01)
 .linkThreeObjectExtend(true)
 .linkColor(() => '#708090')
 .linkWidth(1)
--linkDirectionalParticlesはノードの矢印にアニメーションを追加することで,矢印の向きが分かりやすいようにしている.
-初期設定では背景の色が黒色だが見にくいため白色に変更している.
 .backgroundColor("#f8f8f8")

htmlの
 <div id="three" style="background-color: aliceblue;"></div>
javascritpの
 <div id="three" style="background-color: aliceblue;"></div>
の部分のidが同じになっていないといけないので注意.
~
実際の出力結果
#ref(3d.jpg)
~
*その他 [#dd9fe27e]

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

 <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>
-<div>タグの<p>の中身を変更すれば画面に表示される文字が変わる.~

-ページをブラウザバックしたときにロード画面がそのままになっていたため,ページ遷移が行われるとロード画面が消えるように設定している.~
⚠別のタブに移動したときも消えるので注意!

**ブラウザバック [#dcb69e5a]
ブラウザバックをしたときにフォームなどのユーザからの入力が保持されていないとエラーが起こるため,それを回避している.
-ページ遷移するときにそのまま処理の部分に飛ぶのではなく以下のようにしている.
 @app.route('/post', methods=['POST'])
 def post():
     session["class"] = request.form.get("class_select")
     return redirect(url_for('select'))
 
 @app.route("/select", methods=["GET", "POST"])
 def select():
     if 'class' not in session:
         return redirect(url_for('select'))
     class_set = session["class"]
 ~~~~~~~~~~省略~~~~~~~~~~


[[技術資料]]

*目次 [#m8988ea1]

#CONTENTS

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


*使用するファイル [#pe64d1b6]

本研究で使用するファイルはすべてGoogleDriveにある.
&br;
user:iie.lab.tpu.2324@gmail.com
&br;

「マイドライブ」→「学生」→「24_r2戸田」→「保存期間5年」→「3)卒論(プログラム)」の中にある。
&br;
すべてのzipをダウンロードする.~

&color(red){中山実行プログラム};
#ref(中山実行プログラム.zip)


|フォルダ名|用途|ファイル名|ファイルの場所|
|kawasehendou|為替変動の影響分析|''&color(red){7to12_Yfinance.ipynb};''|kawasehendou|
|kabuka|株価データのデータ収集|''&color(red){7to12_JPX401.ipynb};''|kabuka|
|sankakuka_2|産業連関表の三角化|''&color(red){sankakuka_2020.py};''|sankakuka_2|
|soukan|相関ヒートマップの作成|''&color(red){soukan_7to12_map_yfinance.ipynb};''|soukan|
|3Dgraph|jsonファイルの作成|''&color(red){3Dgraph_kihon.ipynb};''|3Dgraph|
|3Dgraph|3Dグラフを可視化するための実行ファイル|app_0114.py|3Dgraph|
|3Dgraph|javascriptで読み込む用のjsonファイル|output_toda_kihon_zenbu.json|3Dgraph/static/json|
|3Dgraph|3Dグラフのデータの可視化|index_0114.html|3Dgraph/templates|
|3Dgraph|3Dグラフを作成するときのjavascriptのファイル|main2_0114.js|3Dgraph/static|
~
***プログラムを修正する [#zb7feed2]

上記で''&color(red){あかで書かれているプログラム};''は,~
csvファイルを書きこんだり,読みこんだりするときに,絶対パスを使ってあるが,~
そのままでは,「''C:/Users/ta_na/''/...」になっているはずである.~

その「''C:/Users/ta_na/''」のところを,''すべてのプログラムにおいて,すべての箇所で,''~
上記でフォルダを置いた絶対パスに修正する必要がある.~

「Visual Studio Code」では,特定の文字列をすべて別の文字列に~
置換する機能があるので,それを利用しよう.~

***動作環境 [#x9020b74]

Pythonのバージョンは3.12.4を用いる

|ライブラリ名|バージョン|
|selenium|4.27.1|
|panads|1.3.5|
|numpy|1.26.4|
|yfinance|0.2.52|
|matplotlib|3.9.2|
|webdriver-manager|4.0.2|
|beautifulsoup4|4.12.3|
|seaborn|0.13.2|
|pulp|2.9.0|
|networkx|3.4.2|
|flask|3.0.3|
~
モジュールのインストールはコマンドプロンプトでpip install モジュール名

バージョンまで指定する場合はコマンドプロンプトでpip install モジュール名==指定するバージョン
でインストールする
~

*** 各プログラムを実行する [#ce4e9821]
各プログラムを,以下のとおり,''&color(red){順番に};''実行する.~

――――――――――――――~

''1. 7to12_Yfinance.ipynb''~

''2. sankakuka_2020.py''~

''3. 3Dgraph_kihon.ipynb''~

''4. app_0114.py''~

''5. 7to12_JPX401.ipynb''~

''6. soukan_7to12_map_yfinance.ipynb''~
――――――――――――――~

*産業連関分析 [#ea813e90]

**投入係数行列の作成 [#i8722536]

-7to12_Yfinance.ipynb

  import numpy as np
  import pandas as pd

  #2020年国産表37部門

  df_kokusan = pd.read_csv("C:/Users/ta_na/OneDrive/デスクトップ/kawasehendou/kawasehendou/csv/kokusan2020.csv", header=None)

  # 3行目以降(インデックス 2 以降)、3列目以降(インデックス 2 以降)を取得
  subset_df_kokusan = df_kokusan.iloc[3:,2:]
  # データ型を確認し、必要ならfloatに変換
  subset_df_kokusan = subset_df_kokusan.astype(float)

  # インデックスをリセットして1からの連番にする 
  subset_df_kokusan.reset_index(drop=True, inplace=True)  # 現在のインデックスをリセット
  subset_df_kokusan.index = subset_df_kokusan.index + 1        # インデックスを1から始める

  # 列名をリセットして1からの連番にする
  subset_df_kokusan.columns = range(1, len(subset_df_kokusan.columns) + 1)

  #内生部門だけ抽出
  # インデックスと列の39以降を削除(1始まりの場合)
  subset_df_kokusan = subset_df_kokusan.loc[:37, :37]
  print(subset_df_kokusan)

取得した産業連関表から内生部門(37×37の行列)を抜き出す.
~

  #国内生産額の抽出
  # インデックス48番の行を取得
  row_48 = df_kihon.loc[48]

  # NaN を含む行を削除 
  kokunaiseisan_row = row_48.dropna()

  # 最初の2行と最後の1行を削除
  kokunaiseisan_row = kokunaiseisan_row.iloc[2:-1]

  # データ型を確認し、必要ならfloatに変換
  kokunaiseisan_row = kokunaiseisan_row.astype(float)

  # インデックスをリセットして1からの連番にする
  kokunaiseisan_row.reset_index(drop=True, inplace=True)  # 現在のインデックスをリセット
  kokunaiseisan_row.index = kokunaiseisan_row.index + 1        # インデックスを1から始める

  print(kokunaiseisan_row)

産業連関表から国内生産額を抽出する.

  #粗付加価値額の抽出
  # インデックス47番の行を取得
  row_47 = df_kihon.loc[47]

  # NaN を含む行を削除
  arahukakathi_row = row_47.dropna()

  # 最初の2行と最後の1行を削除 
  arahukakathi_row = arahukakathi_row.iloc[2:-1]

  # データ型を確認し、必要ならfloatに変換
  arahukakathi_row = arahukakathi_row.astype(float)

  # インデックスをリセットして1からの連番にする
  arahukakathi_row.reset_index(drop=True, inplace=True)  # 現在のインデックスをリセット
  arahukakathi_row.index = arahukakathi_row.index + 1        # インデックスを1から始める

  print(arahukakathi_row)

産業連関表から粗付加価値額を抽出する.

  # 国産品投入係数行列を計算
  coefficient_matrix_kokusan = subset_df_kokusan.div(kokunaiseisan_row, axis=1)
  coefficient_matrix_kokusan

国産品投入係数行列を作成

  # 粗付加価値率(国内生産額÷粗付加価値額)を計算
  coefficient_matrix_arahukakathi = arahukakathi_row / kokunaiseisan_row
  coefficient_matrix_arahukakathi



  #こっちのyfinanceのドル円レートを使う
  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)


  # 終値を取得(Series にする)/ここから、変更が必要になる可能性あり
  usd_jpy = usd_jpy[['Close']].reset_index().set_index('Date').squeeze()

  # 結果を表示
  print(type(usd_jpy))

  # 結果を表示
  print(usd_jpy)

※DataframeがSeriesに変換されない場合、各々で調べてコードを変える必要がある.
Seriesに変換されていない場合、この先でエラーが発生する.~

※Yahoo Finance(YF)のAPIやウェブサービスを使用してUSD/JPYのデータを取得しようとした際に、リクエスト数が多すぎるとエラーが発生する.~
→時間をおいて再度リクエストを試す

※''&color(red){yfinanceのバージョンを0.2.52};''に必ずする必要がある.~

  # 一日の変動率を計算する(当日−前日)/前日
  daily_change_rate = usd_jpy.pct_change() * 100

  # 最初の1行を削除
  daily_change_rate = daily_change_rate.iloc[1:]

  # 結果を表示
  print(daily_change_rate)

**輸入価格変動の影響 [#x8fea72e]

  # 米ドル建て輸入比率を辞書形式で定義
  import_ratio_data = {
      "食料品・飼料": 56.2,
      "繊維品": 36.8,
      "金属・同製品": 79.6,
      "木材・同製品": 77.1,
      "石油・石炭・天然ガス": 97.3,
      "化学製品": 28.1,
      "はん用・生産用・業務用機器" : 39.3,
      "電気・電子機器" : 68.5,
      "輸送用機器" : 37.8,
      "その他産品・製品" : 62.6,
      "輸入計" : 64.4
  }

  # データをリストに変換
  import_ratio_list = [{"カテゴリ": category, "比率": ratio} for category, ratio in import_ratio_data.items()]

  # 米ドル建て契約比率
  R_yunyu = pd.Series([56.2, 79.6, 56.2, 36.8, 77.1, 28.1, 97.3, 64.4, 62.6, 79.6, 79.6, 79.6, 39.3, 39.3, 39.3, 68.5, 68.5, 68.5, 37.8, 62.6, 64.4, 97.3, 64.4, 64.4, 64.4, 64.4, 64.4, 97.9, 64.4, 64.4, 64.4, 64.4, 64.4, 64.4, 64.4, 64.4, 64.4])

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

輸入価格変動の影響を求める際に輸入品価格ベクトル Pim を,「為替の変動率×米ドル建て比率」として求めた.
~
各産業の米ドル建て契約比率には日本銀行「輸出・輸入物価指数の契約通貨別構成比」の
2023 年 12 月の値を用いた
https://www.boj.or.jp/statistics/outline/exp/pi/cgpi_2020/crcc2023.pdf
~



  #輸入の影響

  # 日付ごとの結果を格納する辞書
  result_yunyu_dict = {}

  # 各日付ごとに計算を実施
  for date, delta_E in daily_change_rate.items():
      #print(delta_E)
      # Delta_P_im の計算
      delta_P_im_yunyu = R_yunyu * (-delta_E) / 100  # 各日の為替変動率で計算


      # 一次波及の計算
      # delta_P_im に一致するインデックスを coefficient_matrix_yunyu から抽出
      common_indices = 
  coefficient_matrix_yunyu.index.intersection(delta_P_im_yunyu.index)
      sub_matrix = coefficient_matrix_yunyu.loc[common_indices]  # 対応する行を抽出
      delta_P_d_1 = sub_matrix.multiply(delta_P_im_yunyu[common_indices], axis=0)

      # 完全波及の計算
      # 1. I - A_d^T を計算
      I = np.eye(len(coefficient_matrix_kokusan))  # 単位行列
      I_minus_Ad_T = I - coefficient_matrix_kokusan.T.values

      # 2. 逆行列を計算
      I_minus_Ad_T_inv = np.linalg.inv(I_minus_Ad_T)

      # 3. A_mi^T を計算
      A_mi_T = coefficient_matrix_yunyu.T.values

      # 4. ΔP_d を計算
      delta_P_d_2 = I_minus_Ad_T_inv @ (A_mi_T @ delta_P_im_yunyu.values)

      # 結果を Pandas の Series として保存
      delta_P_d_2 = pd.Series(delta_P_d_2, index=range(37))

      # インデックスをリセットして1からの連番にする
      delta_P_d_2.reset_index(drop=True, inplace=True)  # 現在のインデックスをリセット
      delta_P_d_2.index = delta_P_d_2.index + 1        # インデックスを1から始める

      # 付加価値を分母にしたものに変換
      result_yunyu = delta_P_d_2 / coefficient_matrix_arahukakathi

      # インデックスを産業名に置き換える
      result_yunyu.index = industry_names

      # 結果を辞書に保存
      result_yunyu_dict[date] = result_yunyu

  # 各日付の結果を表示(例)
  for date, result in result_yunyu_dict.items():
      print(f"=== {date} ===")
      print(result)
      print("\n")

#ref(nikkei2.png,,720x400)

以下のリンク先の資料の6,7ページ目の計算のプログラムコード
https://www.jcer.or.jp/jcer_download_log.php?post_id=55288&file_post_id=55335



  # 産業・日付ごとのデータを DataFrame に変換
  all_results_yunyu = []

  for industry in industry_names:
      for date, result in result_yunyu_dict.items():
          value = result[industry]
          all_results_yunyu.append({"日付": date, "産業": industry, "値": value})

  result_df_yunyu = pd.DataFrame(all_results_yunyu)
  print(result_df_yunyu)

  # CSV 保存
  #result_df_yunyu.to_csv("csv/out_data/industry_date_yunyu_2015_2020.csv", 
  index=False, encoding="utf-8-sig")

  # 日付を統一形式に変換
  result_df_yunyu['日付'] = pd.to_datetime(result_df_yunyu['日付'], 
  format='%Y.%m.%d')

  # ピボットテーブルで変換
  reshaped_df_yunyu = result_df_yunyu.pivot(index='日付', columns='産業', values='値')

  # 列名をリセットし、日付を列として戻す
  reshaped_df_yunyu.reset_index(inplace=True)
  reshaped_df_yunyu.rename(columns={'日付': 'Date'}, inplace=True)

  # 結果を表示
  print(reshaped_df_yunyu)

  # CSV 保存
  reshaped_df_yunyu.to_csv("csv/out_data/industry_date_yunyu_7to12.csv", index=False, encoding="utf-8-sig")

結果を見やすい形にしてcsvファイルに保存する.


**輸出価格変動の影響 [#k13ded11]

輸出価格変動の影響を求める際に輸出価格の変化率を「為替の変動率×米ドル建て比率」として求めた.

輸入価格変動と同様に日本銀行「輸出・輸入物価指数の契約通貨別構成比」の
2023 年 12 月の値を用いる.


  # 日付を統一形式に変換
  result_df_yusyutsu['日付'] = pd.to_datetime(result_df_yusyutsu['日付'], format='%Y.%m.%d')
  result_df_yunyu['日付'] = pd.to_datetime(result_df_yunyu['日付'], format='%Y.%m.%d')

  # 二つのデータフレームをマージ
  merged_df = pd.merge(result_df_yusyutsu, result_df_yunyu, on=['日付', '産業'], suffixes=('_輸出', '_輸入'))

  # 値を加算
  merged_df['値_合計'] = merged_df['値_輸出'] + merged_df['値_輸入']

  # 結果を表示
  print(merged_df)


  # 元の産業順序を取得
  original_order = merged_df['産業'].drop_duplicates().tolist()

  # ピボットテーブルで変換
  reshaped_df_marge = merged_df.pivot(index='日付', columns='産業', values='値_合計')

  # 列の順序を元の順序に並び替え
  reshaped_df_marge = reshaped_df_marge[original_order]

  # 列名をリセットし、日付を列として戻す
  reshaped_df_marge.reset_index(inplace=True)
  reshaped_df_marge.rename(columns={'日付': 'Date'}, inplace=True)

  # 結果を表示
  print(reshaped_df_marge)

  # CSV 保存
  #reshaped_df_marge.to_csv("csv/industry_date_marge_2015_2020.csv", index=False, encoding="utf-8-sig")

輸出・輸入の価格変動の影響の差し引きを求める.


  # 指定した列を足し合わせて新しい列を作成
  reshaped_df_marge['非鉄金属・金属製品'] = (
      reshaped_df_marge['非鉄金属'] +
      reshaped_df_marge['金属製品']
  )

  reshaped_df_marge['はん用機械・生産用機械・業務用機械'] = (
      reshaped_df_marge['はん用機械'] +
      reshaped_df_marge['生産用機械'] +
      reshaped_df_marge['業務用機械']
  )

  reshaped_df_marge['電気機械・電子部品・情報通信機器'] = (
      reshaped_df_marge['電気機械'] +
      reshaped_df_marge['電子部品'] +
      reshaped_df_marge['情報通信機器']
  )

  reshaped_df_marge['対事業所サービス・対個人サービス'] = (
      reshaped_df_marge['対事業所サービス'] +
      reshaped_df_marge['対個人サービス']
  )

  # 元の列を削除
  reshaped_df_marge.drop(
      ['非鉄金属', '金属製品',
     'はん用機械', '生産用機械', '業務用機械',
     '電気機械', '電子部品', '情報通信機器',
     '対事業所サービス', '対個人サービス', 
     '水道', '廃棄物処理', '公務', '教育・研究',
     '他に分類されない会員制団体', '事務用品', '分類不明'],
      axis=1,
      inplace=True
  )

  # 結果を表示
  print(reshaped_df_marge)

  # CSV 保存
  reshaped_df_marge.to_csv("csv/out_data/industry_date_marge_7to2.csv", index=False, encoding="utf-8-sig")

産業連関表における部門の分け方と業種別株価における業種の分け方とで違いがあるため, 産業連関データと業種別株価データを統合したデータセットの作成を行う.


  import matplotlib.pyplot as plt
  import numpy as np
  from matplotlib import rcParams

  # 日本語フォントを指定
  rcParams['font.family'] = 'Meiryo'  # Windowsの場合はMeiryoを推奨。Macではヒラギノを使用。

  # 折れ線グラフのプロット
  plt.figure(figsize=(15, 6))
  for column in reshaped_df_marge.columns:
      plt.plot(reshaped_df_marge.index, reshaped_df_marge[column], label=column)

  # グラフの設定
  plt.title('為替変動による各産業への影響値の推移(2024年7月から12月)', fontsize=14)
  plt.xlabel('Date', fontsize=12)
  plt.ylabel('付加価値比', fontsize=12)

  # 凡例をグラフの外側に配置
  plt.legend(title="産業", bbox_to_anchor=(1.05, 1), loc='upper left')
  plt.grid(True)

  # グラフを表示
  plt.tight_layout()
  plt.show()


#ref(output_1.png,,実行結果,,720x400)

*3Dグラフの作成 [#eeca9a90]
産業連関表の三角化を行う.

#ref(sankakuka2.png,,720x400)

~
-sankakuka_2020.py

 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}")


目的関数は, Aij と Aji の値を比べて, 産業部門 i と産業部門 j の上下関係を決め, 制約条件は, 産業部門を最適な順列に並び直す際に, 同じ順位の産業部門が存在しないようにするためのもの



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

-3Dgraph_kihon.ipynb

  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
        }
    ]
 }

-"nodes"にはグラフに表示される単語の定義を行う.~
--"id"はノードの単語.~
--"group"は色分けなどをしたいときにノードのグループを指定する.~

-"links"には産業部門間の関係を記述する.~
--"source"は供給元の単語.~
--"target"は供給先の単語.~
--"value"は結ぶ線の大きさを変更するときなどに利用される.~

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

 <!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>


--<script type="module" src="https://unpkg.com/three@0.158.0/build/three.js" defer></script>~
three.jsのインポート

--<script type="module" src="https://unpkg.com/3d-force-graph@1.73.0/dist/3d-force-graph.min.js" defer></script>~
3D Force-Directed Graphのモジュールのインポート

--<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>
プログラムに使うjavascriptのファイルの指定

~
-main2_0114.js

  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;
      });
  }
 
     
 
-ここで産業部門間の関係を記述したjsonファイルを指定している.~
 .jsonUrl('http://127.0.0.1:5000/static/json/output_toda_kihon_zenbu.json')
-実際に3Dグラフの描画の処理を行っている.~
ノードの色の変更の処理もここで行う.~
 .nodeThreeObject
-初期設定だとグラフの回転軸が中央で固定になってしまうため,今回はクリックしたノードを中心に回転できるようにしている.
 .onNodeClick
-矢印の設定もろもろ
 .linkOpacity(0.25)
 .linkDirectionalArrowLength(3)
 .linkDirectionalArrowRelPos(1)
 .linkCurvature(0.01)
 .linkDirectionalParticleWidth(2)
 .linkDirectionalParticles("value")
 .linkDirectionalParticleSpeed(d => d.value * 0.01)
 .linkThreeObjectExtend(true)
 .linkColor(() => '#708090')
 .linkWidth(1)
--linkDirectionalParticlesはノードの矢印にアニメーションを追加することで,矢印の向きが分かりやすいようにしている.
-初期設定では背景の色が黒色だが見にくいため白色に変更している.
 .backgroundColor("#f8f8f8")


~
-app_0114.py
~

app_0114.pyを実行し、下の図のようなコマンドプロンプト内に表示されるURLをコピーし、ブラウザ上で検索する

#ref(実行.PNG,,40%)~

実際の出力結果

#ref(3Dgraph.png,,720x400)~

*データ収集 [#q47f7fcb]
**産業連関表の収集 [#u7cb95fe]
産業連関表は以下の総務省のリンク先からダウンロードする
~
https://www.soumu.go.jp/toukei_toukatsu/data/io/2020/io20_00001.html

#ref(総務省.png,,総務省 産業連関表,,720x400)
~
**スクレイピングによるデータ収集 [#ocdbc129]
-7to12_JPX401.ipynb
~
各企業のティッカーシンボル(株式会社で取引される企業を識別するためのコード)を取得するために、日本経済新聞のWebサイトからスクレイピングを行う.

  #ここからスタート
  #日本経済新聞のページからティッカーのみを取得する.
  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)

※''&color(red){yfinanceのバージョンを0.2.52};''に必ずする必要がある.

                  # データが取得できた場合、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()

~
※Yahoo Finance(YF)のAPIやウェブサービスを使用してUSD/JPYのデータを取得しようとした際に、リクエスト数が多すぎるとエラーが発生する.~
→時間をおいて再度リクエストを試す~

※''&color(red){yfinanceのバージョンを0.2.52};''に必ずする必要がある.


*相関 [#u016835a]

-soukan_7to12_map_yfinance.ipynb

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

  sangyou_df = pd.read_csv("C:/Users/ta_na/OneDrive/デスクトップ/kawasehendou/kawasehendou/csv/out_data/industry_date_marge_7to2.csv")
  daily_change_rate = pd.read_csv("C:/Users/ta_na/OneDrive/デスクトップ/kabuka/kabuka/csv/out_data/final2_7to12.csv")

  # Date列をdatetime型に変換
  daily_change_rate['Date'] = pd.to_datetime(daily_change_rate['Date'])
  sangyou_df['Date'] = pd.to_datetime(sangyou_df['Date'])

  # 共通のDateを抽出
  common_dates =   set(daily_change_rate['Date']).intersection(set(sangyou_df['Date']))

  # 共通のDateでフィルタリング
  filtered_daily_change_rate = daily_change_rate[daily_change_rate['Date'].isin(common_dates)]
  filtered_sangyou_df = sangyou_df[sangyou_df['Date'].isin(common_dates)]

  # 必要に応じてDateでソート
  filtered_kabuka_df = filtered_daily_change_rate.sort_values(by='Date')
  filtered_sangyou_df = filtered_sangyou_df.sort_values(by='Date')

  # 結果を表示
  print(filtered_kabuka_df)
  print(filtered_sangyou_df)

   filtered_kabuka_df.to_csv("csv/out_data/final_kabuka_7to12.csv",index=False,encoding="utf-8-sig")
  filtered_sangyou_df.to_csv("csv/out_data/final_sangyou_7to12.csv",index=False,encoding="utf-8-sig")

ドル円為替レートと日経 500 種平均株価それぞれの取引市場の営業日が異なっているな
どの理由により, 取得できるデータの日付がドル円為替レートと業種別平均株価とで異なっているため, 両方とも取得できている日付のデータを抽出し. データフレームに格納する.
~

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

  sangyou = pd.read_csv("C:/Users/ta_na/OneDrive/デスクトップ/soukan/soukan/csv/out_data/final_sangyou_7to12.csv")
  #kabuka = pd.read_csv("C:/Users/pi/Desktop/soukan/csv/out_data/final_kabuka_7to12.csv")
  kawase = pd.read_csv("C:/Users/ta_na/OneDrive/デスクトップ/kabuka/kabuka/csv/out_data/final2_7to12.csv")

  # Date列をインデックスに設定
  sangyou.set_index('Date', inplace=True)

  # すべての列名に (産業) を追加
  sangyou.columns = [f"{col}(産業)" for col in sangyou.columns]

  sangyou

  # Date列をインデックスに設定
  kawase.set_index('Date', inplace=True)
  kawase

  #Open, High, Low, Adj Close, Volume の列を削除
  kawase = kawase.drop(columns=["('High', 'JPY=X')", "('Low', 'JPY=X')", "('Open', 'JPY=X')", "('Volume', 'JPY=X')"])

※Pythonのバージョンによって、データのコラム名が異なるため、コラム名の部分を各々変える必要がある。("('High', 'JPY=X')"のような部分)
 
 # すべての列名に (株価) を追加
  kawase.columns = [f"{col}(株価)" for col in kawase.columns]

  # Close を usdjpy に変更
  kawase = kawase.rename(columns={"('Close', 'JPY=X')(株価)": "usdjpy"})

  kawase.to_csv("csv/out_data/final_kabuka_usdjpy_7to12.csv",encoding="utf-8-sig")

  kawase  

  # 横に結合(列方向)
  merge_df = pd.concat([sangyou, kawase], axis=1)
  merge_df

データにコラム名を付ける.
~
  import pandas as pd
  import numpy as np
  import seaborn as sns
  import matplotlib.pyplot as plt
  from matplotlib import rcParams

  # 日本語フォントを指定
  rcParams['font.family'] = 'Meiryo'  # Windowsの場合はMeiryoを推奨。Macではヒラギノを使用。

  # 相関行列を計算(merge_dfは既に存在する前提)
  corr_matrix = merge_df.corr()

  # 相関行列をヒートマップとして保存
  def save_corr_as_image_with_values(corr_matrix,  file_name='correlation_matrix_2_2.png'):
      plt.figure(figsize=(12, 10))  # 図のサイズを大きく調整
    
      # ヒートマップを作成
      sns.heatmap(corr_matrix, cmap='Reds', annot=True, fmt=".2f", cbar=True, annot_kws={"size": 4})
    
      # ラベルのフォーマット調整
      plt.xticks(rotation=90, fontsize=8)  # x軸ラベル
      plt.yticks(fontsize=8)  # y軸ラベル
    
      # 余白調整と保存
      plt.tight_layout()
      plt.savefig(file_name, dpi=300, bbox_inches='tight')  # bbox_inchesでラベルの切れを防止
      plt.close()

  # 関数を実行
  save_corr_as_image_with_values(corr_matrix,  'figure/correlation_matrix_2_2.png')

  print("相関係数を表示した相関行列を画像として保存しました。")


相関を求めて、ヒートマップを作成.

soukan/figureフォルダ内にcorrelation_matrix_2_2.pngが保存される

実行結果
#ref(correlation_matrix_2_2.png,,500x500)



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