@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.
@@ -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` | フレームワーク比較 | → 選択指針 |