euler314 commited on
Commit
16ed767
·
verified ·
1 Parent(s): 373b768

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +162 -28
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'}, # Pure Red
51
- 'C4 Very Strong Typhoon': {'wind_speed': 113, 'color': 'Orange', 'hex': '#FFA500'}, # Bright Orange
52
- 'C3 Strong Typhoon': {'wind_speed': 96, 'color': 'Yellow', 'hex': '#FFFF00'}, # Pure Yellow
53
- 'C2 Typhoon': {'wind_speed': 83, 'color': 'Green', 'hex': '#00FF00'}, # Pure Green
54
- 'C1 Typhoon': {'wind_speed': 64, 'color': 'Cyan', 'hex': '#00FFFF'}, # Pure Cyan
55
- 'Tropical Storm': {'wind_speed': 34, 'color': 'Blue', 'hex': '#0000FF'}, # Pure Blue
56
- 'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'} # Medium Gray
57
  }
58
 
59
  taiwan_standard = {
60
- 'Strong Typhoon': {'wind_speed': 51.0, 'color': 'Red', 'hex': '#FF0000'}, # Pure Red
61
- 'Medium Typhoon': {'wind_speed': 33.7, 'color': 'Orange', 'hex': '#FFA500'}, # Bright Orange
62
- 'Mild Typhoon': {'wind_speed': 17.2, 'color': 'Yellow', 'hex': '#FFFF00'}, # Pure Yellow
63
- 'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'} # Medium Gray
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 color examples, distinct colors, and dynamic sidebar
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 with adjusted size to fit Gradio (900x700 pixels at 100 DPI)
345
- fig = plt.figure(figsize=(9, 7), dpi=100) # 9x7 inches = 900x700 pixels
346
- ax = plt.axes([0.05, 0.15, 0.60, 0.80], projection=ccrs.PlateCarree()) # Map on left 60%
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 with typhoon details on the right
364
- details_title = fig.text(0.65, 0.90, "Typhoon Details", fontsize=12, fontweight='bold', verticalalignment='top', horizontalalignment='left')
365
- details_text = fig.text(0.65, 0.85, '', fontsize=10, verticalalignment='top', horizontalalignment='left',
366
  bbox=dict(facecolor='white', alpha=0.8, boxstyle='round,pad=0.5'))
367
 
368
- # Add color legend with colored markers below the details
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
- markerfacecolor=details['hex'], markersize=10)
372
  for cat, details in standard_dict.items()]
373
- fig.legend(handles=legend_elements, title="Color Legend", loc='center right',
374
- bbox_to_anchor=(0.95, 0.5), fontsize=10)
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
- # Gradio interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- - **Statistical Analysis**: Examine relationships between ONI values and typhoon characteristics
455
- - **Path Animation**: Watch an animated typhoon path with video controls, world map, color legend with examples, and dynamic sidebar
456
- - **Regression Analysis**: Perform statistical regression on typhoon data
 
 
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 (e.g., Red: C5 Super Typhoon) on the right
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("""