Severe Tropical Cyclones#
Figure. Severe Tropical cyclones (TCs) in the vicinity of Palau since 1950. Map showing all severe TC tracks in the vicinity of Palau (top) and annual storm count (bottom). Severe cyclones are those classified as Category 3 or greater on the Saffir Simpson scale (i.e., winds greater than 96kt [110 miles/hour]). The dashed black line represents a trend that is not statistically significant.
Setup#
First, we need to import all the necessary libraries. Some of them are specifically developed to handle the download and plotting of the data and are hosted at the indicators set-up repository in GitHub
Show code cell source
import warnings
warnings.filterwarnings("ignore")
import sys
import os.path as op
import xarray as xr
import pandas as pd
from myst_nb import glue
import numpy as np
import matplotlib.pyplot as plt
sys.path.append("../../../functions")
from tcs import Extract_Circle
from data_downloaders import download_ibtracs
sys.path.append("../../../../indicators_setup")
from ind_setup.plotting_tcs import Plot_TCs_HistoricalTracks_Category
from ind_setup.plotting import plot_bar_probs, plot_tc_categories_trend
from ind_setup.plotting import plot_bar_probs_ONI, add_oni_cat
from data_downloaders import download_oni_index
lon_lat = [134.5, 5.5] #Palau location lon, lat
basin = 'WP'
r1 = 5 # Radius of the circular area in degrees
IBTrACS#
IBTrACS (International Best Track Archive for Climate Stewardship) is the most comprehensive global database of tropical cyclones. It compiles data from all major meteorological agencies worldwide, providing track, intensity, and metadata for storms from 1842 to present. It supports research on cyclone trends, risks, and climate variability.
update_data = False
path_data = "../../../data"
path_figs = "../../../matrix_cc/figures"
Show code cell source
if update_data:
url = 'https://www.ncei.noaa.gov/data/international-best-track-archive-for-climate-stewardship-ibtracs/v04r01/access/netcdf/IBTrACS.ALL.v04r01.nc'
tcs = download_ibtracs(url, basin = basin)
tcs.to_netcdf(f"{path_data}/tcs_{basin}.nc")
else:
tcs = xr.load_dataset(f"{path_data}/tcs_{basin}.nc")
Analysis#
tcs = tcs.isel(storm = np.where(tcs.isel(date_time = 0).time.dt.year >= 1950)[0])
Show code cell source
d_vns = {
'longitude': 'lon',
'latitude': 'lat',
'time': 'time',
'pressure': 'wmo_pres',
'wind': 'wmo_wind',
}
tcs_sel, tcs_sel_params = Extract_Circle(tcs, lon_lat[0], lon_lat[1], r1, d_vns, fillwinds=True)
tcm = tcs_sel.max(dim = 'date_time')
tcmin = tcs_sel.min(dim = 'date_time')
The static plot below shows all the severe TCs in the vicinity of Palau colored by its category.
Severe TCs are defined here as those having a category equal or larger than 3
tcs_sel_params['category'] = (('storm'), np.where(np.isnan(tcs_sel_params.category), -1, tcs_sel_params.category))
tcs_sel_params_severe = tcs_sel_params.where(tcs_sel_params.category >=3, drop=True)
tcs_sel_severe = tcs_sel.sel(storm=tcs_sel_params_severe.storm)
Plotting#
lon1, lon2 = 90, 200
lat1, lat2 = -10, 70
# r1
fig, ax = Plot_TCs_HistoricalTracks_Category(
tcs_sel_severe, tcs_sel_params_severe.category,
lon1, lon2, lat1, lat2,
lon_lat[0], lon_lat[1], r1,
)
ax.set_title('Severe Historical TCs [Categories 3, 4 and 5]', fontsize = 14)
glue("tracks_fig", fig, display=False)
plt.savefig(op.join(path_figs, 'F8_TCs_Historical_Severe.png'), dpi=300, bbox_inches='tight')
The plot below shows the trend of the number of severe TCs per year over time.
tcs_sel_severe_params = tcs_sel_params.where(tcs_sel_params.category >=3, drop = True)
time = tcs_sel_severe_params.dmin_date.dt.year.values
u, cu = np.unique(time, return_counts=True)
years = np.unique(tcs.isel(date_time = 0).time.dt.year)
cyclone_counts = np.zeros(len(years), dtype=int)
idx = np.searchsorted(years, u)
cyclone_counts[idx] = cu
plot_bar_probs(x = years, y = cyclone_counts, figsize= (15, 4), trendline = True,
y_label = 'Number of severe TCs')
(<Figure size 1500x400 with 1 Axes>, <Axes: ylabel='Number of severe TCs'>)
The following bar plot displays the TC count per year and per category, as well as the trend.
The grey color represent those TCs in the record where no pressure or wind information is available to determine the corresponding category.
tcs_sel_severe_params['month'] = tcs_sel_severe_params.dmin_date.dt.month
fig = plot_tc_categories_trend(tcs_sel_severe_params, trendline_plot = False)
plt.savefig(op.join(path_figs, 'F8_TCs_Historical_bars_category_severe.png'), dpi=300, bbox_inches='tight')
glue("time_cat_fig", fig, display=False)
plt.savefig(op.join(path_figs, 'F8_TCs_Historical_Severe_cat_time.png'), dpi=300, bbox_inches='tight')
To showcase the spatial distribution of the TC tracks for the different categories, a map is shown below representing all the TCs in the record for each category.
for category in np.arange(3, 6, 1):
tcs_cat = tcs_sel.where(tcs_sel_params.category == category, drop = True)
tcs_cat_params = tcs_sel_params.where(tcs_sel_params.category == category, drop = True)
# r1
fig, ax = Plot_TCs_HistoricalTracks_Category(
tcs_cat, tcs_cat_params.category,
lon1, lon2, lat1, lat2,
lon_lat[0], lon_lat[1], r1,
)
ax.set_title(f'Historical TCs Category {category}', fontsize=15)
Generate table#
Table sumarizing different metrics of the data analyzed in the plots above
Number of TCs for each category
df_tcs = tcs_sel_params.to_dataframe()
df_tcs['year'] = df_tcs.dmin_date.dt.year
df_t = df_tcs.groupby('category').count()[['pressure_min']]
# fig = plot_df_table(df_t, figsize = (300, 220))
mean_tcs_per_year = df_tcs.groupby(df_tcs['dmin_date'].dt.year)['pressure_min'].count()
df_sev = df_tcs.loc[df_tcs['category'] >=3]
mean_tcs_per_year_sev = df_sev.groupby(df_sev['dmin_date'].dt.year)['pressure_min'].count()
print('Mean TCs per year: ', np.nanmean(mean_tcs_per_year))
print('Mean number of severe TCs per year: ', np.round(np.nanmean(mean_tcs_per_year_sev), 2))
Mean TCs per year: 2.780821917808219
Mean number of severe TCs per year: 1.12
ONI index#
The Oceanic Niño Index (ONI) is the standard measure used to monitor El Niño and La Niña events. It is based on sea surface temperature anomalies in the central equatorial Pacific (Niño 3.4 region) averaged over 3-month periods.
https://origin.cpc.ncep.noaa.gov/products/analysis_monitoring/ensostuff/ONI_v5.php
p_data = 'https://psl.noaa.gov/data/correlation/oni.data'
if update_data:
df1 = download_oni_index(p_data)
df1.to_pickle(op.join(path_data, 'oni_index.pkl'))
else:
df1 = pd.read_pickle(op.join(path_data, 'oni_index.pkl'))
oni = df1
lims = [-.5, .5]
df1 = add_oni_cat(df1, lims = lims)
import pandas as pd
tcs_g = pd.DataFrame(tcs_sel.isel(date_time = 0).time.values)
tcs_g.index = tcs_g[0]
tcs_g.index = pd.DatetimeIndex(tcs_g.index).to_period('M').to_timestamp() + pd.offsets.MonthBegin(0)
tcs_g['oni_cat'] = oni.oni_cat
tcs_sel_params['oni_cat'] = (('storm'), tcs_g['oni_cat'].values)
tcs_sel['oni_cat'] = (('storm'), tcs_g['oni_cat'].values)
# oni['ONI_cat'] = np.where(oni.ONI < lims[0], -1, np.where(oni.ONI > lims[1], 1, 0))
tcs_sel_params['oni_cat'] = (('storm'), tcs_sel.oni_cat.values)
oni_perc_cat = oni.groupby('oni_cat').size() / oni.shape[0] * 100
oni_perc_cat
oni_cat
-1 33.333333
0 37.333333
1 29.333333
dtype: float64
tcs_perc_cat = tcs_sel_params.to_dataframe().groupby('oni_cat').size() * 100 / tcs_sel_params.to_dataframe().shape[0]
tcs_perc_cat
oni_cat
-1.0 30.383481
0.0 43.952802
1.0 25.073746
dtype: float64
#Relavice probability
tcs_perc_cat / oni_perc_cat
oni_cat
-1.0 0.911504
0.0 1.177307
1.0 0.854787
dtype: float64
time = tcs_sel_params.dmin_date.dt.year.values
u, cu = np.unique(time, return_counts=True)
tc_c = pd.DataFrame(cu, index = u)
time_sev = tcs_sel_params.where(tcs_sel_params.category >= 3, drop = True).dmin_date.dt.year.values
u_sev, cu_sev = np.unique(time_sev, return_counts=True)
tc_c_sev = pd.DataFrame(cu_sev, index = u_sev)
oni_y = oni.groupby(oni.index.year).min()
oni_y['tc_counts'] = tc_c
oni_y['tc_counts_sev'] = tc_c_sev
oni_y['oni_cat'] = oni_y.oni_cat.values
oni_y
| ONI | oni_cat | tc_counts | tc_counts_sev | |
|---|---|---|---|---|
| 1951 | -0.82 | 1 | 7.0 | NaN |
| 1952 | -0.08 | 0 | 10.0 | NaN |
| 1953 | 0.40 | 1 | 6.0 | NaN |
| 1954 | -0.90 | -1 | 4.0 | NaN |
| 1955 | -1.67 | -1 | 4.0 | NaN |
| ... | ... | ... | ... | ... |
| 2021 | -1.05 | -1 | 6.0 | NaN |
| 2022 | -1.06 | -1 | 2.0 | NaN |
| 2023 | -0.68 | 1 | 2.0 | NaN |
| 2024 | -0.53 | 0 | 3.0 | NaN |
| 2025 | -0.59 | 0 | 1.0 | NaN |
75 rows × 4 columns
The following bar plot represents the severe TC counts over time. The color of the bar represents wether it is a El Niño or La Niña year.
ax = plot_bar_probs_ONI(oni_y.fillna(0), 'tc_counts_sev', y_label= 'TC counts - Severe');
plt.savefig(op.join(path_figs, f'F9_TCs_severe_bars_trend.png'), dpi=300, bbox_inches='tight')
SEVERE TCs#
Category 3, 4 and 5
The following plots represent all the severe TCs in the record for the three different phases of the El Niño Southern Oscillation: “El Niño”, “Neutral” and “La Niña”
storms_severe_ids = tcs_sel_params.storm.where(tcs_sel_params.category >= 3, drop = True).values
storms_severe = tcs_sel.sel(storm = storms_severe_ids)
storm_severe_params = tcs_sel_params.sel(storm = storms_severe_ids)
names_cat = ['La Niña', 'Neutral', 'El Niño']
for ic, category in enumerate([-1, 0, 1]):
tcs_cat = storms_severe.where(storm_severe_params.oni_cat == category, drop = True)
tcs_cat_params = storm_severe_params.where(storm_severe_params.oni_cat == category, drop = True)
fig, ax = Plot_TCs_HistoricalTracks_Category(
tcs_cat, tcs_cat_params.category,
lon1, lon2, lat1, lat2,
lon_lat[0], lon_lat[1], r1,
)
ax.set_title(f'Historical SEVERE TCs: {names_cat[ic]}', fontsize=15)
plt.savefig(op.join(path_figs, f'F9_TCs_{names_cat[ic]}_SEVERE.png'), dpi=300, bbox_inches='tight')