Skip to contents

1 Purpose

This vignette demonstrates how to apply climate perturbations to daily weather series using weathergenr. The workflow modifies precipitation and temperature in a controlled way to reflect user-defined stress scenarios while preserving realistic daily variability. This is useful for hydrological stress testing, climate sensitivity analysis, and impact modeling.

2 Setup

library(weathergenr)
library(ggplot2)

ncfile <- system.file("extdata", "ntoum_era5_data.nc", package = "weathergenr")
ncdata <- read_netcdf(ncfile)

3 Define climate scenario

We define a stress-test scenario representing a drier and warmer climate: 30% reduction in mean precipitation and +2 degrees Celsius temperature increase.

obs_date <- ncdata$date
obs_month <- as.integer(format(obs_date, "%m"))
obs_year <- as.integer(format(obs_date, "%Y"))
obs_year_idx <- obs_year - min(obs_year) + 1L
n_years <- max(obs_year_idx)

# Monthly perturbation vectors (length 12)
precip_mean_change <- rep(0.7, 12)
precip_var_change <- rep(1.0, 12)
temp_mean_change <- rep(2, 12)

# Expand to matrices (n_years x 12) for quantile mapping
precip_mean_change_m <- matrix(precip_mean_change, nrow = n_years, ncol = 12, byrow = TRUE)
precip_var_change_m <- matrix(precip_var_change, nrow = n_years, ncol = 12, byrow = TRUE)

4 Preview quantile mapping

Before applying perturbations across all grid cells, we preview the quantile-mapping diagnostics on a single cell to verify target changes are achieved.

precip_qm <- adjust_precipitation_qm(
  precip = ncdata$data[[1]]$precip,
  mean_factor = precip_mean_change_m,
  var_factor = precip_var_change_m,
  scale_var_with_mean = TRUE,
  exaggerate_extremes = FALSE,
  enforce_target_mean = TRUE,
  month = obs_month,
  year = obs_year_idx,
  min_events = 10,
  validate_output = TRUE,
  diagnostics = TRUE,
  verbose = FALSE
)

print(precip_qm$diagnostics)

=== Precipitation QM Diagnostics ===

Mean and variance changes:
 Statistic Original Adjusted Target Achieved Error
      Mean     7.64     5.35   -30%     -30%   +0%
  Variance    87.85    32.48   -51%     -63%  -12%

Wet/dry day counts:
 Category Original Adjusted Difference
 Dry days      161      183         22
 Wet days     7510     7488        -22

Percentiles (wet days):
   Percentile Original Adjusted Change
 P50 (Median)     5.62     4.03   -28%
          P90    16.13    11.41   -29%
          P95    20.52    14.55   -29%
          P99    39.35    25.23   -36%

Extreme tails:
             Tail Threshold change Mean change
    Top 10% (P90)             -29%        -34%
     Top 5% (P95)             -29%        -36%
     Top 1% (P99)             -36%        -43%
 Top 0.1% (P99.9)             -47%        -51%

Spell lengths:
       Type Original (days) Adjusted (days) Change
 Dry spells             1.3             1.3    +3%
 Wet spells            60.6            54.7   -10%

Monthly mean changes:
 Month Target Achieved Error
   Jan   -30%     -30%   +0%
   Feb   -30%     -30%   +0%
   Mar   -30%     -30%   +0%
   Apr   -30%     -30%   +0%
   May   -30%     -30%   +0%
   Jun   -30%     -30%   +0%
   Jul   -30%     -30%   -0%
   Aug   -30%     -30%   +0%
   Sep   -30%     -30%   +0%
   Oct   -30%     -30%   +0%
   Nov   -30%     -30%   +0%
   Dec   -30%     -30%   +0%

5 Apply climate perturbations

The apply_climate_perturbations() function processes all grid cells. It accepts a list of data frames (data), grid metadata (grid), and a date vector (date). Monthly perturbation factors control precipitation mean/variance scaling and temperature shifts. When diagnostic = TRUE, the function returns both the perturbed data and validation diagnostics.

rlz_future <- apply_climate_perturbations(
  data = ncdata$data,
  grid = ncdata$grid,
  date = ncdata$date,
  precip_mean_factor = precip_mean_change,
  precip_var_factor = precip_var_change,
  temp_delta = temp_mean_change,
  precip_occurrence_factor = NULL,
  precip_intensity_threshold = 0,
  temp_transient = TRUE,
  precip_transient = TRUE,
  precip_occurrence_transient = TRUE,
  compute_pet = FALSE,
  pet_method = "hargreaves",
  qm_fit_method = "mme",
  scale_var_with_mean = TRUE,
  exaggerate_extremes = FALSE,
  extreme_prob_threshold = 0.95,
  extreme_k = 1.2,
  enforce_target_mean = TRUE,
  precip_cap_mm_day = NULL,
  precip_floor_mm_day = NULL,
  precip_cap_quantile = NULL,
  seed = 42,
  diagnostic = TRUE,
  verbose = FALSE
)

The output contains data (list of perturbed data frames) and diagnostic (list of per-grid validation objects, precipitation only).

6 Visualizations

6.1 Monthly precipitation

obs_cell <- ncdata$data[[1]]
sim_cell <- rlz_future$data[[1]]

obs_monthly <- tapply(obs_cell$precip, obs_month, mean, na.rm = TRUE)
sim_monthly <- tapply(sim_cell$precip, obs_month, mean, na.rm = TRUE)

plot_data <- data.frame(
  Month = factor(month.abb[1:12], levels = month.abb),
  Observed = as.numeric(obs_monthly),
  Perturbed = as.numeric(sim_monthly)
)
plot_data_long <- tidyr::pivot_longer(
  plot_data, cols = c("Observed", "Perturbed"),
  names_to = "Series", values_to = "Precipitation"
)

ggplot(plot_data_long, aes(x = Month, y = Precipitation, fill = Series)) +
  geom_col(position = "dodge", width = 0.7) +
  scale_fill_manual(values = c(Observed = "steelblue", Perturbed = "coral")) +
  labs(
    title = "Monthly Mean Precipitation",
    subtitle = "Observed vs. perturbed (30% reduction scenario)",
    y = "Precipitation (mm/day)", x = NULL
  ) +
  theme_bw() +
  theme(legend.position = "bottom")

6.2 Distribution comparison

wet_obs <- obs_cell$precip[obs_cell$precip > 0]
wet_sim <- sim_cell$precip[sim_cell$precip > 0]

dist_data <- data.frame(

  Precipitation = c(wet_obs, wet_sim),
  Series = factor(
    c(rep("Observed", length(wet_obs)), rep("Perturbed", length(wet_sim))),
    levels = c("Observed", "Perturbed")
  )
)

ggplot(dist_data, aes(x = Precipitation, color = Series, fill = Series)) +
  geom_density(alpha = 0.3, linewidth = 1) +
  scale_color_manual(values = c(Observed = "steelblue", Perturbed = "coral")) +
  scale_fill_manual(values = c(Observed = "steelblue", Perturbed = "coral")) +
  scale_x_continuous(limits = c(0, quantile(c(wet_obs, wet_sim), 0.98))) +
  labs(
    title = "Wet-Day Precipitation Distribution",
    x = "Precipitation (mm/day)", y = "Density"
  ) +
  theme_bw() +
  theme(legend.position = "bottom")

6.3 Temperature time series

plot_idx <- seq_len(nrow(obs_cell))

temp_ts <- data.frame(
  Day = plot_idx,
  Observed = obs_cell$temp[plot_idx],
  Perturbed = sim_cell$temp[plot_idx]
)
temp_ts_long <- tidyr::pivot_longer(
  temp_ts, cols = c("Observed", "Perturbed"),
  names_to = "Series", values_to = "Temperature"
)

ggplot(temp_ts_long, aes(x = Day, y = Temperature, color = Series)) +
  geom_line(alpha = 0.7) +
  scale_color_manual(values = c(Observed = "steelblue", Perturbed = "coral")) +
  labs(
    title = "Daily Temperature Time Series",
    subtitle = "Observed vs. +2 deg C shift scenario",
    x = "Day", y = "Temperature (deg C)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")

7 Use cases and extensions

7.1 Step changes

For immediate perturbations instead of transient ramps:

rlz_step <- apply_climate_perturbations(
  data = ncdata$data, grid = ncdata$grid, date = ncdata$date,
  precip_mean_factor = precip_mean_change,
  precip_var_factor = precip_var_change,
  temp_delta = temp_mean_change,
  temp_transient = FALSE,
  precip_transient = FALSE,
  diagnostic = TRUE
)

7.2 Occurrence perturbations

To modify wet-day frequency:

occurrence_factor <- rep(1.0, 12)
occurrence_factor[6:8] <- 0.8  # 20% fewer wet days in summer

rlz_dry_summer <- apply_climate_perturbations(
  data = ncdata$data, grid = ncdata$grid, date = ncdata$date,
  precip_mean_factor = precip_mean_change,
  precip_var_factor = precip_var_change,
  precip_occurrence_factor = occurrence_factor,
  temp_delta = temp_mean_change,
  diagnostic = TRUE
)

7.3 PET recalculation

To propagate temperature changes to PET:

rlz_with_pet <- apply_climate_perturbations(
  data = ncdata$data, grid = ncdata$grid, date = ncdata$date,
  precip_mean_factor = precip_mean_change,
  precip_var_factor = precip_var_change,
  temp_delta = temp_mean_change,
  compute_pet = TRUE,
  pet_method = "hargreaves",
  diagnostic = TRUE
)

7.4 Extreme amplification

To amplify upper-tail changes:

rlz_extreme <- apply_climate_perturbations(
  data = ncdata$data, grid = ncdata$grid, date = ncdata$date,
  precip_mean_factor = precip_mean_change,
  precip_var_factor = precip_var_change,
  temp_delta = temp_mean_change,
  exaggerate_extremes = TRUE,
  extreme_prob_threshold = 0.95,
  extreme_k = 1.2,
  diagnostic = TRUE
)

8 Session info

R version 4.5.2 (2025-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26200)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=English_United States.utf8 
[2] LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: Europe/Amsterdam
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggplot2_4.0.1     weathergenr_1.1.0

loaded via a namespace (and not attached):
 [1] generics_0.1.4     tidyr_1.3.2        class_7.3-23       lattice_0.22-7    
 [5] digest_0.6.39      magrittr_2.0.4     evaluate_1.0.5     grid_4.5.2        
 [9] RColorBrewer_1.1-3 waveslim_1.8.5     multitaper_1.0-17  iterators_1.0.14  
[13] fastmap_1.2.0      foreach_1.5.2      jsonlite_2.0.0     Matrix_1.7-4      
[17] e1071_1.7-17       survival_3.8-6     purrr_1.2.1        scales_1.4.0      
[21] codetools_0.2-20   cli_3.6.5          rlang_1.1.7        splines_4.5.2     
[25] withr_3.0.2        yaml_2.3.12        otel_0.2.0         tools_4.5.2       
[29] dplyr_1.1.4        ncdf4_1.24         fitdistrplus_1.2-4 vctrs_0.6.5       
[33] R6_2.6.1           proxy_0.4-29       lifecycle_1.0.5    MASS_7.3-65       
[37] pkgconfig_2.0.3    pillar_1.11.1      gtable_0.3.6       glue_1.8.0        
[41] xfun_0.55          tibble_3.3.1       tidyselect_1.2.1   rstudioapi_0.18.0 
[45] knitr_1.51         farver_2.1.2       htmltools_0.5.9    patchwork_1.3.2   
[49] labeling_0.4.3     rmarkdown_2.30     compiler_4.5.2     S7_0.2.1