Reproducing results from Hart et al., 2025#
Here, we use climepi to reproduce results from Hart et al., PNAS, 2025 (https://doi.org/10.1073/pnas.2507311122).
Note: Running the code in step 1 will trigger the download of ~130 MB of climate projection data.
[1]:
import climepi # noqa
from climepi import climdata, epimod
2025-12-29 12:24:28,256 [INFO]: rcparams.py(_validate_backend:241) >> Found 'auto' as default backend, checking available backends
2025-12-29 12:24:28,256 [INFO]: rcparams.py(_validate_backend:241) >> Found 'auto' as default backend, checking available backends
2025-12-29 12:24:28,257 [INFO]: rcparams.py(_validate_backend:247) >> Matplotlib is available, defining as default backend
2025-12-29 12:24:28,257 [INFO]: rcparams.py(_validate_backend:247) >> Matplotlib is available, defining as default backend
2025-12-29 12:24:29,355 [INFO]: preview.py(<module>:14) >> arviz_base available, exposing its functions as part of arviz.preview
2025-12-29 12:24:29,355 [INFO]: preview.py(<module>:14) >> arviz_base available, exposing its functions as part of arviz.preview
2025-12-29 12:24:29,399 [INFO]: preview.py(<module>:33) >> arviz_stats available, exposing its functions as part of arviz.preview
2025-12-29 12:24:29,399 [INFO]: preview.py(<module>:33) >> arviz_stats available, exposing its functions as part of arviz.preview
2025-12-29 12:24:29,463 [INFO]: preview.py(<module>:47) >> arviz_plots available, exposing its functions as part of arviz.preview
2025-12-29 12:24:29,463 [INFO]: preview.py(<module>:47) >> arviz_plots available, exposing its functions as part of arviz.preview
1. Loading climate data#
We load daily global climate projections for 2030-2100 in five cities from the ISIMIP project. The data are included as an example dataset (stored on the climepi GitHub repository) accessible via the get_example_dataset() method of the climdata subpackage, but can also be downloaded from the original source using climdata.get_climate_data() as follows:
ds_clim = climdata.get_climate_data(
data_source="isimip",
frequency="daily",
subset={
"years": list(range(2030, 2101)),
"locations": ["London", "Paris", "Istanbul", "Cape Town", "Los Angeles"],
"lon": [-0.08, 2.35, 28.98, 18.42, -118.42],
"lat": [51.51, 48.86, 41.01, -33.93, 33.94],
},
save_dir="some/directory",
)
By default, the data are downloaded to the OS cache directory; change the ‘base_dir’ argument below to use a different file path.
[2]:
ds_clim = climdata.get_example_dataset("isimip_cities_daily", base_dir=None)
ds_clim
[2]:
<xarray.Dataset> Size: 32MB
Dimensions: (time: 25932, bnds: 2, location: 5, scenario: 3, model: 10,
realization: 1)
Coordinates:
* time (time) datetime64[ns] 207kB 2030-01-01T12:00:00 ... 2100-1...
* location (location) <U11 220B 'London' 'Paris' ... 'Los Angeles'
* scenario (scenario) <U6 72B 'ssp126' 'ssp370' 'ssp585'
* model (model) <U13 520B 'gfdl-esm4' 'ipsl-cm6a-lr' ... 'miroc6'
* realization (realization) int64 8B 0
lat (location) float64 40B dask.array<chunksize=(1,), meta=np.ndarray>
lon (location) float64 40B dask.array<chunksize=(1,), meta=np.ndarray>
Dimensions without coordinates: bnds
Data variables:
time_bnds (time, bnds) datetime64[ns] 415kB 2030-01-01 ... 2101-01-01
temperature (location, scenario, model, realization, time) float32 16MB dask.array<chunksize=(1, 1, 1, 1, 25932), meta=np.ndarray>
precipitation (location, scenario, model, realization, time) float32 16MB dask.array<chunksize=(1, 1, 1, 1, 25932), meta=np.ndarray>
lon_bnds (location, bnds) float64 80B dask.array<chunksize=(1, 2), meta=np.ndarray>
lat_bnds (location, bnds) float64 80B dask.array<chunksize=(1, 2), meta=np.ndarray>
Attributes:
title: ISIMIP3b bias-adjusted climate input data
institution: Potsdam Institute for Climate Impact Research (PIK)
project: Inter-Sectoral Impact Model Intercomparison Project phase 3...
contact: ISIMIP cross-sectoral science team <info@isimip.org> <https...
summary: CMIP6 daily output data bias-adjusted and statistically dow...
references: Lange (2019) <https://doi.org/10.5194/gmd-12-3055-2019> and...2. Defining the epidemiological model#
We use the temperature niche model for dengue transmission Aedes albopictus from Mordecai et al., PLOS Negl Trop Dis, 2017 (https://doi.org/10.1371/journal.pntd.0005568), which is included as an example model accessible via the get_example_model() method of the epimod subpackage.
[3]:
suitability_model = epimod.get_example_model("mordecai_ae_albopictus_niche")
We can use the plot_suitability() method to visualize the model, which here simply comprises a range of temperatures within which transmission is assumed to be possible.
[4]:
suitability_model.plot_suitability()
[4]:
3. Running the model#
We use the climepi accessor for xarray Datasets to run the epidemiological model on on the climate data, obtaining projections of the number of days suitable for transmission each year.
[5]:
ds_months_suitable = ds_clim.climepi.run_epi_model(
suitability_model, return_yearly_portion_suitable=True
)
ds_months_suitable
[5]:
<xarray.Dataset> Size: 88kB
Dimensions: (time: 71, location: 5, scenario: 3, model: 10,
realization: 1, bnds: 2)
Coordinates:
* time (time) object 568B 2030-07-02 12:00:00 ... 2100-07-02 1...
* location (location) <U11 220B 'London' 'Paris' ... 'Los Angeles'
* scenario (scenario) <U6 72B 'ssp126' 'ssp370' 'ssp585'
* model (model) <U13 520B 'gfdl-esm4' 'ipsl-cm6a-lr' ... 'miroc6'
* realization (realization) int64 8B 0
lat (location) float64 40B dask.array<chunksize=(1,), meta=np.ndarray>
lon (location) float64 40B dask.array<chunksize=(1,), meta=np.ndarray>
Dimensions without coordinates: bnds
Data variables:
portion_suitable (time, location, scenario, model, realization) int64 85kB dask.array<chunksize=(71, 1, 1, 1, 1), meta=np.ndarray>
lat_bnds (location, bnds) float64 80B dask.array<chunksize=(1, 2), meta=np.ndarray>
lon_bnds (location, bnds) float64 80B dask.array<chunksize=(1, 2), meta=np.ndarray>
time_bnds (time, bnds) object 1kB 2030-01-01 00:00:00 ... 2101-01...4. Visualizing the results#
We now reproduce Figure 1 from Hart et al., 2025, which shows uncertainty in the future number of months suitable, decomposed into contributions from internal climate variability, climate model uncertainty and scenario uncertainty. This is achieved by using the plot_uncertainty_interval_decomposition method from the climepi package. Since only one simulation is available for each model/scenario pair, we estimate the extent of internal variability for each pair based on year-to-year
deviations around a cubic polynomial fit (keyword arguments internal_variability_method='polyfit' and deg=3; note these are the default settings when a single simulation is available per model/scenario pair, but are explicitly set here for clarity). For methodological details, see Hart et al., 2025, as well as Hawkins and Sutton, Bull Am Meteorol Soc, 2009 (https://doi.org/10.1175/2009BAMS2607.1), from which the uncertainty decomposition methodology is adapted.
The return value of plot_uncertainty_interval_decomposition() method is a HoloViews Overlay object. Customization options can be applied using the opts() method.
[6]:
ds_months_suitable.sel(
location="London"
).climepi.plot_uncertainty_interval_decomposition(
uncertainty_level=90,
internal_variability_method="polyfit",
deg=3,
).opts(
ylim=(0, 220),
show_title=False,
legend_position="top_left",
legend_opts={
"location": (5, 150),
"label_text_font_size": "9pt",
"padding": 4,
"spacing": 1,
},
)
[6]: