euler314 commited on
Commit
a566db2
·
verified ·
1 Parent(s): a2d2271

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +240 -350
app.py CHANGED
@@ -1,25 +1,20 @@
1
  import gradio as gr
2
- import plotly.graph_objects as go
3
- import plotly.express as px
4
  import pandas as pd
5
  import numpy as np
6
- from datetime import datetime
7
- from scipy import stats
8
- from sklearn.linear_model import LinearRegression
9
- from sklearn.cluster import KMeans
10
- from scipy.interpolate import interp1d
11
- from fractions import Fraction
12
- import statsmodels.api as sm
13
  import tropycal.tracks as tracks
14
- import os
15
  import pickle
16
  import requests
17
- import tempfile
 
 
 
18
  import shutil
19
- import filecmp
20
  import csv
21
  from collections import defaultdict
22
- import argparse
23
 
24
  # Command-line argument parsing
25
  parser = argparse.ArgumentParser(description='Typhoon Analysis Dashboard')
@@ -27,7 +22,6 @@ parser.add_argument('--data_path', type=str, default=os.getcwd(), help='Path to
27
  args = parser.parse_args()
28
  DATA_PATH = args.data_path
29
 
30
- # File paths
31
  ONI_DATA_PATH = os.path.join(DATA_PATH, 'oni_data.csv')
32
  TYPHOON_DATA_PATH = os.path.join(DATA_PATH, 'processed_typhoon_data.csv')
33
  LOCAL_iBtrace_PATH = os.path.join(DATA_PATH, 'ibtracs.WP.list.v04r01.csv')
@@ -35,7 +29,7 @@ iBtrace_uri = 'https://www.ncei.noaa.gov/data/international-best-track-archive-f
35
  CACHE_FILE = 'ibtracs_cache.pkl'
36
  CACHE_EXPIRY_DAYS = 1
37
 
38
- # Color map for categories
39
  color_map = {
40
  'C5 Super Typhoon': 'rgb(255, 0, 0)',
41
  'C4 Very Strong Typhoon': 'rgb(255, 63, 0)',
@@ -64,47 +58,18 @@ taiwan_standard = {
64
  'Tropical Depression': {'wind_speed': 0, 'color': 'rgb(173, 216, 230)'}
65
  }
66
 
67
- # Data loading and processing functions (unchanged from Dash)
68
- def convert_typhoondata(input_file, output_file):
69
- with open(input_file, 'r') as infile:
70
- next(infile)
71
- next(infile)
72
- reader = csv.reader(infile)
73
- sid_data = defaultdict(list)
74
- for row in reader:
75
- if not row:
76
- continue
77
- sid = row[0]
78
- iso_time = row[6]
79
- sid_data[sid].append((row, iso_time))
80
- with open(output_file, 'w', newline='') as outfile:
81
- fieldnames = ['SID', 'ISO_TIME', 'LAT', 'LON', 'SEASON', 'NAME', 'WMO_WIND', 'WMO_PRES', 'USA_WIND', 'USA_PRES', 'START_DATE', 'END_DATE']
82
- writer = csv.DictWriter(outfile, fieldnames=fieldnames)
83
- writer.writeheader()
84
- for sid, data in sid_data.items():
85
- start_date = min(data, key=lambda x: x[1])[1]
86
- end_date = max(data, key=lambda x: x[1])[1]
87
- for row, iso_time in data:
88
- writer.writerow({
89
- 'SID': row[0], 'ISO_TIME': iso_time, 'LAT': row[8], 'LON': row[9], 'SEASON': row[1], 'NAME': row[5],
90
- 'WMO_WIND': row[10].strip() or ' ', 'WMO_PRES': row[11].strip() or ' ',
91
- 'USA_WIND': row[23].strip() or ' ', 'USA_PRES': row[24].strip() or ' ',
92
- 'START_DATE': start_date, 'END_DATE': end_date
93
- })
94
-
95
  def download_oni_file(url, filename):
96
- try:
97
- response = requests.get(url)
98
- response.raise_for_status()
99
- with open(filename, 'wb') as f:
100
- f.write(response.content)
101
- return True
102
- except requests.RequestException:
103
- return False
104
 
105
  def convert_oni_ascii_to_csv(input_file, output_file):
106
  data = defaultdict(lambda: [''] * 12)
107
- season_to_month = {'DJF': 12, 'JFM': 1, 'FMA': 2, 'MAM': 3, 'AMJ': 4, 'MJJ': 5, 'JJA': 6, 'JAS': 7, 'ASO': 8, 'SON': 9, 'OND': 10, 'NDJ': 11}
 
108
  with open(input_file, 'r') as f:
109
  lines = f.readlines()[1:]
110
  for line in lines:
@@ -128,7 +93,7 @@ def update_oni_data():
128
  input_file = os.path.join(DATA_PATH, "oni.ascii.txt")
129
  output_file = ONI_DATA_PATH
130
  if download_oni_file(url, temp_file):
131
- if not os.path.exists(input_file) or not filecmp.cmp(temp_file, input_file, shallow=False):
132
  os.replace(temp_file, input_file)
133
  convert_oni_ascii_to_csv(input_file, output_file)
134
  else:
@@ -145,16 +110,47 @@ def load_ibtracs_data():
145
  response.raise_for_status()
146
  with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv') as temp_file:
147
  temp_file.write(response.text)
148
- temp_file_path = temp_file.name
149
- shutil.move(temp_file_path, LOCAL_iBtrace_PATH)
150
  ibtracs = tracks.TrackDataset(basin='west_pacific', source='ibtracs', ibtracs_url=LOCAL_iBtrace_PATH)
151
  with open(CACHE_FILE, 'wb') as f:
152
  pickle.dump(ibtracs, f)
153
  return ibtracs
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  def process_oni_data(oni_data):
156
  oni_long = oni_data.melt(id_vars=['Year'], var_name='Month', value_name='ONI')
157
- month_map = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'}
 
158
  oni_long['Month'] = oni_long['Month'].map(month_map)
159
  oni_long['Date'] = pd.to_datetime(oni_long['Year'].astype(str) + '-' + oni_long['Month'] + '-01')
160
  oni_long['ONI'] = pd.to_numeric(oni_long['ONI'], errors='coerce')
@@ -166,7 +162,8 @@ def process_typhoon_data(typhoon_data):
166
  typhoon_data['USA_PRES'] = pd.to_numeric(typhoon_data['USA_PRES'], errors='coerce')
167
  typhoon_data['LON'] = pd.to_numeric(typhoon_data['LON'], errors='coerce')
168
  typhoon_max = typhoon_data.groupby('SID').agg({
169
- 'USA_WIND': 'max', 'USA_PRES': 'min', 'ISO_TIME': 'first', 'SEASON': 'first', 'NAME': 'first', 'LAT': 'first', 'LON': 'first'
 
170
  }).reset_index()
171
  typhoon_max['Month'] = typhoon_max['ISO_TIME'].dt.strftime('%m')
172
  typhoon_max['Year'] = typhoon_max['ISO_TIME'].dt.year
@@ -177,18 +174,18 @@ def merge_data(oni_long, typhoon_max):
177
  return pd.merge(typhoon_max, oni_long, on=['Year', 'Month'])
178
 
179
  def categorize_typhoon(wind_speed):
180
- wind_speed_kt = wind_speed / 2
181
- if wind_speed_kt >= 137/2.35:
182
  return 'C5 Super Typhoon'
183
- elif wind_speed_kt >= 113/2.35:
184
  return 'C4 Very Strong Typhoon'
185
- elif wind_speed_kt >= 96/2.35:
186
  return 'C3 Strong Typhoon'
187
- elif wind_speed_kt >= 83/2.35:
188
  return 'C2 Typhoon'
189
- elif wind_speed_kt >= 64/2.35:
190
  return 'C1 Typhoon'
191
- elif wind_speed_kt >= 34/2.35:
192
  return 'Tropical Storm'
193
  else:
194
  return 'Tropical Depression'
@@ -203,238 +200,121 @@ def classify_enso_phases(oni_value):
203
  else:
204
  return 'Neutral'
205
 
206
- def filter_west_pacific_coordinates(lons, lats):
207
- mask = (100 <= lons) & (lons <= 180) & (0 <= lats) & (lats <= 40)
208
- return lons[mask], lats[mask]
209
-
210
- def get_storm_data(storm_id):
211
- return ibtracs.get_storm(storm_id)
212
-
213
  # Load data globally
214
  update_oni_data()
215
  ibtracs = load_ibtracs_data()
216
  convert_typhoondata(LOCAL_iBtrace_PATH, TYPHOON_DATA_PATH)
217
- oni_data = pd.read_csv(ONI_DATA_PATH)
218
- typhoon_data = pd.read_csv(TYPHOON_DATA_PATH, low_memory=False)
219
  oni_long = process_oni_data(oni_data)
220
  typhoon_max = process_typhoon_data(typhoon_data)
221
  merged_data = merge_data(oni_long, typhoon_max)
222
- oni_df = pd.read_csv(ONI_DATA_PATH, index_col='Date', parse_dates=True)
223
-
224
- # Main Analysis Function
225
- def main_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search):
226
- start_date = datetime(start_year, start_month, 1)
227
- end_date = datetime(end_year, end_month, 28)
228
- filtered_oni_df = oni_df[(oni_df.index >= start_date) & (oni_df.index <= end_date)]
229
- filtered_data = merged_data[(merged_data['Year'] >= start_year) & (merged_data['Year'] <= end_year) &
230
- (merged_data['Month'].astype(int) >= start_month) & (merged_data['Month'].astype(int) <= end_month)]
231
-
232
- # Typhoon Tracks
233
- fig_tracks = go.Figure()
234
- regression_data = {'El Nino': {'longitudes': [], 'oni_values': [], 'names': []}, 'La Nina': {'longitudes': [], 'oni_values': [], 'names': []},
235
- 'Neutral': {'longitudes': [], 'oni_values': [], 'names': []}, 'All': {'longitudes': [], 'oni_values': [], 'names': []}}
236
- for year in range(start_year, end_year + 1):
237
- season = ibtracs.get_season(year)
238
- for storm_id in season.summary()['id']:
239
- storm = get_storm_data(storm_id)
240
- storm_dates = storm.time
241
- if any(start_date <= date <= end_date for date in storm_dates):
242
- storm_oni = filtered_oni_df.loc[storm_dates[0].strftime('%Y-%b')]['ONI']
243
- if isinstance(storm_oni, pd.Series):
244
- storm_oni = storm_oni.iloc[0]
245
- phase = classify_enso_phases(storm_oni)
246
- regression_data[phase]['longitudes'].append(storm.lon[0])
247
- regression_data[phase]['oni_values'].append(storm_oni)
248
- regression_data[phase]['names'].append(f'{storm.name} ({year})')
249
- regression_data['All']['longitudes'].append(storm.lon[0])
250
- regression_data['All']['oni_values'].append(storm_oni)
251
- regression_data['All']['names'].append(f'{storm.name} ({year})')
252
- if (enso_phase == 'All Years' or (enso_phase == 'El Niño Years' and phase == 'El Nino') or
253
- (enso_phase == 'La Niña Years' and phase == 'La Nina') or (enso_phase == 'Neutral Years' and phase == 'Neutral')):
254
- color = {'El Nino': 'red', 'La Nina': 'blue', 'Neutral': 'green'}[phase]
255
- fig_tracks.add_trace(go.Scattergeo(lon=storm.lon, lat=storm.lat, mode='lines', name=storm.name,
256
- text=f'{storm.name} ({year})', hoverinfo='text', line=dict(width=2, color=color)))
257
- fig_tracks.update_layout(title=f'Typhoon Tracks from {start_year}-{start_month} to {end_year}-{end_month}', geo=dict(projection_type='natural earth', showland=True))
258
-
259
- # All Years Regression
260
- all_years_fig = go.Figure()
261
- df_all = pd.DataFrame({'Longitude': regression_data['All']['longitudes'], 'ONI': regression_data['All']['oni_values'], 'Name': regression_data['All']['names']})
262
- if not df_all.empty and len(df_all) > 1:
263
- all_years_fig = px.scatter(df_all, x='Longitude', y='ONI', hover_data=['Name'], title='All Years Typhoon Generation vs. ONI')
264
- X = np.array(df_all['Longitude']).reshape(-1, 1)
265
- y = df_all['ONI']
266
- model = LinearRegression().fit(X, y)
267
- y_pred = model.predict(X)
268
- all_years_fig.add_trace(go.Scatter(x=df_all['Longitude'], y=y_pred, mode='lines', name='Regression Line'))
269
 
270
- # Regression Graphs by Phase
271
- regression_html = ""
272
- slopes_html = ""
273
- for phase in ['El Nino', 'La Nina', 'Neutral']:
274
- df = pd.DataFrame({'Longitude': regression_data[phase]['longitudes'], 'ONI': regression_data[phase]['oni_values'], 'Name': regression_data[phase]['names']})
275
- if not df.empty and len(df) > 1:
276
- fig = px.scatter(df, x='Longitude', y='ONI', hover_data=['Name'], title=f'{phase} Typhoon Generation vs. ONI')
277
- X = np.array(df['Longitude']).reshape(-1, 1)
278
- y = df['ONI']
279
- model = LinearRegression().fit(X, y)
280
- y_pred = model.predict(X)
281
- slope = model.coef_[0]
282
- correlation_coef = np.corrcoef(df['Longitude'], df['ONI'])[0, 1]
283
- fig.add_trace(go.Scatter(x=df['Longitude'], y=y_pred, mode='lines', name='Regression Line'))
284
- regression_html += fig.to_html(include_plotlyjs=False)
285
- slopes_html += f"<p>{phase} Regression Slope: {slope:.4f}, Correlation Coefficient: {correlation_coef:.4f}</p>"
286
-
287
- # Wind and Pressure Scatter Plots
288
- wind_oni_scatter = px.scatter(filtered_data, x='ONI', y='USA_WIND', color='Category', hover_data=['NAME', 'Year', 'Category'],
289
- title='Wind Speed vs ONI', labels={'USA_WIND': 'Maximum Wind Speed (knots)'}, color_discrete_map=color_map)
290
- pressure_oni_scatter = px.scatter(filtered_data, x='ONI', y='USA_PRES', color='Category', hover_data=['NAME', 'Year', 'Category'],
291
- title='Pressure vs ONI', labels={'USA_PRES': 'Minimum Pressure (hPa)'}, color_discrete_map=color_map)
292
  if typhoon_search:
293
- for fig in [wind_oni_scatter, pressure_oni_scatter]:
294
- mask = filtered_data['NAME'].str.contains(typhoon_search, case=False, na=False)
295
- fig.add_trace(go.Scatter(x=filtered_data.loc[mask, 'ONI'], y=filtered_data.loc[mask, 'USA_WIND' if 'Wind' in fig.layout.title.text else 'USA_PRES'],
296
- mode='markers', marker=dict(size=10, color='red', symbol='star'), name=f'Matched: {typhoon_search}'))
297
-
298
- # Additional Metrics
299
- max_wind_speed = filtered_data['USA_WIND'].max()
300
- min_pressure = filtered_data['USA_PRES'].min()
301
- typhoon_counts = filtered_data['ONI'].apply(classify_enso_phases).value_counts().to_dict()
302
- month_counts = filtered_data.groupby([filtered_data['ONI'].apply(classify_enso_phases), filtered_data['ISO_TIME'].dt.month]).size().unstack(fill_value=0)
303
- concentrated_months = month_counts.idxmax(axis=1).to_dict()
304
- month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
305
- count_analysis_html = "".join([f"<p>{phase}: {count} typhoons</p>" for phase, count in typhoon_counts.items()])
306
- month_analysis_html = "".join([f"<p>{phase}: Most concentrated in {month_names[month-1]}</p>" for phase, month in concentrated_months.items()])
307
-
308
- return (fig_tracks, all_years_fig, regression_html, slopes_html, wind_oni_scatter, pressure_oni_scatter,
309
- "Logistic Regression Results: See Logistic Regression Tab", f"Maximum Wind Speed: {max_wind_speed:.2f} knots",
310
- f"Minimum Pressure: {min_pressure:.2f} hPa", "Wind-ONI correlation: See Logistic Regression Tab",
311
- "Pressure-ONI correlation: See Logistic Regression Tab", count_analysis_html, month_analysis_html)
312
-
313
- # Cluster Analysis Function
314
- def cluster_analysis(n_clusters, show_clusters, show_routes, fourier_series, start_year, start_month, end_year, end_month, enso_phase):
315
- start_date = datetime(start_year, start_month, 1)
316
- end_date = datetime(end_year, end_month, 28)
317
- filtered_oni_df = oni_df[(oni_df.index >= start_date) & (oni_df.index <= end_date)]
318
- fig_routes = go.Figure()
319
- west_pacific_storms = []
320
- for year in range(start_year, end_year + 1):
321
- season = ibtracs.get_season(year)
322
- for storm_id in season.summary()['id']:
323
- storm = get_storm_data(storm_id)
324
- storm_date = storm.time[0]
325
- storm_oni = filtered_oni_df.loc[storm_date.strftime('%Y-%b')]['ONI']
326
- if isinstance(storm_oni, pd.Series):
327
- storm_oni = storm_oni.iloc[0]
328
- storm_phase = classify_enso_phases(storm_oni)
329
- if (enso_phase == 'All Years' or (enso_phase == 'El Niño Years' and storm_phase == 'El Nino') or
330
- (enso_phase == 'La Niña Years' and storm_phase == 'La Nina') or (enso_phase == 'Neutral Years' and storm_phase == 'Neutral')):
331
- lons, lats = filter_west_pacific_coordinates(np.array(storm.lon), np.array(storm.lat))
332
- if len(lons) > 1:
333
- west_pacific_storms.append((lons, lats))
334
 
335
- max_length = max(len(storm[0]) for storm in west_pacific_storms)
336
- standardized_routes = []
337
- for lons, lats in west_pacific_storms:
338
- if len(lons) < 2:
339
- continue
340
- t = np.linspace(0, 1, len(lons))
341
- t_new = np.linspace(0, 1, max_length)
342
- lon_interp = interp1d(t, lons, kind='linear')(t_new)
343
- lat_interp = interp1d(t, lats, kind='linear')(t_new)
344
- route_vector = np.column_stack((lon_interp, lat_interp)).flatten()
345
- standardized_routes.append(route_vector)
 
 
 
346
 
347
- kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
348
- clusters = kmeans.fit_predict(standardized_routes)
349
- cluster_counts = np.bincount(clusters)
350
- equations_html = ""
351
- if show_routes:
352
- for lons, lats in west_pacific_storms:
353
- fig_routes.add_trace(go.Scattergeo(lon=lons, lat=lats, mode='lines', line=dict(width=1, color='lightgray'), showlegend=False, hoverinfo='none'))
354
- if show_clusters:
355
- for i in range(n_clusters):
356
- cluster_center = kmeans.cluster_centers_[i].reshape(-1, 2)
357
- fig_routes.add_trace(go.Scattergeo(lon=cluster_center[:, 0], lat=cluster_center[:, 1], mode='lines', name=f'Cluster {i+1} (n={cluster_counts[i]})', line=dict(width=3)))
358
- if fourier_series:
359
- X = cluster_center[:, 0]
360
- y = cluster_center[:, 1]
361
- x_min, x_max = X.min(), X.max()
362
- X_normalized = 2 * np.pi * (X - x_min) / (x_max - x_min)
363
- params, _ = curve_fit(lambda x, a0, a1, b1, a2, b2, a3, b3, a4, b4: a0 + a1*np.cos(x) + b1*np.sin(x) +
364
- a2*np.cos(2*x) + b2*np.sin(2*x) + a3*np.cos(3*x) + b3*np.sin(3*x) + a4*np.cos(4*x) + b4*np.sin(4*x),
365
- X_normalized, y)
366
- a0, a1, b1, a2, b2, a3, b3, a4, b4 = params
367
- equations_html += f"<h4>Cluster {i+1} (Typhoons: {cluster_counts[i]})</h4><p>Fourier Series: y = {a0:.4f} + {a1:.4f}*cos(x) + {b1:.4f}*sin(x) + " \
368
- f"{a2:.4f}*cos(2x) + {b2:.4f}*sin(2x) + {a3:.4f}*cos(3x) + {b3:.4f}*sin(3x) + {a4:.4f}*cos(4x) + {b4:.4f}*sin(4x)</p>" \
369
- f"<p>X Range: 0 to {2*np.pi:.4f}</p><p>Longitude Range: {x_min:.4f}°E to {x_max:.4f}°E</p><hr>"
370
 
371
- fig_routes.update_layout(title=f'Typhoon Routes Clustering ({start_year}-{end_year}) - {enso_phase}', geo=dict(projection_type='mercator', showland=True,
372
- lataxis={'range': [0, 40]}, lonaxis={'range': [100, 180]}))
373
- return fig_routes, equations_html
 
 
 
 
 
 
 
 
 
 
 
374
 
375
- # Logistic Regression Functions
376
- def logistic_regression(regression_type, start_year, start_month, end_year, end_month):
377
  start_date = datetime(start_year, start_month, 1)
378
  end_date = datetime(end_year, end_month, 28)
379
- filtered_data = merged_data[(merged_data['ISO_TIME'] >= start_date) & (merged_data['ISO_TIME'] <= end_date)]
380
- if regression_type == 'Wind':
381
- filtered_data['severe_typhoon'] = (filtered_data['USA_WIND'] >= 64).astype(int)
382
- X = sm.add_constant(filtered_data['ONI'])
383
- y = filtered_data['severe_typhoon']
384
- model = sm.Logit(y, X).fit()
385
- beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI']
386
- el_nino_severe = filtered_data[filtered_data['ONI'] >= 0.5]['severe_typhoon'].mean()
387
- la_nina_severe = filtered_data[filtered_data['ONI'] <= -0.5]['severe_typhoon'].mean()
388
- neutral_severe = filtered_data[(filtered_data['ONI'] > -0.5) & (filtered_data['ONI'] < 0.5)]['severe_typhoon'].mean()
389
- return f"<h3>Wind Speed Logistic Regression</h3><p>β1: {beta_1:.4f}</p><p>Odds Ratio: {exp_beta_1:.4f}</p><p>P-value: {p_value:.4f}</p>" \
390
- f"<p>El Niño: {el_nino_severe:.2%}</p><p>La Niña: {la_nina_severe:.2%}</p><p>Neutral: {neutral_severe:.2%}</p>"
391
- elif regression_type == 'Pressure':
392
- filtered_data['intense_typhoon'] = (filtered_data['USA_PRES'] <= 950).astype(int)
393
- X = sm.add_constant(filtered_data['ONI'])
394
- y = filtered_data['intense_typhoon']
395
- model = sm.Logit(y, X).fit()
396
- beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI']
397
- el_nino_intense = filtered_data[filtered_data['ONI'] >= 0.5]['intense_typhoon'].mean()
398
- la_nina_intense = filtered_data[filtered_data['ONI'] <= -0.5]['intense_typhoon'].mean()
399
- neutral_intense = filtered_data[(filtered_data['ONI'] > -0.5) & (filtered_data['ONI'] < 0.5)]['intense_typhoon'].mean()
400
- return f"<h3>Pressure Logistic Regression</h3><p>β1: {beta_1:.4f}</p><p>Odds Ratio: {exp_beta_1:.4f}</p><p>P-value: {p_value:.4f}</p>" \
401
- f"<p>El Niño: {el_nino_intense:.2%}</p><p>La Niña: {la_nina_intense:.2%}</p><p>Neutral: {neutral_intense:.2%}</p>"
402
- elif regression_type == 'Longitude':
403
- filtered_data = filtered_data.dropna(subset=['LON'])
404
- filtered_data['western_typhoon'] = (filtered_data['LON'] <= 140).astype(int)
405
- X = sm.add_constant(filtered_data['ONI'])
406
- y = filtered_data['western_typhoon']
407
- model = sm.Logit(y, X).fit()
408
- beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI']
409
- el_nino_western = filtered_data[filtered_data['ONI'] >= 0.5]['western_typhoon'].mean()
410
- la_nina_western = filtered_data[filtered_data['ONI'] <= -0.5]['western_typhoon'].mean()
411
- neutral_western = filtered_data[(filtered_data['ONI'] > -0.5) & (filtered_data['ONI'] < 0.5)]['western_typhoon'].mean()
412
- return f"<h3>Longitude Logistic Regression</h3><p>β1: {beta_1:.4f}</p><p>Odds Ratio: {exp_beta_1:.4f}</p><p>P-value: {p_value:.4f}</p>" \
413
- f"<p>El Niño: {el_nino_western:.2%}</p><p>La Niña: {la_nina_western:.2%}</p><p>Neutral: {neutral_western:.2%}</p>"
414
-
415
- # Typhoon Path Animation Function
416
- def typhoon_path_animation(year, typhoon, standard):
417
- storm = ibtracs.get_storm(typhoon)
418
  fig = go.Figure()
419
- fig.add_trace(go.Scattergeo(lon=storm.lon, lat=storm.lat, mode='lines', line=dict(width=2, color='gray'), name='Path', showlegend=False))
420
- fig.add_trace(go.Scattergeo(lon=[storm.lon[0]], lat=[storm.lat[0]], mode='markers', marker=dict(size=10, color='green', symbol='star'),
421
- name='Starting Point', text=storm.time[0].strftime('%Y-%m-%d %H:%M'), hoverinfo='text+name'))
422
- frames = []
423
- for i in range(len(storm.time)):
424
- category, color = categorize_typhoon_by_standard(storm.vmax[i], standard)
425
- frame_data = [
426
- go.Scattergeo(lon=storm.lon[:i+1], lat=storm.lat[:i+1], mode='lines', line=dict(width=2, color='blue'), name='Path Traveled', showlegend=False),
427
- go.Scattergeo(lon=[storm.lon[i]], lat=[storm.lat[i]], mode='markers+text', marker=dict(size=10, color=color, symbol='star'),
428
- text=category, textposition="top center", name='Current Location', hovertext=f"{storm.time[i].strftime('%Y-%m-%d %H:%M')}<br>Category: {category}<br>Wind Speed: {storm.vmax[i]:.1f} m/s")
429
- ]
430
- frames.append(go.Frame(data=frame_data, name=f"frame{i}"))
431
  fig.frames = frames
432
- fig.update_layout(title=f"{year} {storm.name} Typhoon Path", geo=dict(projection_type='natural earth', showland=True),
433
- updatemenus=[{"buttons": [{"args": [None, {"frame": {"duration": 100, "redraw": True}, "fromcurrent": True, "transition": {"duration": 0}}], "label": "Play", "method": "animate"},
434
- {"args": [[None], {"frame": {"duration": 0, "redraw": True}, "mode": "immediate", "transition": {"duration": 0}}], "label": "Pause", "method": "animate"}],
435
- "direction": "left", "pad": {"r": 10, "t": 87}, "showactive": False, "type": "buttons", "x": 0.1, "xanchor": "right", "y": 0, "yanchor": "top"}],
436
- sliders=[{"steps": [{"args": [[f"frame{k}"], {"frame": {"duration": 100, "redraw": True}, "mode": "immediate", "transition": {"duration": 0}}],
437
- "label": storm.time[k].strftime('%Y-%m-%d %H:%M'), "method": "animate"} for k in range(len(storm.time))]}])
438
  return fig
439
 
440
  def categorize_typhoon_by_standard(wind_speed, standard):
@@ -446,8 +326,7 @@ def categorize_typhoon_by_standard(wind_speed, standard):
446
  return 'Medium Typhoon', taiwan_standard['Medium Typhoon']['color']
447
  elif wind_speed_ms >= 17.2:
448
  return 'Mild Typhoon', taiwan_standard['Mild Typhoon']['color']
449
- else:
450
- return 'Tropical Depression', taiwan_standard['Tropical Depression']['color']
451
  else:
452
  if wind_speed >= 137:
453
  return 'C5 Super Typhoon', atlantic_standard['C5 Super Typhoon']['color']
@@ -461,83 +340,94 @@ def categorize_typhoon_by_standard(wind_speed, standard):
461
  return 'C1 Typhoon', atlantic_standard['C1 Typhoon']['color']
462
  elif wind_speed >= 34:
463
  return 'Tropical Storm', atlantic_standard['Tropical Storm']['color']
464
- else:
465
- return 'Tropical Depression', atlantic_standard['Tropical Depression']['color']
466
-
467
- # Update Typhoon Dropdown
468
- def update_typhoon_dropdown(selected_year):
469
- season = ibtracs.get_season(selected_year)
470
- storm_summary = season.summary()
471
- options = [f"{storm_summary['name'][i]} ({storm_summary['id'][i]})" for i in range(storm_summary['season_storms'])]
472
- values = [storm_summary['id'][i] for i in range(storm_summary['season_storms'])]
473
- return gr.Dropdown.update(choices=options, value=values[0] if values else None)
474
 
475
- # Gradio Interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
477
  gr.Markdown("# Typhoon Analysis Dashboard")
478
-
479
  with gr.Tab("Main Analysis"):
480
  with gr.Row():
481
  start_year = gr.Number(label="Start Year", value=2000, minimum=1900, maximum=2024, step=1)
482
- start_month = gr.Number(label="Start Month", value=1, minimum=1, maximum=12, step=1)
483
  end_year = gr.Number(label="End Year", value=2024, minimum=1900, maximum=2024, step=1)
484
- end_month = gr.Number(label="End Month", value=6, minimum=1, maximum=12, step=1)
485
- enso_dropdown = gr.Dropdown(label="ENSO Phase", choices=["All Years", "El Niño Years", "La Niña Years", "Neutral Years"], value="All Years")
486
- typhoon_search = gr.Textbox(label="Search Typhoon Name")
487
- analyze_button = gr.Button("Analyze")
488
- typhoon_tracks = gr.Plot(label="Typhoon Tracks")
489
- all_years_regression = gr.Plot(label="All Years Regression")
490
- regression_graphs = gr.HTML(label="Regression Graphs by ENSO Phase")
491
- slopes = gr.HTML(label="Slopes")
492
- wind_oni_scatter = gr.Plot(label="Wind Speed vs ONI")
493
- pressure_oni_scatter = gr.Plot(label="Pressure vs ONI")
494
- correlation_text = gr.HTML(label="Correlation Coefficient")
495
- max_wind_speed_text = gr.HTML(label="Max Wind Speed")
496
- min_pressure_text = gr.HTML(label="Min Pressure")
497
- wind_oni_correlation = gr.HTML(label="Wind-ONI Correlation")
498
- pressure_oni_correlation = gr.HTML(label="Pressure-ONI Correlation")
499
- count_analysis = gr.HTML(label="Typhoon Count Analysis")
500
- month_analysis = gr.HTML(label="Concentrated Months Analysis")
501
- analyze_button.click(main_analysis, inputs=[start_year, start_month, end_year, end_month, enso_dropdown, typhoon_search],
502
- outputs=[typhoon_tracks, all_years_regression, regression_graphs, slopes, wind_oni_scatter, pressure_oni_scatter,
503
- correlation_text, max_wind_speed_text, min_pressure_text, wind_oni_correlation, pressure_oni_correlation,
504
- count_analysis, month_analysis])
505
-
506
- with gr.Tab("Cluster Analysis"):
507
- n_clusters = gr.Number(label="Number of Clusters", value=5, minimum=1, maximum=20, step=1)
508
- show_clusters = gr.Checkbox(label="Show Clusters")
509
- show_routes = gr.Checkbox(label="Show Typhoon Routes")
510
- fourier_series = gr.Checkbox(label="Fourier Series")
511
- with gr.Row():
512
- cluster_start_year = gr.Number(label="Start Year", value=2000, minimum=1900, maximum=2024, step=1)
513
- cluster_start_month = gr.Number(label="Start Month", value=1, minimum=1, maximum=12, step=1)
514
- cluster_end_year = gr.Number(label="End Year", value=2024, minimum=1900, maximum=2024, step=1)
515
- cluster_end_month = gr.Number(label="End Month", value=6, minimum=1, maximum=12, step=1)
516
- cluster_enso = gr.Dropdown(label="ENSO Phase", choices=["All Years", "El Niño Years", "La Niña Years", "Neutral Years"], value="All Years")
517
- cluster_button = gr.Button("Generate Cluster Analysis")
518
- cluster_figure = gr.Plot(label="Cluster Routes")
519
- equations_output = gr.HTML(label="Cluster Equations")
520
- cluster_button.click(cluster_analysis, inputs=[n_clusters, show_clusters, show_routes, fourier_series, cluster_start_year, cluster_start_month, cluster_end_year, cluster_end_month, cluster_enso],
521
- outputs=[cluster_figure, equations_output])
522
-
523
- with gr.Tab("Logistic Regression"):
524
- regression_type = gr.Dropdown(label="Regression Type", choices=["Wind", "Pressure", "Longitude"], value="Wind")
525
  with gr.Row():
526
- reg_start_year = gr.Number(label="Start Year", value=2000, minimum=1900, maximum=2024, step=1)
527
- reg_start_month = gr.Number(label="Start Month", value=1, minimum=1, maximum=12, step=1)
528
- reg_end_year = gr.Number(label="End Year", value=2024, minimum=1900, maximum=2024, step=1)
529
- reg_end_month = gr.Number(label="End Month", value=6, minimum=1, maximum=12, step=1)
530
- regression_button = gr.Button("Run Regression")
531
- regression_results = gr.HTML(label="Regression Results")
532
- regression_button.click(logistic_regression, inputs=[regression_type, reg_start_year, reg_start_month, reg_end_year, reg_end_month], outputs=[regression_results])
533
-
 
 
 
534
  with gr.Tab("Typhoon Path Animation"):
535
- year_dropdown = gr.Dropdown(label="Year", choices=[str(year) for year in range(1950, 2025)], value="2024")
536
- typhoon_dropdown = gr.Dropdown(label="Typhoon", choices=[])
537
- standard_dropdown = gr.Dropdown(label="Classification Standard", choices=["Atlantic", "Taiwan"], value="Atlantic")
538
- animation_button = gr.Button("Generate Animation")
539
- animation_figure = gr.Plot(label="Typhoon Path Animation")
540
- year_dropdown.change(update_typhoon_dropdown, inputs=[year_dropdown], outputs=[typhoon_dropdown])
541
- animation_button.click(typhoon_path_animation, inputs=[year_dropdown, typhoon_dropdown, standard_dropdown], outputs=[animation_figure])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
 
543
  demo.launch()
 
1
  import gradio as gr
 
 
2
  import pandas as pd
3
  import numpy as np
4
+ import plotly.graph_objects as go
5
+ import plotly.express as px
 
 
 
 
 
6
  import tropycal.tracks as tracks
 
7
  import pickle
8
  import requests
9
+ import os
10
+ import argparse
11
+ from datetime import datetime
12
+ import statsmodels.api as sm
13
  import shutil
14
+ import tempfile
15
  import csv
16
  from collections import defaultdict
17
+ import filecmp
18
 
19
  # Command-line argument parsing
20
  parser = argparse.ArgumentParser(description='Typhoon Analysis Dashboard')
 
22
  args = parser.parse_args()
23
  DATA_PATH = args.data_path
24
 
 
25
  ONI_DATA_PATH = os.path.join(DATA_PATH, 'oni_data.csv')
26
  TYPHOON_DATA_PATH = os.path.join(DATA_PATH, 'processed_typhoon_data.csv')
27
  LOCAL_iBtrace_PATH = os.path.join(DATA_PATH, 'ibtracs.WP.list.v04r01.csv')
 
29
  CACHE_FILE = 'ibtracs_cache.pkl'
30
  CACHE_EXPIRY_DAYS = 1
31
 
32
+ # Color map for typhoon categories
33
  color_map = {
34
  'C5 Super Typhoon': 'rgb(255, 0, 0)',
35
  'C4 Very Strong Typhoon': 'rgb(255, 63, 0)',
 
58
  'Tropical Depression': {'wind_speed': 0, 'color': 'rgb(173, 216, 230)'}
59
  }
60
 
61
+ # Data loading and preprocessing functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  def download_oni_file(url, filename):
63
+ response = requests.get(url)
64
+ response.raise_for_status()
65
+ with open(filename, 'wb') as f:
66
+ f.write(response.content)
67
+ return True
 
 
 
68
 
69
  def convert_oni_ascii_to_csv(input_file, output_file):
70
  data = defaultdict(lambda: [''] * 12)
71
+ season_to_month = {'DJF': 12, 'JFM': 1, 'FMA': 2, 'MAM': 3, 'AMJ': 4, 'MJJ': 5,
72
+ 'JJA': 6, 'JAS': 7, 'ASO': 8, 'SON': 9, 'OND': 10, 'NDJ': 11}
73
  with open(input_file, 'r') as f:
74
  lines = f.readlines()[1:]
75
  for line in lines:
 
93
  input_file = os.path.join(DATA_PATH, "oni.ascii.txt")
94
  output_file = ONI_DATA_PATH
95
  if download_oni_file(url, temp_file):
96
+ if not os.path.exists(input_file) or not filecmp.cmp(temp_file, input_file):
97
  os.replace(temp_file, input_file)
98
  convert_oni_ascii_to_csv(input_file, output_file)
99
  else:
 
110
  response.raise_for_status()
111
  with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv') as temp_file:
112
  temp_file.write(response.text)
113
+ shutil.move(temp_file.name, LOCAL_iBtrace_PATH)
 
114
  ibtracs = tracks.TrackDataset(basin='west_pacific', source='ibtracs', ibtracs_url=LOCAL_iBtrace_PATH)
115
  with open(CACHE_FILE, 'wb') as f:
116
  pickle.dump(ibtracs, f)
117
  return ibtracs
118
 
119
+ def convert_typhoondata(input_file, output_file):
120
+ with open(input_file, 'r') as infile:
121
+ next(infile); next(infile) # Skip header lines
122
+ reader = csv.reader(infile)
123
+ sid_data = defaultdict(list)
124
+ for row in reader:
125
+ if row:
126
+ sid = row[0]
127
+ sid_data[sid].append((row, row[6]))
128
+ with open(output_file, 'w', newline='') as outfile:
129
+ fieldnames = ['SID', 'ISO_TIME', 'LAT', 'LON', 'SEASON', 'NAME', 'WMO_WIND', 'WMO_PRES', 'USA_WIND', 'USA_PRES', 'START_DATE', 'END_DATE']
130
+ writer = csv.DictWriter(outfile, fieldnames=fieldnames)
131
+ writer.writeheader()
132
+ for sid, data in sid_data.items():
133
+ start_date = min(data, key=lambda x: x[1])[1]
134
+ end_date = max(data, key=lambda x: x[1])[1]
135
+ for row, iso_time in data:
136
+ writer.writerow({
137
+ 'SID': row[0], 'ISO_TIME': iso_time, 'LAT': row[8], 'LON': row[9], 'SEASON': row[1], 'NAME': row[5],
138
+ 'WMO_WIND': row[10].strip() or ' ', 'WMO_PRES': row[11].strip() or ' ',
139
+ 'USA_WIND': row[23].strip() or ' ', 'USA_PRES': row[24].strip() or ' ',
140
+ 'START_DATE': start_date, 'END_DATE': end_date
141
+ })
142
+
143
+ def load_data(oni_path, typhoon_path):
144
+ oni_data = pd.read_csv(oni_path)
145
+ typhoon_data = pd.read_csv(typhoon_path, low_memory=False)
146
+ typhoon_data['ISO_TIME'] = pd.to_datetime(typhoon_data['ISO_TIME'], errors='coerce')
147
+ typhoon_data = typhoon_data.dropna(subset=['ISO_TIME'])
148
+ return oni_data, typhoon_data
149
+
150
  def process_oni_data(oni_data):
151
  oni_long = oni_data.melt(id_vars=['Year'], var_name='Month', value_name='ONI')
152
+ month_map = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
153
+ 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'}
154
  oni_long['Month'] = oni_long['Month'].map(month_map)
155
  oni_long['Date'] = pd.to_datetime(oni_long['Year'].astype(str) + '-' + oni_long['Month'] + '-01')
156
  oni_long['ONI'] = pd.to_numeric(oni_long['ONI'], errors='coerce')
 
162
  typhoon_data['USA_PRES'] = pd.to_numeric(typhoon_data['USA_PRES'], errors='coerce')
163
  typhoon_data['LON'] = pd.to_numeric(typhoon_data['LON'], errors='coerce')
164
  typhoon_max = typhoon_data.groupby('SID').agg({
165
+ 'USA_WIND': 'max', 'USA_PRES': 'min', 'ISO_TIME': 'first', 'SEASON': 'first', 'NAME': 'first',
166
+ 'LAT': 'first', 'LON': 'first'
167
  }).reset_index()
168
  typhoon_max['Month'] = typhoon_max['ISO_TIME'].dt.strftime('%m')
169
  typhoon_max['Year'] = typhoon_max['ISO_TIME'].dt.year
 
174
  return pd.merge(typhoon_max, oni_long, on=['Year', 'Month'])
175
 
176
  def categorize_typhoon(wind_speed):
177
+ wind_speed_kt = wind_speed # Assuming input is already in knots
178
+ if wind_speed_kt >= 137:
179
  return 'C5 Super Typhoon'
180
+ elif wind_speed_kt >= 113:
181
  return 'C4 Very Strong Typhoon'
182
+ elif wind_speed_kt >= 96:
183
  return 'C3 Strong Typhoon'
184
+ elif wind_speed_kt >= 83:
185
  return 'C2 Typhoon'
186
+ elif wind_speed_kt >= 64:
187
  return 'C1 Typhoon'
188
+ elif wind_speed_kt >= 34:
189
  return 'Tropical Storm'
190
  else:
191
  return 'Tropical Depression'
 
200
  else:
201
  return 'Neutral'
202
 
 
 
 
 
 
 
 
203
  # Load data globally
204
  update_oni_data()
205
  ibtracs = load_ibtracs_data()
206
  convert_typhoondata(LOCAL_iBtrace_PATH, TYPHOON_DATA_PATH)
207
+ oni_data, typhoon_data = load_data(ONI_DATA_PATH, TYPHOON_DATA_PATH)
 
208
  oni_long = process_oni_data(oni_data)
209
  typhoon_max = process_typhoon_data(typhoon_data)
210
  merged_data = merge_data(oni_long, typhoon_max)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
+ # Main analysis functions
213
+ def generate_typhoon_tracks(filtered_data, typhoon_search):
214
+ fig = go.Figure()
215
+ for sid in filtered_data['SID'].unique():
216
+ storm_data = filtered_data[filtered_data['SID'] == sid]
217
+ color = {'El Nino': 'red', 'La Nina': 'blue', 'Neutral': 'green'}[storm_data['ENSO_Phase'].iloc[0]]
218
+ fig.add_trace(go.Scattergeo(
219
+ lon=storm_data['LON'], lat=storm_data['LAT'], mode='lines',
220
+ name=storm_data['NAME'].iloc[0], line=dict(width=2, color=color)
221
+ ))
 
 
 
 
 
 
 
 
 
 
 
 
222
  if typhoon_search:
223
+ mask = filtered_data['NAME'].str.contains(typhoon_search, case=False, na=False)
224
+ if mask.any():
225
+ storm_data = filtered_data[mask]
226
+ fig.add_trace(go.Scattergeo(
227
+ lon=storm_data['LON'], lat=storm_data['LAT'], mode='lines',
228
+ name=f'Matched: {typhoon_search}', line=dict(width=5, color='yellow')
229
+ ))
230
+ fig.update_layout(title='Typhoon Tracks', geo=dict(projection_type='natural earth', showland=True))
231
+ return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
+ def generate_wind_oni_scatter(filtered_data, typhoon_search):
234
+ fig = px.scatter(filtered_data, x='ONI', y='USA_WIND', color='Category', hover_data=['NAME', 'Year', 'Category'],
235
+ title='Wind Speed vs ONI', labels={'ONI': 'ONI Value', 'USA_WIND': 'Max Wind Speed (knots)'},
236
+ color_discrete_map=color_map)
237
+ if typhoon_search:
238
+ mask = filtered_data['NAME'].str.contains(typhoon_search, case=False, na=False)
239
+ if mask.any():
240
+ fig.add_trace(go.Scatter(
241
+ x=filtered_data.loc[mask, 'ONI'], y=filtered_data.loc[mask, 'USA_WIND'],
242
+ mode='markers', marker=dict(size=10, color='red', symbol='star'),
243
+ name=f'Matched: {typhoon_search}',
244
+ text=filtered_data.loc[mask, 'NAME'] + ' (' + filtered_data.loc[mask, 'Year'].astype(str) + ')'
245
+ ))
246
+ return fig
247
 
248
+ def generate_pressure_oni_scatter(filtered_data, typhoon_search):
249
+ fig = px.scatter(filtered_data, x='ONI', y='USA_PRES', color='Category', hover_data=['NAME', 'Year', 'Category'],
250
+ title='Pressure vs ONI', labels={'ONI': 'ONI Value', 'USA_PRES': 'Min Pressure (hPa)'},
251
+ color_discrete_map=color_map)
252
+ if typhoon_search:
253
+ mask = filtered_data['NAME'].str.contains(typhoon_search, case=False, na=False)
254
+ if mask.any():
255
+ fig.add_trace(go.Scatter(
256
+ x=filtered_data.loc[mask, 'ONI'], y=filtered_data.loc[mask, 'USA_PRES'],
257
+ mode='markers', marker=dict(size=10, color='red', symbol='star'),
258
+ name=f'Matched: {typhoon_search}',
259
+ text=filtered_data.loc[mask, 'NAME'] + ' (' + filtered_data.loc[mask, 'Year'].astype(str) + ')'
260
+ ))
261
+ return fig
 
 
 
 
 
 
 
 
 
262
 
263
+ def generate_regression_analysis(filtered_data):
264
+ fig = px.scatter(filtered_data, x='LON', y='ONI', hover_data=['NAME'],
265
+ title='Typhoon Generation Longitude vs ONI (All Years)')
266
+ if len(filtered_data) > 1:
267
+ X = np.array(filtered_data['LON']).reshape(-1, 1)
268
+ y = filtered_data['ONI']
269
+ model = sm.OLS(y, sm.add_constant(X)).fit()
270
+ y_pred = model.predict(sm.add_constant(X))
271
+ fig.add_trace(go.Scatter(x=filtered_data['LON'], y=y_pred, mode='lines', name='Regression Line'))
272
+ slope = model.params[1]
273
+ slopes_text = f"All Years Slope: {slope:.4f}"
274
+ else:
275
+ slopes_text = "Insufficient data for regression"
276
+ return fig, slopes_text
277
 
278
+ def generate_main_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search):
 
279
  start_date = datetime(start_year, start_month, 1)
280
  end_date = datetime(end_year, end_month, 28)
281
+ filtered_data = merged_data[
282
+ (merged_data['ISO_TIME'] >= start_date) &
283
+ (merged_data['ISO_TIME'] <= end_date)
284
+ ]
285
+ filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
286
+ if enso_phase != 'all':
287
+ filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
288
+
289
+ tracks_fig = generate_typhoon_tracks(filtered_data, typhoon_search)
290
+ wind_scatter = generate_wind_oni_scatter(filtered_data, typhoon_search)
291
+ pressure_scatter = generate_pressure_oni_scatter(filtered_data, typhoon_search)
292
+ regression_fig, slopes_text = generate_regression_analysis(filtered_data)
293
+
294
+ return tracks_fig, wind_scatter, pressure_scatter, regression_fig, slopes_text
295
+
296
+ # Path animation function
297
+ def generate_path_animation(year, typhoon, standard):
298
+ typhoon_id = typhoon.split('(')[-1].strip(')')
299
+ storm = ibtracs.get_storm(typhoon_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  fig = go.Figure()
301
+ fig.add_trace(go.Scattergeo(lon=storm.lon, lat=storm.lat, mode='lines', line=dict(width=2, color='gray')))
302
+ fig.add_trace(go.Scattergeo(lon=[storm.lon[0]], lat=[storm.lat[0]], mode='markers',
303
+ marker=dict(size=10, color='green', symbol='star'), name='Start'))
304
+ frames = [
305
+ go.Frame(data=[
306
+ go.Scattergeo(lon=storm.lon[:i+1], lat=storm.lat[:i+1], mode='lines', line=dict(width=2, color='blue')),
307
+ go.Scattergeo(lon=[storm.lon[i]], lat=[storm.lat[i]], mode='markers',
308
+ marker=dict(size=10, color=categorize_typhoon_by_standard(storm.vmax[i], standard)[1]))
309
+ ], name=f"frame{i}") for i in range(len(storm.time))
310
+ ]
 
 
311
  fig.frames = frames
312
+ fig.update_layout(
313
+ title=f"{year} {storm.name} Typhoon Path",
314
+ geo=dict(projection_type='natural earth', showland=True),
315
+ updatemenus=[{"buttons": [{"label": "Play", "method": "animate", "args": [None, {"frame": {"duration": 100}}]},
316
+ {"label": "Pause", "method": "animate", "args": [[None], {"mode": "immediate"}]}]}]
317
+ )
318
  return fig
319
 
320
  def categorize_typhoon_by_standard(wind_speed, standard):
 
326
  return 'Medium Typhoon', taiwan_standard['Medium Typhoon']['color']
327
  elif wind_speed_ms >= 17.2:
328
  return 'Mild Typhoon', taiwan_standard['Mild Typhoon']['color']
329
+ return 'Tropical Depression', taiwan_standard['Tropical Depression']['color']
 
330
  else:
331
  if wind_speed >= 137:
332
  return 'C5 Super Typhoon', atlantic_standard['C5 Super Typhoon']['color']
 
340
  return 'C1 Typhoon', atlantic_standard['C1 Typhoon']['color']
341
  elif wind_speed >= 34:
342
  return 'Tropical Storm', atlantic_standard['Tropical Storm']['color']
343
+ return 'Tropical Depression', atlantic_standard['Tropical Depression']['color']
 
 
 
 
 
 
 
 
 
344
 
345
+ # Logistic regression functions
346
+ def perform_wind_regression(start_year, start_month, end_year, end_month):
347
+ start_date = datetime(start_year, start_month, 1)
348
+ end_date = datetime(end_year, end_month, 28)
349
+ data = merged_data[(merged_data['ISO_TIME'] >= start_date) & (merged_data['ISO_TIME'] <= end_date)].dropna(subset=['USA_WIND', 'ONI'])
350
+ data['severe_typhoon'] = (data['USA_WIND'] >= 64).astype(int)
351
+ X = sm.add_constant(data['ONI'])
352
+ y = data['severe_typhoon']
353
+ model = sm.Logit(y, X).fit()
354
+ beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI']
355
+ return f"Wind Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}"
356
+
357
+ def perform_pressure_regression(start_year, start_month, end_year, end_month):
358
+ start_date = datetime(start_year, start_month, 1)
359
+ end_date = datetime(end_year, end_month, 28)
360
+ data = merged_data[(merged_data['ISO_TIME'] >= start_date) & (merged_data['ISO_TIME'] <= end_date)].dropna(subset=['USA_PRES', 'ONI'])
361
+ data['intense_typhoon'] = (data['USA_PRES'] <= 950).astype(int)
362
+ X = sm.add_constant(data['ONI'])
363
+ y = data['intense_typhoon']
364
+ model = sm.Logit(y, X).fit()
365
+ beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI']
366
+ return f"Pressure Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}"
367
+
368
+ def perform_longitude_regression(start_year, start_month, end_year, end_month):
369
+ start_date = datetime(start_year, start_month, 1)
370
+ end_date = datetime(end_year, end_month, 28)
371
+ data = merged_data[(merged_data['ISO_TIME'] >= start_date) & (merged_data['ISO_TIME'] <= end_date)].dropna(subset=['LON', 'ONI'])
372
+ data['western_typhoon'] = (data['LON'] <= 140).astype(int)
373
+ X = sm.add_constant(data['ONI'])
374
+ y = data['western_typhoon']
375
+ model = sm.Logit(y, X).fit()
376
+ beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI']
377
+ return f"Longitude Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}"
378
+
379
+ # Gradio interface
380
  with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
381
  gr.Markdown("# Typhoon Analysis Dashboard")
382
+
383
  with gr.Tab("Main Analysis"):
384
  with gr.Row():
385
  start_year = gr.Number(label="Start Year", value=2000, minimum=1900, maximum=2024, step=1)
386
+ start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1)
387
  end_year = gr.Number(label="End Year", value=2024, minimum=1900, maximum=2024, step=1)
388
+ end_month = gr.Dropdown(label="End Month", choices=list(range(1, 13)), value=6)
389
+ enso_phase = gr.Dropdown(label="ENSO Phase", choices=['all', 'El Nino', 'La Nina', 'Neutral'], value='all')
390
+ typhoon_search = gr.Textbox(label="Typhoon Search")
391
+ analyze_btn = gr.Button("Analyze")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
  with gr.Row():
393
+ tracks_plot = gr.Plot(label="Typhoon Tracks")
394
+ wind_scatter = gr.Plot(label="Wind Speed vs ONI")
395
+ pressure_scatter = gr.Plot(label="Pressure vs ONI")
396
+ regression_plot = gr.Plot(label="Regression Analysis")
397
+ slopes_text = gr.Textbox(label="Regression Slopes")
398
+ analyze_btn.click(
399
+ fn=generate_main_analysis,
400
+ inputs=[start_year, start_month, end_year, end_month, enso_phase, typhoon_search],
401
+ outputs=[tracks_plot, wind_scatter, pressure_scatter, regression_plot, slopes_text]
402
+ )
403
+
404
  with gr.Tab("Typhoon Path Animation"):
405
+ year_dropdown = gr.Dropdown(label="Year", choices=[str(y) for y in range(1950, 2025)], value="2024")
406
+ typhoon_dropdown = gr.Dropdown(label="Typhoon")
407
+ standard_dropdown = gr.Dropdown(label="Classification Standard", choices=['atlantic', 'taiwan'], value='atlantic')
408
+ path_plot = gr.Plot(label="Typhoon Path Animation")
409
+
410
+ def update_typhoon_options(year):
411
+ season = ibtracs.get_season(int(year))
412
+ storm_summary = season.summary()
413
+ options = [f"{storm_summary['name'][i]} ({storm_summary['id'][i]})" for i in range(storm_summary['season_storms'])]
414
+ return gr.update(choices=options, value=options[0] if options else None)
415
+
416
+ year_dropdown.change(fn=update_typhoon_options, inputs=year_dropdown, outputs=typhoon_dropdown)
417
+ gr.Button("Generate Animation").click(
418
+ fn=generate_path_animation,
419
+ inputs=[year_dropdown, typhoon_dropdown, standard_dropdown],
420
+ outputs=path_plot
421
+ )
422
+
423
+ with gr.Tab("Logistic Regressions"):
424
+ with gr.Row():
425
+ wind_btn = gr.Button("Wind Speed Regression")
426
+ pressure_btn = gr.Button("Pressure Regression")
427
+ longitude_btn = gr.Button("Longitude Regression")
428
+ regression_results = gr.Textbox(label="Regression Results", lines=10)
429
+ wind_btn.click(fn=perform_wind_regression, inputs=[start_year, start_month, end_year, end_month], outputs=regression_results)
430
+ pressure_btn.click(fn=perform_pressure_regression, inputs=[start_year, start_month, end_year, end_month], outputs=regression_results)
431
+ longitude_btn.click(fn=perform_longitude_regression, inputs=[start_year, start_month, end_year, end_month], outputs=regression_results)
432
 
433
  demo.launch()