Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -23,7 +23,7 @@ from sklearn.manifold import TSNE
|
|
23 |
from sklearn.cluster import DBSCAN
|
24 |
from scipy.interpolate import interp1d
|
25 |
|
26 |
-
# Import tropycal for IBTrACS processing (for typhoon
|
27 |
import tropycal.tracks as tracks
|
28 |
|
29 |
# ------------------ Argument Parsing ------------------
|
@@ -189,6 +189,46 @@ def classify_enso_phases(oni_value):
|
|
189 |
else:
|
190 |
return 'Neutral'
|
191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
# ------------------ IBTrACS Data Loading (for typhoon options) ------------------
|
193 |
def load_ibtracs_data():
|
194 |
ibtracs_data = {}
|
@@ -403,7 +443,6 @@ def categorize_typhoon_by_standard(wind_speed, standard):
|
|
403 |
|
404 |
# ------------------ Animation Functions Using Processed CSV ------------------
|
405 |
def generate_track_video_from_csv(year, storm_id, standard):
|
406 |
-
# Filter processed CSV data for the storm ID
|
407 |
storm_df = typhoon_data[typhoon_data['SID'] == storm_id].copy()
|
408 |
if storm_df.empty:
|
409 |
print("No data found for storm:", storm_id)
|
@@ -419,20 +458,17 @@ def generate_track_video_from_csv(year, storm_id, standard):
|
|
419 |
storm_name = storm_df['NAME'].iloc[0]
|
420 |
season = storm_df['SEASON'].iloc[0]
|
421 |
|
422 |
-
# Set up map boundaries
|
423 |
min_lat, max_lat = np.min(lats), np.max(lats)
|
424 |
min_lon, max_lon = np.min(lons), np.max(lons)
|
425 |
lat_padding = max((max_lat - min_lat) * 0.3, 5)
|
426 |
lon_padding = max((max_lon - min_lon) * 0.3, 5)
|
427 |
|
428 |
-
# Create a larger figure with custom central longitude for better regional focus
|
429 |
fig = plt.figure(figsize=(12, 9), dpi=100)
|
430 |
ax = plt.axes([0.05, 0.05, 0.60, 0.90],
|
431 |
projection=ccrs.PlateCarree(central_longitude=-25))
|
432 |
ax.set_extent([min_lon - lon_padding, max_lon + lon_padding, min_lat - lat_padding, max_lat + lat_padding],
|
433 |
crs=ccrs.PlateCarree())
|
434 |
|
435 |
-
# Add map features
|
436 |
ax.add_feature(cfeature.LAND, facecolor='lightgray')
|
437 |
ax.add_feature(cfeature.OCEAN, facecolor='lightblue')
|
438 |
ax.add_feature(cfeature.COASTLINE, edgecolor='black')
|
@@ -440,18 +476,17 @@ def generate_track_video_from_csv(year, storm_id, standard):
|
|
440 |
ax.gridlines(draw_labels=True, linestyle='--', color='gray', alpha=0.5)
|
441 |
ax.set_title(f"{year} {storm_name} - {season}", fontsize=16)
|
442 |
|
443 |
-
# Plot track and state marker
|
444 |
line, = ax.plot([], [], 'b-', linewidth=2, transform=ccrs.PlateCarree())
|
445 |
point, = ax.plot([], [], 'o', markersize=10, transform=ccrs.PlateCarree())
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
state_text = fig.text(0.70, 0.60, '', fontsize=14, verticalalignment='top',
|
450 |
-
|
451 |
|
452 |
-
# Persistent legend for
|
453 |
legend_elements = [plt.Line2D([0], [0], marker='o', color='w', label=f"{cat}",
|
454 |
-
markerfacecolor=details['hex'], markersize=10)
|
455 |
for cat, details in (atlantic_standard if standard=='atlantic' else taiwan_standard).items()]
|
456 |
ax.legend(handles=legend_elements, title="Storm Categories", loc='upper right', fontsize=10)
|
457 |
|
@@ -463,16 +498,13 @@ def generate_track_video_from_csv(year, storm_id, standard):
|
|
463 |
return line, point, date_text, state_text
|
464 |
|
465 |
def update(frame):
|
466 |
-
# Update the track line
|
467 |
line.set_data(lons[:frame+1], lats[:frame+1])
|
468 |
-
# Update the marker position using lists to avoid deprecation warning
|
469 |
point.set_data([lons[frame]], [lats[frame]])
|
470 |
wind_speed = winds[frame] if frame < len(winds) else np.nan
|
471 |
category, color = categorize_typhoon_by_standard(wind_speed, standard)
|
472 |
point.set_color(color)
|
473 |
dt_str = pd.to_datetime(times[frame]).strftime('%Y-%m-%d %H:%M')
|
474 |
date_text.set_text(dt_str)
|
475 |
-
# Update state information dynamically in the sidebar
|
476 |
state_info = (f"Name: {storm_name}\n"
|
477 |
f"Date: {dt_str}\n"
|
478 |
f"Wind: {wind_speed:.1f} kt\n"
|
@@ -542,7 +574,7 @@ def update_typhoon_options(year, basin):
|
|
542 |
|
543 |
def update_typhoon_options_anim(year, basin):
|
544 |
try:
|
545 |
-
# For animation, use the processed CSV data
|
546 |
data = typhoon_data.copy()
|
547 |
data['Year'] = pd.to_datetime(data['ISO_TIME']).dt.year
|
548 |
season_data = data[data['Year'] == int(year)]
|
@@ -625,8 +657,8 @@ def update_route_clusters(start_year, start_month, end_year, end_month, enso_val
|
|
625 |
name=f"Cluster {label}"
|
626 |
))
|
627 |
fig_tsne.update_layout(title="t-SNE of WP Storm Routes")
|
628 |
-
fig_routes = go.Figure()
|
629 |
-
fig_stats = make_subplots(rows=2, cols=1)
|
630 |
info = "TSNE clustering complete."
|
631 |
return fig_tsne, fig_routes, fig_stats, info
|
632 |
|
@@ -645,7 +677,7 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
|
|
645 |
- **Wind Analysis**: Examine wind speed vs ONI relationships
|
646 |
- **Pressure Analysis**: Analyze pressure vs ONI relationships
|
647 |
- **Longitude Analysis**: Study typhoon generation longitude vs ONI
|
648 |
-
- **Path Animation**: Watch animated tropical cyclone paths with dynamic state display and
|
649 |
- **TSNE Cluster**: Perform t-SNE clustering on WP storm routes with mean routes and region analysis
|
650 |
|
651 |
Select a tab above to begin your analysis.
|
@@ -715,11 +747,11 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
|
|
715 |
path_video = gr.Video(label="Tropical Cyclone Path Animation", format="mp4", interactive=False, elem_id="path_video")
|
716 |
animation_info = gr.Markdown("""
|
717 |
### Animation Instructions
|
718 |
-
1. Select a year and basin from the dropdowns. (This animation uses
|
719 |
2. Choose a tropical cyclone from the populated list.
|
720 |
3. Select a classification standard (Atlantic or Taiwan).
|
721 |
4. Click "Generate Animation".
|
722 |
-
5. The animation displays the storm track along with a dynamic sidebar that
|
723 |
""")
|
724 |
year_dropdown.change(fn=update_typhoon_options_anim, inputs=[year_dropdown, basin_dropdown], outputs=typhoon_dropdown)
|
725 |
basin_dropdown.change(fn=update_typhoon_options_anim, inputs=[year_dropdown, basin_dropdown], outputs=typhoon_dropdown)
|
|
|
23 |
from sklearn.cluster import DBSCAN
|
24 |
from scipy.interpolate import interp1d
|
25 |
|
26 |
+
# Import tropycal for IBTrACS processing (for typhoon options)
|
27 |
import tropycal.tracks as tracks
|
28 |
|
29 |
# ------------------ Argument Parsing ------------------
|
|
|
189 |
else:
|
190 |
return 'Neutral'
|
191 |
|
192 |
+
# ------------------ Regression Functions ------------------
|
193 |
+
def perform_wind_regression(start_year, start_month, end_year, end_month):
|
194 |
+
start_date = datetime(start_year, start_month, 1)
|
195 |
+
end_date = datetime(end_year, end_month, 28)
|
196 |
+
data = merged_data[(merged_data['ISO_TIME'] >= start_date) & (merged_data['ISO_TIME'] <= end_date)].dropna(subset=['USA_WIND', 'ONI'])
|
197 |
+
data['severe_typhoon'] = (data['USA_WIND'] >= 64).astype(int)
|
198 |
+
X = sm.add_constant(data['ONI'])
|
199 |
+
y = data['severe_typhoon']
|
200 |
+
model = sm.Logit(y, X).fit(disp=0)
|
201 |
+
beta_1 = model.params['ONI']
|
202 |
+
exp_beta_1 = np.exp(beta_1)
|
203 |
+
p_value = model.pvalues['ONI']
|
204 |
+
return f"Wind Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}"
|
205 |
+
|
206 |
+
def perform_pressure_regression(start_year, start_month, end_year, end_month):
|
207 |
+
start_date = datetime(start_year, start_month, 1)
|
208 |
+
end_date = datetime(end_year, end_month, 28)
|
209 |
+
data = merged_data[(merged_data['ISO_TIME'] >= start_date) & (merged_data['ISO_TIME'] <= end_date)].dropna(subset=['USA_PRES', 'ONI'])
|
210 |
+
data['intense_typhoon'] = (data['USA_PRES'] <= 950).astype(int)
|
211 |
+
X = sm.add_constant(data['ONI'])
|
212 |
+
y = data['intense_typhoon']
|
213 |
+
model = sm.Logit(y, X).fit(disp=0)
|
214 |
+
beta_1 = model.params['ONI']
|
215 |
+
exp_beta_1 = np.exp(beta_1)
|
216 |
+
p_value = model.pvalues['ONI']
|
217 |
+
return f"Pressure Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}"
|
218 |
+
|
219 |
+
def perform_longitude_regression(start_year, start_month, end_year, end_month):
|
220 |
+
start_date = datetime(start_year, start_month, 1)
|
221 |
+
end_date = datetime(end_year, end_month, 28)
|
222 |
+
data = merged_data[(merged_data['ISO_TIME'] >= start_date) & (merged_data['ISO_TIME'] <= end_date)].dropna(subset=['LON', 'ONI'])
|
223 |
+
data['western_typhoon'] = (data['LON'] <= 140).astype(int)
|
224 |
+
X = sm.add_constant(data['ONI'])
|
225 |
+
y = data['western_typhoon']
|
226 |
+
model = sm.Logit(y, X).fit(disp=0)
|
227 |
+
beta_1 = model.params['ONI']
|
228 |
+
exp_beta_1 = np.exp(beta_1)
|
229 |
+
p_value = model.pvalues['ONI']
|
230 |
+
return f"Longitude Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}"
|
231 |
+
|
232 |
# ------------------ IBTrACS Data Loading (for typhoon options) ------------------
|
233 |
def load_ibtracs_data():
|
234 |
ibtracs_data = {}
|
|
|
443 |
|
444 |
# ------------------ Animation Functions Using Processed CSV ------------------
|
445 |
def generate_track_video_from_csv(year, storm_id, standard):
|
|
|
446 |
storm_df = typhoon_data[typhoon_data['SID'] == storm_id].copy()
|
447 |
if storm_df.empty:
|
448 |
print("No data found for storm:", storm_id)
|
|
|
458 |
storm_name = storm_df['NAME'].iloc[0]
|
459 |
season = storm_df['SEASON'].iloc[0]
|
460 |
|
|
|
461 |
min_lat, max_lat = np.min(lats), np.max(lats)
|
462 |
min_lon, max_lon = np.min(lons), np.max(lons)
|
463 |
lat_padding = max((max_lat - min_lat) * 0.3, 5)
|
464 |
lon_padding = max((max_lon - min_lon) * 0.3, 5)
|
465 |
|
|
|
466 |
fig = plt.figure(figsize=(12, 9), dpi=100)
|
467 |
ax = plt.axes([0.05, 0.05, 0.60, 0.90],
|
468 |
projection=ccrs.PlateCarree(central_longitude=-25))
|
469 |
ax.set_extent([min_lon - lon_padding, max_lon + lon_padding, min_lat - lat_padding, max_lat + lat_padding],
|
470 |
crs=ccrs.PlateCarree())
|
471 |
|
|
|
472 |
ax.add_feature(cfeature.LAND, facecolor='lightgray')
|
473 |
ax.add_feature(cfeature.OCEAN, facecolor='lightblue')
|
474 |
ax.add_feature(cfeature.COASTLINE, edgecolor='black')
|
|
|
476 |
ax.gridlines(draw_labels=True, linestyle='--', color='gray', alpha=0.5)
|
477 |
ax.set_title(f"{year} {storm_name} - {season}", fontsize=16)
|
478 |
|
|
|
479 |
line, = ax.plot([], [], 'b-', linewidth=2, transform=ccrs.PlateCarree())
|
480 |
point, = ax.plot([], [], 'o', markersize=10, transform=ccrs.PlateCarree())
|
481 |
+
date_text = ax.text(0.02, 0.02, '', transform=ax.transAxes, fontsize=12,
|
482 |
+
bbox=dict(facecolor='white', alpha=0.8))
|
483 |
+
# Dynamic state display at right side
|
484 |
state_text = fig.text(0.70, 0.60, '', fontsize=14, verticalalignment='top',
|
485 |
+
bbox=dict(facecolor='white', alpha=0.8, boxstyle='round,pad=0.5'))
|
486 |
|
487 |
+
# Persistent legend for category colors
|
488 |
legend_elements = [plt.Line2D([0], [0], marker='o', color='w', label=f"{cat}",
|
489 |
+
markerfacecolor=details['hex'], markersize=10)
|
490 |
for cat, details in (atlantic_standard if standard=='atlantic' else taiwan_standard).items()]
|
491 |
ax.legend(handles=legend_elements, title="Storm Categories", loc='upper right', fontsize=10)
|
492 |
|
|
|
498 |
return line, point, date_text, state_text
|
499 |
|
500 |
def update(frame):
|
|
|
501 |
line.set_data(lons[:frame+1], lats[:frame+1])
|
|
|
502 |
point.set_data([lons[frame]], [lats[frame]])
|
503 |
wind_speed = winds[frame] if frame < len(winds) else np.nan
|
504 |
category, color = categorize_typhoon_by_standard(wind_speed, standard)
|
505 |
point.set_color(color)
|
506 |
dt_str = pd.to_datetime(times[frame]).strftime('%Y-%m-%d %H:%M')
|
507 |
date_text.set_text(dt_str)
|
|
|
508 |
state_info = (f"Name: {storm_name}\n"
|
509 |
f"Date: {dt_str}\n"
|
510 |
f"Wind: {wind_speed:.1f} kt\n"
|
|
|
574 |
|
575 |
def update_typhoon_options_anim(year, basin):
|
576 |
try:
|
577 |
+
# For animation, use the processed CSV data (without filtering by a specific prefix)
|
578 |
data = typhoon_data.copy()
|
579 |
data['Year'] = pd.to_datetime(data['ISO_TIME']).dt.year
|
580 |
season_data = data[data['Year'] == int(year)]
|
|
|
657 |
name=f"Cluster {label}"
|
658 |
))
|
659 |
fig_tsne.update_layout(title="t-SNE of WP Storm Routes")
|
660 |
+
fig_routes = go.Figure() # Placeholder
|
661 |
+
fig_stats = make_subplots(rows=2, cols=1) # Placeholder
|
662 |
info = "TSNE clustering complete."
|
663 |
return fig_tsne, fig_routes, fig_stats, info
|
664 |
|
|
|
677 |
- **Wind Analysis**: Examine wind speed vs ONI relationships
|
678 |
- **Pressure Analysis**: Analyze pressure vs ONI relationships
|
679 |
- **Longitude Analysis**: Study typhoon generation longitude vs ONI
|
680 |
+
- **Path Animation**: Watch animated tropical cyclone paths with a dynamic state display and persistent legend (using processed CSV data)
|
681 |
- **TSNE Cluster**: Perform t-SNE clustering on WP storm routes with mean routes and region analysis
|
682 |
|
683 |
Select a tab above to begin your analysis.
|
|
|
747 |
path_video = gr.Video(label="Tropical Cyclone Path Animation", format="mp4", interactive=False, elem_id="path_video")
|
748 |
animation_info = gr.Markdown("""
|
749 |
### Animation Instructions
|
750 |
+
1. Select a year and basin from the dropdowns. (This animation uses processed CSV data.)
|
751 |
2. Choose a tropical cyclone from the populated list.
|
752 |
3. Select a classification standard (Atlantic or Taiwan).
|
753 |
4. Click "Generate Animation".
|
754 |
+
5. The animation displays the storm track along with a dynamic sidebar that shows the current state (name, date, wind, category) and a persistent legend for colors.
|
755 |
""")
|
756 |
year_dropdown.change(fn=update_typhoon_options_anim, inputs=[year_dropdown, basin_dropdown], outputs=typhoon_dropdown)
|
757 |
basin_dropdown.change(fn=update_typhoon_options_anim, inputs=[year_dropdown, basin_dropdown], outputs=typhoon_dropdown)
|