Spaces:
Sleeping
Sleeping
import gradio as gr | |
import pandas as pd | |
import numpy as np | |
import matplotlib.pyplot as plt | |
import matplotlib.animation as animation | |
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas | |
import cartopy.crs as ccrs | |
import cartopy.feature as cfeature | |
import plotly.graph_objects as go | |
import plotly.express as px | |
from plotly.subplots import make_subplots | |
import tropycal.tracks as tracks | |
import pickle | |
import requests | |
import os | |
import argparse | |
from datetime import datetime | |
import statsmodels.api as sm | |
import shutil | |
import tempfile | |
import csv | |
from collections import defaultdict | |
import filecmp | |
from sklearn.manifold import TSNE | |
from sklearn.cluster import DBSCAN | |
from scipy.interpolate import interp1d | |
# Command-line argument parsing | |
parser = argparse.ArgumentParser(description='Typhoon Analysis Dashboard') | |
parser.add_argument('--data_path', type=str, default=os.getcwd(), help='Path to the data directory') | |
args = parser.parse_args() | |
DATA_PATH = args.data_path | |
ONI_DATA_PATH = os.path.join(DATA_PATH, 'oni_data.csv') | |
TYPHOON_DATA_PATH = os.path.join(DATA_PATH, 'processed_typhoon_data.csv') | |
LOCAL_iBtrace_PATH = os.path.join(DATA_PATH, 'ibtracs.WP.list.v04r01.csv') | |
iBtrace_uri = 'https://www.ncei.noaa.gov/data/international-best-track-archive-for-climate-stewardship-ibtracs/v04r01/access/csv/ibtracs.ALL.list.v04r01.csv' | |
CACHE_FILE = 'ibtracs_cache.pkl' | |
CACHE_EXPIRY_DAYS = 1 | |
# Color maps for Plotly (RGB) | |
color_map = { | |
'C5 Super Typhoon': 'rgb(255, 0, 0)', | |
'C4 Very Strong Typhoon': 'rgb(255, 165, 0)', | |
'C3 Strong Typhoon': 'rgb(255, 255, 0)', | |
'C2 Typhoon': 'rgb(0, 255, 0)', | |
'C1 Typhoon': 'rgb(0, 255, 255)', | |
'Tropical Storm': 'rgb(0, 0, 255)', | |
'Tropical Depression': 'rgb(128, 128, 128)' | |
} | |
# Classification standards with distinct colors for Matplotlib | |
atlantic_standard = { | |
'C5 Super Typhoon': {'wind_speed': 137, 'color': 'Red', 'hex': '#FF0000'}, | |
'C4 Very Strong Typhoon': {'wind_speed': 113, 'color': 'Orange', 'hex': '#FFA500'}, | |
'C3 Strong Typhoon': {'wind_speed': 96, 'color': 'Yellow', 'hex': '#FFFF00'}, | |
'C2 Typhoon': {'wind_speed': 83, 'color': 'Green', 'hex': '#00FF00'}, | |
'C1 Typhoon': {'wind_speed': 64, 'color': 'Cyan', 'hex': '#00FFFF'}, | |
'Tropical Storm': {'wind_speed': 34, 'color': 'Blue', 'hex': '#0000FF'}, | |
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'} | |
} | |
taiwan_standard = { | |
'Strong Typhoon': {'wind_speed': 51.0, 'color': 'Red', 'hex': '#FF0000'}, | |
'Medium Typhoon': {'wind_speed': 33.7, 'color': 'Orange', 'hex': '#FFA500'}, | |
'Mild Typhoon': {'wind_speed': 17.2, 'color': 'Yellow', 'hex': '#FFFF00'}, | |
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'} | |
} | |
# Season months mapping | |
season_months = { | |
'all': list(range(1, 13)), | |
'summer': [6, 7, 8], | |
'winter': [12, 1, 2] | |
} | |
# Regions for duration calculations | |
regions = { | |
"Taiwan Land": {"lat_min": 21.8, "lat_max": 25.3, "lon_min": 119.5, "lon_max": 122.1}, | |
"Taiwan Sea": {"lat_min": 19, "lat_max": 28, "lon_min": 117, "lon_max": 125}, | |
"Japan": {"lat_min": 20, "lat_max": 45, "lon_min": 120, "lon_max": 150}, | |
"China": {"lat_min": 18, "lat_max": 53, "lon_min": 73, "lon_max": 135}, | |
"Hong Kong": {"lat_min": 21.5, "lat_max": 23, "lon_min": 113, "lon_max": 115}, | |
"Philippines": {"lat_min": 5, "lat_max": 21, "lon_min": 115, "lon_max": 130} | |
} | |
# Data loading and preprocessing functions | |
def download_oni_file(url, filename): | |
response = requests.get(url) | |
response.raise_for_status() | |
with open(filename, 'wb') as f: | |
f.write(response.content) | |
return True | |
def convert_oni_ascii_to_csv(input_file, output_file): | |
data = defaultdict(lambda: [''] * 12) | |
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} | |
with open(input_file, 'r') as f: | |
lines = f.readlines()[1:] | |
for line in lines: | |
parts = line.split() | |
if len(parts) >= 4: | |
season, year, anom = parts[0], parts[1], parts[-1] | |
if season in season_to_month: | |
month = season_to_month[season] | |
if season == 'DJF': | |
year = str(int(year) - 1) | |
data[year][month-1] = anom | |
with open(output_file, 'w', newline='') as f: | |
writer = csv.writer(f) | |
writer.writerow(['Year', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']) | |
for year in sorted(data.keys()): | |
writer.writerow([year] + data[year]) | |
def update_oni_data(): | |
url = "https://www.cpc.ncep.noaa.gov/data/indices/oni.ascii.txt" | |
temp_file = os.path.join(DATA_PATH, "temp_oni.ascii.txt") | |
input_file = os.path.join(DATA_PATH, "oni.ascii.txt") | |
output_file = ONI_DATA_PATH | |
if download_oni_file(url, temp_file): | |
if not os.path.exists(input_file) or not filecmp.cmp(temp_file, input_file): | |
os.replace(temp_file, input_file) | |
convert_oni_ascii_to_csv(input_file, output_file) | |
else: | |
os.remove(temp_file) | |
def load_ibtracs_data(): | |
if os.path.exists(CACHE_FILE) and (datetime.now() - datetime.fromtimestamp(os.path.getmtime(CACHE_FILE))).days < CACHE_EXPIRY_DAYS: | |
with open(CACHE_FILE, 'rb') as f: | |
return pickle.load(f) | |
if os.path.exists(LOCAL_iBtrace_PATH): | |
ibtracs = tracks.TrackDataset(basin='west_pacific', source='ibtracs', ibtracs_url=LOCAL_iBtrace_PATH) | |
else: | |
response = requests.get(iBtrace_uri) | |
response.raise_for_status() | |
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv') as temp_file: | |
temp_file.write(response.text) | |
shutil.move(temp_file.name, LOCAL_iBtrace_PATH) | |
ibtracs = tracks.TrackDataset(basin='west_pacific', source='ibtracs', ibtracs_url=LOCAL_iBtrace_PATH) | |
with open(CACHE_FILE, 'wb') as f: | |
pickle.dump(ibtracs, f) | |
return ibtracs | |
def convert_typhoondata(input_file, output_file): | |
with open(input_file, 'r') as infile: | |
next(infile); next(infile) | |
reader = csv.reader(infile) | |
sid_data = defaultdict(list) | |
for row in reader: | |
if row: | |
sid = row[0] | |
sid_data[sid].append((row, row[6])) | |
with open(output_file, 'w', newline='') as outfile: | |
fieldnames = ['SID', 'ISO_TIME', 'LAT', 'LON', 'SEASON', 'NAME', 'WMO_WIND', 'WMO_PRES', 'USA_WIND', 'USA_PRES', 'START_DATE', 'END_DATE'] | |
writer = csv.DictWriter(outfile, fieldnames=fieldnames) | |
writer.writeheader() | |
for sid, data in sid_data.items(): | |
start_date = min(data, key=lambda x: x[1])[1] | |
end_date = max(data, key=lambda x: x[1])[1] | |
for row, iso_time in data: | |
writer.writerow({ | |
'SID': row[0], 'ISO_TIME': iso_time, 'LAT': row[8], 'LON': row[9], 'SEASON': row[1], 'NAME': row[5], | |
'WMO_WIND': row[10].strip() or ' ', 'WMO_PRES': row[11].strip() or ' ', | |
'USA_WIND': row[23].strip() or ' ', 'USA_PRES': row[24].strip() or ' ', | |
'START_DATE': start_date, 'END_DATE': end_date | |
}) | |
def load_data(oni_path, typhoon_path): | |
oni_data = pd.read_csv(oni_path) | |
typhoon_data = pd.read_csv(typhoon_path, low_memory=False) | |
typhoon_data['ISO_TIME'] = pd.to_datetime(typhoon_data['ISO_TIME'], errors='coerce') | |
typhoon_data = typhoon_data.dropna(subset=['ISO_TIME']) | |
return oni_data, typhoon_data | |
def process_oni_data(oni_data): | |
oni_long = oni_data.melt(id_vars=['Year'], var_name='Month', value_name='ONI') | |
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'} | |
oni_long['Month'] = oni_long['Month'].map(month_map) | |
oni_long['Date'] = pd.to_datetime(oni_long['Year'].astype(str) + '-' + oni_long['Month'] + '-01') | |
oni_long['ONI'] = pd.to_numeric(oni_long['ONI'], errors='coerce') | |
return oni_long | |
def process_typhoon_data(typhoon_data): | |
typhoon_data['ISO_TIME'] = pd.to_datetime(typhoon_data['ISO_TIME'], errors='coerce') | |
typhoon_data['USA_WIND'] = pd.to_numeric(typhoon_data['USA_WIND'], errors='coerce') | |
typhoon_data['USA_PRES'] = pd.to_numeric(typhoon_data['USA_PRES'], errors='coerce') | |
typhoon_data['LON'] = pd.to_numeric(typhoon_data['LON'], errors='coerce') | |
typhoon_max = typhoon_data.groupby('SID').agg({ | |
'USA_WIND': 'max', 'USA_PRES': 'min', 'ISO_TIME': 'first', 'SEASON': 'first', 'NAME': 'first', | |
'LAT': 'first', 'LON': 'first' | |
}).reset_index() | |
typhoon_max['Month'] = typhoon_max['ISO_TIME'].dt.strftime('%m') | |
typhoon_max['Year'] = typhoon_max['ISO_TIME'].dt.year | |
typhoon_max['Category'] = typhoon_max['USA_WIND'].apply(categorize_typhoon) | |
return typhoon_max | |
def merge_data(oni_long, typhoon_max): | |
return pd.merge(typhoon_max, oni_long, on=['Year', 'Month']) | |
def categorize_typhoon(wind_speed): | |
wind_speed_kt = wind_speed | |
if wind_speed_kt >= 137: | |
return 'C5 Super Typhoon' | |
elif wind_speed_kt >= 113: | |
return 'C4 Very Strong Typhoon' | |
elif wind_speed_kt >= 96: | |
return 'C3 Strong Typhoon' | |
elif wind_speed_kt >= 83: | |
return 'C2 Typhoon' | |
elif wind_speed_kt >= 64: | |
return 'C1 Typhoon' | |
elif wind_speed_kt >= 34: | |
return 'Tropical Storm' | |
else: | |
return 'Tropical Depression' | |
def classify_enso_phases(oni_value): | |
if isinstance(oni_value, pd.Series): | |
oni_value = oni_value.iloc[0] | |
if oni_value >= 0.5: | |
return 'El Nino' | |
elif oni_value <= -0.5: | |
return 'La Nina' | |
else: | |
return 'Neutral' | |
# Load data globally | |
update_oni_data() | |
ibtracs = load_ibtracs_data() | |
convert_typhoondata(LOCAL_iBtrace_PATH, TYPHOON_DATA_PATH) | |
oni_data, typhoon_data = load_data(ONI_DATA_PATH, TYPHOON_DATA_PATH) | |
oni_long = process_oni_data(oni_data) | |
typhoon_max = process_typhoon_data(typhoon_data) | |
merged_data = merge_data(oni_long, typhoon_max) | |
# Main analysis functions (using Plotly) | |
def generate_typhoon_tracks(filtered_data, typhoon_search): | |
fig = go.Figure() | |
for sid in filtered_data['SID'].unique(): | |
storm_data = filtered_data[filtered_data['SID'] == sid] | |
color = {'El Nino': 'red', 'La Nina': 'blue', 'Neutral': 'green'}[storm_data['ENSO_Phase'].iloc[0]] | |
fig.add_trace(go.Scattergeo( | |
lon=storm_data['LON'], lat=storm_data['LAT'], mode='lines', | |
name=storm_data['NAME'].iloc[0], line=dict(width=2, color=color) | |
)) | |
if typhoon_search: | |
mask = filtered_data['NAME'].str.contains(typhoon_search, case=False, na=False) | |
if mask.any(): | |
storm_data = filtered_data[mask] | |
fig.add_trace(go.Scattergeo( | |
lon=storm_data['LON'], lat=storm_data['LAT'], mode='lines', | |
name=f'Matched: {typhoon_search}', line=dict(width=5, color='yellow') | |
)) | |
fig.update_layout( | |
title='Typhoon Tracks', | |
geo=dict(projection_type='natural earth', showland=True), | |
height=700 | |
) | |
return fig | |
def generate_wind_oni_scatter(filtered_data, typhoon_search): | |
fig = px.scatter(filtered_data, x='ONI', y='USA_WIND', color='Category', hover_data=['NAME', 'Year', 'Category'], | |
title='Wind Speed vs ONI', labels={'ONI': 'ONI Value', 'USA_WIND': 'Max Wind Speed (knots)'}, | |
color_discrete_map=color_map) | |
if typhoon_search: | |
mask = filtered_data['NAME'].str.contains(typhoon_search, case=False, na=False) | |
if mask.any(): | |
fig.add_trace(go.Scatter( | |
x=filtered_data.loc[mask, 'ONI'], y=filtered_data.loc[mask, 'USA_WIND'], | |
mode='markers', marker=dict(size=10, color='red', symbol='star'), | |
name=f'Matched: {typhoon_search}', | |
text=filtered_data.loc[mask, 'NAME'] + ' (' + filtered_data.loc[mask, 'Year'].astype(str) + ')' | |
)) | |
return fig | |
def generate_pressure_oni_scatter(filtered_data, typhoon_search): | |
fig = px.scatter(filtered_data, x='ONI', y='USA_PRES', color='Category', hover_data=['NAME', 'Year', 'Category'], | |
title='Pressure vs ONI', labels={'ONI': 'ONI Value', 'USA_PRES': 'Min Pressure (hPa)'}, | |
color_discrete_map=color_map) | |
if typhoon_search: | |
mask = filtered_data['NAME'].str.contains(typhoon_search, case=False, na=False) | |
if mask.any(): | |
fig.add_trace(go.Scatter( | |
x=filtered_data.loc[mask, 'ONI'], y=filtered_data.loc[mask, 'USA_PRES'], | |
mode='markers', marker=dict(size=10, color='red', symbol='star'), | |
name=f'Matched: {typhoon_search}', | |
text=filtered_data.loc[mask, 'NAME'] + ' (' + filtered_data.loc[mask, 'Year'].astype(str) + ')' | |
)) | |
return fig | |
def generate_regression_analysis(filtered_data): | |
fig = px.scatter(filtered_data, x='LON', y='ONI', hover_data=['NAME'], | |
title='Typhoon Generation Longitude vs ONI (All Years)') | |
if len(filtered_data) > 1: | |
X = np.array(filtered_data['LON']).reshape(-1, 1) | |
y = filtered_data['ONI'] | |
model = sm.OLS(y, sm.add_constant(X)).fit() | |
y_pred = model.predict(sm.add_constant(X)) | |
fig.add_trace(go.Scatter(x=filtered_data['LON'], y=y_pred, mode='lines', name='Regression Line')) | |
slope = model.params[1] | |
slopes_text = f"All Years Slope: {slope:.4f}" | |
else: | |
slopes_text = "Insufficient data for regression" | |
return fig, slopes_text | |
def generate_main_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search): | |
start_date = datetime(start_year, start_month, 1) | |
end_date = datetime(end_year, end_month, 28) | |
filtered_data = merged_data[ | |
(merged_data['ISO_TIME'] >= start_date) & | |
(merged_data['ISO_TIME'] <= end_date) | |
] | |
filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases) | |
if enso_phase != 'all': | |
filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()] | |
tracks_fig = generate_typhoon_tracks(filtered_data, typhoon_search) | |
wind_scatter = generate_wind_oni_scatter(filtered_data, typhoon_search) | |
pressure_scatter = generate_pressure_oni_scatter(filtered_data, typhoon_search) | |
regression_fig, slopes_text = generate_regression_analysis(filtered_data) | |
return tracks_fig, wind_scatter, pressure_scatter, regression_fig, slopes_text | |
# Video animation function with fixed sidebar | |
def categorize_typhoon_by_standard(wind_speed, standard): | |
if standard == 'taiwan': | |
wind_speed_ms = wind_speed * 0.514444 | |
if wind_speed_ms >= 51.0: | |
return 'Strong Typhoon', taiwan_standard['Strong Typhoon']['hex'] | |
elif wind_speed_ms >= 33.7: | |
return 'Medium Typhoon', taiwan_standard['Medium Typhoon']['hex'] | |
elif wind_speed_ms >= 17.2: | |
return 'Mild Typhoon', taiwan_standard['Mild Typhoon']['hex'] | |
return 'Tropical Depression', taiwan_standard['Tropical Depression']['hex'] | |
else: | |
if wind_speed >= 137: | |
return 'C5 Super Typhoon', atlantic_standard['C5 Super Typhoon']['hex'] | |
elif wind_speed >= 113: | |
return 'C4 Very Strong Typhoon', atlantic_standard['C4 Very Strong Typhoon']['hex'] | |
elif wind_speed >= 96: | |
return 'C3 Strong Typhoon', atlantic_standard['C3 Strong Typhoon']['hex'] | |
elif wind_speed >= 83: | |
return 'C2 Typhoon', atlantic_standard['C2 Typhoon']['hex'] | |
elif wind_speed >= 64: | |
return 'C1 Typhoon', atlantic_standard['C1 Typhoon']['hex'] | |
elif wind_speed >= 34: | |
return 'Tropical Storm', atlantic_standard['Tropical Storm']['hex'] | |
return 'Tropical Depression', atlantic_standard['Tropical Depression']['hex'] | |
def generate_track_video(year, typhoon, standard): | |
if not typhoon: | |
return None | |
typhoon_id = typhoon.split('(')[-1].strip(')') | |
storm = ibtracs.get_storm(typhoon_id) | |
# Map focus | |
min_lat, max_lat = min(storm.lat), max(storm.lat) | |
min_lon, max_lon = min(storm.lon), max(storm.lon) | |
lat_padding = max((max_lat - min_lat) * 0.3, 5) | |
lon_padding = max((max_lon - min_lon) * 0.3, 5) | |
# Set up the figure (900x700 pixels at 100 DPI) | |
fig = plt.figure(figsize=(9, 7), dpi=100) | |
ax = plt.axes([0.05, 0.05, 0.65, 0.90], projection=ccrs.PlateCarree()) # Adjusted to leave space for sidebar | |
ax.set_extent([min_lon - lon_padding, max_lon + lon_padding, min_lat - lat_padding, max_lat + lat_padding], crs=ccrs.PlateCarree()) | |
# Add world map features | |
ax.add_feature(cfeature.LAND, facecolor='lightgray') | |
ax.add_feature(cfeature.OCEAN, facecolor='lightblue') | |
ax.add_feature(cfeature.COASTLINE, edgecolor='black') | |
ax.add_feature(cfeature.BORDERS, linestyle=':', edgecolor='gray') | |
ax.gridlines(draw_labels=True, linestyle='--', color='gray', alpha=0.5) | |
ax.set_title(f"{year} {storm.name} Typhoon Path") | |
# Initialize the line and point | |
line, = ax.plot([], [], 'b-', linewidth=2, transform=ccrs.PlateCarree()) | |
point, = ax.plot([], [], 'o', markersize=8, transform=ccrs.PlateCarree()) | |
date_text = ax.text(0.02, 0.02, '', transform=ax.transAxes, fontsize=10, bbox=dict(facecolor='white', alpha=0.8)) | |
# Add sidebar on the right with adjusted positions | |
details_title = fig.text(0.7, 0.95, "Typhoon Details", fontsize=12, fontweight='bold', verticalalignment='top') | |
details_text = fig.text(0.7, 0.85, '', fontsize=12, verticalalignment='top', | |
bbox=dict(facecolor='white', alpha=0.8, boxstyle='round,pad=0.5')) | |
# Add color legend | |
standard_dict = atlantic_standard if standard == 'atlantic' else taiwan_standard | |
legend_elements = [plt.Line2D([0], [0], marker='o', color='w', label=f"{cat}", | |
markerfacecolor=details['hex'], markersize=10) | |
for cat, details in standard_dict.items()] | |
fig.legend(handles=legend_elements, title="Color Legend", loc='center right', | |
bbox_to_anchor=(0.95, 0.5), fontsize=10) | |
def init(): | |
line.set_data([], []) | |
point.set_data([], []) | |
date_text.set_text('') | |
details_text.set_text('') | |
return line, point, date_text, details_text | |
def update(frame): | |
line.set_data(storm.lon[:frame+1], storm.lat[:frame+1]) | |
category, color = categorize_typhoon_by_standard(storm.vmax[frame], standard) | |
point.set_data([storm.lon[frame]], [storm.lat[frame]]) | |
point.set_color(color) | |
date_text.set_text(storm.time[frame].strftime('%Y-%m-%d %H:%M')) | |
details = f"Name: {storm.name}\n" \ | |
f"Date: {storm.time[frame].strftime('%Y-%m-%d %H:%M')}\n" \ | |
f"Wind Speed: {storm.vmax[frame]:.1f} kt\n" \ | |
f"Category: {category}" | |
details_text.set_text(details) | |
return line, point, date_text, details_text | |
ani = animation.FuncAnimation(fig, update, init_func=init, frames=len(storm.time), | |
interval=200, blit=True, repeat=True) | |
# Save as video | |
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') | |
writer = animation.FFMpegWriter(fps=5, bitrate=1800) | |
ani.save(temp_file.name, writer=writer) | |
plt.close(fig) | |
return temp_file.name | |
# Logistic regression functions | |
def perform_wind_regression(start_year, start_month, end_year, end_month): | |
start_date = datetime(start_year, start_month, 1) | |
end_date = datetime(end_year, end_month, 28) | |
data = merged_data[(merged_data['ISO_TIME'] >= start_date) & (merged_data['ISO_TIME'] <= end_date)].dropna(subset=['USA_WIND', 'ONI']) | |
data['severe_typhoon'] = (data['USA_WIND'] >= 64).astype(int) | |
X = sm.add_constant(data['ONI']) | |
y = data['severe_typhoon'] | |
model = sm.Logit(y, X).fit() | |
beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI'] | |
return f"Wind Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}" | |
def perform_pressure_regression(start_year, start_month, end_year, end_month): | |
start_date = datetime(start_year, start_month, 1) | |
end_date = datetime(end_year, end_month, 28) | |
data = merged_data[(merged_data['ISO_TIME'] >= start_date) & (merged_data['ISO_TIME'] <= end_date)].dropna(subset=['USA_PRES', 'ONI']) | |
data['intense_typhoon'] = (data['USA_PRES'] <= 950).astype(int) | |
X = sm.add_constant(data['ONI']) | |
y = data['intense_typhoon'] | |
model = sm.Logit(y, X).fit() | |
beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI'] | |
return f"Pressure Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}" | |
def perform_longitude_regression(start_year, start_month, end_year, end_month): | |
start_date = datetime(start_year, start_month, 1) | |
end_date = datetime(end_year, end_month, 28) | |
data = merged_data[(merged_data['ISO_TIME'] >= start_date) & (merged_data['ISO_TIME'] <= end_date)].dropna(subset=['LON', 'ONI']) | |
data['western_typhoon'] = (data['LON'] <= 140).astype(int) | |
X = sm.add_constant(data['ONI']) | |
y = data['western_typhoon'] | |
model = sm.Logit(y, X).fit() | |
beta_1, exp_beta_1, p_value = model.params['ONI'], np.exp(model.params['ONI']), model.pvalues['ONI'] | |
return f"Longitude Regression: β1={beta_1:.4f}, Odds Ratio={exp_beta_1:.4f}, P-value={p_value:.4f}" | |
# t-SNE clustering functions | |
def filter_west_pacific_coordinates(lons, lats): | |
mask = (lons >= 100) & (lons <= 180) & (lats >= 0) & (lats <= 40) | |
return lons[mask], lats[mask] | |
def filter_storm_by_season(storm, season): | |
start_month = storm.time[0].month | |
if season == 'all': | |
return True | |
elif season == 'summer': | |
return 4 <= start_month <= 8 | |
elif season == 'winter': | |
return 9 <= start_month <= 12 | |
return False | |
def point_region(lat, lon): | |
twl = regions["Taiwan Land"] | |
if twl["lat_min"] <= lat <= twl["lat_max"] and twl["lon_min"] <= lon <= twl["lon_max"]: | |
return "Taiwan Land" | |
tws = regions["Taiwan Sea"] | |
if tws["lat_min"] <= lat <= tws["lat_max"] and tws["lon_min"] <= lon <= tws["lon_max"]: | |
if not (twl["lat_min"] <= lat <= twl["lat_max"] and twl["lon_min"] <= lon <= twl["lon_max"]): | |
return "Taiwan Sea" | |
for rg in ["Japan", "China", "Hong Kong", "Philippines"]: | |
box = regions[rg] | |
if box["lat_min"] <= lat <= box["lat_max"] and box["lon_min"] <= lon <= box["lon_max"]: | |
return rg | |
return None | |
def calculate_region_durations(lons, lats, times): | |
region_times = defaultdict(float) | |
point_regions_list = [point_region(lats[i], lons[i]) for i in range(len(lons))] | |
for i in range(len(lons) - 1): | |
dt = (times[i + 1] - times[i]).total_seconds() / 3600.0 | |
r1 = point_regions_list[i] | |
r2 = point_regions_list[i + 1] | |
if r1 and r2: | |
if r1 == r2: | |
region_times[r1] += dt | |
else: | |
region_times[r1] += dt / 2 | |
region_times[r2] += dt / 2 | |
elif r1 and not r2: | |
region_times[r1] += dt / 2 | |
elif r2 and not r1: | |
region_times[r2] += dt / 2 | |
return dict(region_times) | |
def endpoint_region_label(cluster_label, cluster_labels, filtered_storms): | |
indices = np.where(cluster_labels == cluster_label)[0] | |
if len(indices) == 0: | |
return "" | |
end_count = defaultdict(int) | |
for idx in indices: | |
lons, lats, vmax_, mslp_, times = filtered_storms[idx] | |
reg = point_region(lats[-1], lons[-1]) | |
if reg: | |
end_count[reg] += 1 | |
if end_count: | |
max_reg = max(end_count, key=end_count.get) | |
ratio = end_count[max_reg] / len(indices) | |
if ratio > 0.5: | |
return max_reg | |
return "" | |
def dynamic_dbscan(tsne_results, min_clusters=10, max_clusters=20, eps_values=np.linspace(1.0, 10.0, 91)): | |
best_labels = None | |
best_n_clusters = 0 | |
best_n_noise = len(tsne_results) | |
best_eps = None | |
for eps in eps_values: | |
dbscan = DBSCAN(eps=eps, min_samples=3) | |
labels = dbscan.fit_predict(tsne_results) | |
unique_labels = set(labels) | |
if -1 in unique_labels: | |
unique_labels.remove(-1) | |
n_clusters = len(unique_labels) | |
n_noise = np.sum(labels == -1) | |
if min_clusters <= n_clusters <= max_clusters and n_noise < best_n_noise: | |
best_labels = labels | |
best_n_clusters = n_clusters | |
best_n_noise = n_noise | |
best_eps = eps | |
if best_labels is None: | |
for eps in eps_values[::-1]: | |
dbscan = DBSCAN(eps=eps, min_samples=3) | |
labels = dbscan.fit_predict(tsne_results) | |
unique_labels = set(labels) | |
if -1 in unique_labels: | |
unique_labels.remove(-1) | |
n_clusters = len(unique_labels) | |
if n_clusters == max_clusters: | |
best_labels = labels | |
best_n_clusters = n_clusters | |
best_n_noise = np.sum(labels == -1) | |
best_eps = eps | |
break | |
return best_labels, best_n_clusters, best_n_noise, best_eps | |
def update_route_clusters(start_year, start_month, end_year, end_month, enso_value, season): | |
start_date = datetime(int(start_year), int(start_month), 1) | |
end_date = datetime(int(end_year), int(end_month), 28) | |
all_storms_data = [] | |
for year in range(int(start_year), int(end_year) + 1): | |
season_data = ibtracs.get_season(year) | |
for storm_id in season_data.summary()['id']: | |
storm = ibtracs.get_storm(storm_id) | |
if storm.time[0] >= start_date and storm.time[-1] <= end_date and filter_storm_by_season(storm, season): | |
lons, lats = filter_west_pacific_coordinates(np.array(storm.lon), np.array(storm.lat)) | |
if len(lons) > 1: | |
start_time = storm.time[0] | |
start_year_storm = start_time.year | |
start_month_storm = start_time.month | |
oni_row = oni_long[(oni_long['Year'] == start_year_storm) & (oni_long['Month'] == f'{start_month_storm:02d}')] | |
if not oni_row.empty: | |
oni_value_storm = oni_row['ONI'].iloc[0] | |
enso_phase_storm = classify_enso_phases(oni_value_storm) | |
if enso_value == 'all' or enso_phase_storm == enso_value.capitalize(): | |
all_storms_data.append((lons, lats, np.array(storm.vmax), np.array(storm.mslp), np.array(storm.time), storm.name, enso_phase_storm)) | |
if not all_storms_data: | |
return go.Figure(), go.Figure(), make_subplots(rows=2, cols=1), "No storms found in the selected period." | |
# Prepare route vectors for t-SNE | |
max_length = max(len(st[0]) for st in all_storms_data) | |
route_vectors = [] | |
filtered_storms = [] | |
storms_vmax_list = [] | |
storms_mslp_list = [] | |
for idx, (lons, lats, vmax, mslp, times, name, enso_phase) in enumerate(all_storms_data): | |
t = np.linspace(0, 1, len(lons)) | |
t_new = np.linspace(0, 1, max_length) | |
try: | |
lon_i = interp1d(t, lons, kind='linear', fill_value='extrapolate')(t_new) | |
lat_i = interp1d(t, lats, kind='linear', fill_value='extrapolate')(t_new) | |
vmax_i = interp1d(t, vmax, kind='linear', fill_value='extrapolate')(t_new) | |
if not np.all(np.isnan(mslp)): | |
mslp_i = interp1d(t, mslp, kind='linear', fill_value='extrapolate')(t_new) | |
else: | |
mslp_i = np.full(max_length, np.nan) | |
except Exception as e: | |
continue | |
route_vector = np.column_stack((lon_i, lat_i)).flatten() | |
if np.isnan(route_vector).any(): | |
continue | |
route_vectors.append(route_vector) | |
filtered_storms.append((lons, lats, vmax_i, mslp_i, times)) | |
storms_vmax_list.append(vmax_i) | |
storms_mslp_list.append(mslp_i) | |
route_vectors = np.array(route_vectors) | |
if len(route_vectors) == 0: | |
return go.Figure(), go.Figure(), make_subplots(rows=2, cols=1), "No valid storms after interpolation." | |
# Perform t-SNE | |
tsne = TSNE(n_components=2, random_state=42, verbose=1) | |
tsne_results = tsne.fit_transform(route_vectors) | |
# Dynamic DBSCAN clustering | |
best_labels, best_n_clusters, best_n_noise, best_eps = dynamic_dbscan(tsne_results) | |
# Calculate region durations and mean routes | |
unique_labels = sorted(set(best_labels) - {-1}) | |
label_to_idx = {label: i for i, label in enumerate(unique_labels)} | |
cluster_region_durations = [defaultdict(float) for _ in range(len(unique_labels))] | |
cluster_mean_routes = [] | |
cluster_mean_vmax = [] | |
cluster_mean_mslp = [] | |
for i, (lons, lats, vmax, mslp, times) in enumerate(filtered_storms): | |
c = best_labels[i] | |
if c == -1: | |
continue | |
durations = calculate_region_durations(lons, lats, times) | |
idx = label_to_idx[c] | |
for r, val in durations.items(): | |
cluster_region_durations[idx][r] += val | |
for c in unique_labels: | |
indices = np.where(best_labels == c)[0] | |
if len(indices) == 0: | |
cluster_mean_routes.append(([], [])) | |
cluster_mean_vmax.append([]) | |
cluster_mean_mslp.append([]) | |
continue | |
cluster_lons = [] | |
cluster_lats = [] | |
cluster_v = [] | |
cluster_p = [] | |
for idx in indices: | |
lons, lats, vmax_, mslp_, times = filtered_storms[idx] | |
t = np.linspace(0, 1, len(lons)) | |
t_new = np.linspace(0, 1, max_length) | |
lon_i = interp1d(t, lons, kind='linear', fill_value='extrapolate')(t_new) | |
lat_i = interp1d(t, lats, kind='linear', fill_value='extrapolate')(t_new) | |
cluster_lons.append(lon_i) | |
cluster_lats.append(lat_i) | |
cluster_v.append(storms_vmax_list[idx]) | |
if not np.all(np.isnan(storms_mslp_list[idx])): | |
cluster_p.append(storms_mslp_list[idx]) | |
if cluster_lons and cluster_lats: | |
mean_lon = np.mean(cluster_lons, axis=0) | |
mean_lat = np.mean(cluster_lats, axis=0) | |
mean_v = np.mean(cluster_v, axis=0) | |
if cluster_p: | |
mean_p = np.nanmean(cluster_p, axis=0) | |
else: | |
mean_p = np.full(max_length, np.nan) | |
cluster_mean_routes.append((mean_lon, mean_lat)) | |
cluster_mean_vmax.append(mean_v) | |
cluster_mean_mslp.append(mean_p) | |
else: | |
cluster_mean_routes.append(([], [])) | |
cluster_mean_vmax.append([]) | |
cluster_mean_mslp.append([]) | |
# t-SNE Scatter Plot | |
fig_tsne = go.Figure() | |
cluster_colors = px.colors.qualitative.Safe | |
if len(cluster_colors) < len(unique_labels): | |
cluster_colors = px.colors.qualitative.Dark24 | |
for i, c in enumerate(unique_labels): | |
indices = np.where(best_labels == c)[0] | |
end_reg = endpoint_region_label(c, best_labels, filtered_storms) | |
name = f"Cluster {i+1}" + (f" (towards {end_reg})" if end_reg else "") | |
fig_tsne.add_trace(go.Scatter( | |
x=tsne_results[indices, 0], | |
y=tsne_results[indices, 1], | |
mode='markers', | |
marker=dict(size=5, color=cluster_colors[i % len(cluster_colors)]), | |
name=name | |
)) | |
noise_indices = np.where(best_labels == -1)[0] | |
if len(noise_indices) > 0: | |
fig_tsne.add_trace(go.Scatter( | |
x=tsne_results[noise_indices, 0], | |
y=tsne_results[noise_indices, 1], | |
mode='markers', | |
marker=dict(size=5, color='grey'), | |
name='Noise' | |
)) | |
fig_tsne.update_layout( | |
title="TSNE of Typhoon Routes", | |
xaxis_title="TSNE Dim 1", | |
yaxis_title="TSNE Dim 2", | |
legend_title="Clusters" | |
) | |
# Typhoon Routes Plot with Mean Routes | |
fig_routes = go.Figure() | |
for i, (lons, lats, _, _, _) in enumerate(filtered_storms): | |
c = best_labels[i] | |
if c == -1: | |
continue | |
color_idx = label_to_idx[c] | |
fig_routes.add_trace( | |
go.Scattergeo( | |
lon=lons, | |
lat=lats, | |
mode='lines', | |
opacity=0.3, | |
line=dict(width=1, color=cluster_colors[color_idx % len(cluster_colors)]), | |
showlegend=False | |
) | |
) | |
for i, c in enumerate(unique_labels): | |
mean_lon, mean_lat = cluster_mean_routes[i] | |
if len(mean_lon) == 0: | |
continue | |
end_reg = endpoint_region_label(c, best_labels, filtered_storms) | |
name = f"Cluster {i+1}" + (f" (towards {end_reg})" if end_reg else "") | |
fig_routes.add_trace( | |
go.Scattergeo( | |
lon=mean_lon, | |
lat=mean_lat, | |
mode='lines', | |
line=dict(width=4, color=cluster_colors[i % len(cluster_colors)]), | |
name=name | |
) | |
) | |
fig_routes.add_trace( | |
go.Scattergeo( | |
lon=[mean_lon[0]], | |
lat=[mean_lat[0]], | |
mode='markers', | |
marker=dict(size=10, color='green', symbol='triangle-up'), | |
name=f"Cluster {i+1} Start" | |
) | |
) | |
fig_routes.add_trace( | |
go.Scattergeo( | |
lon=[mean_lon[-1]], | |
lat=[mean_lat[-1]], | |
mode='markers', | |
marker=dict(size=10, color='red', symbol='x'), | |
name=f"Cluster {i+1} End" | |
) | |
) | |
enso_phase_text = {'all': 'All Years', 'El Nino': 'El Niño', 'La Nina': 'La Niña', 'Neutral': 'Neutral Years'} | |
fig_routes.update_layout( | |
title=f"West Pacific Typhoon Routes ({start_year}-{end_year}, {season.capitalize()}, {enso_phase_text.get(enso_value, 'All Years')})", | |
geo=dict(scope='asia', projection_type='mercator', showland=True, landcolor='lightgray') | |
) | |
# Cluster Statistics Plot | |
fig_stats = make_subplots(rows=2, cols=1, shared_xaxes=True, subplot_titles=("Average Wind Speed", "Average Pressure")) | |
for i, c in enumerate(unique_labels): | |
if len(cluster_mean_vmax[i]) > 0: | |
end_reg = endpoint_region_label(c, best_labels, filtered_storms) | |
name = f"Cluster {i+1}" + (f" ({end_reg})" if end_reg else "") | |
fig_stats.add_trace( | |
go.Scatter(y=cluster_mean_vmax[i], mode='lines', line=dict(width=2, color=cluster_colors[i % len(cluster_colors)]), name=name), | |
row=1, col=1 | |
) | |
if not np.all(np.isnan(cluster_mean_mslp[i])): | |
fig_stats.add_trace( | |
go.Scatter(y=cluster_mean_mslp[i], mode='lines', line=dict(width=2, color=cluster_colors[i % len(cluster_colors)]), name=name), | |
row=2, col=1 | |
) | |
fig_stats.update_layout( | |
title="Cluster Average Wind & Pressure Profiles", | |
xaxis_title="Route Normalized Index", | |
yaxis_title="Wind Speed (knots)", | |
xaxis2_title="Route Normalized Index", | |
yaxis2_title="Pressure (hPa)", | |
showlegend=True, | |
legend_tracegroupgap=300 | |
) | |
# Cluster Information | |
cluster_info_lines = [f"Selected DBSCAN eps: {best_eps:.2f}", f"Number of noise points: {best_n_noise}"] | |
for i, c in enumerate(unique_labels): | |
indices = np.where(best_labels == c)[0] | |
count = len(indices) | |
if count == 0: | |
continue | |
avg_durations = {r: (cluster_region_durations[i][r] / count) for r in cluster_region_durations[i]} | |
end_reg = endpoint_region_label(c, best_labels, filtered_storms) | |
name = f"Cluster {i+1}" + (f" (towards {end_reg})" if end_reg else "") | |
cluster_info_lines.append(f"\n{name}") | |
if avg_durations: | |
for reg, hrs in avg_durations.items(): | |
cluster_info_lines.append(f"{reg}: {hrs:.2f} hours") | |
else: | |
cluster_info_lines.append("No significant region durations.") | |
if end_reg in ["Taiwan Land", "Taiwan Sea"] and len(cluster_mean_vmax[i]) > 0: | |
final_wind = cluster_mean_vmax[i][-1] | |
if final_wind >= 34: | |
cluster_info_lines.append( | |
"CWA would issue a land warning ~18 hours before arrival." if end_reg == "Taiwan Land" | |
else "CWA would issue a sea warning ~24 hours before arrival." | |
) | |
if len(noise_indices) > 0: | |
cluster_info_lines.append(f"\nNoise Cluster\nNumber of storms classified as noise: {len(noise_indices)}") | |
cluster_info_text = "\n".join(cluster_info_lines) | |
return fig_tsne, fig_routes, fig_stats, cluster_info_text | |
# Gradio Interface | |
with gr.Blocks(title="Typhoon Analysis Dashboard") as demo: | |
gr.Markdown("# Typhoon Analysis Dashboard") | |
with gr.Tab("Overview"): | |
gr.Markdown(""" | |
## Welcome to the Typhoon Analysis Dashboard | |
This dashboard allows you to analyze typhoon data in relation to ENSO phases. | |
### Features: | |
- **Track Visualization**: View typhoon tracks by time period and ENSO phase | |
- **Wind Analysis**: Examine wind speed vs ONI relationships | |
- **Pressure Analysis**: Analyze pressure vs ONI relationships | |
- **Longitude Analysis**: Study typhoon generation longitude vs ONI | |
- **Path Animation**: Watch animated typhoon paths with a sidebar | |
- **TSNE Cluster**: Perform t-SNE clustering on typhoon routes with mean routes and region analysis | |
Select a tab above to begin your analysis. | |
""") | |
with gr.Tab("Track Visualization"): | |
with gr.Row(): | |
start_year = gr.Number(label="Start Year", value=2000, minimum=1900, maximum=2024, step=1) | |
start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1) | |
end_year = gr.Number(label="End Year", value=2024, minimum=1900, maximum=2024, step=1) | |
end_month = gr.Dropdown(label="End Month", choices=list(range(1, 13)), value=6) | |
enso_phase = gr.Dropdown(label="ENSO Phase", choices=['all', 'El Nino', 'La Nina', 'Neutral'], value='all') | |
typhoon_search = gr.Textbox(label="Typhoon Search") | |
analyze_btn = gr.Button("Generate Tracks") | |
tracks_plot = gr.Plot(label="Typhoon Tracks", elem_id="tracks_plot") | |
typhoon_count = gr.Textbox(label="Number of Typhoons Displayed") | |
def get_full_tracks(start_year, start_month, end_year, end_month, enso_phase, typhoon_search): | |
start_date = datetime(start_year, start_month, 1) | |
end_date = datetime(end_year, end_month, 28) | |
filtered_data = merged_data[ | |
(merged_data['ISO_TIME'] >= start_date) & | |
(merged_data['ISO_TIME'] <= end_date) | |
] | |
filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases) | |
if enso_phase != 'all': | |
filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()] | |
unique_storms = filtered_data['SID'].unique() | |
count = len(unique_storms) | |
fig = go.Figure() | |
for sid in unique_storms: | |
storm_data = typhoon_data[typhoon_data['SID'] == sid] | |
name = storm_data['NAME'].iloc[0] if not pd.isna(storm_data['NAME'].iloc[0]) else "Unnamed" | |
storm_oni = filtered_data[filtered_data['SID'] == sid]['ONI'].iloc[0] | |
color = 'red' if storm_oni >= 0.5 else ('blue' if storm_oni <= -0.5 else 'green') | |
fig.add_trace(go.Scattergeo( | |
lon=storm_data['LON'], lat=storm_data['LAT'], mode='lines', | |
name=f"{name} ({storm_data['SEASON'].iloc[0]})", | |
line=dict(width=1.5, color=color), | |
hoverinfo="name" | |
)) | |
if typhoon_search: | |
search_mask = typhoon_data['NAME'].str.contains(typhoon_search, case=False, na=False) | |
if search_mask.any(): | |
for sid in typhoon_data[search_mask]['SID'].unique(): | |
storm_data = typhoon_data[typhoon_data['SID'] == sid] | |
fig.add_trace(go.Scattergeo( | |
lon=storm_data['LON'], lat=storm_data['LAT'], mode='lines+markers', | |
name=f"MATCHED: {storm_data['NAME'].iloc[0]} ({storm_data['SEASON'].iloc[0]})", | |
line=dict(width=3, color='yellow'), | |
marker=dict(size=5), | |
hoverinfo="name" | |
)) | |
fig.update_layout( | |
title=f"Typhoon Tracks ({start_year}-{start_month} to {end_year}-{end_month})", | |
geo=dict( | |
projection_type='natural earth', | |
showland=True, | |
showcoastlines=True, | |
landcolor='rgb(243, 243, 243)', | |
countrycolor='rgb(204, 204, 204)', | |
coastlinecolor='rgb(204, 204, 204)', | |
center=dict(lon=140, lat=20), | |
projection_scale=3 | |
), | |
legend_title="Typhoons by ENSO Phase", | |
showlegend=True, | |
height=700 | |
) | |
fig.add_annotation( | |
x=0.02, y=0.98, xref="paper", yref="paper", | |
text="Red: El Niño, Blue: La Niña, Green: Neutral", | |
showarrow=False, align="left", | |
bgcolor="rgba(255,255,255,0.8)" | |
) | |
return fig, f"Total typhoons displayed: {count}" | |
analyze_btn.click( | |
fn=get_full_tracks, | |
inputs=[start_year, start_month, end_year, end_month, enso_phase, typhoon_search], | |
outputs=[tracks_plot, typhoon_count] | |
) | |
with gr.Tab("Wind Analysis"): | |
with gr.Row(): | |
wind_start_year = gr.Number(label="Start Year", value=2000, minimum=1900, maximum=2024, step=1) | |
wind_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1) | |
wind_end_year = gr.Number(label="End Year", value=2024, minimum=1900, maximum=2024, step=1) | |
wind_end_month = gr.Dropdown(label="End Month", choices=list(range(1, 13)), value=6) | |
wind_enso_phase = gr.Dropdown(label="ENSO Phase", choices=['all', 'El Nino', 'La Nina', 'Neutral'], value='all') | |
wind_typhoon_search = gr.Textbox(label="Typhoon Search") | |
wind_analyze_btn = gr.Button("Generate Wind Analysis") | |
wind_scatter = gr.Plot(label="Wind Speed vs ONI") | |
wind_regression_results = gr.Textbox(label="Wind Regression Results") | |
def get_wind_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search): | |
results = generate_main_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search) | |
regression = perform_wind_regression(start_year, start_month, end_year, end_month) | |
return results[1], regression | |
wind_analyze_btn.click( | |
fn=get_wind_analysis, | |
inputs=[wind_start_year, wind_start_month, wind_end_year, wind_end_month, wind_enso_phase, wind_typhoon_search], | |
outputs=[wind_scatter, wind_regression_results] | |
) | |
with gr.Tab("Pressure Analysis"): | |
with gr.Row(): | |
pressure_start_year = gr.Number(label="Start Year", value=2000, minimum=1900, maximum=2024, step=1) | |
pressure_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1) | |
pressure_end_year = gr.Number(label="End Year", value=2024, minimum=1900, maximum=2024, step=1) | |
pressure_end_month = gr.Dropdown(label="End Month", choices=list(range(1, 13)), value=6) | |
pressure_enso_phase = gr.Dropdown(label="ENSO Phase", choices=['all', 'El Nino', 'La Nina', 'Neutral'], value='all') | |
pressure_typhoon_search = gr.Textbox(label="Typhoon Search") | |
pressure_analyze_btn = gr.Button("Generate Pressure Analysis") | |
pressure_scatter = gr.Plot(label="Pressure vs ONI") | |
pressure_regression_results = gr.Textbox(label="Pressure Regression Results") | |
def get_pressure_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search): | |
results = generate_main_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search) | |
regression = perform_pressure_regression(start_year, start_month, end_year, end_month) | |
return results[2], regression | |
pressure_analyze_btn.click( | |
fn=get_pressure_analysis, | |
inputs=[pressure_start_year, pressure_start_month, pressure_end_year, pressure_end_month, pressure_enso_phase, pressure_typhoon_search], | |
outputs=[pressure_scatter, pressure_regression_results] | |
) | |
with gr.Tab("Longitude Analysis"): | |
with gr.Row(): | |
lon_start_year = gr.Number(label="Start Year", value=2000, minimum=1900, maximum=2024, step=1) | |
lon_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1) | |
lon_end_year = gr.Number(label="End Year", value=2024, minimum=1900, maximum=2024, step=1) | |
lon_end_month = gr.Dropdown(label="End Month", choices=list(range(1, 13)), value=6) | |
lon_enso_phase = gr.Dropdown(label="ENSO Phase", choices=['all', 'El Nino', 'La Nina', 'Neutral'], value='all') | |
lon_typhoon_search = gr.Textbox(label="Typhoon Search (Optional)") | |
lon_analyze_btn = gr.Button("Generate Longitude Analysis") | |
regression_plot = gr.Plot(label="Longitude vs ONI") | |
slopes_text = gr.Textbox(label="Regression Slopes") | |
lon_regression_results = gr.Textbox(label="Longitude Regression Results") | |
def get_longitude_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search): | |
results = generate_main_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search) | |
regression = perform_longitude_regression(start_year, start_month, end_year, end_month) | |
return results[3], results[4], regression | |
lon_analyze_btn.click( | |
fn=get_longitude_analysis, | |
inputs=[lon_start_year, lon_start_month, lon_end_year, lon_end_month, lon_enso_phase, lon_typhoon_search], | |
outputs=[regression_plot, slopes_text, lon_regression_results] | |
) | |
with gr.Tab("Typhoon Path Animation"): | |
with gr.Row(): | |
year_dropdown = gr.Dropdown(label="Year", choices=[str(y) for y in range(1950, 2025)], value="2024") | |
typhoon_dropdown = gr.Dropdown(label="Typhoon") | |
standard_dropdown = gr.Dropdown(label="Classification Standard", choices=['atlantic', 'taiwan'], value='atlantic') | |
animate_btn = gr.Button("Generate Animation") | |
path_video = gr.Video(label="Typhoon Path Animation", elem_id="path_video") | |
animation_info = gr.Markdown(""" | |
### Animation Instructions | |
1. Select a year and typhoon from the dropdowns | |
2. Choose a classification standard (Atlantic or Taiwan) | |
3. Click "Generate Animation" | |
4. Use the video player's built-in controls to play, pause, or scrub through the animation | |
5. The animation shows the typhoon track growing over a world map, with: | |
- Date on the bottom left | |
- Sidebar on the right showing typhoon details (name, date, wind speed, category) as it moves | |
- Color legend with colored markers centered on the right | |
""") | |
def update_typhoon_options(year): | |
season = ibtracs.get_season(int(year)) | |
storm_summary = season.summary() | |
options = [f"{storm_summary['name'][i]} ({storm_summary['id'][i]})" for i in range(storm_summary['season_storms'])] | |
return gr.update(choices=options, value=options[0] if options else None) | |
year_dropdown.change(fn=update_typhoon_options, inputs=year_dropdown, outputs=typhoon_dropdown) | |
animate_btn.click( | |
fn=generate_track_video, | |
inputs=[year_dropdown, typhoon_dropdown, standard_dropdown], | |
outputs=path_video | |
) | |
with gr.Tab("TSNE Cluster"): | |
with gr.Row(): | |
tsne_start_year = gr.Number(label="Start Year", value=2000, minimum=1900, maximum=2024, step=1) | |
tsne_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1) | |
tsne_end_year = gr.Number(label="End Year", value=2024, minimum=1900, maximum=2024, step=1) | |
tsne_end_month = gr.Dropdown(label="End Month", choices=list(range(1, 13)), value=12) | |
tsne_enso_phase = gr.Dropdown(label="ENSO Phase", choices=['all', 'El Nino', 'La Nina', 'Neutral'], value='all') | |
tsne_season = gr.Dropdown(label="Season", choices=['all', 'summer', 'winter'], value='all') | |
tsne_analyze_btn = gr.Button("Analyze") | |
tsne_plot = gr.Plot(label="t-SNE Clusters") | |
routes_plot = gr.Plot(label="Typhoon Routes with Mean Routes") | |
stats_plot = gr.Plot(label="Cluster Statistics") | |
cluster_info = gr.Textbox(label="Cluster Information", lines=10) | |
tsne_analyze_btn.click( | |
fn=update_route_clusters, | |
inputs=[tsne_start_year, tsne_start_month, tsne_end_year, tsne_end_month, tsne_enso_phase, tsne_season], | |
outputs=[tsne_plot, routes_plot, stats_plot, cluster_info] | |
) | |
demo.launch(share=True) |