@nahisaho/satori 0.22.0 → 0.24.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 +52 -20
- 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-anomaly-detection/SKILL.md +296 -0
- package/src/.github/skills/scientific-automl/SKILL.md +264 -0
- package/src/.github/skills/scientific-causal-ml/SKILL.md +240 -0
- package/src/.github/skills/scientific-data-profiling/SKILL.md +247 -0
- package/src/.github/skills/scientific-ensemble-methods/SKILL.md +263 -0
- package/src/.github/skills/scientific-geospatial-analysis/SKILL.md +274 -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-model-monitoring/SKILL.md +247 -0
- package/src/.github/skills/scientific-network-visualization/SKILL.md +278 -0
- package/src/.github/skills/scientific-reproducible-reporting/SKILL.md +330 -0
- package/src/.github/skills/scientific-time-series-forecasting/SKILL.md +246 -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,274 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scientific-geospatial-analysis
|
|
3
|
+
description: |
|
|
4
|
+
地理空間データ解析スキル。GeoPandas ベクターデータ処理・
|
|
5
|
+
Rasterio ラスター解析・Folium/Kepler.gl インタラクティブ地図・
|
|
6
|
+
空間自己相関 (Moran's I)・クリギング補間・CRS 変換。
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Scientific Geospatial Analysis
|
|
10
|
+
|
|
11
|
+
地理空間データの前処理・空間統計・インタラクティブ地図可視化
|
|
12
|
+
パイプラインを提供する。
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
- GeoPandas でベクターデータ (Shapefile/GeoJSON) を処理するとき
|
|
17
|
+
- ラスターデータ (GeoTIFF) を読み込み解析するとき
|
|
18
|
+
- 空間自己相関 (Moran's I / LISA) を検定するとき
|
|
19
|
+
- クリギング (Kriging) で空間補間するとき
|
|
20
|
+
- Folium/Kepler.gl でインタラクティブ地図を作成するとき
|
|
21
|
+
- CRS (座標参照系) 変換・空間結合をするとき
|
|
22
|
+
|
|
23
|
+
> **Note**: 環境特化 GIS (SoilGrids/WorldClim) は `scientific-environmental-geodata` を参照。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
## 1. GeoPandas ベクターデータ処理
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import numpy as np
|
|
33
|
+
import pandas as pd
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_and_process_geodata(filepath, target_crs="EPSG:4326"):
|
|
37
|
+
"""
|
|
38
|
+
GeoPandas ベクター/ポイントデータ読み込み・CRS 変換。
|
|
39
|
+
|
|
40
|
+
Parameters:
|
|
41
|
+
filepath: str — Shapefile / GeoJSON / GPKG パス
|
|
42
|
+
target_crs: str — 変換先座標系
|
|
43
|
+
"""
|
|
44
|
+
import geopandas as gpd
|
|
45
|
+
|
|
46
|
+
gdf = gpd.read_file(filepath)
|
|
47
|
+
original_crs = gdf.crs
|
|
48
|
+
|
|
49
|
+
if gdf.crs != target_crs:
|
|
50
|
+
gdf = gdf.to_crs(target_crs)
|
|
51
|
+
|
|
52
|
+
# 基本統計
|
|
53
|
+
bounds = gdf.total_bounds # [minx, miny, maxx, maxy]
|
|
54
|
+
geom_types = gdf.geometry.geom_type.value_counts().to_dict()
|
|
55
|
+
|
|
56
|
+
print(f"GeoData: {len(gdf)} features, CRS: {original_crs} → {target_crs}")
|
|
57
|
+
print(f" Bounds: [{bounds[0]:.4f}, {bounds[1]:.4f}] "
|
|
58
|
+
f"to [{bounds[2]:.4f}, {bounds[3]:.4f}]")
|
|
59
|
+
print(f" Geometry types: {geom_types}")
|
|
60
|
+
return gdf
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def spatial_join(gdf_left, gdf_right, how="inner", predicate="intersects"):
|
|
64
|
+
"""
|
|
65
|
+
空間結合 (Spatial Join)。
|
|
66
|
+
|
|
67
|
+
Parameters:
|
|
68
|
+
gdf_left: GeoDataFrame — 左テーブル
|
|
69
|
+
gdf_right: GeoDataFrame — 右テーブル
|
|
70
|
+
how: str — "inner" / "left" / "right"
|
|
71
|
+
predicate: str — "intersects" / "within" / "contains"
|
|
72
|
+
"""
|
|
73
|
+
import geopandas as gpd
|
|
74
|
+
|
|
75
|
+
if gdf_left.crs != gdf_right.crs:
|
|
76
|
+
gdf_right = gdf_right.to_crs(gdf_left.crs)
|
|
77
|
+
|
|
78
|
+
joined = gpd.sjoin(gdf_left, gdf_right, how=how, predicate=predicate)
|
|
79
|
+
|
|
80
|
+
print(f"Spatial Join ({predicate}, {how}): "
|
|
81
|
+
f"{len(gdf_left)} × {len(gdf_right)} → {len(joined)}")
|
|
82
|
+
return joined
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 2. 空間自己相関 (Moran's I / LISA)
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
def spatial_autocorrelation(gdf, value_col, weight_type="queen"):
|
|
89
|
+
"""
|
|
90
|
+
空間自己相関検定 — Global Moran's I + LISA。
|
|
91
|
+
|
|
92
|
+
Parameters:
|
|
93
|
+
gdf: GeoDataFrame — ジオメトリ + 属性データ
|
|
94
|
+
value_col: str — 解析対象カラム
|
|
95
|
+
weight_type: str — "queen" / "rook" / "knn"
|
|
96
|
+
"""
|
|
97
|
+
from libpysal.weights import Queen, Rook, KNN
|
|
98
|
+
from esda.moran import Moran, Moran_Local
|
|
99
|
+
import matplotlib.pyplot as plt
|
|
100
|
+
|
|
101
|
+
# 空間重み行列
|
|
102
|
+
if weight_type == "queen":
|
|
103
|
+
w = Queen.from_dataframe(gdf)
|
|
104
|
+
elif weight_type == "rook":
|
|
105
|
+
w = Rook.from_dataframe(gdf)
|
|
106
|
+
elif weight_type == "knn":
|
|
107
|
+
w = KNN.from_dataframe(gdf, k=5)
|
|
108
|
+
|
|
109
|
+
w.transform = "r"
|
|
110
|
+
y = gdf[value_col].values
|
|
111
|
+
|
|
112
|
+
# Global Moran's I
|
|
113
|
+
moran_global = Moran(y, w)
|
|
114
|
+
|
|
115
|
+
# LISA (Local Indicators of Spatial Association)
|
|
116
|
+
moran_local = Moran_Local(y, w)
|
|
117
|
+
|
|
118
|
+
gdf = gdf.copy()
|
|
119
|
+
gdf["lisa_cluster"] = moran_local.q # 1=HH, 2=LH, 3=LL, 4=HL
|
|
120
|
+
gdf["lisa_significant"] = moran_local.p_sim < 0.05
|
|
121
|
+
gdf["local_moran_i"] = moran_local.Is
|
|
122
|
+
|
|
123
|
+
# 可視化
|
|
124
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 7))
|
|
125
|
+
|
|
126
|
+
gdf.plot(column=value_col, ax=ax1, legend=True,
|
|
127
|
+
cmap="RdYlBu_r", edgecolor="gray", linewidth=0.3)
|
|
128
|
+
ax1.set_title(f"{value_col} (Moran's I={moran_global.I:.4f}, "
|
|
129
|
+
f"p={moran_global.p_sim:.4f})")
|
|
130
|
+
|
|
131
|
+
cluster_labels = {1: "High-High", 2: "Low-High",
|
|
132
|
+
3: "Low-Low", 4: "High-Low", 0: "Not Significant"}
|
|
133
|
+
sig_gdf = gdf[gdf["lisa_significant"]]
|
|
134
|
+
if len(sig_gdf) > 0:
|
|
135
|
+
sig_gdf.plot(column="lisa_cluster", ax=ax2,
|
|
136
|
+
categorical=True, legend=True,
|
|
137
|
+
edgecolor="gray", linewidth=0.3)
|
|
138
|
+
ax2.set_title("LISA Clusters (p < 0.05)")
|
|
139
|
+
|
|
140
|
+
plt.tight_layout()
|
|
141
|
+
path = "spatial_autocorrelation.png"
|
|
142
|
+
plt.savefig(path, dpi=150, bbox_inches="tight")
|
|
143
|
+
plt.close()
|
|
144
|
+
|
|
145
|
+
print(f"Moran's I = {moran_global.I:.4f}, p = {moran_global.p_sim:.4f}")
|
|
146
|
+
print(f"LISA: {gdf['lisa_significant'].sum()} significant clusters")
|
|
147
|
+
return {"moran_i": moran_global.I, "p_value": moran_global.p_sim,
|
|
148
|
+
"gdf": gdf, "fig": path}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 3. クリギング空間補間
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
def kriging_interpolation(points_df, x_col, y_col, value_col,
|
|
155
|
+
grid_resolution=100,
|
|
156
|
+
variogram_model="spherical"):
|
|
157
|
+
"""
|
|
158
|
+
Ordinary Kriging 空間補間。
|
|
159
|
+
|
|
160
|
+
Parameters:
|
|
161
|
+
points_df: pd.DataFrame — 観測点データ
|
|
162
|
+
x_col, y_col: str — 座標カラム
|
|
163
|
+
value_col: str — 補間対象カラム
|
|
164
|
+
grid_resolution: int — グリッド解像度
|
|
165
|
+
variogram_model: str — "spherical" / "exponential" / "gaussian"
|
|
166
|
+
"""
|
|
167
|
+
from pykrige.ok import OrdinaryKriging
|
|
168
|
+
import matplotlib.pyplot as plt
|
|
169
|
+
|
|
170
|
+
x = points_df[x_col].values
|
|
171
|
+
y = points_df[y_col].values
|
|
172
|
+
z = points_df[value_col].values
|
|
173
|
+
|
|
174
|
+
ok = OrdinaryKriging(
|
|
175
|
+
x, y, z,
|
|
176
|
+
variogram_model=variogram_model,
|
|
177
|
+
verbose=False, enable_plotting=False)
|
|
178
|
+
|
|
179
|
+
grid_x = np.linspace(x.min(), x.max(), grid_resolution)
|
|
180
|
+
grid_y = np.linspace(y.min(), y.max(), grid_resolution)
|
|
181
|
+
z_pred, ss_pred = ok.execute("grid", grid_x, grid_y)
|
|
182
|
+
|
|
183
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
|
|
184
|
+
|
|
185
|
+
im1 = ax1.imshow(z_pred, origin="lower",
|
|
186
|
+
extent=[x.min(), x.max(), y.min(), y.max()],
|
|
187
|
+
cmap="viridis")
|
|
188
|
+
ax1.scatter(x, y, c="red", s=10, edgecolors="black", linewidths=0.5)
|
|
189
|
+
ax1.set_title(f"Kriging Prediction ({variogram_model})")
|
|
190
|
+
plt.colorbar(im1, ax=ax1)
|
|
191
|
+
|
|
192
|
+
im2 = ax2.imshow(ss_pred, origin="lower",
|
|
193
|
+
extent=[x.min(), x.max(), y.min(), y.max()],
|
|
194
|
+
cmap="Reds")
|
|
195
|
+
ax2.set_title("Kriging Variance (Uncertainty)")
|
|
196
|
+
plt.colorbar(im2, ax=ax2)
|
|
197
|
+
|
|
198
|
+
plt.tight_layout()
|
|
199
|
+
path = "kriging_result.png"
|
|
200
|
+
plt.savefig(path, dpi=150, bbox_inches="tight")
|
|
201
|
+
plt.close()
|
|
202
|
+
|
|
203
|
+
print(f"Kriging ({variogram_model}): {grid_resolution}×{grid_resolution} grid, "
|
|
204
|
+
f"{len(x)} observation points")
|
|
205
|
+
return {"z_pred": z_pred, "variance": ss_pred,
|
|
206
|
+
"grid_x": grid_x, "grid_y": grid_y, "fig": path}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## 4. Folium インタラクティブ地図
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
def interactive_map(gdf, value_col=None, popup_cols=None,
|
|
213
|
+
tiles="CartoDB positron",
|
|
214
|
+
output="interactive_map.html"):
|
|
215
|
+
"""
|
|
216
|
+
Folium インタラクティブ地図。
|
|
217
|
+
|
|
218
|
+
Parameters:
|
|
219
|
+
gdf: GeoDataFrame — 地理空間データ
|
|
220
|
+
value_col: str | None — Choropleth カラム
|
|
221
|
+
popup_cols: list[str] | None — ポップアップ表示カラム
|
|
222
|
+
tiles: str — タイル名
|
|
223
|
+
output: str — 出力 HTML
|
|
224
|
+
"""
|
|
225
|
+
import folium
|
|
226
|
+
|
|
227
|
+
center = [gdf.geometry.centroid.y.mean(),
|
|
228
|
+
gdf.geometry.centroid.x.mean()]
|
|
229
|
+
m = folium.Map(location=center, zoom_start=8, tiles=tiles)
|
|
230
|
+
|
|
231
|
+
if value_col and gdf.geometry.geom_type.iloc[0] in ["Polygon", "MultiPolygon"]:
|
|
232
|
+
folium.Choropleth(
|
|
233
|
+
geo_data=gdf.__geo_interface__,
|
|
234
|
+
data=gdf, columns=[gdf.index.name or "index", value_col],
|
|
235
|
+
key_on="feature.id",
|
|
236
|
+
fill_color="YlOrRd",
|
|
237
|
+
legend_name=value_col
|
|
238
|
+
).add_to(m)
|
|
239
|
+
else:
|
|
240
|
+
for _, row in gdf.iterrows():
|
|
241
|
+
popup_text = ""
|
|
242
|
+
if popup_cols:
|
|
243
|
+
popup_text = "<br>".join(
|
|
244
|
+
[f"<b>{c}</b>: {row[c]}" for c in popup_cols])
|
|
245
|
+
folium.CircleMarker(
|
|
246
|
+
location=[row.geometry.centroid.y, row.geometry.centroid.x],
|
|
247
|
+
radius=5, popup=popup_text,
|
|
248
|
+
color="blue", fill=True
|
|
249
|
+
).add_to(m)
|
|
250
|
+
|
|
251
|
+
m.save(output)
|
|
252
|
+
print(f"Interactive map → {output} ({len(gdf)} features)")
|
|
253
|
+
return output
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## パイプライン統合
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
environmental-geodata → geospatial-analysis → advanced-visualization
|
|
262
|
+
(環境データ取得) (空間解析) (高度可視化)
|
|
263
|
+
│ │ ↓
|
|
264
|
+
epidemiology ───────────────┘ interactive-dashboard
|
|
265
|
+
(空間疫学) (ダッシュボード)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## パイプライン出力
|
|
269
|
+
|
|
270
|
+
| ファイル | 説明 | 次スキル |
|
|
271
|
+
|---------|------|---------|
|
|
272
|
+
| `spatial_autocorrelation.png` | Moran's I + LISA | → reporting |
|
|
273
|
+
| `kriging_result.png` | クリギング補間 | → visualization |
|
|
274
|
+
| `interactive_map.html` | Folium 地図 | → dashboard |
|
|
@@ -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` | フレームワーク比較 | → 選択指針 |
|