euler314 commited on
Commit
bc15b27
·
verified ·
1 Parent(s): 2c3b963

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -20
app.py CHANGED
@@ -41,7 +41,7 @@ import tropycal.tracks as tracks
41
  # Configuration and Setup
42
  # -----------------------------
43
  logging.basicConfig(
44
- level=logging.INFO, # Use DEBUG for more details
45
  format='%(asctime)s - %(levelname)s - %(message)s'
46
  )
47
 
@@ -53,18 +53,18 @@ DATA_PATH = args.data_path
53
  # Data paths
54
  ONI_DATA_PATH = os.path.join(DATA_PATH, 'oni_data.csv')
55
  TYPHOON_DATA_PATH = os.path.join(DATA_PATH, 'processed_typhoon_data.csv')
56
- MERGED_DATA_CSV = os.path.join(DATA_PATH, 'merged_typhoon_era5_data.csv') # used by other analyses
57
 
58
- # IBTrACS (used only for typhoon option updates)
59
- LOCAL_IBTRACS_PATH = os.path.join(DATA_PATH, 'ibtracs.WP.list.v04r01.csv')
60
- IBTRACS_URI = 'https://www.ncei.noaa.gov/data/international-best-track-archive-for-climate-stewardship-ibtracs/v04r01/access/csv/ibtracs.WP.list.v04r01.csv'
61
- CACHE_FILE = os.path.join(DATA_PATH, 'ibtracs_cache.pkl')
62
- CACHE_EXPIRY_DAYS = 1
63
  BASIN_FILES = {
64
  'EP': 'ibtracs.EP.list.v04r01.csv',
65
  'NA': 'ibtracs.NA.list.v04r01.csv',
66
  'WP': 'ibtracs.WP.list.v04r01.csv'
67
  }
 
 
 
 
68
 
69
  # -----------------------------
70
  # Color Maps and Standards
@@ -261,7 +261,7 @@ def load_ibtracs_data():
261
  local_path = os.path.join(DATA_PATH, filename)
262
  if not os.path.exists(local_path):
263
  logging.info(f"Downloading {basin} basin file...")
264
- response = requests.get(IBTRACS_URI+filename)
265
  response.raise_for_status()
266
  with open(local_path, 'wb') as f:
267
  f.write(response.content)
@@ -366,7 +366,7 @@ def generate_main_analysis(start_year, start_month, end_year, end_month, enso_ph
366
  filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
367
  filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
368
  if enso_phase != 'all':
369
- filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
370
  tracks_fig = generate_typhoon_tracks(filtered_data, typhoon_search)
371
  wind_scatter = generate_wind_oni_scatter(filtered_data, typhoon_search)
372
  pressure_scatter = generate_pressure_oni_scatter(filtered_data, typhoon_search)
@@ -379,7 +379,7 @@ def get_full_tracks(start_year, start_month, end_year, end_month, enso_phase, ty
379
  filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
380
  filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
381
  if enso_phase != 'all':
382
- filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
383
  unique_storms = filtered_data['SID'].unique()
384
  count = len(unique_storms)
385
  fig = go.Figure()
@@ -468,10 +468,10 @@ def categorize_typhoon_by_standard(wind_speed, standard='atlantic'):
468
  return 'Tropical Storm', atlantic_standard['Tropical Storm']['hex']
469
  return 'Tropical Depression', atlantic_standard['Tropical Depression']['hex']
470
 
471
- # ------------- Updated TSNE Cluster Function -------------
472
  def update_route_clusters(start_year, start_month, end_year, end_month, enso_value, season):
473
  try:
474
- # Use raw typhoon data (with multiple observations per storm) merged with ONI info.
475
  raw_data = typhoon_data.copy()
476
  raw_data['Year'] = raw_data['ISO_TIME'].dt.year
477
  raw_data['Month'] = raw_data['ISO_TIME'].dt.strftime('%m')
@@ -483,13 +483,13 @@ def update_route_clusters(start_year, start_month, end_year, end_month, enso_val
483
  merged_raw = merged_raw[(merged_raw['ISO_TIME'] >= start_date) & (merged_raw['ISO_TIME'] <= end_date)]
484
  logging.info(f"Total points after date filtering: {merged_raw.shape[0]}")
485
 
486
- # Filter by ENSO phase if not 'all'
487
  merged_raw['ENSO_Phase'] = merged_raw['ONI'].apply(classify_enso_phases)
488
  if enso_value != 'all':
489
  merged_raw = merged_raw[merged_raw['ENSO_Phase'] == enso_value.capitalize()]
490
  logging.info(f"Total points after ENSO filtering: {merged_raw.shape[0]}")
491
 
492
- # Use regional filtering for Western Pacific (adjust boundaries as needed)
493
  wp_data = merged_raw[(merged_raw['LON'] >= 100) & (merged_raw['LON'] <= 180) &
494
  (merged_raw['LAT'] >= 0) & (merged_raw['LAT'] <= 40)]
495
  logging.info(f"Total points after WP regional filtering: {wp_data.shape[0]}")
@@ -497,7 +497,7 @@ def update_route_clusters(start_year, start_month, end_year, end_month, enso_val
497
  logging.info("WP regional filter returned no data; using all filtered data.")
498
  wp_data = merged_raw
499
 
500
- # Group by SID so each storm route has multiple observations
501
  all_storms_data = []
502
  for sid, group in wp_data.groupby('SID'):
503
  group = group.sort_values('ISO_TIME')
@@ -511,7 +511,7 @@ def update_route_clusters(start_year, start_month, end_year, end_month, enso_val
511
  if not all_storms_data:
512
  return go.Figure(), go.Figure(), make_subplots(rows=2, cols=1), "No valid storms for clustering."
513
 
514
- # Interpolate each storm's route to a common length
515
  max_length = max(len(item[1]) for item in all_storms_data)
516
  route_vectors = []
517
  storm_ids = []
@@ -567,10 +567,53 @@ def update_route_clusters(start_year, start_month, end_year, end_month, enso_val
567
  yaxis_title="t-SNE Dim 2"
568
  )
569
 
570
- # Placeholder figures for routes and stats
571
  fig_routes = go.Figure()
572
  fig_stats = make_subplots(rows=2, cols=1, shared_xaxes=True,
573
- subplot_titles=("Average Wind Speed", "Average Pressure"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
574
  info = "TSNE clustering complete."
575
  return fig_tsne, fig_routes, fig_stats, info
576
  except Exception as e:
@@ -741,7 +784,7 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
741
  - **Pressure Analysis**: Analyze pressure vs ONI relationships.
742
  - **Longitude Analysis**: Study typhoon generation longitude vs ONI.
743
  - **Path Animation**: View animated storm tracks on a free stock world map (centered at 180°) with a dynamic sidebar and persistent legend.
744
- - **TSNE Cluster**: Perform t-SNE clustering on WP storm routes using raw merged typhoon+ONI data with detailed error management.
745
  """)
746
 
747
  with gr.Tab("Track Visualization"):
@@ -820,7 +863,7 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
820
  2. Choose a tropical cyclone from the populated list.
821
  3. Select a classification standard (Atlantic or Taiwan).
822
  4. Click "Generate Animation".
823
- 5. The animation displays the storm track on a free stock world map (centered at 180°) with a dynamic sidebar and a persistent legend.
824
  """)
825
  year_dropdown.change(fn=update_typhoon_options_anim, inputs=[year_dropdown, basin_dropdown], outputs=typhoon_dropdown)
826
  basin_dropdown.change(fn=update_typhoon_options_anim, inputs=[year_dropdown, basin_dropdown], outputs=typhoon_dropdown)
 
41
  # Configuration and Setup
42
  # -----------------------------
43
  logging.basicConfig(
44
+ level=logging.INFO, # Change to DEBUG for more details
45
  format='%(asctime)s - %(levelname)s - %(message)s'
46
  )
47
 
 
53
  # Data paths
54
  ONI_DATA_PATH = os.path.join(DATA_PATH, 'oni_data.csv')
55
  TYPHOON_DATA_PATH = os.path.join(DATA_PATH, 'processed_typhoon_data.csv')
56
+ MERGED_DATA_CSV = os.path.join(DATA_PATH, 'merged_typhoon_era5_data.csv') # Used by other analyses
57
 
58
+ # IBTrACS settings (used only for typhoon option updates)
 
 
 
 
59
  BASIN_FILES = {
60
  'EP': 'ibtracs.EP.list.v04r01.csv',
61
  'NA': 'ibtracs.NA.list.v04r01.csv',
62
  'WP': 'ibtracs.WP.list.v04r01.csv'
63
  }
64
+ IBTRACS_BASE_URL = 'https://www.ncei.noaa.gov/data/international-best-track-archive-for-climate-stewardship-ibtracs/v04r01/access/csv/'
65
+ LOCAL_IBTRACS_PATH = os.path.join(DATA_PATH, 'ibtracs.WP.list.v04r01.csv')
66
+ CACHE_FILE = os.path.join(DATA_PATH, 'ibtracs_cache.pkl')
67
+ CACHE_EXPIRY_DAYS = 1
68
 
69
  # -----------------------------
70
  # Color Maps and Standards
 
261
  local_path = os.path.join(DATA_PATH, filename)
262
  if not os.path.exists(local_path):
263
  logging.info(f"Downloading {basin} basin file...")
264
+ response = requests.get(IBTRACS_BASE_URL+filename)
265
  response.raise_for_status()
266
  with open(local_path, 'wb') as f:
267
  f.write(response.content)
 
366
  filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
367
  filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
368
  if enso_phase != 'all':
369
+ filtered_data = filtered_data[filtered_data['ENSO_Phase']==enso_phase.capitalize()]
370
  tracks_fig = generate_typhoon_tracks(filtered_data, typhoon_search)
371
  wind_scatter = generate_wind_oni_scatter(filtered_data, typhoon_search)
372
  pressure_scatter = generate_pressure_oni_scatter(filtered_data, typhoon_search)
 
379
  filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
380
  filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
381
  if enso_phase != 'all':
382
+ filtered_data = filtered_data[filtered_data['ENSO_Phase']==enso_phase.capitalize()]
383
  unique_storms = filtered_data['SID'].unique()
384
  count = len(unique_storms)
385
  fig = go.Figure()
 
468
  return 'Tropical Storm', atlantic_standard['Tropical Storm']['hex']
469
  return 'Tropical Depression', atlantic_standard['Tropical Depression']['hex']
470
 
471
+ # ------------- Updated TSNE Cluster Function with Mean Path and Stats -------------
472
  def update_route_clusters(start_year, start_month, end_year, end_month, enso_value, season):
473
  try:
474
+ # Use raw typhoon data merged with ONI info so each storm has multiple points.
475
  raw_data = typhoon_data.copy()
476
  raw_data['Year'] = raw_data['ISO_TIME'].dt.year
477
  raw_data['Month'] = raw_data['ISO_TIME'].dt.strftime('%m')
 
483
  merged_raw = merged_raw[(merged_raw['ISO_TIME'] >= start_date) & (merged_raw['ISO_TIME'] <= end_date)]
484
  logging.info(f"Total points after date filtering: {merged_raw.shape[0]}")
485
 
486
+ # Filter by ENSO phase if specified
487
  merged_raw['ENSO_Phase'] = merged_raw['ONI'].apply(classify_enso_phases)
488
  if enso_value != 'all':
489
  merged_raw = merged_raw[merged_raw['ENSO_Phase'] == enso_value.capitalize()]
490
  logging.info(f"Total points after ENSO filtering: {merged_raw.shape[0]}")
491
 
492
+ # Apply regional filter for Western Pacific (adjust as needed)
493
  wp_data = merged_raw[(merged_raw['LON'] >= 100) & (merged_raw['LON'] <= 180) &
494
  (merged_raw['LAT'] >= 0) & (merged_raw['LAT'] <= 40)]
495
  logging.info(f"Total points after WP regional filtering: {wp_data.shape[0]}")
 
497
  logging.info("WP regional filter returned no data; using all filtered data.")
498
  wp_data = merged_raw
499
 
500
+ # Group by SID so that each storm has multiple points
501
  all_storms_data = []
502
  for sid, group in wp_data.groupby('SID'):
503
  group = group.sort_values('ISO_TIME')
 
511
  if not all_storms_data:
512
  return go.Figure(), go.Figure(), make_subplots(rows=2, cols=1), "No valid storms for clustering."
513
 
514
+ # Interpolate each storm route to a common length
515
  max_length = max(len(item[1]) for item in all_storms_data)
516
  route_vectors = []
517
  storm_ids = []
 
567
  yaxis_title="t-SNE Dim 2"
568
  )
569
 
570
+ # Compute mean route for each cluster and cluster stats (average wind and pressure)
571
  fig_routes = go.Figure()
572
  fig_stats = make_subplots(rows=2, cols=1, shared_xaxes=True,
573
+ subplot_titles=("Average Wind Speed (knots)", "Average MSLP (hPa)"))
574
+ for i, label in enumerate(unique_labels):
575
+ indices = np.where(labels == label)[0]
576
+ cluster_ids = [storm_ids[j] for j in indices]
577
+ cluster_vectors = route_vectors[indices, :]
578
+ mean_vector = np.mean(cluster_vectors, axis=0)
579
+ mean_route = mean_vector.reshape((max_length, 2))
580
+ mean_lon = mean_route[:, 0]
581
+ mean_lat = mean_route[:, 1]
582
+ fig_routes.add_trace(go.Scattergeo(
583
+ lon=mean_lon,
584
+ lat=mean_lat,
585
+ mode='lines',
586
+ line=dict(width=4, color=colors[i % len(colors)]),
587
+ name=f"Cluster {label} Mean Route"
588
+ ))
589
+ # Get cluster data from raw filtered data (wp_data)
590
+ cluster_data = wp_data[wp_data['SID'].isin(cluster_ids)]
591
+ avg_wind = cluster_data['USA_WIND'].mean() if 'USA_WIND' in cluster_data.columns else np.nan
592
+ avg_pres = cluster_data['USA_PRES'].mean() if 'USA_PRES' in cluster_data.columns else np.nan
593
+ # Plot horizontal lines for cluster stats at index i
594
+ fig_stats.add_trace(go.Scatter(
595
+ x=[i, i],
596
+ y=[avg_wind, avg_wind],
597
+ mode='lines+markers',
598
+ line=dict(width=2, color=colors[i % len(colors)]),
599
+ name=f"Cluster {label} Avg Wind"
600
+ ), row=1, col=1)
601
+ fig_stats.add_trace(go.Scatter(
602
+ x=[i, i],
603
+ y=[avg_pres, avg_pres],
604
+ mode='lines+markers',
605
+ line=dict(width=2, color=colors[i % len(colors)]),
606
+ name=f"Cluster {label} Avg Pres"
607
+ ), row=2, col=1)
608
+
609
+ fig_stats.update_layout(
610
+ title="Cluster Statistics",
611
+ xaxis_title="Cluster Index",
612
+ yaxis_title="Average Wind Speed (knots)",
613
+ xaxis2_title="Cluster Index",
614
+ yaxis2_title="Average MSLP (hPa)",
615
+ showlegend=True
616
+ )
617
  info = "TSNE clustering complete."
618
  return fig_tsne, fig_routes, fig_stats, info
619
  except Exception as e:
 
784
  - **Pressure Analysis**: Analyze pressure vs ONI relationships.
785
  - **Longitude Analysis**: Study typhoon generation longitude vs ONI.
786
  - **Path Animation**: View animated storm tracks on a free stock world map (centered at 180°) with a dynamic sidebar and persistent legend.
787
+ - **TSNE Cluster**: Perform t-SNE clustering on WP storm routes using raw merged typhoon+ONI data with detailed error management. Mean storm routes and cluster-level average wind/pressure are computed.
788
  """)
789
 
790
  with gr.Tab("Track Visualization"):
 
863
  2. Choose a tropical cyclone from the populated list.
864
  3. Select a classification standard (Atlantic or Taiwan).
865
  4. Click "Generate Animation".
866
+ 5. The animation displays the storm track on a free stock world map (centered at 180°) with a dynamic sidebar and persistent legend.
867
  """)
868
  year_dropdown.change(fn=update_typhoon_options_anim, inputs=[year_dropdown, basin_dropdown], outputs=typhoon_dropdown)
869
  basin_dropdown.change(fn=update_typhoon_options_anim, inputs=[year_dropdown, basin_dropdown], outputs=typhoon_dropdown)