Heatwave and coldwave duration

Heatwave and coldwave duration is the total duration of “extreme spells”; the total number of days in a season where the temperature exceeds a threshold over a minimum number of consecutive days. Other tools for computing such events are also available, but the novelty of this tool is that the users can select their own thresholds (based on quantiles) and minimum duration for an event. The duration of heatwaves and coldwaves helps to understand potential changes in energy demand.

Note: This vignette has been written to process and plot the output of an ESMValTool namelist configured with a specific set of parameters. If you wish to run and plot the outputs for another set of parameters, you need to re-run the corresponding namelist with the desired parameters, interpolate the output NetCDF files to a common grid, and modify the vignette code accordingly.

The following example will compute the heatwvaves and coldwaves duration in the North Atlantic - European Sector [20ºW-50ºE; 30ºN-80ºN] using the CMIP5 projection for the 8.5 scenario.

1- Load dependencies

This example requires the following system libraries:

  • libssl-dev
  • libnecdf-dev
  • cdo

You will need to install specific versions of certain R packages as follows:

library(devtools)
install_git('https://earth.bsc.es/gitlab/es/startR', branch = 'develop-hotfixes-0.0.2')

Six functions should be loaded by running the following lines in R, until integrated into the new magic.bsc package.

source('https://earth.bsc.es/gitlab/nperez/magic.bsc/raw/develop-bugfixes-0.0.0/R/Climdex.R')
source('https://earth.bsc.es/gitlab/nperez/magic.bsc/raw/develop-bugfixes-0.0.0/R/Threshold.R')
source('https://earth.bsc.es/gitlab/nperez/magic.bsc/raw/develop-bugfixes-0.0.0/R/DailyAno.R')
source('https://earth.bsc.es/gitlab/nperez/magic.bsc/raw/develop-bugfixes-0.0.0/R/WeightedMean.R')
source('https://earth.bsc.es/gitlab/nperez/magic.bsc/raw/develop-bugfixes-0.0.0/R/CombineIndices.R')
source('https://earth.bsc.es/gitlab/nperez/magic.bsc/raw/develop-bugfixes-0.0.0/R/SeasonSelect.R')
source('https://earth.bsc.es/gitlab/nperez/s2dverification/raw/develop-magic/R/WaveDuration.R')
source('https://earth.bsc.es/gitlab/nperez/s2dverification/raw/develop-magic/R/WaveIntensity.R')
source('https://earth.bsc.es/gitlab/nperez/s2dverification/raw/develop-magic/R/WaveStart.R')

All the other R packages involved can be installed directly from CRAN and loaded as follows:

library(s2dverification)
library(startR)
library(multiApply)
library(ggplot2)
library(climdex.pcic)
library(parallel)

2- Heatwaves duration

The Heatwaves duration is the number of consecutive days for which the maximum temperature is exceeding a threshold over a minimum number of days during summer.

The next image shows an example of 17 days heatwave duration in a year corresponding to the red bars. Five days is the minimum number of consecutive days considered in the example.

2.1- Defining heatwaves threshold

In this example, the threshold is defined as the 90th percentile of maximum temprature during the period 1971-2000.

The historical simulations for the CMIP5 model should be loaded by the function Start from s2dverification package:

var0 <- 'tasmax'
start_climatology <- '1971-01-01'
end_climatology <- '2000-12-31'
lat.min <- 30;  lat.max <- 80
lon.min <- -20; lon.max <- 50
climatology_filenames <- paste0("/esarchive/scratch/pbretonn/cmip5-cp4cds/historical/day/tasmax/",
                                "tasmax_day_IPSL-CM5A-LR_historical_r1i1p1_19500101-20051231.nc")

reference_data <- Start(model = climatology_filenames, var = var0, var_var = 'var_names', 
                        time = values(list(as.POSIXct(start_climatology), as.POSIXct(end_climatology))),
                        lat = values(list(lat.min, lat.max)), lon = values(list(lon.min, lon.max)), 
                        lon_var = 'lon', lon_reorder = CircularSort(0, 360), return_vars = list(time = 'model', 
                        lon = 'model', lat = 'model'), retrieve = TRUE)
metadata <- attributes(reference_data)

The summer data can be picked by SeasonSelect function from the magic.bsc package.

reference_data <- SeasonSelect(reference_data, seasons = 'JJA')

To define the heat wave it is necessary to select the maximum temperature threshold that must be exceeded. In this example, the 90 th percentile of the maximum temperature during the reference periode is selected. This calculation is performed using Threshold function from magic.bsc package.

quantile <- 0.9
base_range <- as.numeric(c(substr(start_climatology, 1, 4), substr(end_climatology, 1, 4)))
thresholds <- Threshold(reference_data, base_range = as.numeric(base_range), 
                        qtiles = quantile, ncores = detectCores() -1)[152:243,,]
names(dim(thresholds)) <- c('jdays', 'lat', 'lon')                    

2.2- Heatwaves projection

Considering the 8.5 rcp scenario of the IPSL-CM5A-LR model during the period 2020-2040, the data is loaded using the function Start.

start_model <- '2020-01-01'
end_model <- '2040-12-31'
model_filenames <- paste0("/esarchive/scratch/pbretonn/cmip5-cp4cds/",
                          "rcp85/day/tasmax/tasmax_day_IPSL-CM5A-LR_rcp85_r1i1p1_20060101-22051231.nc")
rcp_data <- Start(model = model_filenames, var = var0, var_var = 'var_names',
                  time = values(list(as.POSIXct(start_model), as.POSIXct(end_model))),
                  lat = values(list(lat.min, lat.max)), lon = values(list(lon.min, lon.max)), 
                  lon_var = 'lon', lon_reorder = CircularSort(0, 360), 
                  return_vars = list(time = 'model', lon = 'model', lat = 'model'),
                  retrieve = TRUE)

rcp_data <- SeasonSelect(rcp_data, seasons = 'JJA')

It will be necessary an index identifiying the year in which each value is recorded. In this case, a simple ordered sequence of years is built by repeting the value of the year 92 times (the number of days in summer):

years <- sort(rep(substr(start_model, 1, 4):substr(end_model, 1, 4), 92))

The duration of the heatwaves is obtained by using the function Apply with WaveDuration function. The parameter case is defined as heat to indicate that we are considering heatwaves and the minimum length of a heatwave is 5 days in this example. This function returns the number of days for each summer in which the maximum temperature is exceeding the 90th percentile of the reference period when they occur in a cluster of a minimum length of 5 consequtive days. This means that isolated events are not included.

duration <- Apply(list(rcp_data, thresholds), target_dims = list(c('time'), c('jdays')), AtomicFun = WaveDuration, 
             case = 'heat', min.conse = 5, indices = years, output_dims = 'time')

The spatial representation of the maximum, mean and minimum duration of heat waves can be plotted and saved in the working directory by running:

lat <- attr(rcp_data, "Variables")$dat1$lat
lon <- attr(rcp_data, "Variables")$dat1$lon
breaks <- seq(0,92,4)
png("Spatial_Heatwave_rcp85_2020-2040_90thpercentile_1971-2000.png", width = 8, height = 12, units = 'in', 
    res = 100, type = "cairo")
par(mfrow=c(4,1))
PlotEquiMap(apply(duration$output1, c(2,3), max), lon = lon, lat = lat, brks = breaks, drawleg = FALSE, 
            filled.continents = FALSE, toptitle = c("Heatwave duration rcp 8.5 2020-2040 compared 90th percentile 1971-2000","Maximum"), title_scale = 0.8,
            cols = heat.colors(length(breaks)-1)[(length(breaks)-1):1])
PlotEquiMap(apply(duration$output1, c(2,3), mean), lon = lon, lat = lat, filled.continents = FALSE, brks = breaks, 
            drawleg = FALSE, toptitle = "Mean", title_scale = 0.8, 
            cols = heat.colors(length(breaks)-1)[(length(breaks)-1):1])
PlotEquiMap(apply(duration$output1, c(2,3), min), lon = lon, lat = lat, filled.continents = FALSE, brks = breaks, 
            drawleg = FALSE, toptitle = "Minimum", title_scale = 0.8, 
            cols = heat.colors(length(breaks)-1)[(length(breaks)-1):1])
ColorBar(brks = breaks, vertical = FALSE, extra_margin = c(8,1.5,0.5,0.5), title = "Number of summer days",
            cols = heat.colors(length(breaks)-1)[(length(breaks)-1):1])
dev.off()

The zonal average can be computed and plotted to detect differences with latitude:

png("Latitudinal_Heatwave_rcp85_2020-2040_90thpercentile_1971-2000.png", width = 8, height = 5, units = 'in', 
    res = 100, type = "cairo")
layout(matrix(c(1,1,1,2), ncol =4, nrow=1))
matplot(2020:2040, apply(duration$output1, c(1,2), mean), type = "l", lwd = 2, col = heat.colors(26)[26:1], bty = 'n', xlab = "Time (years)", ylab = "Duration (days)", main = "Zonal Average of Heatwaves Durations", cex.axis = 1.3)
ColorBar(brks = c(lat[1] - 0.9473675, lat + 0.9473675), vertical = TRUE, extra_margin = c(2,1.5,2,1.5), 
          title = "Latitude", cols = heat.colors(26)[26:1])
dev.off()

Intensities of heatwave are calculated as the maximum difference between the threshold and the maximum temperature achieved for each summer during a heatwave. Again, Apply function is used with WaveIntensity function:

intensities <- Apply(list(rcp_data, thresholds), target_dims = list(c('time'), c('jdays')), AtomicFun = WaveIntensity, 
             case = 'heat', min.conse = 5, indices = years, output_dims = 'time')

The mean spatial distribution can be plotted and saved by running:

breaks = 0:18
PlotEquiMap(apply(intensities$output1, c(2,3), mean), lon = lon, lat = lat, filled.continents = FALSE, 
            brks = breaks, drawleg = TRUE, toptitle = c("Heatwaves rcp 8.5 2020-2040 compared 90th percentile 1971-2000",             "Mean Intensity"), title_scale = 0.6, cols = heat.colors(length(breaks)-1)[(length(breaks)-1):1], 
            fileout = "MeanIntensityHeatwaves_rcp85_2020-2040_90thpercentile_1971-2000.png")

The temporal evolution of the zonal average can be visualized and saved by:

png("Latitudinal_Intensity_Heatwave_rcp85_2020-2040_90thpercentile_1971-2000.png", width = 8, height = 5, units = 'in', 
    res = 100, type = "cairo")
layout(matrix(c(1,1,1,2), ncol =4, nrow=1))
matplot(2020:2040, apply(intensities$output1, c(1,2), mean), type = "l", lwd = 2, col = heat.colors(26)[26:1], bty = 'n', xlab = "Time (years)", ylab = "Intensity (ºC)", main = "Zonal Average of Heatwaves Intensity", cex.axis = 1.3)
ColorBar(brks = c(lat[1] - 0.9473675, lat + 0.9473675), vertical = TRUE, extra_margin = c(2,1.5,2,1.5), 
          title = "Latitude", cols = heat.colors(26)[26:1])
dev.off()

Another interesting value can be the date in wich the first day of a heatwave is recorded:

start_date <- Apply(list(rcp_data, thresholds), target_dims = list(c('time'), c('jdays')), AtomicFun = WaveStart, 
             case = 'heat', min.conse = 5, indices = years, output_dims = 'time')

The function WaveStart returns the month and day in the format ‘month-day’. It can be convert to a number from 1 to 92 to facilitate future calculations: Notice that function WaveStart returns 0 wether the minimum duration of the heatwaves is not reached.

summer <- c(paste(rep(6,30), 1:30, sep = "-"), paste(rep(7,31), 1:31, sep = "-"), paste(rep(8,31), 1:31, sep = "-"))
start_date <- apply(start_date$output1, c(1,2,3), match, summer)

The spatial distribution of the earliest starting date of a Heatwave can be plotted and saved:

PlotEquiMap(apply(start_date, c(2,3), min, na.rm = TRUE), lon = lon, lat = lat, filled.continents = FALSE, brks = 0:92,
            fileout = "EarliestStartingDateHeatwaves_rcp85_2020-2040_90thpercentile_1971-2000.png",
            toptitle = c("Heatwaves rcp 8.5 2020-2040 compared to 90th percentile of 1971-2000", 
            "Earliest Starting Date"), title_scale = 0.6)

And its temporal evolution by running:

png("Latitudinal_Start_Heatwave_rcp85_2020-2040_90thpercentile_1971-2000.png", width = 8, height = 5, units = 'in', 
    res = 100, type = "cairo")
layout(matrix(c(1,1,1,2), ncol =4, nrow=1))
matplot(2020:2040, apply(start_date, c(1,2), mean, na.rm = TRUE), type = "l", lwd = 2, col = heat.colors(26)[26:1], bty = 'n', xlab = "Time (years)", ylab = "Start Date (days)", main = "Zonal Average of Heatwaves Start Date", cex.axis = 1.3)
ColorBar(brks = c(lat[1] - 0.9473675, lat + 0.9473675), vertical = TRUE, extra_margin = c(2,1.5,2,1.5), 
          title = "Latitude", cols = heat.colors(26)[26:1])
dev.off()