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)
|