@nahisaho/satori 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENCE +0 -0
- package/README.md +191 -0
- package/bin/satori.js +95 -0
- package/package.json +29 -0
- package/src/.github/skills/scientific-academic-writing/SKILL.md +361 -0
- package/src/.github/skills/scientific-academic-writing/assets/acs_article.md +199 -0
- package/src/.github/skills/scientific-academic-writing/assets/elsevier_article.md +244 -0
- package/src/.github/skills/scientific-academic-writing/assets/ieee_transactions.md +212 -0
- package/src/.github/skills/scientific-academic-writing/assets/imrad_standard.md +181 -0
- package/src/.github/skills/scientific-academic-writing/assets/nature_article.md +179 -0
- package/src/.github/skills/scientific-academic-writing/assets/qiita_technical_article.md +385 -0
- package/src/.github/skills/scientific-academic-writing/assets/science_research_article.md +169 -0
- package/src/.github/skills/scientific-bioinformatics/SKILL.md +220 -0
- package/src/.github/skills/scientific-biosignal-processing/SKILL.md +357 -0
- package/src/.github/skills/scientific-causal-inference/SKILL.md +347 -0
- package/src/.github/skills/scientific-cheminformatics/SKILL.md +196 -0
- package/src/.github/skills/scientific-data-preprocessing/SKILL.md +413 -0
- package/src/.github/skills/scientific-data-simulation/SKILL.md +244 -0
- package/src/.github/skills/scientific-doe/SKILL.md +360 -0
- package/src/.github/skills/scientific-eda-correlation/SKILL.md +141 -0
- package/src/.github/skills/scientific-feature-importance/SKILL.md +208 -0
- package/src/.github/skills/scientific-image-analysis/SKILL.md +310 -0
- package/src/.github/skills/scientific-materials-characterization/SKILL.md +368 -0
- package/src/.github/skills/scientific-meta-analysis/SKILL.md +352 -0
- package/src/.github/skills/scientific-metabolomics/SKILL.md +326 -0
- package/src/.github/skills/scientific-ml-classification/SKILL.md +265 -0
- package/src/.github/skills/scientific-ml-regression/SKILL.md +215 -0
- package/src/.github/skills/scientific-multi-omics/SKILL.md +303 -0
- package/src/.github/skills/scientific-network-analysis/SKILL.md +257 -0
- package/src/.github/skills/scientific-pca-tsne/SKILL.md +235 -0
- package/src/.github/skills/scientific-pipeline-scaffold/SKILL.md +331 -0
- package/src/.github/skills/scientific-process-optimization/SKILL.md +215 -0
- package/src/.github/skills/scientific-publication-figures/SKILL.md +208 -0
- package/src/.github/skills/scientific-sequence-analysis/SKILL.md +389 -0
- package/src/.github/skills/scientific-spectral-signal/SKILL.md +227 -0
- package/src/.github/skills/scientific-statistical-testing/SKILL.md +240 -0
- package/src/.github/skills/scientific-survival-clinical/SKILL.md +239 -0
- package/src/.github/skills/scientific-time-series/SKILL.md +291 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scientific-pca-tsne
|
|
3
|
+
description: |
|
|
4
|
+
PCA・t-SNE・UMAP による次元削減と空間マッピングのスキル。化学空間・特徴量空間・
|
|
5
|
+
多技法融合空間の可視化を行う際に使用。Scientific Skills Exp-02, 03, 05, 07, 11, 13
|
|
6
|
+
で汎用的に使用されたパターン。
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Scientific Dimensionality Reduction & Space Mapping
|
|
10
|
+
|
|
11
|
+
高次元データを 2D/3D 空間に射影して構造を可視化するスキル。
|
|
12
|
+
PCA(線形)、t-SNE(非線形、局所構造保存)、UMAP(非線形、大域+局所)の
|
|
13
|
+
3 手法を使い分ける。
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- 高次元データの構造を 2D で可視化したいとき
|
|
18
|
+
- 材料・化合物・サンプルのクラスター構造を発見したいとき
|
|
19
|
+
- 多技法の測定データを統合したいとき
|
|
20
|
+
- 主成分の寄与率や負荷量を解釈したいとき
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
## 手法選択ガイド
|
|
25
|
+
|
|
26
|
+
| 手法 | 特徴 | 推奨場面 |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| PCA | 線形・解釈容易・寄与率あり | 最初の概観、PC 負荷量の解釈 |
|
|
29
|
+
| t-SNE | 非線形・局所構造保存 | クラスター分離の可視化 |
|
|
30
|
+
| UMAP | 非線形・大域+局所 | 大規模データ、連続的な勾配の可視化 |
|
|
31
|
+
|
|
32
|
+
## 標準パイプライン
|
|
33
|
+
|
|
34
|
+
### 1. PCA + スクリープロット
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from sklearn.decomposition import PCA
|
|
38
|
+
from sklearn.preprocessing import StandardScaler
|
|
39
|
+
import matplotlib.pyplot as plt
|
|
40
|
+
import numpy as np
|
|
41
|
+
import pandas as pd
|
|
42
|
+
|
|
43
|
+
def pca_analysis(df, feature_cols, n_components=None, figsize=(14, 5)):
|
|
44
|
+
"""PCA を実行し、スクリープロットと PC1-PC2 散布図を描画する。"""
|
|
45
|
+
scaler = StandardScaler()
|
|
46
|
+
X_sc = scaler.fit_transform(df[feature_cols])
|
|
47
|
+
|
|
48
|
+
if n_components is None:
|
|
49
|
+
n_components = min(len(feature_cols), 10)
|
|
50
|
+
|
|
51
|
+
pca = PCA(n_components=n_components)
|
|
52
|
+
pcs = pca.fit_transform(X_sc)
|
|
53
|
+
|
|
54
|
+
# スクリープロット
|
|
55
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)
|
|
56
|
+
|
|
57
|
+
cumvar = np.cumsum(pca.explained_variance_ratio_) * 100
|
|
58
|
+
ax1.bar(range(1, n_components + 1), pca.explained_variance_ratio_ * 100,
|
|
59
|
+
color="steelblue", edgecolor="black")
|
|
60
|
+
ax1.plot(range(1, n_components + 1), cumvar, "ro-", linewidth=2)
|
|
61
|
+
ax1.set_xlabel("Principal Component")
|
|
62
|
+
ax1.set_ylabel("Explained Variance (%)")
|
|
63
|
+
ax1.set_title("Scree Plot", fontweight="bold")
|
|
64
|
+
ax1.axhline(y=80, color="gray", linestyle="--", alpha=0.5)
|
|
65
|
+
|
|
66
|
+
# PC1 vs PC2
|
|
67
|
+
ax2.scatter(pcs[:, 0], pcs[:, 1], alpha=0.6, s=30, edgecolors="k", linewidth=0.3)
|
|
68
|
+
ax2.set_xlabel(f"PC1 ({pca.explained_variance_ratio_[0]*100:.1f}%)")
|
|
69
|
+
ax2.set_ylabel(f"PC2 ({pca.explained_variance_ratio_[1]*100:.1f}%)")
|
|
70
|
+
ax2.set_title("PCA Projection", fontweight="bold")
|
|
71
|
+
|
|
72
|
+
plt.tight_layout()
|
|
73
|
+
plt.savefig("figures/pca_screeplot.png", dpi=300, bbox_inches="tight")
|
|
74
|
+
plt.close()
|
|
75
|
+
|
|
76
|
+
return pca, pcs
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def pca_loading_plot(pca, feature_names, figsize=(10, 8)):
|
|
80
|
+
"""PC1-PC2 の負荷量(loading)バイプロットを描画する。"""
|
|
81
|
+
loadings = pca.components_[:2].T
|
|
82
|
+
|
|
83
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
84
|
+
for i, name in enumerate(feature_names):
|
|
85
|
+
ax.arrow(0, 0, loadings[i, 0], loadings[i, 1],
|
|
86
|
+
head_width=0.02, head_length=0.01, fc="red", ec="red", alpha=0.7)
|
|
87
|
+
ax.text(loadings[i, 0] * 1.1, loadings[i, 1] * 1.1, name,
|
|
88
|
+
fontsize=8, ha="center")
|
|
89
|
+
ax.set_xlabel(f"PC1 Loading")
|
|
90
|
+
ax.set_ylabel(f"PC2 Loading")
|
|
91
|
+
ax.set_title("PCA Loading Plot (Biplot)", fontweight="bold")
|
|
92
|
+
ax.axhline(0, color="gray", linewidth=0.5)
|
|
93
|
+
ax.axvline(0, color="gray", linewidth=0.5)
|
|
94
|
+
plt.tight_layout()
|
|
95
|
+
plt.savefig("figures/pca_loadings.png", dpi=300, bbox_inches="tight")
|
|
96
|
+
plt.close()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. t-SNE 可視化
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from sklearn.manifold import TSNE
|
|
103
|
+
|
|
104
|
+
def tsne_visualization(X_scaled, labels, label_name="Group",
|
|
105
|
+
perplexity=30, random_state=42, figsize=(8, 8)):
|
|
106
|
+
"""t-SNE 2D 射影を描画する。"""
|
|
107
|
+
tsne = TSNE(n_components=2, perplexity=perplexity,
|
|
108
|
+
random_state=random_state, n_iter=1000)
|
|
109
|
+
coords = tsne.fit_transform(X_scaled)
|
|
110
|
+
|
|
111
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
112
|
+
for label in sorted(set(labels)):
|
|
113
|
+
mask = labels == label
|
|
114
|
+
ax.scatter(coords[mask, 0], coords[mask, 1],
|
|
115
|
+
label=label, alpha=0.7, s=40, edgecolors="k", linewidth=0.3)
|
|
116
|
+
ax.set_xlabel("t-SNE 1")
|
|
117
|
+
ax.set_ylabel("t-SNE 2")
|
|
118
|
+
ax.set_title("t-SNE Projection", fontweight="bold")
|
|
119
|
+
ax.legend(title=label_name, bbox_to_anchor=(1.05, 1))
|
|
120
|
+
plt.tight_layout()
|
|
121
|
+
plt.savefig("figures/tsne_projection.png", dpi=300, bbox_inches="tight")
|
|
122
|
+
plt.close()
|
|
123
|
+
return coords
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 3. PCA + t-SNE 並列パネル(Exp-11, 13 パターン)
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
def pca_tsne_panel(df, feature_cols, hue_col, figsize=(16, 7)):
|
|
130
|
+
"""PCA と t-SNE を横並びで描画する。"""
|
|
131
|
+
scaler = StandardScaler()
|
|
132
|
+
X_sc = scaler.fit_transform(df[feature_cols])
|
|
133
|
+
|
|
134
|
+
pca = PCA(n_components=2)
|
|
135
|
+
pcs = pca.fit_transform(X_sc)
|
|
136
|
+
|
|
137
|
+
tsne = TSNE(n_components=2, perplexity=30, random_state=42)
|
|
138
|
+
tsne_coords = tsne.fit_transform(X_sc)
|
|
139
|
+
|
|
140
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)
|
|
141
|
+
labels = df[hue_col].values
|
|
142
|
+
|
|
143
|
+
for label in sorted(set(labels)):
|
|
144
|
+
mask = labels == label
|
|
145
|
+
ax1.scatter(pcs[mask, 0], pcs[mask, 1], label=label,
|
|
146
|
+
alpha=0.7, s=40, edgecolors="k", linewidth=0.3)
|
|
147
|
+
ax2.scatter(tsne_coords[mask, 0], tsne_coords[mask, 1], label=label,
|
|
148
|
+
alpha=0.7, s=40, edgecolors="k", linewidth=0.3)
|
|
149
|
+
|
|
150
|
+
ev = pca.explained_variance_ratio_ * 100
|
|
151
|
+
ax1.set_xlabel(f"PC1 ({ev[0]:.1f}%)")
|
|
152
|
+
ax1.set_ylabel(f"PC2 ({ev[1]:.1f}%)")
|
|
153
|
+
ax1.set_title("(A) PCA", fontweight="bold")
|
|
154
|
+
ax1.legend(title=hue_col)
|
|
155
|
+
|
|
156
|
+
ax2.set_xlabel("t-SNE 1")
|
|
157
|
+
ax2.set_ylabel("t-SNE 2")
|
|
158
|
+
ax2.set_title("(B) t-SNE", fontweight="bold")
|
|
159
|
+
ax2.legend(title=hue_col)
|
|
160
|
+
|
|
161
|
+
plt.tight_layout()
|
|
162
|
+
plt.savefig("figures/pca_tsne_panel.png", dpi=300, bbox_inches="tight")
|
|
163
|
+
plt.close()
|
|
164
|
+
|
|
165
|
+
# 座標を保存
|
|
166
|
+
coords_df = pd.DataFrame({
|
|
167
|
+
"PC1": pcs[:, 0], "PC2": pcs[:, 1],
|
|
168
|
+
"tSNE1": tsne_coords[:, 0], "tSNE2": tsne_coords[:, 1],
|
|
169
|
+
hue_col: labels,
|
|
170
|
+
})
|
|
171
|
+
coords_df.to_csv("results/pca_tsne_coordinates.csv", index=False)
|
|
172
|
+
return pca, tsne_coords
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 4. 階層的クラスタリング + Silhouette 最適 k
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from scipy.cluster.hierarchy import linkage, fcluster, dendrogram
|
|
179
|
+
from sklearn.metrics import silhouette_score
|
|
180
|
+
|
|
181
|
+
def hierarchical_clustering(X_scaled, method="ward", max_k=10, figsize=(12, 5)):
|
|
182
|
+
"""階層的クラスタリングと最適クラスター数の決定。"""
|
|
183
|
+
Z = linkage(X_scaled, method=method)
|
|
184
|
+
|
|
185
|
+
# Silhouette スコアで最適 k を探索
|
|
186
|
+
sil_scores = []
|
|
187
|
+
for k in range(2, max_k + 1):
|
|
188
|
+
labels = fcluster(Z, k, criterion="maxclust")
|
|
189
|
+
sil = silhouette_score(X_scaled, labels)
|
|
190
|
+
sil_scores.append({"k": k, "silhouette": sil})
|
|
191
|
+
|
|
192
|
+
sil_df = pd.DataFrame(sil_scores)
|
|
193
|
+
best_k = sil_df.loc[sil_df["silhouette"].idxmax(), "k"]
|
|
194
|
+
|
|
195
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)
|
|
196
|
+
|
|
197
|
+
# デンドログラム
|
|
198
|
+
dendrogram(Z, ax=ax1, truncate_mode="lastp", p=30, leaf_rotation=90)
|
|
199
|
+
ax1.set_title("Dendrogram", fontweight="bold")
|
|
200
|
+
ax1.set_ylabel("Distance")
|
|
201
|
+
|
|
202
|
+
# Silhouette スコア
|
|
203
|
+
ax2.plot(sil_df["k"], sil_df["silhouette"], "bo-", linewidth=2)
|
|
204
|
+
ax2.axvline(best_k, color="red", linestyle="--", label=f"Best k={int(best_k)}")
|
|
205
|
+
ax2.set_xlabel("Number of Clusters (k)")
|
|
206
|
+
ax2.set_ylabel("Silhouette Score")
|
|
207
|
+
ax2.set_title("Optimal k (Silhouette)", fontweight="bold")
|
|
208
|
+
ax2.legend()
|
|
209
|
+
|
|
210
|
+
plt.tight_layout()
|
|
211
|
+
plt.savefig("figures/clustering_analysis.png", dpi=300, bbox_inches="tight")
|
|
212
|
+
plt.close()
|
|
213
|
+
return Z, int(best_k)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## References
|
|
217
|
+
|
|
218
|
+
### Output Files
|
|
219
|
+
|
|
220
|
+
| ファイル | 形式 |
|
|
221
|
+
|---|---|
|
|
222
|
+
| `results/pca_tsne_coordinates.csv` | CSV |
|
|
223
|
+
| `figures/pca_screeplot.png` | PNG |
|
|
224
|
+
| `figures/pca_loadings.png` | PNG |
|
|
225
|
+
| `figures/tsne_projection.png` | PNG |
|
|
226
|
+
| `figures/pca_tsne_panel.png` | PNG |
|
|
227
|
+
| `figures/clustering_analysis.png` | PNG |
|
|
228
|
+
|
|
229
|
+
#### 参照実験
|
|
230
|
+
|
|
231
|
+
- **Exp-02**: 化学空間 PCA/t-SNE(EGFR 阻害剤)
|
|
232
|
+
- **Exp-03**: PCA / UMAP / t-SNE 3 手法比較
|
|
233
|
+
- **Exp-07**: PLS-DA + PCA メタボロミクス
|
|
234
|
+
- **Exp-11**: ラマンスペクトルの PCA/t-SNE + 階層的クラスタリング
|
|
235
|
+
- **Exp-13**: 多技法融合 PCA/t-SNE(XRD+AFM+電気+光学)
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scientific-pipeline-scaffold
|
|
3
|
+
description: |
|
|
4
|
+
科学データ解析パイプラインの基盤スキル。ディレクトリ構造の自動構築、再現性のためのシード管理、
|
|
5
|
+
進捗ログ出力、実行時間計測、JSON サマリー生成、ダッシュボード総括図の作成を行う際に使用。
|
|
6
|
+
全 13 実験に共通する足場パターンを統合。
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Scientific Pipeline Scaffold
|
|
10
|
+
|
|
11
|
+
全 Exp-01〜13 に共通する「足場(scaffold)」パターンを統合したスキル。
|
|
12
|
+
新しい実験パイプラインを立ち上げる際に最初に適用し、再現性・可読性・構造を保証する。
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
- 新しい解析実験のスクリプトを立ち上げるとき
|
|
17
|
+
- 再現可能な実験パイプラインを構築するとき
|
|
18
|
+
- 解析結果をJSON サマリーとしてエクスポートしたいとき
|
|
19
|
+
- 総括ダッシュボード図を自動生成したいとき
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
## 1. スクリプトヘッダーテンプレート
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
#!/usr/bin/env python3
|
|
27
|
+
"""
|
|
28
|
+
Exp-XX: [実験タイトル]
|
|
29
|
+
Scientific Skills Series
|
|
30
|
+
|
|
31
|
+
Description:
|
|
32
|
+
[1-2行の概要]
|
|
33
|
+
|
|
34
|
+
Author: [名前]
|
|
35
|
+
Date: [YYYY-MM-DD]
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import warnings
|
|
39
|
+
warnings.filterwarnings("ignore")
|
|
40
|
+
|
|
41
|
+
import matplotlib
|
|
42
|
+
matplotlib.use("Agg") # headless 環境対応
|
|
43
|
+
|
|
44
|
+
import numpy as np
|
|
45
|
+
import pandas as pd
|
|
46
|
+
import matplotlib.pyplot as plt
|
|
47
|
+
import seaborn as sns
|
|
48
|
+
from pathlib import Path
|
|
49
|
+
import json
|
|
50
|
+
import time
|
|
51
|
+
|
|
52
|
+
# === 再現性設定 ===
|
|
53
|
+
SEED = 42
|
|
54
|
+
np.random.seed(SEED)
|
|
55
|
+
|
|
56
|
+
# === ディレクトリ構造 ===
|
|
57
|
+
BASE_DIR = Path(__file__).resolve().parent
|
|
58
|
+
DATA_DIR = BASE_DIR / "data"
|
|
59
|
+
FIG_DIR = BASE_DIR / "figures"
|
|
60
|
+
RESULTS_DIR = BASE_DIR / "results"
|
|
61
|
+
|
|
62
|
+
for d in [DATA_DIR, FIG_DIR, RESULTS_DIR]:
|
|
63
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
|
|
65
|
+
# === 出版品質スタイル ===
|
|
66
|
+
plt.rcParams.update({
|
|
67
|
+
"font.family": "sans-serif",
|
|
68
|
+
"font.sans-serif": ["Arial", "Helvetica", "DejaVu Sans"],
|
|
69
|
+
"font.size": 10,
|
|
70
|
+
"axes.titlesize": 12,
|
|
71
|
+
"axes.labelsize": 11,
|
|
72
|
+
"axes.spines.top": False,
|
|
73
|
+
"axes.spines.right": False,
|
|
74
|
+
"savefig.dpi": 300,
|
|
75
|
+
"savefig.bbox": "tight",
|
|
76
|
+
"figure.facecolor": "white",
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 2. メイン関数テンプレート
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
def main():
|
|
84
|
+
"""メインパイプライン実行関数。"""
|
|
85
|
+
start_time = time.time()
|
|
86
|
+
print("=" * 60)
|
|
87
|
+
print("Exp-XX: [実験タイトル]")
|
|
88
|
+
print("=" * 60)
|
|
89
|
+
|
|
90
|
+
# ──── Step 1: データ読み込み / 生成 ────
|
|
91
|
+
print("\n[Step 1] データ読み込み...")
|
|
92
|
+
# df = pd.read_csv(DATA_DIR / "dataset.csv")
|
|
93
|
+
# df = generate_dataset()
|
|
94
|
+
# df.to_csv(DATA_DIR / "dataset.csv", index=False)
|
|
95
|
+
|
|
96
|
+
# ──── Step 2: EDA ────
|
|
97
|
+
print("\n[Step 2] 探索的データ解析...")
|
|
98
|
+
# → scientific-eda-correlation スキル参照
|
|
99
|
+
|
|
100
|
+
# ──── Step 3: 前処理 ────
|
|
101
|
+
print("\n[Step 3] 前処理...")
|
|
102
|
+
# → scientific-data-preprocessing スキル参照
|
|
103
|
+
|
|
104
|
+
# ──── Step 4: モデル学習 ────
|
|
105
|
+
print("\n[Step 4] モデル学習...")
|
|
106
|
+
# → scientific-ml-regression / ml-classification スキル参照
|
|
107
|
+
|
|
108
|
+
# ──── Step 5: 可視化 ────
|
|
109
|
+
print("\n[Step 5] 可視化...")
|
|
110
|
+
# → scientific-publication-figures スキル参照
|
|
111
|
+
|
|
112
|
+
# ──── Step 6: サマリー ────
|
|
113
|
+
elapsed = time.time() - start_time
|
|
114
|
+
print(f"\n[Step 6] サマリー生成... (elapsed: {elapsed:.1f}s)")
|
|
115
|
+
# → generate_summary() 呼び出し
|
|
116
|
+
|
|
117
|
+
print("\n" + "=" * 60)
|
|
118
|
+
print(f"完了! ({elapsed:.1f} 秒)")
|
|
119
|
+
print("=" * 60)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
main()
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 3. 進捗ログユーティリティ
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
class StepLogger:
|
|
130
|
+
"""段階的進捗ログを管理するユーティリティ。"""
|
|
131
|
+
|
|
132
|
+
def __init__(self, experiment_name):
|
|
133
|
+
self.experiment_name = experiment_name
|
|
134
|
+
self.step_count = 0
|
|
135
|
+
self.start_time = time.time()
|
|
136
|
+
self.step_times = {}
|
|
137
|
+
print("=" * 60)
|
|
138
|
+
print(f"{experiment_name}")
|
|
139
|
+
print("=" * 60)
|
|
140
|
+
|
|
141
|
+
def step(self, description):
|
|
142
|
+
"""新しい Step を開始する。"""
|
|
143
|
+
self.step_count += 1
|
|
144
|
+
step_start = time.time()
|
|
145
|
+
if self.step_count > 1:
|
|
146
|
+
prev = self.step_count - 1
|
|
147
|
+
self.step_times[prev] = time.time() - self._current_step_start
|
|
148
|
+
self._current_step_start = step_start
|
|
149
|
+
print(f"\n[Step {self.step_count}] {description}...")
|
|
150
|
+
|
|
151
|
+
def finish(self):
|
|
152
|
+
"""パイプライン完了を記録する。"""
|
|
153
|
+
elapsed = time.time() - self.start_time
|
|
154
|
+
if self.step_count > 0:
|
|
155
|
+
self.step_times[self.step_count] = time.time() - self._current_step_start
|
|
156
|
+
print(f"\n{'=' * 60}")
|
|
157
|
+
print(f"完了! (合計 {elapsed:.1f} 秒)")
|
|
158
|
+
for step_num, t in self.step_times.items():
|
|
159
|
+
print(f" Step {step_num}: {t:.1f}s")
|
|
160
|
+
print(f"{'=' * 60}")
|
|
161
|
+
return elapsed
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 4. JSON サマリー生成
|
|
165
|
+
|
|
166
|
+
全実験で共通する `analysis_summary.json` のスキーマ。
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
def generate_summary(results_dict, experiment_name, elapsed_seconds,
|
|
170
|
+
output_path=None):
|
|
171
|
+
"""
|
|
172
|
+
標準フォーマットの analysis_summary.json を生成する。
|
|
173
|
+
|
|
174
|
+
results_dict はドメイン固有の結果を含む辞書。
|
|
175
|
+
この関数がメタ情報を自動追加する。
|
|
176
|
+
"""
|
|
177
|
+
import datetime
|
|
178
|
+
|
|
179
|
+
summary = {
|
|
180
|
+
"experiment": experiment_name,
|
|
181
|
+
"timestamp": datetime.datetime.now().isoformat(),
|
|
182
|
+
"elapsed_seconds": round(elapsed_seconds, 2),
|
|
183
|
+
"environment": {
|
|
184
|
+
"python": __import__("sys").version,
|
|
185
|
+
"seed": SEED,
|
|
186
|
+
},
|
|
187
|
+
"data": {
|
|
188
|
+
"n_samples": results_dict.get("n_samples"),
|
|
189
|
+
"n_features": results_dict.get("n_features"),
|
|
190
|
+
"source": results_dict.get("data_source", "simulation"),
|
|
191
|
+
},
|
|
192
|
+
"results": results_dict,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if output_path is None:
|
|
196
|
+
output_path = RESULTS_DIR / "analysis_summary.json"
|
|
197
|
+
|
|
198
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
199
|
+
json.dump(summary, f, indent=2, ensure_ascii=False, default=str)
|
|
200
|
+
|
|
201
|
+
print(f" → Summary saved: {output_path}")
|
|
202
|
+
return summary
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### JSON サマリー標準キー一覧
|
|
206
|
+
|
|
207
|
+
| キー | 型 | 説明 | 必須 |
|
|
208
|
+
|---|---|---|---|
|
|
209
|
+
| `experiment` | str | 実験名 | ✅ |
|
|
210
|
+
| `timestamp` | str | ISO 8601 日時 | ✅ |
|
|
211
|
+
| `elapsed_seconds` | float | 実行時間 | ✅ |
|
|
212
|
+
| `environment.python` | str | Python バージョン | ✅ |
|
|
213
|
+
| `environment.seed` | int | 乱数シード | ✅ |
|
|
214
|
+
| `data.n_samples` | int | サンプル数 | ✅ |
|
|
215
|
+
| `data.n_features` | int | 特徴量数 | ○ |
|
|
216
|
+
| `data.source` | str | データソース | ○ |
|
|
217
|
+
| `results.best_model` | str | 最良モデル名 | ○ |
|
|
218
|
+
| `results.best_r2` / `best_auc` | float | 最良スコア | ○ |
|
|
219
|
+
| `results.n_figures` | int | 生成図数 | ○ |
|
|
220
|
+
|
|
221
|
+
## 5. 総括ダッシュボードパネル
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
import matplotlib.gridspec as gridspec
|
|
225
|
+
|
|
226
|
+
def create_summary_panel(panel_data, experiment_name, figsize=(20, 14)):
|
|
227
|
+
"""
|
|
228
|
+
解析結果の総括ダッシュボードを 1 枚の Figure にまとめる。
|
|
229
|
+
|
|
230
|
+
panel_data: [
|
|
231
|
+
{"type": "table", "title": "...", "data": df_or_dict},
|
|
232
|
+
{"type": "plot_func", "title": "...", "func": callable, "kwargs": {}},
|
|
233
|
+
{"type": "text", "title": "...", "text": "..."},
|
|
234
|
+
{"type": "metrics_bar", "title": "...", "names": [...], "values": [...]},
|
|
235
|
+
]
|
|
236
|
+
"""
|
|
237
|
+
n_panels = len(panel_data)
|
|
238
|
+
ncols = min(3, n_panels)
|
|
239
|
+
nrows = (n_panels + ncols - 1) // ncols
|
|
240
|
+
|
|
241
|
+
fig = plt.figure(figsize=figsize)
|
|
242
|
+
gs = gridspec.GridSpec(nrows, ncols, figure=fig, hspace=0.4, wspace=0.3)
|
|
243
|
+
|
|
244
|
+
for i, panel in enumerate(panel_data):
|
|
245
|
+
row, col = divmod(i, ncols)
|
|
246
|
+
ax = fig.add_subplot(gs[row, col])
|
|
247
|
+
|
|
248
|
+
ptype = panel["type"]
|
|
249
|
+
title = panel.get("title", f"Panel {chr(65 + i)}")
|
|
250
|
+
|
|
251
|
+
if ptype == "metrics_bar":
|
|
252
|
+
ax.barh(panel["names"], panel["values"],
|
|
253
|
+
color="steelblue", edgecolor="black")
|
|
254
|
+
ax.set_xlabel("Value")
|
|
255
|
+
|
|
256
|
+
elif ptype == "text":
|
|
257
|
+
ax.text(0.05, 0.95, panel["text"], transform=ax.transAxes,
|
|
258
|
+
fontsize=9, verticalalignment="top", fontfamily="monospace",
|
|
259
|
+
bbox=dict(boxstyle="round", facecolor="wheat", alpha=0.5))
|
|
260
|
+
ax.axis("off")
|
|
261
|
+
|
|
262
|
+
elif ptype == "table":
|
|
263
|
+
ax.axis("off")
|
|
264
|
+
if isinstance(panel["data"], pd.DataFrame):
|
|
265
|
+
tbl = ax.table(cellText=panel["data"].values,
|
|
266
|
+
colLabels=panel["data"].columns,
|
|
267
|
+
cellLoc="center", loc="center")
|
|
268
|
+
tbl.auto_set_font_size(False)
|
|
269
|
+
tbl.set_fontsize(8)
|
|
270
|
+
elif isinstance(panel["data"], dict):
|
|
271
|
+
rows = [[k, str(v)] for k, v in panel["data"].items()]
|
|
272
|
+
tbl = ax.table(cellText=rows, colLabels=["Metric", "Value"],
|
|
273
|
+
cellLoc="center", loc="center")
|
|
274
|
+
tbl.auto_set_font_size(False)
|
|
275
|
+
tbl.set_fontsize(9)
|
|
276
|
+
|
|
277
|
+
elif ptype == "plot_func":
|
|
278
|
+
panel["func"](ax=ax, **panel.get("kwargs", {}))
|
|
279
|
+
|
|
280
|
+
# パネルラベル (A, B, C, ...)
|
|
281
|
+
ax.set_title(f"({chr(65 + i)}) {title}", fontsize=11, fontweight="bold")
|
|
282
|
+
|
|
283
|
+
fig.suptitle(f"Summary: {experiment_name}", fontsize=14, fontweight="bold")
|
|
284
|
+
plt.savefig(FIG_DIR / "summary_panel.png", dpi=300, bbox_inches="tight")
|
|
285
|
+
plt.close()
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## 6. 共通ユーティリティ
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
def save_fig(fig, filename, dpi=300, formats=("png",)):
|
|
292
|
+
"""図を保存してクローズする共通関数。"""
|
|
293
|
+
for fmt in formats:
|
|
294
|
+
fig.savefig(FIG_DIR / f"{filename}.{fmt}",
|
|
295
|
+
dpi=dpi, bbox_inches="tight",
|
|
296
|
+
facecolor="white", edgecolor="none")
|
|
297
|
+
plt.close(fig)
|
|
298
|
+
print(f" → Figure saved: {filename}")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def save_results(df, filename, index=False):
|
|
302
|
+
"""DataFrame を results/ に保存する共通関数。"""
|
|
303
|
+
path = RESULTS_DIR / filename
|
|
304
|
+
df.to_csv(path, index=index)
|
|
305
|
+
print(f" → Results saved: {filename}")
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## ディレクトリ構造の標準
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
Exp-XX/
|
|
312
|
+
├── exp_analysis.py # メインスクリプト
|
|
313
|
+
├── qiita-exp-analysis.md # Qiita 記事
|
|
314
|
+
├── data/
|
|
315
|
+
│ └── dataset.csv # 入力データ
|
|
316
|
+
├── figures/
|
|
317
|
+
│ ├── Fig01_*.png # 個別図(Fig + 連番 + 説明)
|
|
318
|
+
│ ├── Fig02_*.png
|
|
319
|
+
│ └── summary_panel.png # 総括ダッシュボード
|
|
320
|
+
└── results/
|
|
321
|
+
├── descriptive_statistics.csv
|
|
322
|
+
├── model_metrics.csv
|
|
323
|
+
├── feature_importance.csv
|
|
324
|
+
└── analysis_summary.json # JSON サマリー
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## References
|
|
328
|
+
|
|
329
|
+
- **全 13 実験**: ディレクトリ構造、シード管理、warnings 抑制
|
|
330
|
+
- **Exp-12, 13**: `main()` + 実行時間計測 + JSON サマリー + 総括パネル
|
|
331
|
+
- **Exp-10**: `save_fig()` / `write_summary()` ユーティリティ
|