@nahisaho/satori 0.22.0 → 0.23.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/README.md +33 -17
- package/package.json +1 -1
- package/src/.github/skills/scientific-active-learning/SKILL.md +289 -0
- package/src/.github/skills/scientific-advanced-visualization/SKILL.md +310 -0
- package/src/.github/skills/scientific-automl/SKILL.md +264 -0
- package/src/.github/skills/scientific-ensemble-methods/SKILL.md +263 -0
- package/src/.github/skills/scientific-interactive-dashboard/SKILL.md +346 -0
- package/src/.github/skills/scientific-missing-data-analysis/SKILL.md +312 -0
- package/src/.github/skills/scientific-transfer-learning/SKILL.md +298 -0
- package/src/.github/skills/scientific-uncertainty-quantification/SKILL.md +286 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scientific-ensemble-methods
|
|
3
|
+
description: |
|
|
4
|
+
アンサンブル学習スキル。Stacking/Blending 多段積層・
|
|
5
|
+
Boosting (XGBoost/LightGBM/CatBoost) 勾配ブースティング・
|
|
6
|
+
Bagging/Random Subspace・Voting 分類器/回帰器・
|
|
7
|
+
アンサンブル多様性評価・モデル統合パイプライン。
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Scientific Ensemble Methods
|
|
11
|
+
|
|
12
|
+
複数モデルの組み合わせによる予測精度向上・安定化を実現する
|
|
13
|
+
アンサンブル学習手法の設計・評価パイプラインを提供する。
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- XGBoost/LightGBM/CatBoost で勾配ブースティングを実行するとき
|
|
18
|
+
- Stacking/Blending で多段アンサンブルを構築するとき
|
|
19
|
+
- 複数モデルの Voting/Averaging で安定予測を得るとき
|
|
20
|
+
- アンサンブルの多様性を評価するとき
|
|
21
|
+
- Out-of-Fold 予測でリーク防止を行うとき
|
|
22
|
+
- モデルの寄与度を分析するとき
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
## 1. 勾配ブースティング比較
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import numpy as np
|
|
32
|
+
import pandas as pd
|
|
33
|
+
from sklearn.model_selection import cross_val_score
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def compare_boosting(X, y, cv=5, scoring="f1_macro",
|
|
37
|
+
task="classification"):
|
|
38
|
+
"""
|
|
39
|
+
XGBoost / LightGBM / CatBoost 比較。
|
|
40
|
+
|
|
41
|
+
Parameters:
|
|
42
|
+
X: np.ndarray — 特徴量
|
|
43
|
+
y: np.ndarray — ラベル
|
|
44
|
+
cv: int — CV 分割数
|
|
45
|
+
scoring: str — 評価指標
|
|
46
|
+
task: str — "classification" / "regression"
|
|
47
|
+
"""
|
|
48
|
+
results = []
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
from xgboost import XGBClassifier, XGBRegressor
|
|
52
|
+
model = (XGBClassifier(n_estimators=200, max_depth=6,
|
|
53
|
+
learning_rate=0.1, random_state=42,
|
|
54
|
+
use_label_encoder=False, eval_metric="logloss")
|
|
55
|
+
if task == "classification"
|
|
56
|
+
else XGBRegressor(n_estimators=200, max_depth=6,
|
|
57
|
+
learning_rate=0.1, random_state=42))
|
|
58
|
+
scores = cross_val_score(model, X, y, cv=cv, scoring=scoring)
|
|
59
|
+
results.append({"model": "XGBoost", "mean": scores.mean(),
|
|
60
|
+
"std": scores.std()})
|
|
61
|
+
except ImportError:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
from lightgbm import LGBMClassifier, LGBMRegressor
|
|
66
|
+
model = (LGBMClassifier(n_estimators=200, max_depth=6,
|
|
67
|
+
learning_rate=0.1, random_state=42, verbose=-1)
|
|
68
|
+
if task == "classification"
|
|
69
|
+
else LGBMRegressor(n_estimators=200, max_depth=6,
|
|
70
|
+
learning_rate=0.1, random_state=42, verbose=-1))
|
|
71
|
+
scores = cross_val_score(model, X, y, cv=cv, scoring=scoring)
|
|
72
|
+
results.append({"model": "LightGBM", "mean": scores.mean(),
|
|
73
|
+
"std": scores.std()})
|
|
74
|
+
except ImportError:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
from catboost import CatBoostClassifier, CatBoostRegressor
|
|
79
|
+
model = (CatBoostClassifier(iterations=200, depth=6,
|
|
80
|
+
learning_rate=0.1, random_seed=42, verbose=0)
|
|
81
|
+
if task == "classification"
|
|
82
|
+
else CatBoostRegressor(iterations=200, depth=6,
|
|
83
|
+
learning_rate=0.1, random_seed=42, verbose=0))
|
|
84
|
+
scores = cross_val_score(model, X, y, cv=cv, scoring=scoring)
|
|
85
|
+
results.append({"model": "CatBoost", "mean": scores.mean(),
|
|
86
|
+
"std": scores.std()})
|
|
87
|
+
except ImportError:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
df = pd.DataFrame(results).sort_values("mean", ascending=False)
|
|
91
|
+
if not df.empty:
|
|
92
|
+
print(f"Boosting: best = {df.iloc[0]['model']} "
|
|
93
|
+
f"({scoring} = {df.iloc[0]['mean']:.4f})")
|
|
94
|
+
return df
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 2. Stacking アンサンブル
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from sklearn.model_selection import StratifiedKFold
|
|
101
|
+
from sklearn.linear_model import LogisticRegression
|
|
102
|
+
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
|
|
103
|
+
from sklearn.svm import SVC
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def stacking_ensemble(X_train, y_train, X_test,
|
|
107
|
+
base_models=None, meta_model=None,
|
|
108
|
+
n_folds=5):
|
|
109
|
+
"""
|
|
110
|
+
Stacking アンサンブル (Out-of-Fold 予測)。
|
|
111
|
+
|
|
112
|
+
Parameters:
|
|
113
|
+
X_train: np.ndarray — 学習データ
|
|
114
|
+
y_train: np.ndarray — 学習ラベル
|
|
115
|
+
X_test: np.ndarray — テストデータ
|
|
116
|
+
base_models: list | None — ベースモデル
|
|
117
|
+
meta_model: classifier | None — メタモデル
|
|
118
|
+
n_folds: int — CV 分割数
|
|
119
|
+
"""
|
|
120
|
+
if base_models is None:
|
|
121
|
+
base_models = [
|
|
122
|
+
("rf", RandomForestClassifier(n_estimators=200, random_state=42)),
|
|
123
|
+
("gbm", GradientBoostingClassifier(n_estimators=200, random_state=42)),
|
|
124
|
+
("svm", SVC(probability=True, random_state=42)),
|
|
125
|
+
]
|
|
126
|
+
if meta_model is None:
|
|
127
|
+
meta_model = LogisticRegression(max_iter=1000, random_state=42)
|
|
128
|
+
|
|
129
|
+
kf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
|
|
130
|
+
n_classes = len(np.unique(y_train))
|
|
131
|
+
|
|
132
|
+
# Out-of-Fold predictions
|
|
133
|
+
oof_preds = np.zeros((len(y_train), len(base_models) * n_classes))
|
|
134
|
+
test_preds = np.zeros((len(X_test), len(base_models) * n_classes))
|
|
135
|
+
|
|
136
|
+
for i, (name, model) in enumerate(base_models):
|
|
137
|
+
col_start = i * n_classes
|
|
138
|
+
col_end = (i + 1) * n_classes
|
|
139
|
+
test_fold_preds = np.zeros((len(X_test), n_classes, n_folds))
|
|
140
|
+
|
|
141
|
+
for fold, (train_idx, val_idx) in enumerate(kf.split(X_train, y_train)):
|
|
142
|
+
m = model.__class__(**model.get_params()).fit(
|
|
143
|
+
X_train[train_idx], y_train[train_idx])
|
|
144
|
+
oof_preds[val_idx, col_start:col_end] = m.predict_proba(
|
|
145
|
+
X_train[val_idx])
|
|
146
|
+
test_fold_preds[:, :, fold] = m.predict_proba(X_test)
|
|
147
|
+
|
|
148
|
+
test_preds[:, col_start:col_end] = test_fold_preds.mean(axis=2)
|
|
149
|
+
print(f" Stacking base: {name} done")
|
|
150
|
+
|
|
151
|
+
# Meta-model
|
|
152
|
+
meta_model.fit(oof_preds, y_train)
|
|
153
|
+
final_pred = meta_model.predict(test_preds)
|
|
154
|
+
final_proba = meta_model.predict_proba(test_preds)
|
|
155
|
+
|
|
156
|
+
print(f"Stacking: {len(base_models)} base models → meta-model")
|
|
157
|
+
return final_pred, final_proba, meta_model
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## 3. Voting アンサンブル
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from sklearn.ensemble import VotingClassifier, VotingRegressor
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def voting_ensemble(X, y, models=None, voting="soft",
|
|
167
|
+
cv=5, scoring="f1_macro"):
|
|
168
|
+
"""
|
|
169
|
+
Voting アンサンブル。
|
|
170
|
+
|
|
171
|
+
Parameters:
|
|
172
|
+
X: np.ndarray — 特徴量
|
|
173
|
+
y: np.ndarray — ラベル
|
|
174
|
+
models: list | None — (name, model) ペア
|
|
175
|
+
voting: str — "soft" / "hard"
|
|
176
|
+
cv: int — CV 分割数
|
|
177
|
+
scoring: str — 評価指標
|
|
178
|
+
"""
|
|
179
|
+
if models is None:
|
|
180
|
+
models = [
|
|
181
|
+
("rf", RandomForestClassifier(n_estimators=200, random_state=42)),
|
|
182
|
+
("gbm", GradientBoostingClassifier(n_estimators=200, random_state=42)),
|
|
183
|
+
("lr", LogisticRegression(max_iter=1000, random_state=42)),
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
# 個別モデル評価
|
|
187
|
+
results = []
|
|
188
|
+
for name, model in models:
|
|
189
|
+
scores = cross_val_score(model, X, y, cv=cv, scoring=scoring)
|
|
190
|
+
results.append({"model": name, "mean": scores.mean(), "std": scores.std()})
|
|
191
|
+
|
|
192
|
+
# Voting
|
|
193
|
+
vc = VotingClassifier(estimators=models, voting=voting)
|
|
194
|
+
scores = cross_val_score(vc, X, y, cv=cv, scoring=scoring)
|
|
195
|
+
results.append({"model": f"Voting({voting})",
|
|
196
|
+
"mean": scores.mean(), "std": scores.std()})
|
|
197
|
+
|
|
198
|
+
df = pd.DataFrame(results).sort_values("mean", ascending=False)
|
|
199
|
+
print(f"Voting ensemble: {scoring} = {scores.mean():.4f} ± {scores.std():.4f}")
|
|
200
|
+
return df
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## 4. アンサンブル多様性評価
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
def ensemble_diversity(models, X, y):
|
|
207
|
+
"""
|
|
208
|
+
アンサンブル多様性 (Q-statistic / Disagreement)。
|
|
209
|
+
|
|
210
|
+
Parameters:
|
|
211
|
+
models: list — 学習済みモデルリスト
|
|
212
|
+
X: np.ndarray — 評価データ
|
|
213
|
+
y: np.ndarray — 真ラベル
|
|
214
|
+
"""
|
|
215
|
+
predictions = np.array([m.predict(X) for m in models])
|
|
216
|
+
n_models = len(models)
|
|
217
|
+
correct = (predictions == y).astype(int)
|
|
218
|
+
|
|
219
|
+
# 全ペアの Q-statistic
|
|
220
|
+
q_stats = []
|
|
221
|
+
disagree_rates = []
|
|
222
|
+
for i in range(n_models):
|
|
223
|
+
for j in range(i + 1, n_models):
|
|
224
|
+
n11 = np.sum((correct[i] == 1) & (correct[j] == 1))
|
|
225
|
+
n00 = np.sum((correct[i] == 0) & (correct[j] == 0))
|
|
226
|
+
n10 = np.sum((correct[i] == 1) & (correct[j] == 0))
|
|
227
|
+
n01 = np.sum((correct[i] == 0) & (correct[j] == 1))
|
|
228
|
+
|
|
229
|
+
denom = n11 * n00 - n10 * n01
|
|
230
|
+
numer = n11 * n00 + n10 * n01
|
|
231
|
+
q = denom / numer if numer != 0 else 0
|
|
232
|
+
q_stats.append(q)
|
|
233
|
+
disagree_rates.append((n10 + n01) / len(y))
|
|
234
|
+
|
|
235
|
+
result = {
|
|
236
|
+
"mean_q_statistic": round(np.mean(q_stats), 4),
|
|
237
|
+
"mean_disagreement": round(np.mean(disagree_rates), 4),
|
|
238
|
+
"n_models": n_models,
|
|
239
|
+
}
|
|
240
|
+
print(f"Diversity: Q={result['mean_q_statistic']:.3f}, "
|
|
241
|
+
f"Disagree={result['mean_disagreement']:.3f}")
|
|
242
|
+
return result
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## パイプライン統合
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
automl → ensemble-methods → uncertainty-quantification
|
|
251
|
+
(モデル選択) (アンサンブル) (不確実性定量化)
|
|
252
|
+
│ │ ↓
|
|
253
|
+
feature-importance ┘ explainable-ai
|
|
254
|
+
(特徴量重要度) (説明可能 AI)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## パイプライン出力
|
|
258
|
+
|
|
259
|
+
| ファイル | 説明 | 次スキル |
|
|
260
|
+
|---------|------|---------|
|
|
261
|
+
| `stacking_meta.pkl` | Stacking メタモデル | → 予測 |
|
|
262
|
+
| `boosting_comparison.csv` | ブースティング比較 | → レポート |
|
|
263
|
+
| `ensemble_diversity.json` | 多様性指標 | → モデル改善 |
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scientific-interactive-dashboard
|
|
3
|
+
description: |
|
|
4
|
+
インタラクティブダッシュボードスキル。
|
|
5
|
+
Streamlit / Dash / Panel / Voilà による
|
|
6
|
+
科学データダッシュボード構築・リアルタイムパラメータ探索 UI ・
|
|
7
|
+
ウィジェット連動・データアップロード・解析パイプライン UI 化。
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Scientific Interactive Dashboard
|
|
11
|
+
|
|
12
|
+
科学データのインタラクティブダッシュボードを構築し、
|
|
13
|
+
パラメータ探索・結果共有・リアルタイム解析を実現する。
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- Streamlit で迅速にデータ探索ダッシュボードを構築するとき
|
|
18
|
+
- Dash でカスタマイズ性の高い解析 UI を作成するとき
|
|
19
|
+
- Panel / Voilà で Jupyter ノートブックをダッシュボード化するとき
|
|
20
|
+
- パラメータスライダー + リアルタイム更新の UI を実装するとき
|
|
21
|
+
- 複数人で解析結果を共有するとき
|
|
22
|
+
- 非プログラマーに解析ツールを提供するとき
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
## 1. Streamlit 科学データダッシュボード
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
def generate_streamlit_dashboard(output_path="dashboard_app.py"):
|
|
32
|
+
"""
|
|
33
|
+
Streamlit ダッシュボードテンプレート生成。
|
|
34
|
+
|
|
35
|
+
Parameters:
|
|
36
|
+
output_path: str — 出力 Python ファイル
|
|
37
|
+
"""
|
|
38
|
+
code = '''
|
|
39
|
+
import streamlit as st
|
|
40
|
+
import pandas as pd
|
|
41
|
+
import numpy as np
|
|
42
|
+
import plotly.express as px
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
st.set_page_config(page_title="Scientific Data Dashboard",
|
|
46
|
+
layout="wide", page_icon="🔬")
|
|
47
|
+
|
|
48
|
+
st.title("🔬 Scientific Data Dashboard")
|
|
49
|
+
|
|
50
|
+
# --- サイドバー: データアップロード & パラメータ ---
|
|
51
|
+
st.sidebar.header("Settings")
|
|
52
|
+
|
|
53
|
+
uploaded_file = st.sidebar.file_uploader(
|
|
54
|
+
"Upload CSV / Excel", type=["csv", "xlsx"])
|
|
55
|
+
|
|
56
|
+
if uploaded_file is not None:
|
|
57
|
+
if uploaded_file.name.endswith(".csv"):
|
|
58
|
+
df = pd.read_csv(uploaded_file)
|
|
59
|
+
else:
|
|
60
|
+
df = pd.read_excel(uploaded_file)
|
|
61
|
+
else:
|
|
62
|
+
# デモデータ
|
|
63
|
+
np.random.seed(42)
|
|
64
|
+
n = 500
|
|
65
|
+
df = pd.DataFrame({
|
|
66
|
+
"x": np.random.randn(n),
|
|
67
|
+
"y": np.random.randn(n),
|
|
68
|
+
"z": np.random.randn(n),
|
|
69
|
+
"category": np.random.choice(["A", "B", "C"], n),
|
|
70
|
+
"value": np.random.exponential(2, n)
|
|
71
|
+
})
|
|
72
|
+
st.sidebar.info("Demo data loaded (upload your own CSV)")
|
|
73
|
+
|
|
74
|
+
# --- データ概要 ---
|
|
75
|
+
col1, col2, col3 = st.columns(3)
|
|
76
|
+
col1.metric("Rows", len(df))
|
|
77
|
+
col2.metric("Columns", len(df.columns))
|
|
78
|
+
col3.metric("Missing", int(df.isnull().sum().sum()))
|
|
79
|
+
|
|
80
|
+
# --- タブ ---
|
|
81
|
+
tab1, tab2, tab3, tab4 = st.tabs(
|
|
82
|
+
["📊 Explorer", "📈 Distribution", "🔗 Correlation", "📋 Data"])
|
|
83
|
+
|
|
84
|
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
|
85
|
+
cat_cols = df.select_dtypes(exclude=[np.number]).columns.tolist()
|
|
86
|
+
|
|
87
|
+
with tab1:
|
|
88
|
+
st.subheader("Interactive Explorer")
|
|
89
|
+
c1, c2 = st.columns(2)
|
|
90
|
+
x_col = c1.selectbox("X axis", numeric_cols, index=0)
|
|
91
|
+
y_col = c2.selectbox("Y axis", numeric_cols,
|
|
92
|
+
index=min(1, len(numeric_cols)-1))
|
|
93
|
+
color_col = st.selectbox("Color", [None] + cat_cols + numeric_cols)
|
|
94
|
+
|
|
95
|
+
fig = px.scatter(df, x=x_col, y=y_col, color=color_col,
|
|
96
|
+
opacity=0.7, title=f"{x_col} vs {y_col}")
|
|
97
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
98
|
+
|
|
99
|
+
with tab2:
|
|
100
|
+
st.subheader("Distribution Analysis")
|
|
101
|
+
dist_col = st.selectbox("Column", numeric_cols, key="dist")
|
|
102
|
+
n_bins = st.slider("Bins", 10, 100, 30)
|
|
103
|
+
fig2 = px.histogram(df, x=dist_col, nbins=n_bins,
|
|
104
|
+
marginal="box", title=f"Distribution: {dist_col}")
|
|
105
|
+
st.plotly_chart(fig2, use_container_width=True)
|
|
106
|
+
|
|
107
|
+
with tab3:
|
|
108
|
+
st.subheader("Correlation Matrix")
|
|
109
|
+
corr = df[numeric_cols].corr()
|
|
110
|
+
fig3 = px.imshow(corr, text_auto=".2f", color_continuous_scale="RdBu_r",
|
|
111
|
+
title="Correlation Heatmap")
|
|
112
|
+
st.plotly_chart(fig3, use_container_width=True)
|
|
113
|
+
|
|
114
|
+
with tab4:
|
|
115
|
+
st.subheader("Raw Data")
|
|
116
|
+
st.dataframe(df, use_container_width=True)
|
|
117
|
+
csv = df.to_csv(index=False)
|
|
118
|
+
st.download_button("Download CSV", csv, "data.csv", "text/csv")
|
|
119
|
+
'''
|
|
120
|
+
|
|
121
|
+
with open(output_path, "w") as f:
|
|
122
|
+
f.write(code)
|
|
123
|
+
|
|
124
|
+
print(f"Streamlit dashboard → {output_path}")
|
|
125
|
+
print(f" Run: streamlit run {output_path}")
|
|
126
|
+
return output_path
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## 2. Dash コールバックダッシュボード
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
def generate_dash_dashboard(output_path="dash_app.py"):
|
|
133
|
+
"""
|
|
134
|
+
Dash ダッシュボードテンプレート生成。
|
|
135
|
+
|
|
136
|
+
Parameters:
|
|
137
|
+
output_path: str — 出力 Python ファイル
|
|
138
|
+
"""
|
|
139
|
+
code = '''
|
|
140
|
+
from dash import Dash, html, dcc, Input, Output, dash_table
|
|
141
|
+
import pandas as pd
|
|
142
|
+
import numpy as np
|
|
143
|
+
import plotly.express as px
|
|
144
|
+
|
|
145
|
+
app = Dash(__name__)
|
|
146
|
+
|
|
147
|
+
# デモデータ
|
|
148
|
+
np.random.seed(42)
|
|
149
|
+
n = 500
|
|
150
|
+
df = pd.DataFrame({
|
|
151
|
+
"x": np.random.randn(n),
|
|
152
|
+
"y": np.random.randn(n),
|
|
153
|
+
"z": np.random.randn(n),
|
|
154
|
+
"group": np.random.choice(["Control", "Treatment A", "Treatment B"], n),
|
|
155
|
+
"response": np.random.exponential(2, n)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
|
159
|
+
|
|
160
|
+
app.layout = html.Div([
|
|
161
|
+
html.H1("Scientific Data Dashboard", style={"textAlign": "center"}),
|
|
162
|
+
|
|
163
|
+
html.Div([
|
|
164
|
+
html.Div([
|
|
165
|
+
html.Label("X Axis"),
|
|
166
|
+
dcc.Dropdown(id="x-col", options=numeric_cols,
|
|
167
|
+
value=numeric_cols[0])
|
|
168
|
+
], style={"width": "30%", "display": "inline-block"}),
|
|
169
|
+
html.Div([
|
|
170
|
+
html.Label("Y Axis"),
|
|
171
|
+
dcc.Dropdown(id="y-col", options=numeric_cols,
|
|
172
|
+
value=numeric_cols[1])
|
|
173
|
+
], style={"width": "30%", "display": "inline-block"}),
|
|
174
|
+
html.Div([
|
|
175
|
+
html.Label("Color"),
|
|
176
|
+
dcc.Dropdown(id="color-col",
|
|
177
|
+
options=df.columns.tolist(),
|
|
178
|
+
value="group")
|
|
179
|
+
], style={"width": "30%", "display": "inline-block"}),
|
|
180
|
+
], style={"padding": "20px"}),
|
|
181
|
+
|
|
182
|
+
html.Div([
|
|
183
|
+
html.Div([dcc.Graph(id="scatter-plot")],
|
|
184
|
+
style={"width": "50%", "display": "inline-block"}),
|
|
185
|
+
html.Div([dcc.Graph(id="histogram")],
|
|
186
|
+
style={"width": "50%", "display": "inline-block"}),
|
|
187
|
+
]),
|
|
188
|
+
|
|
189
|
+
html.Div([
|
|
190
|
+
html.H3("Summary Statistics"),
|
|
191
|
+
dash_table.DataTable(
|
|
192
|
+
id="summary-table",
|
|
193
|
+
columns=[{"name": c, "id": c}
|
|
194
|
+
for c in ["stat"] + numeric_cols],
|
|
195
|
+
style_table={"overflowX": "auto"})
|
|
196
|
+
], style={"padding": "20px"})
|
|
197
|
+
])
|
|
198
|
+
|
|
199
|
+
@app.callback(
|
|
200
|
+
[Output("scatter-plot", "figure"),
|
|
201
|
+
Output("histogram", "figure"),
|
|
202
|
+
Output("summary-table", "data")],
|
|
203
|
+
[Input("x-col", "value"),
|
|
204
|
+
Input("y-col", "value"),
|
|
205
|
+
Input("color-col", "value")]
|
|
206
|
+
)
|
|
207
|
+
def update_plots(x_col, y_col, color_col):
|
|
208
|
+
fig1 = px.scatter(df, x=x_col, y=y_col, color=color_col,
|
|
209
|
+
opacity=0.7, title=f"{x_col} vs {y_col}")
|
|
210
|
+
fig2 = px.histogram(df, x=x_col, color=color_col,
|
|
211
|
+
marginal="box", barmode="overlay", opacity=0.7)
|
|
212
|
+
stats = df[numeric_cols].describe().reset_index()
|
|
213
|
+
stats.columns = ["stat"] + numeric_cols
|
|
214
|
+
return fig1, fig2, stats.to_dict("records")
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
app.run(debug=True, port=8050)
|
|
218
|
+
'''
|
|
219
|
+
|
|
220
|
+
with open(output_path, "w") as f:
|
|
221
|
+
f.write(code)
|
|
222
|
+
|
|
223
|
+
print(f"Dash dashboard → {output_path}")
|
|
224
|
+
print(f" Run: python {output_path}")
|
|
225
|
+
return output_path
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## 3. Panel ダッシュボード
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
def generate_panel_dashboard(output_path="panel_app.py"):
|
|
232
|
+
"""
|
|
233
|
+
Panel ダッシュボードテンプレート生成。
|
|
234
|
+
|
|
235
|
+
Parameters:
|
|
236
|
+
output_path: str — 出力 Python ファイル
|
|
237
|
+
"""
|
|
238
|
+
code = '''
|
|
239
|
+
import panel as pn
|
|
240
|
+
import pandas as pd
|
|
241
|
+
import numpy as np
|
|
242
|
+
import plotly.express as px
|
|
243
|
+
|
|
244
|
+
pn.extension("plotly")
|
|
245
|
+
|
|
246
|
+
# デモデータ
|
|
247
|
+
np.random.seed(42)
|
|
248
|
+
n = 500
|
|
249
|
+
df = pd.DataFrame({
|
|
250
|
+
"x": np.random.randn(n),
|
|
251
|
+
"y": np.random.randn(n),
|
|
252
|
+
"z": np.random.randn(n),
|
|
253
|
+
"group": np.random.choice(["A", "B", "C"], n),
|
|
254
|
+
"value": np.random.exponential(2, n)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
|
258
|
+
|
|
259
|
+
# ウィジェット
|
|
260
|
+
x_select = pn.widgets.Select(name="X Axis", options=numeric_cols, value="x")
|
|
261
|
+
y_select = pn.widgets.Select(name="Y Axis", options=numeric_cols, value="y")
|
|
262
|
+
n_bins = pn.widgets.IntSlider(name="Histogram Bins", start=10, end=100, value=30)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@pn.depends(x_select, y_select)
|
|
266
|
+
def scatter_plot(x_col, y_col):
|
|
267
|
+
fig = px.scatter(df, x=x_col, y=y_col, color="group",
|
|
268
|
+
opacity=0.7, title=f"{x_col} vs {y_col}")
|
|
269
|
+
return fig
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@pn.depends(x_select, n_bins)
|
|
273
|
+
def hist_plot(x_col, bins):
|
|
274
|
+
fig = px.histogram(df, x=x_col, nbins=bins, color="group",
|
|
275
|
+
barmode="overlay", opacity=0.7)
|
|
276
|
+
return fig
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
dashboard = pn.template.FastListTemplate(
|
|
280
|
+
title="Scientific Data Dashboard",
|
|
281
|
+
sidebar=[x_select, y_select, n_bins],
|
|
282
|
+
main=[
|
|
283
|
+
pn.Row(pn.pane.Plotly(scatter_plot, sizing_mode="stretch_width"),
|
|
284
|
+
pn.pane.Plotly(hist_plot, sizing_mode="stretch_width")),
|
|
285
|
+
pn.pane.DataFrame(df.describe().T, sizing_mode="stretch_width")
|
|
286
|
+
]
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
dashboard.servable()
|
|
290
|
+
'''
|
|
291
|
+
|
|
292
|
+
with open(output_path, "w") as f:
|
|
293
|
+
f.write(code)
|
|
294
|
+
|
|
295
|
+
print(f"Panel dashboard → {output_path}")
|
|
296
|
+
print(f" Run: panel serve {output_path}")
|
|
297
|
+
return output_path
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## 4. ダッシュボード比較ガイド
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
def compare_dashboard_frameworks():
|
|
304
|
+
"""
|
|
305
|
+
Streamlit / Dash / Panel / Voilà 比較表を出力。
|
|
306
|
+
"""
|
|
307
|
+
comparison = pd.DataFrame({
|
|
308
|
+
"Framework": ["Streamlit", "Dash", "Panel", "Voilà"],
|
|
309
|
+
"Ease_of_Use": ["★★★★★", "★★★☆☆", "★★★★☆", "★★★★★"],
|
|
310
|
+
"Customization": ["★★★☆☆", "★★★★★", "★★★★☆", "★★☆☆☆"],
|
|
311
|
+
"Interactivity": ["★★★★☆", "★★★★★", "★★★★★", "★★★☆☆"],
|
|
312
|
+
"Performance": ["★★★☆☆", "★★★★★", "★★★★☆", "★★★☆☆"],
|
|
313
|
+
"Deployment": ["Streamlit Cloud", "Heroku/AWS", "Any ASGI", "Binder/Hub"],
|
|
314
|
+
"Best_For": [
|
|
315
|
+
"Rapid prototyping, data exploration",
|
|
316
|
+
"Production apps, complex callbacks",
|
|
317
|
+
"Jupyter integration, scientific viz",
|
|
318
|
+
"Notebook → dashboard conversion"
|
|
319
|
+
]
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
print("=== Dashboard Framework Comparison ===")
|
|
323
|
+
print(comparison.to_string(index=False))
|
|
324
|
+
return comparison
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## パイプライン統合
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
advanced-visualization → interactive-dashboard → presentation-design
|
|
333
|
+
(高度可視化) (ダッシュボード) (プレゼン)
|
|
334
|
+
│ │ ↓
|
|
335
|
+
missing-data-analysis ────────┘ scientific-schematics
|
|
336
|
+
(欠損値解析) (図式デザイン)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## パイプライン出力
|
|
340
|
+
|
|
341
|
+
| ファイル | 説明 | 次スキル |
|
|
342
|
+
|---------|------|---------|
|
|
343
|
+
| `dashboard_app.py` | Streamlit ダッシュボード | → deployment |
|
|
344
|
+
| `dash_app.py` | Dash ダッシュボード | → deployment |
|
|
345
|
+
| `panel_app.py` | Panel ダッシュボード | → deployment |
|
|
346
|
+
| `framework_comparison.csv` | フレームワーク比較 | → 選択指針 |
|