Severe Tropical Cyclones

Severe Tropical Cyclones#

../../../_images/73bf03689658bc08a57e208edb2131c90e789b16299ec96650dd3abcba4b2e42.png
../../../_images/92e0e59957f268f342a41c7ed08245de136f26b8d38ac82dc90fda1b6934070e.png

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

Hide 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"
Hide 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])    
Hide 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')
../../../_images/73bf03689658bc08a57e208edb2131c90e789b16299ec96650dd3abcba4b2e42.png

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'>)
../../../_images/b4ad47ddebbffc965056b21a91a7156f1a7e3b818e4c76d7567d4d51fb19adb2.png

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')
../../../_images/92e0e59957f268f342a41c7ed08245de136f26b8d38ac82dc90fda1b6934070e.png

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)
../../../_images/4e970140f9b16279112eefb020670676f44d2625d1e6d43ee58a000cfe0fe48b.png ../../../_images/9356350bf05d1f94cf9487e6f7c40d31cd44263bca4656b30455f99e1b8c49f2.png ../../../_images/90a01d92fb162b2965a58557d8c797a798a7525d75caeb924d5160f8d6f8f5b9.png

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')
../../../_images/0ad85d0dad4f7551bfac6fc347a08b65bf7a4a7588ad3241a2f6aff31b3570ab.png

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')
    
../../../_images/5d6a01620711aeef5dc96a97815bb0ae23dd2d1e1b586bfeffeb980615e0d606.png ../../../_images/0c6de4ae5977a17c59961b8357de0e0caa59670a372d0fe0d1931b12ccf5920b.png ../../../_images/cafe9516a7cf658f464ca58caca4c3005dd983596845ea4cb8139f0e390e7be1.png