Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -20,6 +20,8 @@ import tempfile
|
|
20 |
import csv
|
21 |
from collections import defaultdict
|
22 |
import filecmp
|
|
|
|
|
23 |
|
24 |
# Command-line argument parsing
|
25 |
parser = argparse.ArgumentParser(description='Typhoon Analysis Dashboard')
|
@@ -47,20 +49,20 @@ color_map = {
|
|
47 |
|
48 |
# Classification standards with distinct colors for Matplotlib
|
49 |
atlantic_standard = {
|
50 |
-
'C5 Super Typhoon': {'wind_speed': 137, 'color': 'Red', 'hex': '#FF0000'},
|
51 |
-
'C4 Very Strong Typhoon': {'wind_speed': 113, 'color': 'Orange', 'hex': '#FFA500'},
|
52 |
-
'C3 Strong Typhoon': {'wind_speed': 96, 'color': 'Yellow', 'hex': '#FFFF00'},
|
53 |
-
'C2 Typhoon': {'wind_speed': 83, 'color': 'Green', 'hex': '#00FF00'},
|
54 |
-
'C1 Typhoon': {'wind_speed': 64, 'color': 'Cyan', 'hex': '#00FFFF'},
|
55 |
-
'Tropical Storm': {'wind_speed': 34, 'color': 'Blue', 'hex': '#0000FF'},
|
56 |
-
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'}
|
57 |
}
|
58 |
|
59 |
taiwan_standard = {
|
60 |
-
'Strong Typhoon': {'wind_speed': 51.0, 'color': 'Red', 'hex': '#FF0000'},
|
61 |
-
'Medium Typhoon': {'wind_speed': 33.7, 'color': 'Orange', 'hex': '#FFA500'},
|
62 |
-
'Mild Typhoon': {'wind_speed': 17.2, 'color': 'Yellow', 'hex': '#FFFF00'},
|
63 |
-
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'}
|
64 |
}
|
65 |
|
66 |
# Data loading and preprocessing functions
|
@@ -302,7 +304,7 @@ def generate_main_analysis(start_year, start_month, end_year, end_month, enso_ph
|
|
302 |
|
303 |
return tracks_fig, wind_scatter, pressure_scatter, regression_fig, slopes_text
|
304 |
|
305 |
-
# Video animation function with
|
306 |
def categorize_typhoon_by_standard(wind_speed, standard):
|
307 |
if standard == 'taiwan':
|
308 |
wind_speed_ms = wind_speed * 0.514444
|
@@ -341,9 +343,9 @@ def generate_track_video(year, typhoon, standard):
|
|
341 |
lat_padding = max((max_lat - min_lat) * 0.3, 5)
|
342 |
lon_padding = max((max_lon - min_lon) * 0.3, 5)
|
343 |
|
344 |
-
# Set up the figure
|
345 |
-
fig = plt.figure(figsize=(9, 7), dpi=100)
|
346 |
-
ax = plt.axes([0.05, 0.
|
347 |
ax.set_extent([min_lon - lon_padding, max_lon + lon_padding, min_lat - lat_padding, max_lat + lat_padding], crs=ccrs.PlateCarree())
|
348 |
|
349 |
# Add world map features
|
@@ -360,18 +362,18 @@ def generate_track_video(year, typhoon, standard):
|
|
360 |
point, = ax.plot([], [], 'o', markersize=8, transform=ccrs.PlateCarree())
|
361 |
date_text = ax.text(0.02, 0.02, '', transform=ax.transAxes, fontsize=10, bbox=dict(facecolor='white', alpha=0.8))
|
362 |
|
363 |
-
# Add sidebar
|
364 |
-
details_title = fig.text(0.
|
365 |
-
details_text = fig.text(0.
|
366 |
bbox=dict(facecolor='white', alpha=0.8, boxstyle='round,pad=0.5'))
|
367 |
|
368 |
-
# Add color legend
|
369 |
standard_dict = atlantic_standard if standard == 'atlantic' else taiwan_standard
|
370 |
legend_elements = [plt.Line2D([0], [0], marker='o', color='w', label=f"{cat}",
|
371 |
-
|
372 |
for cat, details in standard_dict.items()]
|
373 |
-
fig.legend(handles=legend_elements, title="Color Legend", loc='
|
374 |
-
bbox_to_anchor=(0.95, 0.
|
375 |
|
376 |
def init():
|
377 |
line.set_data([], [])
|
@@ -393,7 +395,6 @@ def generate_track_video(year, typhoon, standard):
|
|
393 |
details_text.set_text(details)
|
394 |
return line, point, date_text, details_text
|
395 |
|
396 |
-
# Create animation
|
397 |
ani = animation.FuncAnimation(fig, update, init_func=init, frames=len(storm.time),
|
398 |
interval=200, blit=True, repeat=True)
|
399 |
|
@@ -439,7 +440,118 @@ def perform_longitude_regression(start_year, start_month, end_year, end_month):
|
|
439 |
beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI']
|
440 |
return f"Longitude Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}"
|
441 |
|
442 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
443 |
with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
|
444 |
gr.Markdown("# Typhoon Analysis Dashboard")
|
445 |
|
@@ -451,9 +563,11 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
|
|
451 |
|
452 |
### Features:
|
453 |
- **Track Visualization**: View typhoon tracks by time period and ENSO phase
|
454 |
-
- **
|
455 |
-
- **
|
456 |
-
- **
|
|
|
|
|
457 |
|
458 |
Select a tab above to begin your analysis.
|
459 |
""")
|
@@ -623,7 +737,7 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
|
|
623 |
5. The animation shows the typhoon track growing over a world map, with:
|
624 |
- Date on the bottom left
|
625 |
- Sidebar on the right showing typhoon details (name, date, wind speed, category) as it moves
|
626 |
-
- Color legend with colored markers
|
627 |
""")
|
628 |
|
629 |
def update_typhoon_options(year):
|
@@ -638,6 +752,26 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
|
|
638 |
inputs=[year_dropdown, typhoon_dropdown, standard_dropdown],
|
639 |
outputs=path_video
|
640 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
641 |
|
642 |
# Custom CSS for better visibility
|
643 |
gr.HTML("""
|
|
|
20 |
import csv
|
21 |
from collections import defaultdict
|
22 |
import filecmp
|
23 |
+
from sklearn.manifold import TSNE
|
24 |
+
from sklearn.cluster import DBSCAN
|
25 |
|
26 |
# Command-line argument parsing
|
27 |
parser = argparse.ArgumentParser(description='Typhoon Analysis Dashboard')
|
|
|
49 |
|
50 |
# Classification standards with distinct colors for Matplotlib
|
51 |
atlantic_standard = {
|
52 |
+
'C5 Super Typhoon': {'wind_speed': 137, 'color': 'Red', 'hex': '#FF0000'},
|
53 |
+
'C4 Very Strong Typhoon': {'wind_speed': 113, 'color': 'Orange', 'hex': '#FFA500'},
|
54 |
+
'C3 Strong Typhoon': {'wind_speed': 96, 'color': 'Yellow', 'hex': '#FFFF00'},
|
55 |
+
'C2 Typhoon': {'wind_speed': 83, 'color': 'Green', 'hex': '#00FF00'},
|
56 |
+
'C1 Typhoon': {'wind_speed': 64, 'color': 'Cyan', 'hex': '#00FFFF'},
|
57 |
+
'Tropical Storm': {'wind_speed': 34, 'color': 'Blue', 'hex': '#0000FF'},
|
58 |
+
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'}
|
59 |
}
|
60 |
|
61 |
taiwan_standard = {
|
62 |
+
'Strong Typhoon': {'wind_speed': 51.0, 'color': 'Red', 'hex': '#FF0000'},
|
63 |
+
'Medium Typhoon': {'wind_speed': 33.7, 'color': 'Orange', 'hex': '#FFA500'},
|
64 |
+
'Mild Typhoon': {'wind_speed': 17.2, 'color': 'Yellow', 'hex': '#FFFF00'},
|
65 |
+
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'}
|
66 |
}
|
67 |
|
68 |
# Data loading and preprocessing functions
|
|
|
304 |
|
305 |
return tracks_fig, wind_scatter, pressure_scatter, regression_fig, slopes_text
|
306 |
|
307 |
+
# Video animation function with fixed sidebar
|
308 |
def categorize_typhoon_by_standard(wind_speed, standard):
|
309 |
if standard == 'taiwan':
|
310 |
wind_speed_ms = wind_speed * 0.514444
|
|
|
343 |
lat_padding = max((max_lat - min_lat) * 0.3, 5)
|
344 |
lon_padding = max((max_lon - min_lon) * 0.3, 5)
|
345 |
|
346 |
+
# Set up the figure (900x700 pixels at 100 DPI)
|
347 |
+
fig = plt.figure(figsize=(9, 7), dpi=100)
|
348 |
+
ax = plt.axes([0.05, 0.05, 0.65, 0.90], projection=ccrs.PlateCarree()) # Adjusted to leave space for sidebar
|
349 |
ax.set_extent([min_lon - lon_padding, max_lon + lon_padding, min_lat - lat_padding, max_lat + lat_padding], crs=ccrs.PlateCarree())
|
350 |
|
351 |
# Add world map features
|
|
|
362 |
point, = ax.plot([], [], 'o', markersize=8, transform=ccrs.PlateCarree())
|
363 |
date_text = ax.text(0.02, 0.02, '', transform=ax.transAxes, fontsize=10, bbox=dict(facecolor='white', alpha=0.8))
|
364 |
|
365 |
+
# Add sidebar on the right
|
366 |
+
details_title = fig.text(0.75, 0.95, "Typhoon Details", fontsize=12, fontweight='bold', verticalalignment='top')
|
367 |
+
details_text = fig.text(0.75, 0.85, '', fontsize=10, verticalalignment='top',
|
368 |
bbox=dict(facecolor='white', alpha=0.8, boxstyle='round,pad=0.5'))
|
369 |
|
370 |
+
# Add color legend
|
371 |
standard_dict = atlantic_standard if standard == 'atlantic' else taiwan_standard
|
372 |
legend_elements = [plt.Line2D([0], [0], marker='o', color='w', label=f"{cat}",
|
373 |
+
markerfacecolor=details['hex'], markersize=10)
|
374 |
for cat, details in standard_dict.items()]
|
375 |
+
fig.legend(handles=legend_elements, title="Color Legend", loc='lower right',
|
376 |
+
bbox_to_anchor=(0.95, 0.05), fontsize=10)
|
377 |
|
378 |
def init():
|
379 |
line.set_data([], [])
|
|
|
395 |
details_text.set_text(details)
|
396 |
return line, point, date_text, details_text
|
397 |
|
|
|
398 |
ani = animation.FuncAnimation(fig, update, init_func=init, frames=len(storm.time),
|
399 |
interval=200, blit=True, repeat=True)
|
400 |
|
|
|
440 |
beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI']
|
441 |
return f"Longitude Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}"
|
442 |
|
443 |
+
# t-SNE clustering functions
|
444 |
+
def filter_west_pacific_coordinates(lons, lats):
|
445 |
+
mask = (lons >= 100) & (lons <= 180) & (lats >= 0) & (lats <= 50)
|
446 |
+
return lons[mask], lats[mask]
|
447 |
+
|
448 |
+
def update_route_clusters(start_year, start_month, end_year, end_month, enso_value, season):
|
449 |
+
start_date = datetime(int(start_year), int(start_month), 1)
|
450 |
+
end_date = datetime(int(end_year), int(end_month), 28)
|
451 |
+
|
452 |
+
all_storms_data = []
|
453 |
+
for year in range(int(start_year), int(end_year) + 1):
|
454 |
+
season_data = ibtracs.get_season(year)
|
455 |
+
for storm_id in season_data.summary()['id']:
|
456 |
+
storm = ibtracs.get_storm(storm_id)
|
457 |
+
if storm.time[0] >= start_date and storm.time[-1] <= end_date:
|
458 |
+
lons, lats = filter_west_pacific_coordinates(np.array(storm.lon), np.array(storm.lat))
|
459 |
+
if len(lons) > 1:
|
460 |
+
all_storms_data.append((lons, lats, np.array(storm.vmax), np.array(storm.mslp), np.array(storm.time), storm.name))
|
461 |
+
|
462 |
+
if not all_storms_data:
|
463 |
+
return go.Figure(), go.Figure(), go.Figure(), "No storms found in the selected period."
|
464 |
+
|
465 |
+
# Prepare route vectors for t-SNE
|
466 |
+
max_length = max(len(st[0]) for st in all_storms_data)
|
467 |
+
route_vectors = []
|
468 |
+
for lons, lats, _, _, _, _ in all_storms_data:
|
469 |
+
interp_lons = np.interp(np.linspace(0, 1, max_length), np.linspace(0, 1, len(lons)), lons)
|
470 |
+
interp_lats = np.interp(np.linspace(0, 1, max_length), np.linspace(0, 1, len(lats)), lats)
|
471 |
+
route_vectors.append(np.column_stack((interp_lons, interp_lats)).flatten())
|
472 |
+
route_vectors = np.array(route_vectors)
|
473 |
+
|
474 |
+
# Perform t-SNE
|
475 |
+
tsne_results = TSNE(n_components=2, random_state=42, perplexity=min(30, len(route_vectors)-1)).fit_transform(route_vectors)
|
476 |
+
|
477 |
+
# Dynamic DBSCAN clustering
|
478 |
+
target_clusters = min(5, len(all_storms_data) // 3)
|
479 |
+
eps_range = np.arange(5.0, 50.0, 5.0)
|
480 |
+
min_samples = max(3, len(all_storms_data) // 20)
|
481 |
+
best_labels = None
|
482 |
+
best_eps = None
|
483 |
+
best_n_clusters = 0
|
484 |
+
best_noise_ratio = 1.0
|
485 |
+
|
486 |
+
for eps in eps_range:
|
487 |
+
dbscan = DBSCAN(eps=eps, min_samples=min_samples)
|
488 |
+
labels = dbscan.fit_predict(tsne_results)
|
489 |
+
n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
|
490 |
+
noise_points = np.sum(labels == -1)
|
491 |
+
noise_ratio = noise_points / len(labels)
|
492 |
+
|
493 |
+
if n_clusters >= target_clusters and noise_ratio < 0.3 and (n_clusters > best_n_clusters or (n_clusters == best_n_clusters and noise_ratio < best_noise_ratio)):
|
494 |
+
best_labels = labels
|
495 |
+
best_eps = eps
|
496 |
+
best_n_clusters = n_clusters
|
497 |
+
best_noise_ratio = noise_ratio
|
498 |
+
|
499 |
+
if best_labels is None:
|
500 |
+
dbscan = DBSCAN(eps=5.0, min_samples=min_samples)
|
501 |
+
best_labels = dbscan.fit_predict(tsne_results)
|
502 |
+
best_eps = 5.0
|
503 |
+
best_n_clusters = len(set(best_labels)) - (1 if -1 in best_labels else 0)
|
504 |
+
|
505 |
+
# t-SNE Scatter Plot
|
506 |
+
fig_tsne = go.Figure()
|
507 |
+
for cluster in set(best_labels):
|
508 |
+
mask = best_labels == cluster
|
509 |
+
name = "Noise" if cluster == -1 else f"Cluster {cluster}"
|
510 |
+
fig_tsne.add_trace(go.Scatter(
|
511 |
+
x=tsne_results[mask, 0], y=tsne_results[mask, 1], mode='markers',
|
512 |
+
name=name, text=[all_storms_data[i][5] for i in range(len(all_storms_data)) if mask[i]],
|
513 |
+
hoverinfo='text'
|
514 |
+
))
|
515 |
+
fig_tsne.update_layout(title="t-SNE Clustering of Typhoon Routes", xaxis_title="t-SNE 1", yaxis_title="t-SNE 2")
|
516 |
+
|
517 |
+
# Typhoon Routes Plot
|
518 |
+
fig_routes = go.Figure()
|
519 |
+
for i, (lons, lats, _, _, _, name) in enumerate(all_storms_data):
|
520 |
+
cluster = best_labels[i]
|
521 |
+
color = 'gray' if cluster == -1 else px.colors.qualitative.Plotly[cluster % len(px.colors.qualitative.Plotly)]
|
522 |
+
fig_routes.add_trace(go.Scattergeo(
|
523 |
+
lon=lons, lat=lats, mode='lines+markers', name=name,
|
524 |
+
line=dict(color=color), marker=dict(size=4), hoverinfo='text', text=name
|
525 |
+
))
|
526 |
+
fig_routes.update_layout(
|
527 |
+
title="Typhoon Routes by Cluster",
|
528 |
+
geo=dict(scope='asia', projection_type='mercator', showland=True, landcolor='lightgray')
|
529 |
+
)
|
530 |
+
|
531 |
+
# Cluster Statistics Plot
|
532 |
+
cluster_stats = []
|
533 |
+
for cluster in set(best_labels) - {-1}:
|
534 |
+
mask = best_labels == cluster
|
535 |
+
winds = [all_storms_data[i][2].max() for i in range(len(all_storms_data)) if mask[i]]
|
536 |
+
pressures = [all_storms_data[i][3].min() for i in range(len(all_storms_data)) if mask[i]]
|
537 |
+
cluster_stats.append({
|
538 |
+
'Cluster': cluster,
|
539 |
+
'Count': np.sum(mask),
|
540 |
+
'Mean Wind': np.mean(winds),
|
541 |
+
'Mean Pressure': np.mean(pressures)
|
542 |
+
})
|
543 |
+
stats_df = pd.DataFrame(cluster_stats)
|
544 |
+
fig_stats = px.bar(stats_df, x='Cluster', y=['Mean Wind', 'Mean Pressure'], barmode='group',
|
545 |
+
title="Cluster Statistics (Mean Wind Speed and Pressure)")
|
546 |
+
|
547 |
+
# Cluster Information
|
548 |
+
cluster_info = f"Number of Clusters: {best_n_clusters}\nBest EPS: {best_eps}\nNoise Points: {best_noise_ratio*100:.1f}%"
|
549 |
+
for stat in cluster_stats:
|
550 |
+
cluster_info += f"\nCluster {stat['Cluster']}: {stat['Count']} storms, Mean Wind: {stat['Mean Wind']:.1f} kt, Mean Pressure: {stat['Mean Pressure']:.1f} hPa"
|
551 |
+
|
552 |
+
return fig_tsne, fig_routes, fig_stats, cluster_info
|
553 |
+
|
554 |
+
# Gradio Interface
|
555 |
with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
|
556 |
gr.Markdown("# Typhoon Analysis Dashboard")
|
557 |
|
|
|
563 |
|
564 |
### Features:
|
565 |
- **Track Visualization**: View typhoon tracks by time period and ENSO phase
|
566 |
+
- **Wind Analysis**: Examine wind speed vs ONI relationships
|
567 |
+
- **Pressure Analysis**: Analyze pressure vs ONI relationships
|
568 |
+
- **Longitude Analysis**: Study typhoon generation longitude vs ONI
|
569 |
+
- **Path Animation**: Watch animated typhoon paths with a sidebar
|
570 |
+
- **TSNE Cluster**: Perform t-SNE clustering on typhoon routes
|
571 |
|
572 |
Select a tab above to begin your analysis.
|
573 |
""")
|
|
|
737 |
5. The animation shows the typhoon track growing over a world map, with:
|
738 |
- Date on the bottom left
|
739 |
- Sidebar on the right showing typhoon details (name, date, wind speed, category) as it moves
|
740 |
+
- Color legend with colored markers on the bottom right
|
741 |
""")
|
742 |
|
743 |
def update_typhoon_options(year):
|
|
|
752 |
inputs=[year_dropdown, typhoon_dropdown, standard_dropdown],
|
753 |
outputs=path_video
|
754 |
)
|
755 |
+
|
756 |
+
with gr.Tab("TSNE Cluster"):
|
757 |
+
with gr.Row():
|
758 |
+
tsne_start_year = gr.Number(label="Start Year", value=2000, minimum=1900, maximum=2024, step=1)
|
759 |
+
tsne_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1)
|
760 |
+
tsne_end_year = gr.Number(label="End Year", value=2024, minimum=1900, maximum=2024, step=1)
|
761 |
+
tsne_end_month = gr.Dropdown(label="End Month", choices=list(range(1, 13)), value=12)
|
762 |
+
tsne_enso_phase = gr.Dropdown(label="ENSO Phase", choices=['all', 'El Nino', 'La Nina', 'Neutral'], value='all')
|
763 |
+
tsne_season = gr.Dropdown(label="Season", choices=['all', 'summer', 'winter'], value='all')
|
764 |
+
tsne_analyze_btn = gr.Button("Analyze")
|
765 |
+
tsne_plot = gr.Plot(label="t-SNE Clusters")
|
766 |
+
routes_plot = gr.Plot(label="Typhoon Routes")
|
767 |
+
stats_plot = gr.Plot(label="Cluster Statistics")
|
768 |
+
cluster_info = gr.Textbox(label="Cluster Information", lines=10)
|
769 |
+
|
770 |
+
tsne_analyze_btn.click(
|
771 |
+
fn=update_route_clusters,
|
772 |
+
inputs=[tsne_start_year, tsne_start_month, tsne_end_year, tsne_end_month, tsne_enso_phase, tsne_season],
|
773 |
+
outputs=[tsne_plot, routes_plot, stats_plot, cluster_info]
|
774 |
+
)
|
775 |
|
776 |
# Custom CSS for better visibility
|
777 |
gr.HTML("""
|