"""
This file is part of CLIMADA.
Copyright (C) 2017 ETH Zurich, CLIMADA contributors listed in AUTHORS.
CLIMADA is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free
Software Foundation, version 3.
CLIMADA is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with CLIMADA. If not, see <https://www.gnu.org/licenses/>.
---
Define AgriculturalDrought (AD) class.
WORK IN PROGRESS
"""
__all__ = ['RelativeCropyield']
import logging
from pathlib import Path
import copy
import numpy as np
from matplotlib import pyplot as plt
import cartopy
import shapely.geometry
from scipy import sparse
import scipy.stats
import h5py
import xarray as xr
from climada.hazard.base import Hazard
from climada.util import dates_times as dt
from climada.util import coordinates as coord
from climada import CONFIG
LOGGER = logging.getLogger(__name__)
HAZ_TYPE = 'RC'
"""Hazard type acronym for Relative Cropyield"""
INT_DEF = 'Yearly Yield'
BBOX = (-180, -85, 180, 85) # [Lon min, lat min, lon max, lat max]
""""Default geographical bounding box of the total global agricultural land extent"""
# ! deposit the input files in: climada_python/data/ISIMIP_crop/Input/Hazard
DATA_DIR = CONFIG.hazard.relative_cropyield.local_data.dir()
INPUT_DIR = DATA_DIR.joinpath('Input', 'Hazard')
"""default paths for input and output data:"""
OUTPUT_DIR = DATA_DIR.joinpath('Output')
#ISIMIP input data specific global variables
"""start and end years per senario as in ISIMIP-filenames"""
YEARCHUNKS = {'ISIMIP2a': {'yearrange': (1980, 1999), 'startyear': 1980, 'endyear': 1999,
'yearrange_mean': (1980, 1999)},
'historical': {'yearrange': (1976, 2005), 'startyear': 1861, 'endyear': 2005,
'yearrange_mean': (1976, 2005)},
'historical_ISIMIP3b': {'yearrange': (1850, 2014),
'startyear': 1850, 'endyear': 2014,
'yearrange_mean': (1983, 2013)},
'rcp26': {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2099},
'rcp26-2': {'yearrange': (2100, 2299), 'startyear': 2100, 'endyear': 2299},
'rcp60': {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2099},
'rcp85': {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2099},
'ssp585': {'yearrange': (2015, 2100), 'startyear': 2015, 'endyear': 2100},
'ssp126': {'yearrange': (2015, 2100), 'startyear': 2015, 'endyear': 2100},
}
FN_STR_VAR = 'global_annual'
"""filename of ISIMIP output constant part"""
[docs]
class RelativeCropyield(Hazard):
"""Agricultural climate risk: Relative Cropyield (relative to historical mean);
Each year corresponds to one hazard event;
Based on modelled crop yield, from ISIMIP (www.isimip.org, required input data).
Attributes as defined in Hazard and the here defined additional attributes.
Attributes
----------
crop_type : str
crop type ('whe' for wheat, 'mai' for maize, 'soy' for soybeans
and 'ric' for rice)
intensity_def : str
intensity defined as:
'Yearly Yield' [t/(ha*y)], 'Relative Yield', or 'Percentile'
"""
[docs]
def __init__(self, crop:str='', intensity_def:str=INT_DEF, **kwargs):
"""Initialize values.
Parameters
----------
crop_type : str, optional
crop type ('whe' for wheat, 'mai' for maize, 'soy' for soybeans
and 'ric' for rice). Default: ''
intensity_def : str, optional
intensity defined as:
'Yearly Yield' [t/(ha*y)], 'Relative Yield', or 'Percentile'
Default: 'Yearly Yield'
**kwargs : Hazard properties, optional
All other keyword arguments are passed to the Hazard constructor.
"""
Hazard.__init__(self, HAZ_TYPE, **kwargs)
self.crop = crop
self.intensity_def = intensity_def
[docs]
def set_from_isimip_netcdf(self, *args, **kwargs):
"""This function is deprecated, use RelativeCropyield.from_isimip_netcdf instead."""
LOGGER.warning("The use of RelativeCropyield.set_from_isimip_netcdf is deprecated."
"Use RelativeCropyield.from_isimip_netcdf instead.")
self.__dict__ = RelativeCropyield.from_isimip_netcdf(*args, **kwargs).__dict__
[docs]
@classmethod
def from_isimip_netcdf(cls, input_dir=None, filename=None, bbox=None,
yearrange=None, ag_model=None, cl_model=None, bias_corr=None,
scenario=None, soc=None, co2=None, crop=None,
irr=None, fn_str_var=None):
"""Wrapper to fill hazard from crop yield NetCDF file.
Build and tested for output from ISIMIP2 and ISIMIP3, but might also work
for other NetCDF containing gridded crop model output from other sources.
Parameters
----------
input_dir : Path or str
path to input data directory,
default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
filename : string
name of netcdf file in input_dir. If filename is given,
the other parameters specifying the model run are not required!
bbox : list of four floats
bounding box:
[lon min, lat min, lon max, lat max]
yearrange : int tuple
year range for hazard set, f.i. (1976, 2005)
ag_model : str
abbrev. agricultural model (only when input_dir is selected)
f.i. 'clm-crop', 'gepic','lpjml','pepic'
cl_model : str
abbrev. climate model (only when input_dir is selected)
f.i. ['gfdl-esm2m', 'hadgem2-es','ipsl-cm5a-lr','miroc5'
bias_corr : str
bias correction of climate forcing,
f.i. 'ewembi' (ISIMIP2b, default) or 'w5e5' (ISIMIP3b)
scenario : str
climate change scenario (only when input_dir is selected)
f.i. 'historical' or 'rcp60' or 'ISIMIP2a'
soc : str
socio-economic trajectory (only when input_dir is selected)
f.i. '2005soc' or 'histsoc'
co2 : str
CO2 forcing scenario (only when input_dir is selected)
f.i. 'co2' or '2005co2'
crop : str
crop type (only when input_dir is selected)
f.i. 'whe', 'mai', 'soy' or 'ric'
irr : str
irrigation type (only when input_dir is selected)
f.i 'noirr' or 'irr'
fn_str_var : str
FileName STRing depending on VARiable and
ISIMIP simuation round
Returns
-------
RelativeCropyield
Raises
------
NameError
"""
if not fn_str_var:
fn_str_var = FN_STR_VAR
if scenario is None:
scenario = 'historical'
if bias_corr is None:
bias_corr = 'ewembi'
if bbox is None:
bbox = BBOX
if input_dir is None:
input_dir = INPUT_DIR
input_dir = Path(input_dir)
if not Path(input_dir).is_dir():
raise NameError('Input directory %s does not exist' % input_dir)
# The filename is set or other variables (cl_model, scenario) are extracted of the
# specified filename
if filename is None:
yearchunk = YEARCHUNKS[scenario]
filename = '{}_{}_{}_{}_{}_{}_yield-{}-{}_{}_{}_{}.nc'.format(
ag_model, cl_model, bias_corr, scenario, soc, co2,
crop, irr, fn_str_var, yearchunk['startyear'], yearchunk['endyear'])
elif scenario == 'ISIMIP2a':
(_, _, _, _, _, _, _, crop, _, _, startyear, endyearnc) = filename.split('_')
endyear, _ = endyearnc.split('.')
yearchunk = dict()
yearchunk = {'yearrange': (int(startyear), int(endyear)),
'startyear': int(startyear), 'endyear': int(endyear)}
elif scenario == 'test_file':
yearchunk = dict()
yearchunk = {'yearrange': (1976, 2005), 'startyear': 1861,
'endyear': 2005, 'yearrange_mean': (1976, 2005)}
ag_model, cl_model, _, _, soc, co2, crop_prop, *_ = filename.split('_')
_, crop, irr = crop_prop.split('-')
else: # get yearchunk from filename, e.g., for rcp2.6 extended and ISIMIP3
(_, _, _, _, _, _, crop_irr, _, _, year1, year2) = filename.split('_')
yearchunk = {'yearrange': (int(year1), int(year2.split('.')[0])),
'startyear': int(year1),
'endyear': int(year2.split('.')[0])}
_, crop, irr = crop_irr.split('-')
# if no yearrange is given, load full range from input file:
if yearrange is None or len(yearrange) == 0:
yearrange = yearchunk['yearrange']
# define indexes of the netcdf-bands to be extracted, and the
# corresponding event names and dates
# corrected indexes due to the bands in input starting with the index=1
id_bands = np.arange(yearrange[0] - yearchunk['startyear'] + 1,
yearrange[1] - yearchunk['startyear'] + 2).tolist()
# hazard setup: set attributes
[lonmin, latmin, lonmax, latmax] = bbox
haz = cls.from_raster([str(Path(input_dir, filename))], band=id_bands,
geometry=list([shapely.geometry.box(lonmin, latmin, lonmax, latmax)]))
haz.intensity_def = INT_DEF
haz.intensity.data[np.isnan(haz.intensity.data)] = 0.0
haz.intensity.todense()
haz.crop = crop
haz.event_name = [str(n) for n in range(int(yearrange[0]), int(yearrange[-1] + 1))]
haz.frequency = np.ones(len(haz.event_name)) * (1 / len(haz.event_name))
haz.fraction = haz.intensity.copy()
haz.fraction.data.fill(1.0)
haz.units = 't / y / ha'
haz.date = np.array(dt.str_to_date(
[event_ + '-01-01' for event_ in haz.event_name]))
haz.centroids.set_meta_to_lat_lon()
haz.centroids.region_id = (
coord.coord_on_land(haz.centroids.lat, haz.centroids.lon)).astype(dtype=int)
haz.check()
return haz
[docs]
def calc_mean(self, yearrange_mean=None,
save=False, output_dir=None):
"""Calculates mean of the hazard for a given reference time period
Parameters
----------
yearrange_mean : array
time period used to calculate the mean intensity
default: 1976-2005 (historical)
save : boolean
save mean to file? default: False
output_dir : str or Path
path of output directory,
default: {CONFIG.exposures.crop_production.local_data}/Output
Returns
-------
hist_mean(array) :
contains mean value over the given reference
time period for each centroid
"""
if output_dir is None:
output_dir = OUTPUT_DIR
output_dir = Path(output_dir)
if yearrange_mean is None:
yearrange_mean = YEARCHUNKS['historical']['yearrange_mean']
startyear, endyear = yearrange_mean
event_list = [str(n) for n in range(int(startyear), int(endyear + 1))]
mean = self.select(event_names=event_list).intensity.mean(axis=0)
hist_mean = np.squeeze(np.asarray(mean))
if save:
# generate output directories if they do not exist yet
mean_dir = output_dir / 'Hist_mean'
mean_dir.mkdir(parents=True, exist_ok=True)
# save mean_file
mean_file = h5py.File(Path(
mean_dir,
f'hist_mean_{self.crop}_{startyear}-{endyear}.hdf5'
), 'w')
mean_file.create_dataset('mean', data=hist_mean)
mean_file.create_dataset('lat', data=self.centroids.lat)
mean_file.create_dataset('lon', data=self.centroids.lon)
mean_file.close()
return hist_mean
[docs]
def set_rel_yield_to_int(self, *args, **kwargs):
"""This function is deprecated, use function rel_yield_to_int instead."""
LOGGER.warning("The use of RelativeCropyield.set_rel_yield_to_int is deprecated."
"Use function rel_yield_to_int instead.")
self.__dict__ = rel_yield_to_int(self, *args, **kwargs).__dict__
[docs]
def set_percentile_to_int(self, *args, **kwargs):
"""This function is deprecated, use function percentile_to_int instead."""
LOGGER.warning("The use of RelativeCropyield.set_percentile_to_int is deprecated."
"Use function percentile_to_int instead.")
self.__dict__ = percentile_to_int(self, *args, **kwargs).__dict__
[docs]
def plot_intensity_cp(self, event=None, dif=False, axis=None, **kwargs):
"""Plots intensity with predefined settings depending on the intensity definition
Parameters
----------
event : int or str
event_id or event_name
dif : boolean
variable signilizing whether absolute values or the difference between
future and historic are plotted (False: his/fut values; True: difference = fut-his)
axis : geoaxes
axes to plot on
Returns
-------
axes (geoaxes)
"""
if not dif:
if self.intensity_def == 'Yearly Yield':
axes = self.plot_intensity(event=event, axis=axis, cmap='YlGn', vmin=0, vmax=10,
**kwargs)
elif self.intensity_def == 'Relative Yield':
axes = self.plot_intensity(event=event, axis=axis, cmap='RdBu', vmin=-1, vmax=1,
**kwargs)
elif self.intensity_def == 'Percentile':
axes = self.plot_intensity(event=event, axis=axis, cmap='RdBu', vmin=0, vmax=1,
**kwargs)
else:
if self.intensity_def == 'Yearly Yield':
axes = self.plot_intensity(event=event, axis=axis, cmap='RdBu', vmin=-2, vmax=2,
**kwargs)
elif self.intensity_def == 'Relative Yield':
axes = self.plot_intensity(event=event, axis=axis, cmap='RdBu', vmin=-0.5,
vmax=0.5, **kwargs)
return axes
[docs]
def plot_time_series(self, event=None):
"""Plots a time series of intensities (a series of sub plots)
Parameters
----------
event : int or str
event_id or event_name
Returns
-------
figure
"""
# if no event range is given, all events contained in self are plotted
# in the case that a specific range is given as input (event) only the events
# within this time range are plotted
if event is None:
event = self.event_name
else:
event = [str(n) for n in range(event[0], event[1] + 1)]
self.centroids.set_meta_to_lat_lon()
# definition of plot extents
len_lat = abs(self.centroids.lat[0] - self.centroids.lat[-1]) * (2.5 / 13.5)
len_lon = abs(self.centroids.lon[0] - self.centroids.lon[-1]) * (5 / 26)
nr_subplots = len(event)
if len_lon >= len_lat:
columns = int(np.floor(np.sqrt(nr_subplots / (len_lon / len_lat))))
rows = int(np.ceil(nr_subplots / columns))
else:
rows = int(np.floor(np.sqrt(nr_subplots / (len_lat / len_lon))))
columns = int(np.ceil(nr_subplots / rows))
fig, axes = plt.subplots(rows, columns, sharex=True, sharey=True,
figsize=(columns * len_lon, rows * len_lat),
subplot_kw=dict(projection=cartopy.crs.PlateCarree()))
column = 0
row = 0
for year in range(nr_subplots):
axes.flat[year].set_extent([np.min(self.centroids.lon), np.max(self.centroids.lon),
np.min(self.centroids.lat), np.max(self.centroids.lat)])
if rows == 1:
self.plot_intensity_cp(event=event[year], axis=axes[column])
elif columns == 1:
self.plot_intensity_cp(event=event[year], axis=axes[row])
else:
self.plot_intensity_cp(event=event[year], axis=axes[row, column])
if column <= columns - 2:
column = column + 1
else:
column = 0
row = row + 1
return fig
def rel_yield_to_int(haz_cy, hist_mean):
"""Return hazard with relative yield (yearly yield / historic mean) as intensity
Parameters
----------
haz_cy : RelativeCropyield
RelativeCropyield hazard instance with abs. yield as intensity.
hist_mean : np.array
historic mean per centroid
Returns
-------
RelativeCropyield
Hazard with modified intensity [unitless]
"""
# determine idx of the centroids with a mean yield !=0
[idx] = np.where(hist_mean != 0)
# initialize new hazard_matrix
hazard_matrix = np.zeros(haz_cy.intensity.shape, dtype=np.float32)
# compute relative yield for each event:
for event in range(len(haz_cy.event_id)):
hazard_matrix[event, idx] = (haz_cy.intensity[event, idx].todense() / hist_mean[idx]) - 1
new_haz = copy.deepcopy(haz_cy)
new_haz.intensity = sparse.csr_matrix(hazard_matrix)
new_haz.intensity_def = 'Relative Yield'
new_haz.units = ''
return new_haz
def percentile_to_int(haz_cy, reference_intensity=None):
"""returns RC hazard with percentile as intensity.
Parameters
----------
haz_cy : RelativeCropyield
RelativeCropyield hazard instance with abs. yield as intensity.
reference_intensity : AD
intensity to be used as reference
(e.g. the historic intensity can be used in order to be able
to directly compare historic and future projection data)
Returns
-------
RelativeCropyield
hazard with modified intensity
"""
hazard_matrix = np.zeros(haz_cy.intensity.shape)
if reference_intensity is None:
reference_intensity = haz_cy.intensity
for centroid in range(haz_cy.intensity.shape[1]):
nevents = reference_intensity.shape[0]
array = reference_intensity[:, centroid].toarray().reshape(nevents)
hazard_matrix[:, centroid] = np.array([scipy.stats.percentileofscore(array, event)
for event in array])/100
new_haz = copy.deepcopy(haz_cy)
new_haz.intensity = sparse.csr_matrix(hazard_matrix)
new_haz.intensity_def = 'Percentile'
new_haz.units = ''
return new_haz
def set_multiple_rc_from_isimip(input_dir=None, output_dir=None, bbox=None,
isimip_run=None, yearrange_his=None, yearrange_mean=None,
return_data=False, save=True, combine_subcrops=True):
"""Wrapper to generate full hazard set from all ISIMIP-NetCDF files with
crop yield in a given input directory and save it to output directory.
Parameters
----------
input_dir : pathlib.Path or str
path to input data directory,
default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
output_dir : pathlib.Path or str
path to output data directory,
default: {CONFIG.exposures.crop_production.local_data}/Output
bbox : list of four floats
bounding box:
[lon min, lat min, lon max, lat max]
isimip_run : string
name of the ISIMIP run (f.i. ISIMIP2a or ISIMIP2b)
yearrange_his : int tuple
year range for the historical hazard sets
yearrange_mean : int tuple
year range for the historical mean
return_data : boolean
returned output
False: returns list of filenames only
True: returns also list of data
save : boolean
save output data to output_dir
combine_subcrops : boolean
combine crops: ric=ri1+ri2, whe=swh+wwh
Returns
-------
filename_list : list
list of filenames
output_list : list, optional
list of generated output data (hazards and historical mean)
"""
if bbox is None:
bbox = BBOX
if isimip_run is None:
isimip_run = 'ISIMIP2b'
if input_dir is None:
input_dir = INPUT_DIR
if output_dir is None:
output_dir = OUTPUT_DIR
if isinstance(input_dir, str):
input_dir = Path(input_dir)
if not (isinstance(input_dir, Path) and input_dir.is_dir()):
raise NameError('input_dir needs to be valid directory given as str or Path instance')
if isinstance(output_dir, str):
output_dir = Path(output_dir)
if not (isinstance(output_dir, Path) and output_dir.is_dir()):
raise NameError('output_dir needs to be valid directory given as str or Path instance')
filenames = [f.name for f in input_dir.iterdir()
if f.is_file() and not f.name.startswith('.') and not f.name.startswith('mask')]
# generate output directories if they do not exist yet
Path(output_dir, 'Hazard').mkdir(parents=True, exist_ok=True)
Path(output_dir, 'Hist_mean').mkdir(parents=True, exist_ok=True)
filename_list = list()
output_list = list()
(his_file_list, file_props, hist_mean_per_crop,
scenario_list, _, combi_crop_list) = init_hazard_sets_isimip(filenames,
input_dir=input_dir,
bbox=bbox, isimip_run=isimip_run,
yearrange_his=yearrange_his,
combine_subcrops=combine_subcrops)
if (yearrange_mean is None) and (isimip_run == 'ISIMIP2b'):
yearrange_mean = YEARCHUNKS[file_props[his_file_list[0]]['scenario']]['yearrange_mean']
elif (yearrange_mean is None) and (isimip_run == 'ISIMIP3b'):
yearrange_mean = YEARCHUNKS['historical_ISIMIP3b']['yearrange_mean']
# (1983, 2013) # c.f. Jaegermeyr et al. on ISIMIP3b
elif (yearrange_mean is None) and (isimip_run == 'ISIMIP2a'):
yearrange_mean = YEARCHUNKS['ISIMIP2a']['yearrange_mean']
# (1980, 1999)
for his_file in his_file_list:
haz_his, filename, hist_mean = calc_his_haz_isimip(his_file, file_props,
input_dir=input_dir, bbox=bbox,
yearrange_mean=yearrange_mean)
# save the historical mean depending on the crop-irrigation combination
# the idx keeps track of the row in which the hist_mean values are written per crop-irr to
# ensure that all files are assigned to the corresponding crop-irr combination
hist_mean_per_crop[file_props[his_file]['combi_crop_irr']]['value'][
hist_mean_per_crop[file_props[his_file]['combi_crop_irr']]['idx'], :] = hist_mean
hist_mean_per_crop[file_props[his_file]['combi_crop_irr']]['idx'] += 1
filename_list.append(filename)
if return_data:
output_list.append(haz_his)
else: output_list.append(None)
if save:
haz_his.select(reg_id=1).write_hdf5(str(Path(output_dir, 'Hazard', filename)))
if isimip_run in ('ISIMIP2b', 'ISIMIP3b'):
# compute the relative yield for all future scenarios with the corresponding
# historic mean
for scenario in scenario_list: # loop over all scenarios except historical
# check whether future file exists for given historical file and scenario:
haz_fut = None
filename = None
fut_file = '{}_{}_{}_{}_{}_{}_yield-{}-{}_{}_{}_{}.nc'.format(
file_props[his_file]['ag_model'],
file_props[his_file]['cl_model'],
file_props[his_file]['bias_corr'],
scenario,
file_props[his_file]['soc'],
file_props[his_file]['co2'],
file_props[his_file]['crop'],
file_props[his_file]['irr'],
FN_STR_VAR,
YEARCHUNKS[scenario]['startyear'],
YEARCHUNKS[scenario]['endyear']
)
if Path(input_dir, fut_file).is_file():
# if true, calculate and save future hazard set:
haz_fut, filename = calc_fut_haz_isimip(his_file, scenario,
file_props, hist_mean,
input_dir=input_dir,
bbox=bbox,
fut_file=fut_file)
filename_list.append(filename)
if save:
haz_fut.select(reg_id=1)\
.write_hdf5(str(Path(output_dir, 'Hazard', filename)))
if return_data:
output_list.append(haz_fut)
else: output_list.append(None)
if scenario == 'rcp26': # also test for extended
# check whether future file exists for given historical file and scenario:
fut_file = '{}_{}_{}_{}_{}_{}_yield-{}-{}_{}_{}_{}.nc'.format(
file_props[his_file]['ag_model'],
file_props[his_file]['cl_model'],
file_props[his_file]['bias_corr'],
scenario,
file_props[his_file]['soc'],
file_props[his_file]['co2'],
file_props[his_file]['crop'],
file_props[his_file]['irr'],
FN_STR_VAR,
YEARCHUNKS['rcp26-2']['startyear'],
YEARCHUNKS['rcp26-2']['endyear']
)
if Path(input_dir, fut_file).is_file():
# if true, calculate and save future hazard set:
haz_fut, filename = calc_fut_haz_isimip(his_file, scenario,
file_props, hist_mean,
input_dir=input_dir,
bbox=bbox, fut_file=fut_file)
filename_list.append(filename)
if save:
haz_fut.select(reg_id=1)\
.write_hdf5(str(Path(output_dir, 'Hazard', filename)))
if return_data:
output_list.append(haz_fut)
else: output_list.append(None)
# calculate mean hist_mean for each crop-irrigation combination and save as hdf5
# in output_dir (required for full exposure set preparation):
for combi_crop_irr in combi_crop_list:
mean = np.mean((hist_mean_per_crop[combi_crop_irr])['value'], 0)
mean_filename = ('hist_mean_' + combi_crop_irr + '_' + str(yearrange_mean[0]) +'-' +
str(yearrange_mean[1]) + '.hdf5')
filename_list.append(mean_filename)
output_list.append(mean)
if save: # save hist_mean files to hdf5 file:
for idx, filename in enumerate(filename_list):
if 'hist_mean_' in filename:
mean_file = h5py.File(str(Path(output_dir, 'Hist_mean', filename)), 'w')
mean_file.create_dataset('mean', data=output_list[idx])
mean_file.create_dataset('lat', data=haz_his.centroids.lat)
mean_file.create_dataset('lon', data=haz_his.centroids.lon)
mean_file.close()
return filename_list, output_list
def init_hazard_sets_isimip(filenames, input_dir=None, bbox=None, isimip_run=None,
yearrange_his=None, combine_subcrops=True):
"""Initialize full hazard set.
Parameters
----------
filenames : list
list of filenames
input_dir : pathlib.Path
path to input data directory, default: INPUT_DIR
bbox : list of four floats
bounding box:
[lon min, lat min, lon max, lat max], default: BBOX
isimip_run : string
name of the ISIMIP run ('ISIMIP2a', 'ISIMIP2b', or 'ISIMIP3b')
Deafult: 'ISIMIP2b''
yearrange_his : int tuple
year range for the historical hazard sets
combine_subcrops : bool
ignore crops ri2 (2nd harvest rice) and wwh (winter wheat)
at this step (will be added to ri1 and wwh to form ric and whe later on)
Returns
-------
his_file_list : list
list of historical input hazard files
file_props : dict
file properties of all historical input hazard files
hist_mean_per_crop : dict
empty dictonary to save hist_mean values for each
crop-irr combination
scenario_list : list
list of all future scenarios
crop_irr_list : list
list of all crop-irr combinations
combi_crop_irr_list : list
list of all crop-irr combinations for combined crops
"""
# set default values for parameters not defined:
if input_dir is None:
input_dir = Path(INPUT_DIR)
if bbox is None:
bbox = BBOX
if isimip_run is None:
isimip_run = 'ISIMIP2b'
crop_irr_list = list()
combi_crop_irr_list = list()
file_props = dict()
his_file_list = list()
scenario_list = list()
for file in filenames:
if isimip_run in ('ISIMIP2b', 'ISIMIP3b'):
ag_model, cl_model, bias_corr, scenario, soc, co2, crop_prop, *_ = file.split('_')
_, crop, irr = crop_prop.split('-')
combi_crop = crop
if combine_subcrops: # applies to ISIMIP3b: sum yield for sub crop types
if crop in ('ri2', 'wwh'):
# skip ri2 (2nd harvest rice) and wwh (winter wheat) at this step
continue
if crop == 'ri1': # first harvest rice
combi_crop = 'ric'
if crop == 'swh': # spring wheat
combi_crop = 'whe'
if scenario == 'historical':
his_file_list.append(file)
if (yearrange_his is None) and (isimip_run == 'ISIMIP2b'):
yearrange_his = YEARCHUNKS[scenario]['yearrange']
elif (yearrange_his is None) and (isimip_run == 'ISIMIP3b'):
yearrange_his = YEARCHUNKS['historical_ISIMIP3b']['yearrange']
startyear, endyear = yearrange_his
file_props[file] = {'ag_model': ag_model, 'cl_model': cl_model,
'bias_corr': bias_corr, 'soc': soc,
'scenario': scenario, 'co2':co2, 'crop': crop, 'irr': irr,
'startyear': startyear, 'endyear': endyear,
'crop_irr': f'{crop}-{irr}', 'combi_crop': combi_crop,
'combi_crop_irr': f'{combi_crop}-{irr}'
}
elif scenario not in scenario_list:
scenario_list.append(scenario)
elif isimip_run == 'ISIMIP2a':
(ag_model, cl_model, biasco, scenario, harm, irr, _, crop, _, _,
startyear, endyearnc) = file.split('_')
combi_crop = crop
endyear, _ = endyearnc.split('.')
if yearrange_his is not None:
startyear, endyear = (YEARCHUNKS[scenario])['yearrange']
file_props[file] = dict()
file_props[file] = {'ag_model': ag_model, 'cl_model': cl_model, 'scenario': 'ISIMIP2a',
'bc':biasco, 'harm':harm, 'crop': crop, 'irr': irr,
'crop_irr': f'{crop}-{irr}', 'startyear': int(startyear),
'endyear': int(endyear), 'combi_crop': combi_crop,
'combi_crop_irr': f'{combi_crop}-{irr}'}
his_file_list.append(file)
elif isimip_run == 'test_file':
ag_model, cl_model, _, _, soc, co2, crop_prop, *_ = file.split('_')
_, crop, irr = crop_prop.split('-')
combi_crop = crop
his_file_list.append(file)
startyear, endyear = yearrange_his
file_props[file] = {'ag_model': ag_model, 'cl_model': cl_model, 'soc':soc,
'scenario': 'test_file', 'co2':co2, 'crop': crop, 'irr': irr,
'startyear': startyear, 'endyear': endyear,
'crop_irr': f'{crop}-{irr}', 'combi_crop': combi_crop,
'combi_crop_irr': f'{combi_crop}-{irr}'}
else:
raise ValueError(f'Invalid value for isimip_run: {isimip_run}')
if f'{crop}-{irr}' not in crop_irr_list:
crop_irr_list.append(f'{crop}-{irr}')
if f'{combi_crop}-{irr}' not in combi_crop_irr_list:
combi_crop_irr_list.append(f'{combi_crop}-{irr}')
# generate hazard using the first file to determine the size of the historic mean
# file structure: ag_model _ cl_model _ scenario _ soc _ co2 _
# yield-crop-irr _ fn_str_var _ startyear _ endyear . nc
#e.g. gepic_gfdl-esm2m_ewembi_historical_2005soc_co2_yield-whe-noirr_
# global_annual_1861_2005.nc
haz_dummy = RelativeCropyield()
haz_dummy.set_from_isimip_netcdf(input_dir=input_dir, filename=his_file_list[0], bbox=bbox,
scenario=file_props[his_file_list[0]]['scenario'],
yearrange=(file_props[his_file_list[0]]['startyear'],
file_props[his_file_list[0]]['endyear']))
# initiate the historic mean for each combination of crop and irrigation type
# the idx keeps track of the row in which the hist_mean values are written per crop-irr to
# ensure that all files are assigned to the corresponding crop-irr combination
hist_mean_per_crop = dict()
for i, combi_crop_irr in enumerate(combi_crop_irr_list):
crop, irr = crop_irr_list[i].split('-')
amount_crop_irr = sum((crop in s) and (irr in s) for s in his_file_list)
hist_mean_per_crop[combi_crop_irr] = dict()
hist_mean_per_crop[combi_crop_irr] = {
'value': np.zeros([amount_crop_irr, haz_dummy.intensity.shape[1]]),
'idx': 0}
return his_file_list, file_props, hist_mean_per_crop, scenario_list, \
crop_irr_list, combi_crop_irr_list
def calc_his_haz_isimip(his_file, file_props, input_dir=None, bbox=None,
yearrange_mean=None):
"""Create historical hazard and calculate historical mean.
Parameters
----------
his_file : string
file name of historical input hazard file
file_props : dict
file properties of all historical input hazard files
input_dir : Path
path to input data directory, default: INPUT_DIR
bbox : list of four floats
bounding box:
[lon min, lat min, lon max, lat max]
yearrange_mean : int tuple
year range for the historical mean
default: 1976 - 2005
Returns
-------
haz_his : RelativeCropyield
historical hazard
filename : string
name to save historical hazard
hist_mean : array
historical mean of the historical hazard
"""
if input_dir is None:
input_dir = Path(INPUT_DIR)
if bbox is None:
bbox = BBOX
haz_his = RelativeCropyield()
haz_his.set_from_isimip_netcdf(input_dir=input_dir, filename=his_file, bbox=bbox,
scenario=file_props[his_file]['scenario'],
yearrange=np.array([file_props[his_file]['startyear'],
file_props[his_file]['endyear']]))
crop = file_props[his_file]['crop']
# combine subcrops if crop and combi_crop not the same:
if crop != file_props[his_file]['combi_crop']:
if crop in ('ri2', 'wwh'):
raise ValueError('Invalid subcrop type before combination')
elif crop == 'swh': # whe = swh+wwh
his_file2 = his_file.replace('_yield-swh-', '_yield-wwh-')
elif crop == 'ri1': # ric = ri1+ri2
his_file2 = his_file.replace('_yield-ri1-', '_yield-ri2-')
else:
his_file2 = None
if his_file2 and (input_dir / his_file2).is_file():
haz_his2 = RelativeCropyield()
haz_his2.set_from_isimip_netcdf(input_dir=input_dir, filename=his_file2, bbox=bbox,
scenario=file_props[his_file]['scenario'],
yearrange=np.array([file_props[his_file]['startyear'],
file_props[his_file]['endyear']]))
# The masks in the NetCDF file divides all wheat growing areas globally in two distinct
# categories, either growing winter wheat (wwh) or spring wheat (swh). Since the hazard
# sets for wwh and swh are calculated for the whole globe ("all crops everywhere") we
# need to decide for each grid cell which one to take when combining 'wwh' and 'swh'
# into one combined crop wheat (whe):
if crop == 'swh':
# mask of winter wheat in spring wheat and vice versa:
whe_mask = read_wheat_mask_isimip3(input_dir=input_dir, bbox=bbox)
haz_his.intensity = sparse.csr_matrix(np.multiply(haz_his.intensity.todense(),
whe_mask.swh_mask.values.flatten()
)
)
haz_his2.intensity = sparse.csr_matrix(
np.multiply(haz_his2.intensity.todense(), whe_mask.wwh_mask.values.flatten()
))
# replace NaN by 0.0:
haz_his.intensity.data[np.isnan(haz_his.intensity.data)] = 0.0
haz_his2.intensity.data[np.isnan(haz_his2.intensity.data)] = 0.0
# sum intensities of subcrops while intensity is still abs. yield:
haz_his.intensity = haz_his.intensity + haz_his2.intensity
haz_his.crop = file_props[his_file]['combi_crop']
# hazard intensity is transformed from absolute yield [t / (ha * yr)]
# to yield relative to historical mean of same run (fractional yield):
hist_mean = haz_his.calc_mean(yearrange_mean)
haz_his.set_rel_yield_to_int(hist_mean)
crop_irr = file_props[his_file]['combi_crop_irr']
if file_props[his_file]['scenario'] == 'ISIMIP2a':
filename = ('haz' + '_' + file_props[his_file]['ag_model'] + '_' +
file_props[his_file]['cl_model'] +'_' + file_props[his_file]['bc'] +
'_' + file_props[his_file]['harm'] + '_' + crop_irr + '_' +
str(file_props[his_file]['startyear']) + '-' +
str(file_props[his_file]['endyear']) + '.hdf5')
else:
filename = ('haz' + '_' + file_props[his_file]['ag_model'] + '_' +
file_props[his_file]['cl_model'] + '_' + file_props[his_file]['scenario'] +
'_' + file_props[his_file]['soc'] + '_' + file_props[his_file]['co2'] +
'_' + crop_irr + '_' + str(file_props[his_file]['startyear']) + '-' +
str(file_props[his_file]['endyear']) + '.hdf5')
return haz_his, filename, hist_mean
def calc_fut_haz_isimip(his_file, scenario, file_props, hist_mean, input_dir=None,
bbox=None, fut_file=None, yearrange_fut=None):
"""Create future hazard.
Parameters
----------
his_file : string
file name of historical input hazard file
scenario : string
future scenario, e.g. rcp60
file_props : dict
file properties of all historical input hazard files
hist_mean : array
historical mean of the historical hazard for the same model
combination and crop-irr cobination
input_dir : Path
path to input data directory, default: INPUT_DIR
bbox : list of four floats
bounding box:
[lon min, lat min, lon max, lat max]
fut_file : string
file name of future input hazard file. If given,
prefered over scenario and file_props
yearrange_fut : tuple
start and end year to be extracted
For None (default) yearrange_fut is set from filename or YEARCHUNKS
Returns
-------
haz_fut : RelativeCropyield
future hazard
filename : string
name to save future hazard
"""
if input_dir is None:
input_dir = Path(INPUT_DIR)
if bbox is None:
bbox = BBOX
if yearrange_fut is None:
if isinstance(fut_file, str) and (len(fut_file.split('_')[-2]) == 4):
yearrange_fut = (int(fut_file.split('_')[-2]),
int(fut_file.split('_')[-1].split('.')[0])
)
else: # define yearrange from defaults if not specified by user or filename:
yearrange_fut = (YEARCHUNKS[scenario]['startyear'],
YEARCHUNKS[scenario]['endyear'])
haz_fut = RelativeCropyield()
haz_fut.set_from_isimip_netcdf(input_dir=input_dir, filename=fut_file,
bbox=bbox, yearrange=yearrange_fut,
ag_model=file_props[his_file]['ag_model'],
cl_model=file_props[his_file]['cl_model'],
bias_corr=file_props[his_file]['bias_corr'],
scenario=scenario,
soc=file_props[his_file]['soc'],
co2=file_props[his_file]['co2'],
crop=file_props[his_file]['crop'],
irr=file_props[his_file]['irr'])
crop = file_props[his_file]['crop']
# combine subcrops if crop and combi_crop not the same:
if crop != file_props[his_file]['combi_crop']:
if crop in ('ri2', 'wwh'):
raise ValueError('Invalid subcrop type before combination')
elif crop == 'swh': # whe = swh+wwh
fut_file2 = fut_file.replace('_yield-swh-', '_yield-wwh-')
elif crop == 'ri1': # ric = ri1+ri2
fut_file2 = fut_file.replace('_yield-ri1-', '_yield-ri2-')
else:
fut_file2 = None
if fut_file2 and Path(input_dir, fut_file2).is_file():
haz_fut2 = RelativeCropyield()
haz_fut2.set_from_isimip_netcdf(input_dir=input_dir, filename=fut_file2,
bbox=bbox, yearrange=yearrange_fut,
ag_model=file_props[his_file]['ag_model'],
cl_model=file_props[his_file]['cl_model'],
bias_corr=file_props[his_file]['bias_corr'],
scenario=scenario,
soc=file_props[his_file]['soc'],
co2=file_props[his_file]['co2'],
crop=file_props[his_file]['crop'],
irr=file_props[his_file]['irr'])
if crop == 'swh':
# mask of winter wheat in spring wheat and vice versa:
whe_mask = read_wheat_mask_isimip3(input_dir=input_dir, bbox=bbox)
haz_fut.intensity = sparse.csr_matrix(np.multiply(haz_fut.intensity.todense(),
whe_mask.swh_mask.values.flatten()
)
)
haz_fut2.intensity = sparse.csr_matrix(
np.multiply(haz_fut2.intensity.todense(), whe_mask.wwh_mask.values.flatten()
))
# replace NaN by 0.0:
haz_fut.intensity.data[np.isnan(haz_fut.intensity.data)] = 0.0
haz_fut2.intensity.data[np.isnan(haz_fut2.intensity.data)] = 0.0
# sum intensities of subcrops while intensity is still abs. yield:
haz_fut.intensity = haz_fut.intensity + haz_fut2.intensity
haz_fut.crop = file_props[his_file]['combi_crop']
haz_fut.set_rel_yield_to_int(hist_mean) # set intensity to relative yield
filename = ('haz' + '_' + file_props[his_file]['ag_model'] + '_' +
file_props[his_file]['cl_model'] + '_' + scenario + '_' +
file_props[his_file]['soc'] + '_' + file_props[his_file]['co2'] +
'_' + file_props[his_file]['combi_crop'] + '-' + file_props[his_file]['irr']+ '_' +
str(yearrange_fut[0]) + '-' + str(yearrange_fut[1]) + '.hdf5')
return haz_fut, filename
def read_wheat_mask_isimip3(input_dir=None, filename=None, bbox=None):
"""for ISIMIP3, get masks for spring wheat (swh) and winter wheat (wwh).
Required in set_multiple_rc_from_isimip() if isimip_version is ISIMIP3b and
combine_crops is True.
Parameters
----------
input_dir : Path or str
path to directory containing input file, default: INPUT_DIR
filename : str
name of file
bbox : tuple
geogr. bounding box, tuple or array with for elements.
Returns
-------
whe_mask (xarray)
"""
if input_dir is None:
input_dir = Path(INPUT_DIR)
if filename is None:
filename = CONFIG.hazard.relative_cropyield.filename_wheat_mask.str()
if bbox is None:
bbox = BBOX
whe_mask = xr.open_dataset(Path(input_dir, filename), decode_times=False)
[lonmin, latmin, lonmax, latmax] = bbox
return whe_mask.sel(lon=slice(lonmin, lonmax), lat=slice(latmax, latmin))
def plot_comparing_maps(haz_his, haz_fut, axes=None, nr_cli_models=1, model=1):
"""Plots comparison maps of historic and future data and their difference fut-his
Parameters
----------
haz_his : RelativeCropyield
historic hazard
haz_fut : RelativeCropyield
future hazard
axes : Geoaxes
subplot axes that are generated if not given
(sets the figure size depending on the extent of the single plots and
the amount of rows)
nr_cli_models : int
number of climate models and respectively nr of rows within
the subplot
model : int
current row to plot - this method can be used in a loop to plot
subplots in one figure consisting of several rows of subplots.
One row displays the intensity for present and future climate and the difference of
the two for one model-combination (ag_model and cl_model)
Returns
-------
figure, geoaxes
"""
if axes is None:
len_lat = (np.max(haz_his.centroids.lat)-np.min(haz_his.centroids.lat))*(2.5/13.5)
len_lon = (np.max(haz_his.centroids.lon)-np.min(haz_his.centroids.lon))*(5/26)
fig, axes = plt.subplots(nr_cli_models, 3, figsize=(3*len_lon, nr_cli_models*len_lat), \
subplot_kw=dict(projection=cartopy.crs.PlateCarree()))
for subplot in range(3*nr_cli_models):
axes.flat[subplot].set_extent([np.min(haz_his.centroids.lon),
np.max(haz_his.centroids.lon),
np.min(haz_his.centroids.lat),
np.max(haz_his.centroids.lat)])
haz2plot = RelativeCropyield()
haz2plot = copy.deepcopy(haz_his)
haz2plot.event_id = 0
his_mean = sparse.csr_matrix(haz_his.intensity.mean(axis=0))
fut_mean = sparse.csr_matrix(haz_fut.intensity.mean(axis=0))
for subplot in range(3):
if subplot == 0:
haz2plot.intensity = his_mean
elif subplot == 1:
haz2plot.intensity = fut_mean
elif subplot == 2:
haz2plot.intensity = fut_mean - his_mean
if nr_cli_models == 1:
ax1 = haz2plot.plot_intensity_cp(event=0, dif=0, axis=axes[subplot])
else:
ax1 = haz2plot.plot_intensity_cp(event=0, dif=1, axis=axes[model, subplot])
ax1.set_title('')
if nr_cli_models == 1:
cols = ['Historical', 'Future', 'Difference = Future - Historical']
for ax0, col in zip(axes, cols):
ax0.set_title(col, size='large')
return fig, axes