【Copilot入門】個別銘柄を特徴量で分類するためにPAC分析のコードを書く🎵

# 動機
ここでの動機は、エニアグラムのような簡単な説明変数で株などの個別銘柄を分類して、投資に役立てることです。そのために、PAC分析を応用したい（エニアグラムのアプリはこんな感じのものだと想定しています）と思います。
今回は、Copilotを利用して、PAC分析の基本的なコードまで出力します。
ちなみに、このコードは、説明変数(と目的変数の関係)があれば、応用が利くものだと思います。
それぞれは、以下の参考のようなものです。
# PAC分析とは
参考
[https://www.ei.tohoku.ac.jp/xkozima/lab/pacTutorial1.html](https://www.ei.tohoku.ac.jp/xkozima/lab/pacTutorial1.html)
# エニアグラムとは
参考
[簡易タイプ診断](https://www.enneagram.ne.jp/about/diagnosis)
# 本編
今回もCopilotへのプロンプトの詳細は余談に記載しました。
その結果、以下のように二番目のプロンプトからの結果は、以下のとおりのグラフが得られます。
```lib.py
import numpy as np
import pandas as pd
from sklearn.cluster import AgglomerativeClustering
import matplotlib.pyplot as plt

# ①適当な質問に対する回答を5つから選ばせる
# ここではサンプルとしてランダムな回答データを生成します
np.random.seed(0)
n = 10
answers = np.random.choice(range(0, 2), (10, n))  # 10人の回答者とnつの質問
answers
```
answersは、１０人の回答者が１０の質問に回答したものとします。
```
array([[5, 1, 4, 4, 4, 2, 4, 3, 5, 1],
       [1, 5, 3, 2, 1, 2, 2, 1, 2, 5],
       [4, 1, 4, 1, 3, 4, 1, 2, 4, 4],
       [4, 1, 2, 2, 2, 1, 3, 5, 4, 4],
       [3, 5, 3, 1, 1, 5, 1, 5, 2, 5],
       [2, 3, 3, 1, 2, 2, 2, 2, 4, 4],
       [3, 4, 1, 4, 5, 2, 3, 5, 4, 5],
       [5, 5, 4, 5, 5, 5, 1, 5, 4, 3],
       [1, 2, 2, 4, 1, 1, 2, 3, 5, 3],
       [1, 4, 3, 3, 1, 2, 1, 3, 3, 4]])
```

```pd_clusterling.py
# 回答データをDataFrameに変換
df_answers = pd.DataFrame(answers, columns=[f'Question {i+1}' for i in range(n)])

# ②質問と回答者を縦軸、横軸にして隣接行列を作る
# 回答データが既に隣接行列の形をしていると仮定します

# ③隣接行列から適当な数のグルーピングをする
# 階層的クラスタリングを使用してグルーピング
clustering = AgglomerativeClustering(n_clusters=None, distance_threshold=0, affinity='euclidean', linkage='ward')
labels = clustering.fit_predict(df_answers)
```
以下のコードでは、「デンドログラムのためのリンケージ行列を作成」を特別に書いています。

```clustering.py
# 階層的クラスタリングの結果をデンドログラムで表示
from scipy.cluster.hierarchy import dendrogram

# デンドログラムのためのリンケージ行列を作成
counts = np.zeros(clustering.children_.shape[0])
n_samples = len(clustering.labels_)
for i, merge in enumerate(clustering.children_):
    current_count = 0
    for child_idx in merge:
        if child_idx < n_samples:
            current_count += 1  # 葉ノード
        else:
            current_count += counts[child_idx - n_samples]
    counts[i] = current_count

linkage_matrix = np.column_stack([clustering.children_, clustering.distances_, counts]).astype(float)
```
以下のコードでは、分類根拠は示されませんでした。デンドログラムからクラスタ数を決定しろと言っています。
```dendrogram.py
plt.figure(figsize=(20, 7))
# デンドログラムをプロット
dendrogram(linkage_matrix, labels=df_answers.index)

plt.title('Hierarchical Clustering Dendrogram')
plt.xlabel('Index of Answer')
plt.ylabel('Distance')
plt.show()

# ④クラスタリングは妥当な数になるように根拠を示す
# エルボー法やシルエット分析などを用いて最適なクラスタ数を決定することができます
# ここではデンドログラムを視覚的に分析してクラスタ数を決定します
```
### 結果デンドログラム
![dendoroA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/688b9efd-9036-569b-673e-4bf29a99af71.png)
### プロンプト３；改善；以下のように改善を要求
つまり、上記のlinkage部分をフレームワークを利用する算段
#ここではデンドログラムを視覚的に分析してクラスタ数を決定します　を行うために隣接行列からグルーピングしたいので、そのコードを書いてください
### linkageを利用して簡単にグルーピングする
データは、上記のanswersを利用します。
```linkage.py
from scipy.cluster.hierarchy import linkage, dendrogram

# 仮の質問と回答者のデータを生成
# 実際には、質問に対する回答データを使用します
np.random.seed(0)
#data = np.random.choice([0, 1], (10, 5))  # 10人の回答者と5つの質問
data = answers #np.random.choice(range(1, 6), (10, 5))  # 10人の回答者と5つの質問
data
```
同じデータを確認
```
array([[5, 1, 4, 4, 4, 2, 4, 3, 5, 1],
       [1, 5, 3, 2, 1, 2, 2, 1, 2, 5],
       [4, 1, 4, 1, 3, 4, 1, 2, 4, 4],
       [4, 1, 2, 2, 2, 1, 3, 5, 4, 4],
       [3, 5, 3, 1, 1, 5, 1, 5, 2, 5],
       [2, 3, 3, 1, 2, 2, 2, 2, 4, 4],
       [3, 4, 1, 4, 5, 2, 3, 5, 4, 5],
       [5, 5, 4, 5, 5, 5, 1, 5, 4, 3],
       [1, 2, 2, 4, 1, 1, 2, 3, 5, 3],
       [1, 4, 3, 3, 1, 2, 1, 3, 3, 4]])
```
以下では、linkageの使い方や隣接行列から、Q１，。。のカラムの作り方が参考になります。
```linked.py
# 隣接行列をDataFrameに変換
df = pd.DataFrame(data, columns=[f'Q{i+1}' for i in range(data.shape[1])])

# 階層的クラスタリングを実行
# 'ward'メソッドを使用してクラスタ間の距離を計算
linked = linkage(df, 'ward')
linked
```
リンケージの結果は、以下のとおり。
index1, 9 ,距離3, 要素数2→index10
index5,10,距離3.5.., 要素数3→index11
。。。index12
。。。index13
。。。index14
index 4, 11,距離6.0..,要素数4→index15
。。。
```
array([[ 1.        ,  9.        ,  3.        ,  2.        ],
       [ 5.        , 10.        ,  3.51188458,  3.        ],
       [ 3.        ,  8.        ,  4.69041576,  2.        ],
       [ 6.        ,  7.        ,  5.65685425,  2.        ],
       [ 0.        ,  2.        ,  5.91607978,  2.        ],
       [ 4.        , 11.        ,  6.09644705,  4.        ],
       [12.        , 14.        ,  6.28490254,  4.        ],
       [13.        , 16.        ,  8.51469318,  6.        ],
       [15.        , 17.        ,  9.80306075, 10.        ]])
```
```dendrogram.py
# デンドログラムをプロット
plt.figure(figsize=(20, 7))
dendrogram(linked, orientation='top', labels=df.index, distance_sort='descending', show_leaf_counts=True)
plt.show()
```
### 結果
![dendoroB.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/6ac4295d-f84b-92ca-dbc8-6998071c0b33.png)
### 同様に100人９０問の計算結果
linkage
```
array([[ 12.        ,  84.        ,  14.35270009,   2.        ],
       [ 18.        ,  72.        ,  14.52583905,   2.        ],
       [  2.        ,  98.        ,  14.52583905,   2.        ],
       [  7.        ,  61.        ,  14.93318452,   2.        ],
       [ 73.        ,  85.        ,  15.19868415,   2.        ],
       [ 20.        ,  35.        ,  15.32970972,   2.        ],
       [ 25.        ,  77.        ,  15.3622915 ,   2.        ],
...
       [177.        , 189.        ,  25.15030574,  22.        ],
       [185.        , 186.        ,  26.12101113,  25.        ],
       [193.        , 194.        ,  27.97091067,  54.        ],
       [191.        , 195.        ,  28.73435554,  46.        ],
       [196.        , 197.        ,  29.81534151, 100.        ]])
```
![dendoroC.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/e5eb4f76-0e32-c255-65c5-5c7cfc19d74d.png)
上と並び順が逆
![dendoroD.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/bfd553a5-0067-7516-2bcc-864998d20841.png)
同じ絵になった。
```dendrogram.py
# デンドログラムをプロット
plt.figure(figsize=(20, 7))
dendrogram(linked, orientation='top', labels=df.index, distance_sort='ascending', show_leaf_counts=True) #descending
plt.show()
```
![dendoroE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/ef64ba33-06b9-0bb6-833d-d4e35dc36505.png)


## 余談
### プロンプト
PAC分析のコードを以下の手順で作ろうと考えています。
①適当な質問に対する回答を5つから選ばせる。
②質問と回答者を縦軸、横軸にして隣接行列を作る。
③隣接業過列から適当な数のグルーピングする。ここでクラスタリングは妥当な数になるように根拠をしめす。　
④できれば、エニアグラムのように７つ、９つなどがいいと思っている
### 回答；大間違いしている。。。PCAじゃないんだけど、ただ、まともな回答なので掲載する
PAC分析（主成分分析）を行うPythonコードを作成する手順について、以下のように進めることができます。この例では、scikit-learnライブラリを使用しています。
```code.py
import numpy as np
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

# ①質問に対する回答を5つから選ばせる
# ここではサンプルデータを生成していますが、実際にはユーザーの回答データを使用します。
np.random.seed(0)
sample_data = np.random.rand(100, 5)  # 100人の回答者と5つの質問を想定

# ②質問と回答者を縦軸、横軸にして隣接行列を作る
# 回答データが既に隣接行列の形をしていると仮定します。

# ③隣接行列から適当な数のグルーピングをする
# 主成分分析を実行して次元を削減
pca = PCA(n_components=2)  # 2次元に削減
pca_result = pca.fit_transform(sample_data)

# クラスタリングを実行
# エルボー法を用いてクラスタ数を決定する
distortions = []
for i in range(1, 11):
    km = KMeans(n_clusters=i, init='k-means++', n_init=10, max_iter=300, random_state=0)
    km.fit(pca_result)
    distortions.append(km.inertia_)

# エルボー図をプロットして最適なクラスタ数を見つける
plt.plot(range(1, 11), distortions, marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.show()

# ここでは例として、クラスタ数を7に設定
km = KMeans(n_clusters=7, init='k-means++', n_init=10, max_iter=300, random_state=0)
y_km = km.fit_predict(pca_result)

# ④クラスタリング結果をプロット
plt.scatter(pca_result[y_km == 0, 0], pca_result[y_km == 0, 1], s=50, c='lightgreen', marker='s', label='cluster 1')
# 同様に他のクラスタもプロット...
plt.legend()
plt.grid()
plt.show()
```
このコードは、質問に対する回答データを用いて、主成分分析を行い次元を削減した後、KMeansクラスタリングを用いてデータをグループ化しています。エルボー法を用いてクラスタ数を決定し、最終的にクラスタリング結果をプロットしています。

エニグラムのように7つや9つのグループに分けたい場合は、n_clustersの値を調整してください。ただし、クラスタ数はデータに基づいて妥当な数を選ぶことが重要です。エルボー図を参考にしながら、データに最も適したクラスタ数を選択してください。どうぞご活用ください！
### プロンプト２（改善）
上は、大間違いしたので、以下のように変更;ここではリンクをmarkdownに変更しています
「PAC分析はPCA分析とは異なり、以下のURLのようなものです。これで再度コード例を教えてください。[https://www.ei.tohoku.ac.jp/xkozima/lab/pacTutorial1.html](https://www.ei.tohoku.ac.jp/xkozima/lab/pacTutorial1.html)　」
### 回答
PAC分析（Positional Analysis of Categorical data）は、カテゴリカルデータの位置分析を行う手法で、質問と回答の関係性を分析する際に使用されます。この分析では、質問と回答者の関係を隣接行列として表現し、クラスタリングを通じてグループ化を行います。以下は、PAC分析を行うための基本的なPythonコードの例です。
```code.py
import numpy as np
import pandas as pd
from sklearn.cluster import AgglomerativeClustering
import matplotlib.pyplot as plt

# ①適当な質問に対する回答を5つから選ばせる
# ここではサンプルとしてランダムな回答データを生成します
np.random.seed(0)
answers = np.random.choice(range(1, 6), (10, 5))  # 10人の回答者と5つの質問

# 回答データをDataFrameに変換
df_answers = pd.DataFrame(answers, columns=[f'Question {i+1}' for i in range(5)])

# ②質問と回答者を縦軸、横軸にして隣接行列を作る
# 回答データが既に隣接行列の形をしていると仮定します

# ③隣接行列から適当な数のグルーピングをする
# 階層的クラスタリングを使用してグルーピング
clustering = AgglomerativeClustering(n_clusters=None, distance_threshold=0, affinity='euclidean', linkage='ward')
labels = clustering.fit_predict(df_answers)

# 階層的クラスタリングの結果をデンドログラムで表示
from scipy.cluster.hierarchy import dendrogram

# デンドログラムのためのリンケージ行列を作成
counts = np.zeros(clustering.children_.shape[0])
n_samples = len(clustering.labels_)
for i, merge in enumerate(clustering.children_):
    current_count = 0
    for child_idx in merge:
        if child_idx < n_samples:
            current_count += 1  # 葉ノード
        else:
            current_count += counts[child_idx - n_samples]
    counts[i] = current_count

linkage_matrix = np.column_stack([clustering.children_, clustering.distances_, counts]).astype(float)

# デンドログラムをプロット
dendrogram(linkage_matrix, labels=df_answers.index)
plt.title('Hierarchical Clustering Dendrogram')
plt.xlabel('Index of Answer')
plt.ylabel('Distance')
plt.show()

# ④クラスタリングは妥当な数になるように根拠を示す
# エルボー法やシルエット分析などを用いて最適なクラスタ数を決定することができます
# ここではデンドログラムを視覚的に分析してクラスタ数を決定します
```
このコードは、階層的クラスタリングを用いて質問と回答者のデータをグループ化し、デンドログラムをプロットしています。クラスタ数の決定には、デンドログラムを視覚的に分析するか、エルボー法やシルエット分析などの定量的な方法を使用します。エニグラムのように特定の数のグループに分けたい場合は、n_clustersパラメータを調整するか、distance_thresholdを設定して適切なクラスタ数を決定します。どうぞご活用ください！
### プロンプト３；改善；以下のように改善を要求
#ここではデンドログラムを視覚的に分析してクラスタ数を決定します　を行うために隣接行列からグルーピングしたいので、そのコードを書いてください
### 回答
PAC分析における隣接行列からのクラスタリングを行うためのPythonコードを以下に示します。このコードは、質問と回答者の関係を表す隣接行列を作成し、階層的クラスタリングを用いてデンドログラムを生成します。
```code.py
import numpy as np
import pandas as pd
from scipy.cluster.hierarchy import linkage, dendrogram
import matplotlib.pyplot as plt

# 仮の質問と回答者のデータを生成
# 実際には、質問に対する回答データを使用します
np.random.seed(0)
data = np.random.choice([0, 1], (10, 5))  # 10人の回答者と5つの質問

# 隣接行列をDataFrameに変換
df = pd.DataFrame(data, columns=[f'Q{i+1}' for i in range(data.shape[1])])

# 階層的クラスタリングを実行
# 'ward'メソッドを使用してクラスタ間の距離を計算
linked = linkage(df, 'ward')

# デンドログラムをプロット
plt.figure(figsize=(10, 7))
dendrogram(linked, orientation='top', labels=df.index, distance_sort='descending', show_leaf_counts=True)
plt.show()
```
このコードは、質問と回答者の関係をランダムに生成した隣接行列を用いていますが、実際には質問に対する回答データを隣接行列として使用します。階層的クラスタリングを行い、wardメソッドを用いてクラスタ間の距離を計算し、デンドログラムをプロットしています。デンドログラムを視覚的に分析することで、クラスタ数を決定することができます。どうぞご活用ください！