From 31d6f3dfd613363a7c693b440868a6f45e0aea37 Mon Sep 17 00:00:00 2001
From: ABOLY PIERRE <59005341+abolfirst@users.noreply.github.com>
Date: Mon, 10 Mar 2025 12:26:04 +0000
Subject: [PATCH 1/3] Add files via upload
Add cassava culture in the list of crop production.
@chapan and @dayoro in contribution
---
crop_production.py | 1185 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1185 insertions(+)
create mode 100644 crop_production.py
diff --git a/crop_production.py b/crop_production.py
new file mode 100644
index 000000000..b193aa880
--- /dev/null
+++ b/crop_production.py
@@ -0,0 +1,1185 @@
+"""
+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 .
+
+---
+
+Agriculture exposures from ISIMIP and FAO.
+"""
+import logging
+from pathlib import Path
+import copy
+
+import numpy as np
+import xarray as xr
+import pandas as pd
+import h5py
+from matplotlib import pyplot as plt
+
+from climada import CONFIG
+from climada.entity import Exposures, INDICATOR_IMPF
+import climada.util.coordinates as u_coord
+
+logging.root.setLevel(logging.DEBUG)
+LOGGER = logging.getLogger(__name__)
+
+DEF_HAZ_TYPE = 'RC'
+"""Default hazard type used in impact functions id."""
+
+BBOX = (-180, -85, 180, 85) # [Lon min, lat min, lon max, lat max]
+""""Default geographical bounding box of the total global agricultural land extent"""
+
+#ISIMIP input data specific global variables
+YEARCHUNKS = dict()
+"""start and end years per ISIMIP version and senario as in ISIMIP-filenames
+of landuse data containing harvest area per crop"""
+# two types of 1860soc (1661-2299 not implemented)
+YEARCHUNKS['ISIMIP2'] = dict()
+YEARCHUNKS['ISIMIP2']['1860soc'] = {'yearrange': (1800, 1860), 'startyear': 1661, 'endyear': 1860}
+YEARCHUNKS['ISIMIP2']['histsoc'] = {'yearrange': (1976, 2005), 'startyear': 1861, 'endyear': 2005}
+YEARCHUNKS['ISIMIP2']['2005soc'] = {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2299}
+YEARCHUNKS['ISIMIP2']['rcp26soc'] = {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2099}
+YEARCHUNKS['ISIMIP2']['rcp60soc'] = {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2099}
+YEARCHUNKS['ISIMIP2']['2100rcp26soc'] = {'yearrange': (2100, 2299), 'startyear': 2100,
+ 'endyear': 2299}
+YEARCHUNKS['ISIMIP3'] = dict()
+YEARCHUNKS['ISIMIP3']['histsoc'] = {'yearrange': (1983, 2013), 'startyear': 1850, 'endyear': 2014}
+YEARCHUNKS['ISIMIP3']['2015soc'] = {'yearrange': (1983, 2013), 'startyear': 1850, 'endyear': 2014}
+
+FN_STR_VAR = 'landuse-15crops_annual'
+"""fix filename part in input data"""
+
+CROP_NAME = dict()
+"""mapping of crop names"""
+CROP_NAME['mai'] = {'input': 'maize', 'fao': 'Maize', 'print': 'Maize'}
+CROP_NAME['ric'] = {'input': 'rice', 'fao': 'Rice, paddy', 'print': 'Rice'}
+CROP_NAME['whe'] = {'input': 'temperate_cereals', 'fao': 'Wheat', 'print': 'Wheat'}
+CROP_NAME['soy'] = {'input': 'oil_crops_soybean', 'fao': 'Soybeans', 'print': 'Soybeans'}
+
+CROP_NAME['ri1'] = {'input': 'rice', 'fao': 'Rice, paddy', 'print': 'Rice 1st season'}
+CROP_NAME['ri2'] = {'input': 'rice', 'fao': 'Rice, paddy', 'print': 'Rice 2nd season'}
+CROP_NAME['swh'] = {'input': 'temperate_cereals', 'fao': 'Wheat', 'print': 'Spring Wheat'}
+CROP_NAME['wwh'] = {'input': 'temperate_cereals', 'fao': 'Wheat', 'print': 'Winter Wheat'}
+CROP_NAME['cas'] = {'input': 'cassava', 'fao': 'Cassava, fresh', 'print': 'Cassava fresh'}
+
+
+"""mapping of irrigation parameter long names"""
+IRR_NAME = {'combined': {'name': 'combined'},
+ 'noirr': {'name': 'rainfed'},
+ 'firr': {'name': 'irrigated'},
+ }
+
+"""Conversion factor weight [tons] to nutritional value [kcal].
+Based on Mueller et al. (2021), https://doi.org/10.1088/1748-9326/abd8fc :
+
+"For the aggregation of different crops, we compute total calories, assuming
+net water contents of 12% for maize, spring and winter wheat, 13% for rice and
+9% for soybean, according to Wirsenius (2000) and caloric contents of the
+“as purchased” biomass (i.e. including the water content) of 3.56kcal/g for maize,
+2.8kcal/g for rice, 3.35kcal/g for soybean and of 3.34kcal/g for spring and
+winter wheat, following FAO (2001).” (Müller et al., 2021)
+
+Based on https://www.retssa-ci.com/pages/Numero5/ADAYE/6-Tome-1.Retssa-Vol.3-N%C2%B05-Juin%202020.pdf :
+"The first excerpt (p. 2) states: "The pH of cassava is approximately 4, and minerals account for less than 1% of the ash content. The moisture content can exceed 50%. Cassava is an energy-dense food (over 300 Kcal/100g), but it is low in protein (1.1%) and fat (0.5%). The majority of its caloric value comes from carbohydrates (93% of the dry matter) and its high fiber content.
+Furthermore, the FAO document "Produce More with Less" specifies that the dry matter content of cassava roots is about 30%, which means that one ton of fresh roots contains approximately 300 kilograms of dry matter (p. 14)."
+
+Version 1: conversion factors for crop biomass "as purchased",
+ here applied as default for FAO-normalized production:
+ Production [kcal] = Production [t] * KCAL_PER_TON [kcal/t]
+"""
+
+KCAL_PER_TON = dict()
+KCAL_PER_TON['biomass'] = {'mai': 3.56e6,
+ 'ric': 2.80e6,
+ 'soy': 3.35e6,
+ 'whe': 3.34e6,
+ 'cas': 3.00e6,
+ }
+"""
+Version 2: conversion factors for crop dry matter as simulated by most crop models,
+ here applied as default for raw ISIMIP model yields and derived production values:
+ Yield [kcal] = Yield [t] * KCAL_PER_TON [kcal/t] / (1-net_water_content_fraction)
+"""
+KCAL_PER_TON['drymatter'] = {'mai': 3.56e6 / (1-.12),
+ 'ric': 2.80e6 / (1-.13),
+ 'soy': 3.35e6 / (1-.09),
+ 'whe': 3.34e6 / (1-.12),
+ 'cas': 3.00e6 / (1-.07),
+ }
+
+# Default folder structure for ISIMIP data:
+# deposit the landuse and FAO files in the directory:
+# {CONFIG.exposures.crop_production.local_data}/Input/Exposure
+# The FAO files need to be downloaded and renamed
+# FAO_FILE: contains producer prices per crop, country and year
+# (http://www.fao.org/faostat/en/#data/PP)
+# FAO_FILE2: contains production quantity per crop, country and year
+# (http://www.fao.org/faostat/en/#data/QC)
+DATA_DIR = CONFIG.exposures.crop_production.local_data.dir()
+INPUT_DIR = DATA_DIR.joinpath('Input', 'Exposure')
+FAO_FILE = "FAOSTAT_data_producer_prices.csv"
+FAO_FILE2 = "FAOSTAT_data_production_quantity.csv"
+
+YEARS_FAO = (2008, 2018)
+"""Default years from FAO used (data file contains values for 1991-2018)"""
+
+# default output directory: climada_python/data/ISIMIP_crop/Output/Exposure
+# by default the hist_mean files created by climada_python/hazard/crop_potential are saved in
+# climada_python/data/ISIMIP_crop/Output/hist_mean/
+HIST_MEAN_PATH = DATA_DIR.joinpath('Output', 'Hist_mean')
+OUTPUT_DIR = DATA_DIR.joinpath('Output')
+
+
+class CropProduction(Exposures):
+ """Defines agriculture exposures from ISIMIP input data and FAO crop data
+
+ geopandas GeoDataFrame with metadata and columns (pd.Series) defined in
+ Attributes and Exposures.
+
+ Attributes
+ ----------
+ crop : str
+ crop typee.g., 'mai', 'ric', 'whe', 'soy','cas'
+ """
+
+ _metadata = Exposures._metadata + ['crop']
+
+ def set_from_isimip_netcdf(self, *args, **kwargs):
+ """This function is deprecated, use LitPop.from_isimip_netcdf instead."""
+ LOGGER.warning("The use of LitPop.set_from_isimip_netcdf is deprecated."
+ "Use LitPop.from_isimip_netcdf instead.")
+ self.__dict__ = CropProduction.from_isimip_netcdf(*args, **kwargs).__dict__
+
+ @classmethod
+ def from_isimip_netcdf(cls, input_dir=None, filename=None, hist_mean=None,
+ bbox=None, yearrange=None, cl_model=None, scenario=None,
+ crop=None, irr=None, isimip_version=None,
+ unit=None, fn_str_var=None):
+
+ """Wrapper to fill exposure from NetCDF file from ISIMIP. Requires historical
+ mean relative cropyield module as additional input.
+
+ Parameters
+ ----------
+ input_dir : Path or str
+ path to input data directory,
+ default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
+ filename : string
+ name of the landuse data file to use,
+ e.g. "histsoc_landuse-15crops_annual_1861_2005.nc""
+ hist_mean : str or array
+ historic mean crop yield per centroid (or path)
+ bbox : list of four floats
+ bounding box:
+ [lon min, lat min, lon max, lat max]
+ yearrange : int tuple
+ year range for exposure set
+ e.g., (1990, 2010)
+ scenario : string
+ climate change and socio economic scenario
+ e.g., '1860soc', 'histsoc', '2005soc', 'rcp26soc','rcp60soc','2100rcp26soc'
+ cl_model : string
+ abbrev. climate model (only for future projections of lu data)
+ e.g., 'gfdl-esm2m', 'hadgem2-es', 'ipsl-cm5a-lr','miroc5'
+ crop : string
+ crop type
+ e.g., 'mai', 'ric', 'whe', 'soy','cas'
+ irr : string
+ irrigation type, default: 'combined'
+ f.i 'firr' (full irrigation), 'noirr' (no irrigation) or 'combined'= firr+noirr
+ isimip_version : str
+ 'ISIMIP2' (default) or 'ISIMIP3'
+ unit : string
+ unit of the exposure (per year)
+ f.i 't/y' (default), 'USD/y', or 'kcal/y'
+ fn_str_var : string
+ FileName STRing depending on VARiable and
+ ISIMIP simuation round
+
+ Returns
+ -------
+ Exposure
+ """
+ # parameters not provided in method call are set to default values:
+ if irr is None:
+ irr = 'combined'
+ if not bbox:
+ bbox = BBOX
+ if not input_dir:
+ input_dir = INPUT_DIR
+ input_dir = Path(input_dir)
+ if hist_mean is None:
+ hist_mean = HIST_MEAN_PATH
+ if isinstance(hist_mean, str):
+ hist_mean = Path(hist_mean)
+ if not fn_str_var:
+ fn_str_var = FN_STR_VAR
+ if (not isimip_version) or (isimip_version in ('ISIMIP2a', 'ISIMIP2b')):
+ isimip_version = 'ISIMIP2'
+ elif isimip_version in ('ISIMIP3a', 'ISIMIP3b'):
+ isimip_version = 'ISIMIP3'
+ if (not scenario) or (scenario in ('historical', 'hist')):
+ scenario = 'histsoc'
+ if yearrange is None:
+ yearrange = YEARCHUNKS[isimip_version][scenario]['yearrange']
+ if not unit:
+ unit = 't/y'
+
+ if isinstance(filename, Path): # if Path, extract pure filename as string
+ if filename.is_file() and filename.parent.is_dir():
+ LOGGER.info('input_dir is reset from %s to %s', input_dir, filename.parent)
+ input_dir = filename.parent
+ filename = filename.parts[-1]
+
+ # The filename is set or other variables (cl_model, scenario) are extracted of the
+ # specified filename
+ if filename is None:
+ yearchunk = YEARCHUNKS[isimip_version][scenario]
+ # if scenario == 'histsoc' or scenario == '1860soc':
+ if scenario in ('histsoc', '1860soc'):
+ string = '{}_{}_{}_{}.nc'
+ filepath = Path(input_dir, string.format(scenario, fn_str_var,
+ yearchunk['startyear'],
+ yearchunk['endyear']))
+ else:
+ string = '{}_{}_{}_{}_{}.nc'
+ filepath = Path(input_dir, string.format(scenario, cl_model, fn_str_var,
+ yearchunk['startyear'],
+ yearchunk['endyear']))
+ elif scenario == 'flexible':
+ _, _, _, _, _, _, startyear, endyearnc = filename.split('_')
+ endyear = endyearnc.split('.')[0]
+ yearchunk = dict()
+ yearchunk = {'yearrange': (int(startyear), int(endyear)),
+ 'startyear': int(startyear), 'endyear': int(endyear)}
+ filepath = Path(input_dir, filename)
+ else:
+ scenario, *_ = filename.split('_')
+ yearchunk = YEARCHUNKS[isimip_version][scenario]
+ filepath = Path(input_dir, filename)
+
+ # Dataset is opened and data within the bbox extends is extracted
+ data_set = xr.open_dataset(filepath, decode_times=False)
+ [lonmin, latmin, lonmax, latmax] = bbox
+ data = data_set.sel(lon=slice(lonmin, lonmax), lat=slice(latmax, latmin))
+
+ # The latitude and longitude are set; the region_id is determined
+ lon, lat = np.meshgrid(data.lon.values, data.lat.values)
+ exp = cls()
+ exp.gdf['latitude'] = lat.flatten()
+ exp.gdf['longitude'] = lon.flatten()
+ exp.gdf['region_id'] = u_coord.get_country_code(exp.gdf.latitude,
+ exp.gdf.longitude,
+ gridded=True)
+
+ # The indeces of the yearrange to be extracted are determined
+ time_idx = (int(yearrange[0] - yearchunk['startyear']),
+ int(yearrange[1] - yearchunk['startyear']))
+
+ # The area covered by a grid cell is calculated depending on the latitude
+ area = u_coord.get_gridcellarea(lat, resolution=0.5)
+
+ # The area covered by a crop is calculated as the product of the fraction and
+ # the grid cell size
+ if irr == 'combined':
+ irr_types = ['firr', 'noirr']
+ else:
+ irr_types = [irr]
+ area_crop = dict()
+ for irr_var in irr_types:
+ area_crop[irr_var] = (
+ getattr(
+ data, (CROP_NAME[crop])['input']+'_'+ (IRR_NAME[irr_var])['name']
+ )[time_idx[0]:time_idx[1], :, :].mean(dim='time')*area
+ ).values
+ area_crop[irr_var] = np.nan_to_num(area_crop[irr_var]).flatten()
+
+ # set historic mean, its latitude, and longitude:
+ hist_mean_dict = dict()
+ # if hist_mean is given as np.ndarray or dict,
+ # code assumes it contains hist_mean as returned by relative_cropyield
+ # however structured in dictionary as hist_mean_dict, with same
+ # bbox extensions as the exposure:
+ if isinstance(hist_mean, dict):
+ if not ('firr' in hist_mean.keys() or 'noirr' in hist_mean.keys()):
+ # as a dict hist_mean, needs to contain key 'firr' or 'noirr';
+ # if irr=='combined', both 'firr' and 'noirr' are required.
+ raise ValueError(f'Invalid hist_mean provided: {hist_mean}')
+ hist_mean_dict = hist_mean
+ lat_mean = exp.gdf.latitude.values
+ elif isinstance(hist_mean, np.ndarray) or isinstance(hist_mean, list):
+ hist_mean_dict[irr_types[0]] = np.array(hist_mean)
+ lat_mean = exp.gdf.latitude.values
+ elif Path(hist_mean).is_dir(): # else if hist_mean is given as path to directory
+ # The adequate file from the directory (depending on crop and irrigation) is extracted
+ # and the variables hist_mean, lat_mean and lon_mean are set accordingly
+ for irr_var in irr_types:
+ filename = str(Path(hist_mean, 'hist_mean_%s-%s_%i-%i.hdf5' %(
+ crop, irr_var, yearrange[0], yearrange[1])))
+ hist_mean_dict[irr_var] = (h5py.File(filename, 'r'))['mean'][()]
+ lat_mean = (h5py.File(filename, 'r'))['lat'][()]
+ lon_mean = (h5py.File(filename, 'r'))['lon'][()]
+ elif Path(input_dir, hist_mean).is_file(): # file in input_dir
+ # Hist_mean, lat_mean and lon_mean are extracted from the given file
+ if len(irr_types) > 1:
+ raise ValueError("For irr=='combined', hist_mean cannot be a single file.")
+ hist_mean = h5py.File(str(Path(input_dir, hist_mean)), 'r')
+ hist_mean_dict[irr_types[0]] = hist_mean['mean'][()]
+ lat_mean = hist_mean['lat'][()]
+ lon_mean = hist_mean['lon'][()]
+ elif hist_mean.is_file(): # fall back: complete file path
+ # Hist_mean, lat_mean and lon_mean are extracted from the given file
+ if len(irr_types) > 1:
+ raise ValueError("For irr=='combined', hist_mean can not be single file.")
+ hist_mean = h5py.File(str(Path(input_dir, hist_mean)), 'r')
+ hist_mean_dict[irr_types[0]] = hist_mean['mean'][()]
+ lat_mean = hist_mean['lat'][()]
+ lon_mean = hist_mean['lon'][()]
+ else:
+ raise ValueError(f"Invalid hist_mean provided: {hist_mean}")
+
+ # The bbox is cut out of the hist_mean data file if needed
+ if len(lat_mean) != len(exp.gdf.latitude.values):
+ idx_mean = np.zeros(len(exp.gdf.latitude.values), dtype=int)
+ for i in range(len(exp.gdf.latitude.values)):
+ idx_mean[i] = np.where(
+ (lat_mean == exp.gdf.latitude.values[i])
+ & (lon_mean == exp.gdf.longitude.values[i])
+ )[0][0]
+ else:
+ idx_mean = np.arange(0, len(lat_mean))
+
+ # The exposure [t/y] is computed per grid cell as the product of the area covered
+ # by a crop [ha] and its yield [t/ha/y]
+ exp.gdf['value'] = np.squeeze(area_crop[irr_types[0]] * \
+ hist_mean_dict[irr_types[0]][idx_mean])
+ exp.gdf['value'] = np.nan_to_num(exp.gdf.value) # replace NaN by 0.0
+ for irr_val in irr_types[1:]: # add other irrigation types if irr=='combined'
+ value_tmp = np.squeeze(area_crop[irr_val]*hist_mean_dict[irr_val][idx_mean])
+ value_tmp = np.nan_to_num(value_tmp) # replace NaN by 0.0
+ exp.gdf['value'] += value_tmp
+ exp.description=("Crop production exposure from ISIMIP"
+ f" {CROP_NAME[crop]['print']} {irr}"
+ f" {yearrange[0]} {yearrange[-1]}")
+ exp.value_unit = 't/y' # input unit, will be reset below if required by user
+ exp.crop = crop
+ exp.ref_year = yearrange
+ try:
+ rows, cols, ras_trans = u_coord.pts_to_raster_meta(
+ (exp.gdf.longitude.min(), exp.gdf.latitude.min(),
+ exp.gdf.longitude.max(), exp.gdf.latitude.max()),
+ u_coord.get_resolution(exp.gdf.longitude, exp.gdf.latitude))
+ exp.meta = {
+ 'width': cols,
+ 'height': rows,
+ 'crs': exp.crs,
+ 'transform': ras_trans,
+ }
+ except ValueError:
+ LOGGER.warning('Could not write attribute meta, because exposure'
+ ' has only 1 data point')
+ exp.meta = {}
+
+ if 'USD' in unit:
+ # set_value_to_usd() is called to compute the exposure in USD/y (country specific)
+ exp.set_value_to_usd(input_dir=input_dir)
+ elif 'kcal' in unit:
+ # set_value_to_kcal() is called to compute the exposure in kcal/y
+ # here, biomass=False because most crop models provide yield weight
+ # for dry matter, not biomass:
+ exp.set_value_to_kcal(biomass=False)
+ exp.check()
+ return exp
+
+ def set_from_area_and_yield_nc4(self, *args, **kwargs):
+ """This function is deprecated, use LitPop.from_area_and_yield_nc4 instead."""
+ LOGGER.warning("The use of LitPop.set_from_area_and_yield_nc4 is deprecated."
+ "Use LitPop.from_area_and_yield_nc4 instead.")
+ self.__dict__ = CropProduction.from_area_and_yield_nc4(*args, **kwargs).__dict__
+
+ @classmethod
+ def from_area_and_yield_nc4(cls, crop_type, layer_yield, layer_area,
+ filename_yield, filename_area, var_yield,
+ var_area, bbox=BBOX, input_dir=INPUT_DIR):
+ """
+ Set crop_production exposure from cultivated area [ha] and
+ yield [t/ha/year] provided in two netcdf files with the same grid.
+
+ Both input files need to be netcdf format and come with dimensions
+ 'lon', 'lat' and 'crop'. The information which crop type is saved in which
+ crop layer in each input files needs to be provided manually via
+ the parameters 'layer_*'.
+
+ A convenience wrapper around this expert method is provided with
+ from_spam_ray_mirca().
+
+ Parameters
+ ----------
+ crop_type : str
+ Crop type, e.g. 'mai' for maize, or 'ric', 'whe', 'soy','cas', etc.
+ layer_yield : int
+ crop layer in yield input data set. Index typically starts with 1.
+ layer_area : int
+ crop layer in area input data set. Index typically starts with 1.
+ filename_yield : str
+ Name of netcdf-file containing gridded yield data.
+ Requires coordinates 'lon', 'lat', and 'crop'.
+ filename_area : str
+ Name of netcdf-file containing gridded cultivated area.
+ Requires coordinates 'lon', 'lat', and 'crop'.
+ var_yield : str
+ variable name to be extracted from yield file, e.g. 'yield.rf',
+ 'yield.ir', 'yield.tot', or depending on netcdf structure.
+ var_area : str
+ variable name to be extracted from area file,
+ e.g. 'cultivated area rainfed', 'cultivated area irrigated',
+ 'cultivated area all', or depending on netcdf structure.
+ bbox (tuple of four floats): bounding box:
+ bounding box to be extracted: (lon min, lat min, lon max, lat max).
+ The default is (-180, -85, 180, 85).
+ input_dir : Path, optional
+ directory where input data is found. The default is
+ {CONFIG.exposures.crop_production.local_data}/Input/Exposure.
+
+ Returns
+ -------
+ CropProduction
+ crop production exposure instance based on yield and area data
+ """
+ if isinstance(input_dir, str):
+ input_dir = Path(input_dir)
+ [lonmin, latmin, lonmax, latmax] = bbox
+
+ # extract yield data to xarray.DataArray:
+ data_set_tmp = xr.open_dataset(input_dir / filename_yield, decode_times=False)
+ data_yield = data_set_tmp.sel(lon=slice(lonmin, lonmax),
+ lat=slice(latmax, latmin),
+ crop=layer_yield
+ )[var_yield]
+ # extract cultivated area data to xarray.DataArray:
+ data_set_tmp = xr.open_dataset(input_dir / filename_area, decode_times=False)
+ data_area = data_set_tmp.sel(lon=slice(lonmin, lonmax),
+ lat=slice(latmax, latmin),
+ crop=layer_area
+ )[var_area]
+ del data_set_tmp
+
+ # The latitude and longitude are set; region_id is determined
+ lon, lat = np.meshgrid(data_area.lon.values, data_area.lat.values)
+
+ exp = cls()
+ # initiate coordinates and values in GeoDatFrame:
+ exp.gdf['latitude'] = lat.flatten()
+ exp.gdf['longitude'] = lon.flatten()
+ exp.gdf['region_id'] = u_coord.get_country_code(exp.gdf.latitude,
+ exp.gdf.longitude, gridded=True)
+ exp.gdf[INDICATOR_IMPF + DEF_HAZ_TYPE] = 1
+ exp.gdf[INDICATOR_IMPF] = 1
+ # calc annual crop production, [t/y] = [ha] * [t/ha/y]:
+ exp.gdf['value'] = np.multiply(data_area.values, data_yield.values).flatten()
+
+ exp.crop = crop_type
+ exp.description=(f"Annual crop production from {var_area} and {var_yield} for"
+ f" {exp.crop} from files {filename_area} and {filename_yield}")
+ exp.value_unit = 't/y'
+ try:
+ rows, cols, ras_trans = u_coord.pts_to_raster_meta(
+ (exp.gdf.longitude.min(), exp.gdf.latitude.min(),
+ exp.gdf.longitude.max(), exp.gdf.latitude.max()),
+ u_coord.get_resolution(exp.gdf.longitude, exp.gdf.latitude))
+ exp.meta = {
+ 'width': cols,
+ 'height': rows,
+ 'crs': exp.crs,
+ 'transform': ras_trans,
+ }
+ except ValueError:
+ LOGGER.warning('Could not write attribute meta, because exposure'
+ ' has only 1 data point')
+ exp.meta = {}
+ return exp
+
+ def set_from_spam_ray_mirca(self, *args, **kwargs):
+ """This function is deprecated, use CropPoduction.from_spam_ray_mirca instead."""
+ LOGGER.warning("The use of CropPoduction.set_from_spam_ray_mirca is deprecated."
+ "Use CropPoduction.from_spam_ray_mirca instead.")
+ self.__dict__ = CropProduction.from_spam_ray_mirca(*args, **kwargs).__dict__
+
+ @classmethod
+ def from_spam_ray_mirca(cls, crop_type, irrigation_type='all',
+ bbox=BBOX, input_dir=INPUT_DIR):
+ """
+ Wrapper method around from_area_and_yield_nc4().
+
+ Set crop_production exposure from cultivated area [ha] and
+ yield [t/ha/year] provided in default input files.
+ The default input files are based on the public yield data from
+ SPAM2005 with gaps filled based on Ray et.al (2012); and cultivated area
+ from MIRCA2000, both as post-processed by Jägermeyr et al. 2020; See
+ https://doi.org/10.1073/pnas.1919049117 for more information and cite
+ when using this data for publication.
+
+ Parameters
+ ----------
+ crop_type : str
+ Crop type, e.g. 'mai' for maize, or 'ric', 'whe', 'soy','cas' etc.
+ irrigation_type : str, optional
+ irrigation type to be extracted, the options are:
+ 'all' : total crop production, i.e. irrigated + rainfed
+ 'firr' : fully irrigated
+ 'noirr' : not irrigated, i.e., rainfed
+ The default is 'all'
+ bbox (list of four floats): bounding box:
+ [lon min, lat min, lon max, lat max]
+ input_dir : Path, optional
+ directory where input data is found. The default is
+ {CONFIG.exposures.crop_production.local_data}/Input/Exposure.
+
+ Returns
+ ------
+ Exposure
+ Crop production exposure based on SPAM and MIRCA data set
+ """
+ filename_yield = 'spam_ray_yields.nc4'
+ filename_area = 'cultivated_area_MIRCA_GGCMI.nc4'
+
+ # crop layers and variable names in default input files:
+ layers_yield = {'mai': 1, 'whe': 2, 'soy': 4, 'ric': 3,'cas': 5}
+ layers_area = {'mai': 1, 'whe': 2, 'soy': 3, 'ric': 4,'cas': 5}
+ # Note: layer numbers fo rice and soybean differ between input files.
+ varnames_yield = {'noirr': 'yield.rf',
+ 'firr': 'yield.ir',
+ 'all': 'yield.tot'}
+ varnames_area = {'noirr': 'cultivated area rainfed',
+ 'firr': 'cultivated area irrigated',
+ 'all': 'cultivated area all'}
+
+ # return exposure from netcdf files:
+ return cls.from_area_and_yield_nc4(crop_type, layers_yield[crop_type],
+ layers_area[crop_type],
+ filename_yield, filename_area,
+ varnames_yield[irrigation_type],
+ varnames_area[irrigation_type],
+ bbox=bbox, input_dir=input_dir)
+
+ def set_mean_of_several_isimip_models(self, *args, **kwargs):
+ """This function is deprecated, use
+ CropPoduction.from_mean_of_several_isimip_models instead."""
+ LOGGER.warning("The use of CropPoduction.set_mean_of_several_isimip_models is deprecated."
+ "Use CropPoduction.from_mean_of_several_isimip_models instead.")
+ self.__dict__ = CropProduction.from_mean_of_several_isimip_models(*args, **kwargs).__dict__
+
+ @classmethod
+ def from_mean_of_several_isimip_models(cls, input_dir=None, hist_mean=None, bbox=None,
+ yearrange=None, cl_model=None, scenario=None,
+ crop=None, irr=None, isimip_version=None,
+ unit=None, fn_str_var=None):
+ """Wrapper to init exposure from several NetCDF files with crop yield data
+ from ISIMIP.
+
+ Parameters
+ ----------
+ input_dir : string
+ path to input data directory
+ hist_mean : array
+ historic mean crop production per centroid
+ bbox : list of four floats
+ bounding box:
+ [lon min, lat min, lon max, lat max]
+ yearrange : int tuple
+ year range for exposure set,e.g., (1976, 2005)
+ scenario : string
+ climate change and socio economic scenario
+ e.g., 'histsoc' or 'rcp60soc'
+ cl_model : string
+ abbrev. climate model (only when landuse data
+ is future projection)
+ e.g., 'gfdl-esm2m' etc.
+ crop : string
+ crop type
+ e.g., 'mai', 'ric', 'whe', 'soy','cas'
+ irr : string
+ irrigation type
+ f.i 'rainfed', 'irrigated' or 'combined'= rainfed+irrigated
+ isimip_version : str
+ 'ISIMIP2' (default) or 'ISIMIP3'
+ unit : string
+ unit of the exposure (per year)
+ f.i 't/y' (default), 'USD/y', or 'kcal/y'
+ fn_str_var : string
+ FileName STRing depending on VARiable and
+ ISIMIP simuation round
+
+ Returns
+ -------
+ Exposure
+ """
+ if not bbox:
+ bbox = BBOX
+ if (not isimip_version) or (isimip_version in ('ISIMIP2a', 'ISIMIP2b')):
+ isimip_version = 'ISIMIP2'
+ elif isimip_version in ('ISIMIP3a', 'ISIMIP3b'):
+ isimip_version = 'ISIMIP3'
+ if not input_dir:
+ input_dir = INPUT_DIR
+ input_dir = Path(input_dir)
+ if not hist_mean:
+ hist_mean = HIST_MEAN_PATH
+ if yearrange is None:
+ yearrange = YEARCHUNKS[isimip_version]['histsoc']['yearrange']
+ if not unit:
+ unit = 't/y'
+ if not fn_str_var:
+ fn_str_var = FN_STR_VAR
+ filenames = dict()
+ filenames['all'] = [f for f in Path(input_dir).iterdir()
+ if f.is_file() and 'nc' in f.name and not f.name.startswith('.')]
+
+ # If only files with a certain scenario and or cl_model shall be considered, they
+ # are extracted from the original list of files
+ filenames['subset'] = list()
+ for name in filenames['all']:
+ if cl_model is not None and scenario is not None:
+ if cl_model in name or scenario in name:
+ filenames['subset'].append(name)
+ elif cl_model is not None and scenario is None:
+ if cl_model in name:
+ filenames['subset'].append(name)
+ elif cl_model is None and scenario is not None:
+ if scenario in name:
+ filenames['subset'].append(name)
+ else:
+ filenames['subset'] = filenames['all']
+
+ # The first exposure is calculate to determine its size
+ # and initialize the combined exposure
+ exp = cls.from_isimip_netcdf(input_dir, filename=filenames['subset'][0],
+ hist_mean=hist_mean, bbox=bbox, yearrange=yearrange,
+ crop=crop, irr=irr, isimip_version=isimip_version,
+ unit=unit, fn_str_var=fn_str_var)
+
+ combined_exp = np.zeros([exp.gdf.value.size, len(filenames['subset'])])
+ combined_exp[:, 0] = exp.gdf.value
+
+ # The calculations are repeated for all remaining exposures (starting from index 1 as
+ # the first exposure has been saved in combined_exp[:, 0])
+ for j, fname in enumerate(filenames['subset'][1:]):
+ exp = cls.from_isimip_netcdf(input_dir, filename=fname, hist_mean=hist_mean,
+ bbox=bbox, yearrange=yearrange,
+ crop=crop, irr=irr, unit=unit,
+ isimip_version=isimip_version)
+ combined_exp[:, j+1] = exp.gdf.value
+
+ exp.gdf.value = np.mean(combined_exp, 1)
+ exp.gdf['crop'] = crop
+ exp.check()
+ return exp
+
+
+ def set_value_to_kcal(self, *args, **kwargs):
+ """This function is deprecated, use function value_to_kcal instead."""
+ LOGGER.warning("The use of CropProduction.set_value_to_kcal is deprecated."
+ "Use function value_to_kcal instead.")
+ self.__dict__ = value_to_kcal(self, *args, **kwargs).__dict__
+
+ def set_value_to_usd(self, *args, **kwargs):
+ """This function is deprecated, use functiom value_to_usd instead."""
+ LOGGER.warning("The use of CropProduction.set_value_to_usd is deprecated."
+ "Use function value_to_usd instead.")
+ self.__dict__ = value_to_usd(self, *args, **kwargs).__dict__
+
+ def aggregate_countries(self):
+ """Aggregate exposure data by country.
+
+ Returns
+ -------
+ list_countries : list
+ country codes (numerical ISO3)
+ country_values : array
+ aggregated exposure value
+ """
+
+ list_countries = np.unique(self.gdf.region_id)
+ country_values = np.zeros(len(list_countries))
+ for i, iso_nr in enumerate(list_countries):
+ country_values[i] = self.gdf.loc[self.gdf.region_id == iso_nr].value.sum()
+
+ return list_countries, country_values
+
+
+def value_to_kcal(exp_cp, biomass=True):
+ """Converts the exposure value from tonnes to kcal per year using
+ conversion factor per crop type.
+
+ Parameters
+ ----------
+ exp_cp : CropProduction
+ CropProduction exposure object with units tonnes per year ('t/y')
+ biomass : bool, optional
+ if true, KCAL_PER_TON['biomass'] is used (default,
+ for FAO normalized crop production). If False, KCAL_PER_TON['drymatter']
+ is used (best for crop model output in dry matter, default for
+ raw crop model output).
+ Default: True
+
+ Returns
+ -------
+ new_exp : CropProduction
+ CropProduction exposure object with unit 'kcal/y'
+ """
+ if exp_cp.value_unit != 't/y':
+ LOGGER.warning('self.unit is not t/y.')
+ # create deep copy of exposure
+ new_exp = copy.deepcopy(exp_cp)
+ new_exp.gdf['tonnes_per_year'] = exp_cp.gdf['value'].values
+ if biomass:
+ new_exp.gdf.value *= KCAL_PER_TON['biomass'][exp_cp.crop]
+ else:
+ new_exp.gdf.value *= KCAL_PER_TON['drymatter'][exp_cp.crop]
+
+ new_exp.value_unit = 'kcal/y'
+ return new_exp
+
+def value_to_usd(exp_cp, input_dir=None, yearrange=None):
+ # to do: check api availability?; default yearrange for single year (e.g. 5a)
+ """Calculates the exposure in USD per year using country and year specific
+ data published by the FAO, requires crop production exposure with unit 't/y'
+
+ Parameters
+ ----------
+ exp_cp : CropProduction
+ CropProduction exposure object with units tonnes per year ('t/y')
+ input_dir : Path or str, optional
+ directory containing the input (FAO pricing) data,
+ default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
+ yearrange : np.array, optional
+ year range for prices, can also be set to a single year
+ Default is set to the arbitrary time range (2000, 2018)
+ The data is available for the years 1991-2018
+ crop : str
+ crop type
+ e.g., 'mai', 'ric', 'whe', 'soy', 'cas'
+
+ Returns
+ -------
+ new_exp : CropProduction
+ CropProduction exposure object with unit 'USD/y'
+ """
+ if not input_dir:
+ input_dir = INPUT_DIR
+ input_dir = Path(input_dir)
+ if yearrange is None:
+ yearrange = YEARS_FAO
+ # create deep copy of exposure
+ new_exp = copy.deepcopy(exp_cp)
+
+ # account for the case of only specifying one year as yearrange
+ if len(yearrange) == 1:
+ yearrange = (yearrange[0], yearrange[0])
+
+ # open both FAO files and extract needed variables
+ # FAO_FILE: contains producer prices per crop, country and year
+ fao = dict()
+ fao['file'] = pd.read_csv(input_dir / FAO_FILE)
+ fao['crops'] = fao['file'].Item.values
+ fao['year'] = fao['file'].Year.values
+ fao['price'] = fao['file'].Value.values
+
+ fao_country = u_coord.country_faocode2iso(getattr(fao['file'], 'Area Code').values)
+
+ # create deep copy of exposure
+ new_exp = copy.deepcopy(exp_cp)
+
+ # create a list of the countries contained in the exposure
+ iso3num = list()
+ new_exp.gdf.region_id[exp_cp.gdf.region_id == -99] = 0
+ iso3num = np.asarray(u_coord.country_to_iso(
+ new_exp.gdf.region_id, representation="numeric", fillvalue=999), dtype=object)
+ list_countries = np.unique(iso3num)
+
+ # iterate over all countries that are covered in the exposure, extract the according price
+ # and calculate the crop production in USD/y
+ area_price = np.zeros(new_exp.gdf.value.size)
+ for country in list_countries:
+ [idx_country] = (iso3num == country).nonzero()
+ if country == 999:
+ price = 0
+ area_price[idx_country] = new_exp.gdf.value[idx_country] * price
+ # zero means no country, 999 other country
+ elif country != 0 and country != 999:
+ idx_price = np.where((np.asarray(fao_country) == country) &
+ (np.asarray(fao['crops']) == \
+ (CROP_NAME[new_exp.crop])['fao']) &
+ (fao['year'] >= yearrange[0]) &
+ (fao['year'] <= yearrange[1]))
+ # if no price can be determined for a specific yearrange and country, the world
+ # average for that crop (in the specified yearrange) is used
+ if idx_price[0].size != 0:
+ price = np.mean(fao['price'][idx_price])
+
+ else:
+ idx_price = np.where((np.asarray(fao['crops']) == \
+ (CROP_NAME[new_exp.crop])['fao']) &
+ (fao['year'] >= yearrange[0]) &
+ (fao['year'] <= yearrange[1]))
+ price = np.mean(fao['price'][idx_price])
+ area_price[idx_country] = new_exp.gdf.value[idx_country] * price
+
+ # the exposure values in t/y is saved as 'tonnes_per_year'
+ new_exp.gdf['tonnes_per_year'] = exp_cp.gdf['value'].values
+ new_exp.gdf['value'] = area_price
+ new_exp.value_unit = 'USD/y'
+ new_exp.check()
+ return new_exp
+
+def init_full_exp_set_isimip(input_dir=None, filename=None, hist_mean_dir=None,
+ output_dir=None, bbox=None, yearrange=None, unit=None,
+ isimip_version=None, return_data=False):
+ """Generates CropProduction instances (exposure sets) for all files found in the
+ input directory and saves them as hdf5 files in the output directory.
+ Exposures are aggregated per crop and irrigation type.
+
+ Parameters
+ ----------
+ input_dir : str or Path
+ path to input data directory,
+ default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
+ filename : string
+ if not specified differently, the file
+ 'histsoc_landuse-15crops_annual_1861_2005.nc' will be used
+ output_dir : string
+ path to output data directory
+ bbox : list of four floats
+ bounding box:
+ [lon min, lat min, lon max, lat max]
+ yearrange : array
+ year range for hazard set, e.g., (1976, 2005)
+ isimip_version : str
+ 'ISIMIP2' (default) or 'ISIMIP3'
+ unit : str
+ unit in which to return exposure (e.g., t/y or USD/y)
+ return_data : boolean
+ returned output
+ False: returns list of filenames only, True: returns also list of data
+
+ Returns
+ -------
+ filename_list : list
+ all filenames of saved initiated exposure files
+ output_list : list
+ list containing all inisiated Exposure instances
+ """
+ if not bbox:
+ bbox = BBOX
+ if (not isimip_version) or (isimip_version in ('ISIMIP2a', 'ISIMIP2b')):
+ isimip_version = 'ISIMIP2'
+ elif isimip_version in ('ISIMIP3a', 'ISIMIP3b'):
+ isimip_version = 'ISIMIP3'
+ if not input_dir:
+ input_dir = INPUT_DIR
+ input_dir = Path(input_dir)
+ if not hist_mean_dir:
+ hist_mean_dir = HIST_MEAN_PATH
+ hist_mean_dir = Path(hist_mean_dir)
+ if not output_dir:
+ output_dir = OUTPUT_DIR
+ output_dir = Path(output_dir)
+ if yearrange is None:
+ yearrange = YEARCHUNKS[isimip_version]['histsoc']['yearrange']
+ if not unit:
+ unit = 't/y'
+
+ filenames = [f.name for f in hist_mean_dir.iterdir()
+ if f.is_file() and not f.name.startswith('.')]
+
+ # generate output directory if it does not exist yet
+ target_dir = output_dir / 'Exposure'
+ target_dir.mkdir(exist_ok=True)
+
+ # create exposures for all crop-irrigation combinations and save them
+ filename_list = list()
+ output_list = list()
+ for file in filenames:
+ _, _, crop_irr, *_ = file.split('_')
+ crop, irr = crop_irr.split('-')
+ crop_production = CropProduction.from_isimip_netcdf(input_dir=input_dir,
+ filename=filename,
+ hist_mean=hist_mean_dir, bbox=bbox,
+ isimip_version=isimip_version,
+ yearrange=yearrange, crop=crop, irr=irr, unit=unit)
+ filename_expo = ('crop_production_' + crop + '-'+ irr + '_'
+ + str(yearrange[0]) + '-' + str(yearrange[1]) + '.hdf5')
+ filename_list.append(filename_expo)
+ crop_production.write_hdf5(str(Path(target_dir, filename_expo)))
+ if return_data:
+ output_list.append(crop_production)
+
+ return filename_list, output_list
+
+def normalize_with_fao_cp(exp_firr, exp_noirr, input_dir=None,
+ yearrange=None, unit=None, return_data=True):
+ """Normalize (i.e., bias correct) the given exposures countrywise with the mean
+ crop production quantity documented by the FAO.
+ Refer to the beginning of the script for guidance on where to download the
+ required crop production data from FAO.Stat.
+
+ Parameters
+ ----------
+ exp_firr : crop_production
+ exposure under full irrigation
+ exp_noirr : crop_production
+ exposure under no irrigation
+ input_dir : Path or str
+ directory containing exposure input data,
+ default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
+ yearrange : array
+ the mean crop production in this year range is used to normalize
+ the exposure data
+ Default is set to the arbitrary time range (2008, 2018)
+ The data is available for the years 1961-2018
+ unit : str
+ unit in which to return exposure (t/y or USD/y)
+ return_data : boolean
+ returned output
+ True: returns country list, ratio = FAO/ISIMIP, normalized exposures, crop production
+ per country as documented by the FAO and calculated by the ISIMIP dataset
+ False: country list, ratio = FAO/ISIMIP, normalized exposures
+
+ Returns
+ -------
+ country_list : list
+ List of country codes (numerical ISO3)
+ ratio : list
+ List of ratio of FAO crop production and aggregated exposure
+ for each country
+ exp_firr_norm : CropProduction
+ Normalized CropProduction (full irrigation)
+ exp_noirr_norm : CropProduction
+ Normalized CropProduction (no irrigation)
+ Returns : optional
+ fao_crop_production : list
+ FAO crop production value per country
+ exp_tot_production : list
+ Exposure crop production value per country
+ (before normalization)
+ """
+ if not input_dir:
+ input_dir = INPUT_DIR
+ input_dir = Path(input_dir)
+ if yearrange is None:
+ yearrange = YEARS_FAO
+ if not unit:
+ unit = 't/y'
+ # if the exposure unit is USD/y or kcal/y, temporarily reset the exposure to t/y
+ # (stored in tonnes_per_year) in order to normalize with FAO crop production
+ # values and then apply set_to_XXX() for the normalized exposure to restore the
+ # initial exposure unit
+ if exp_firr.value_unit == 'USD/y' or 'kcal' in exp_firr.value_unit:
+ exp_firr.gdf.value = exp_firr.tonnes_per_year
+ if exp_noirr.value_unit == 'USD/y' or 'kcal' in exp_noirr.value_unit:
+ exp_noirr.gdf.value = exp_noirr.tonnes_per_year
+
+ country_list, countries_firr = exp_firr.aggregate_countries()
+ country_list, countries_noirr = exp_noirr.aggregate_countries()
+
+ exp_tot_production = countries_firr + countries_noirr
+
+ fao = pd.read_csv(input_dir / FAO_FILE2)
+ fao_crops = fao.Item.values
+ fao_year = fao.Year.values
+ fao_values = fao.Value.values
+ fao_code = getattr(fao, 'Area Code').values
+
+ fao_country = u_coord.country_iso2faocode(country_list)
+
+ fao_crop_production = np.zeros(len(country_list))
+ ratio = np.ones(len(country_list))
+ exp_firr_norm = exp_firr.copy(deep=True)
+ exp_noirr_norm = exp_noirr.copy(deep=True)
+
+ # loop over countries: compute ratio & apply normalization:
+ for country, iso_nr in enumerate(country_list):
+ idx = np.where((np.asarray(fao_code) == fao_country[country])
+ & (np.asarray(fao_crops) == (CROP_NAME[exp_firr.crop])['fao'])
+ & (fao_year >= yearrange[0]) & (fao_year <= yearrange[1]))
+ if len(idx) >= 1:
+ fao_crop_production[country] = np.mean(fao_values[idx])
+
+ # if a country has no values in the exposure (e.g. Cyprus) the exposure value
+ # is set to the FAO average value
+ # in this case the ratio is left being 1 (as initiated)
+ if exp_tot_production[country] == 0:
+ exp_tot_production[country] = fao_crop_production[country]
+ elif fao_crop_production[country] != np.nan and fao_crop_production[country] != 0:
+ ratio[country] = fao_crop_production[country] / exp_tot_production[country]
+
+ exp_firr_norm.gdf.value[exp_firr.gdf.region_id == iso_nr] = ratio[country] * \
+ exp_firr.gdf.value[exp_firr.gdf.region_id == iso_nr]
+ exp_noirr_norm.gdf.value[exp_firr.gdf.region_id == iso_nr] = ratio[country] * \
+ exp_noirr.gdf.value[exp_noirr.gdf.region_id == iso_nr]
+
+ if unit == 'USD/y' or exp_noirr.value_unit == 'USD/y':
+ exp_noirr.set_value_to_usd(input_dir=input_dir)
+ elif 'kcal' in unit or 'kcal' in exp_noirr.value_unit:
+ exp_noirr.set_value_to_kcal(biomass=True)
+ # FAO production is provided in biomass, not dry matter
+ if unit == 'USD/y' or exp_firr.value_unit == 'USD/y':
+ exp_firr.set_value_to_usd(input_dir=input_dir)
+ elif 'kcal' in unit or 'kcal' in exp_firr.value_unit:
+ exp_firr.set_value_to_kcal(biomass=True)
+
+ exp_firr_norm.description = " ".join([exp_firr_norm.description or "", "normalized"])
+ exp_noirr_norm.description = " ".join([exp_noirr_norm.description or "", "normalized"])
+
+ if return_data:
+ return country_list, ratio, exp_firr_norm, exp_noirr_norm, \
+ fao_crop_production, exp_tot_production
+ return country_list, ratio, exp_firr_norm, exp_noirr_norm
+
+def normalize_several_exp(input_dir=None, output_dir=None,
+ yearrange=None, unit=None, return_data=True):
+ """
+ Multiple exposure sets saved as HDF5 files in input directory are normalized
+ (i.e. bias corrected) against FAO statistics of crop production.
+
+ Parameters
+ ----------
+ input_dir : Path or str
+ directory containing exposure input data
+ output_dir : Path or str
+ directory containing exposure datasets (output of
+ exposure creation)
+ yearrange : array
+ the mean crop production in this year range is used to normalize
+ the exposure data (default 2008-2018)
+ unit : str
+ unit in which to return exposure (t/y or USD/y)
+ return_data : boolean
+ returned output
+ True: lists containing data for each exposure file. Lists: crops, country list,
+ ratio = FAO/ISIMIP, normalized exposures, crop production
+ per country as documented by the FAO and calculated by the ISIMIP dataset
+ False: lists containing data for each exposure file. Lists: crops, country list,
+ ratio = FAO/ISIMIP, normalized exposures
+
+ Returns
+ -------
+ crop_list : list
+ List of crops
+ country_list : list
+ List of country codes (numerical ISO3)
+ ratio : list
+ List of ratio of FAO crop production and aggregated exposure
+ for each country
+ exp_firr_norm : list
+ List of normalized CropProduction Exposures (full irrigation)
+ exp_noirr_norm : list
+ List of normalize CropProduction Exposures (no irrigation)
+ fao_crop_production : list, optional
+ FAO crop production value per country
+ exp_tot_production : list, optional
+ Exposure crop production value per country
+ (before normalization)
+ """
+ if input_dir is None:
+ input_dir = INPUT_DIR
+ if output_dir is None:
+ output_dir = OUTPUT_DIR
+ output_dir = Path(output_dir)
+ if not unit:
+ unit = 't/y'
+ if yearrange is None:
+ yearrange = YEARS_FAO
+
+ filenames_firr = [f.parts[-1] for f in (output_dir / 'Exposure').iterdir() if
+ f.is_file() if not f.parts[-1].startswith('.') if
+ 'firr' in f.parts[-1]]
+
+ crop_list = list()
+ countries_list = list()
+ ratio_list = list()
+ exp_firr_norm = list()
+ exp_noirr_norm = list()
+ fao_cp_list = list()
+ exp_tot_cp_list = list()
+
+ for file_firr in filenames_firr:
+ _, _, crop_irr, years = file_firr.split('_')
+ crop, _ = crop_irr.split('-')
+ exp_firr = CropProduction.from_hdf5(str(Path(output_dir, 'Exposure', file_firr)))
+
+ filename_noirr = 'crop_production_' + crop + '-' + 'noirr' + '_' + years
+ exp_noirr = CropProduction.from_hdf5(str(Path(output_dir, 'Exposure', filename_noirr)))
+
+ if return_data:
+ countries, ratio, exp_firr2, exp_noirr2, fao_cp, \
+ exp_tot_cp = normalize_with_fao_cp(exp_firr, exp_noirr, input_dir=input_dir,
+ yearrange=yearrange, unit=unit)
+ fao_cp_list.append(fao_cp)
+ exp_tot_cp_list.append(exp_tot_cp)
+ else:
+ countries, ratio, exp_firr2, exp_noirr2 = normalize_with_fao_cp(
+ exp_firr, exp_noirr, input_dir=input_dir,
+ yearrange=yearrange, unit=unit, return_data=False)
+
+ crop_list.append(crop)
+ countries_list.append(countries)
+ ratio_list.append(ratio)
+ exp_firr_norm.append(exp_firr2)
+ exp_noirr_norm.append(exp_noirr2)
+
+ if return_data:
+ return crop_list, countries_list, ratio_list, exp_firr_norm, exp_noirr_norm, \
+ fao_cp_list, exp_tot_cp_list
+ return crop_list, countries_list, ratio_list, exp_firr_norm, exp_noirr_norm
+
+def semilogplot_ratio(crop, countries, ratio, output_dir=None, save=True):
+ """Plot ratio = FAO/ISIMIP against country codes.
+
+ Parameters
+ ----------
+ crop : str
+ crop to plot
+ countries : list
+ country codes of countries to plot
+ ratio : array
+ ratio = FAO/ISIMIP crop production data of countries to plot
+ save : boolean
+ True saves figure, else figure is not saved.
+ output_dir : str
+ directory to save figure
+
+ Returns
+ -------
+ fig (plt figure handle)
+ axes (plot axes handle)
+ """
+ if output_dir is None:
+ output_dir = OUTPUT_DIR
+ output_dir = Path(output_dir)
+ fig = plt.figure()
+ axes = plt.gca()
+ axes.scatter(countries[ratio != 1], ratio[ratio != 1])
+ axes.set_yscale('log')
+ axes.set_ylabel('Ratio= FAO / ISIMIP')
+ axes.set_xlabel('ISO3 country code')
+ axes.set_ylim(np.nanmin(ratio), np.nanmax(ratio))
+ plt.title(crop)
+
+ if save:
+ target_dir = output_dir / 'Exposure_norm_plots'
+ target_dir.mkdir(exist_ok=True)
+ plt.savefig(target_dir / 'fig_ratio_norm_' + crop)
+ return fig, axes
From cd2723f18419956210f6d36eef9c734f04cfc5f3 Mon Sep 17 00:00:00 2001
From: ABOLY PIERRE <59005341+abolfirst@users.noreply.github.com>
Date: Mon, 10 Mar 2025 12:30:07 +0000
Subject: [PATCH 2/3] Delete crop_production.py
---
crop_production.py | 1185 --------------------------------------------
1 file changed, 1185 deletions(-)
delete mode 100644 crop_production.py
diff --git a/crop_production.py b/crop_production.py
deleted file mode 100644
index b193aa880..000000000
--- a/crop_production.py
+++ /dev/null
@@ -1,1185 +0,0 @@
-"""
-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 .
-
----
-
-Agriculture exposures from ISIMIP and FAO.
-"""
-import logging
-from pathlib import Path
-import copy
-
-import numpy as np
-import xarray as xr
-import pandas as pd
-import h5py
-from matplotlib import pyplot as plt
-
-from climada import CONFIG
-from climada.entity import Exposures, INDICATOR_IMPF
-import climada.util.coordinates as u_coord
-
-logging.root.setLevel(logging.DEBUG)
-LOGGER = logging.getLogger(__name__)
-
-DEF_HAZ_TYPE = 'RC'
-"""Default hazard type used in impact functions id."""
-
-BBOX = (-180, -85, 180, 85) # [Lon min, lat min, lon max, lat max]
-""""Default geographical bounding box of the total global agricultural land extent"""
-
-#ISIMIP input data specific global variables
-YEARCHUNKS = dict()
-"""start and end years per ISIMIP version and senario as in ISIMIP-filenames
-of landuse data containing harvest area per crop"""
-# two types of 1860soc (1661-2299 not implemented)
-YEARCHUNKS['ISIMIP2'] = dict()
-YEARCHUNKS['ISIMIP2']['1860soc'] = {'yearrange': (1800, 1860), 'startyear': 1661, 'endyear': 1860}
-YEARCHUNKS['ISIMIP2']['histsoc'] = {'yearrange': (1976, 2005), 'startyear': 1861, 'endyear': 2005}
-YEARCHUNKS['ISIMIP2']['2005soc'] = {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2299}
-YEARCHUNKS['ISIMIP2']['rcp26soc'] = {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2099}
-YEARCHUNKS['ISIMIP2']['rcp60soc'] = {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2099}
-YEARCHUNKS['ISIMIP2']['2100rcp26soc'] = {'yearrange': (2100, 2299), 'startyear': 2100,
- 'endyear': 2299}
-YEARCHUNKS['ISIMIP3'] = dict()
-YEARCHUNKS['ISIMIP3']['histsoc'] = {'yearrange': (1983, 2013), 'startyear': 1850, 'endyear': 2014}
-YEARCHUNKS['ISIMIP3']['2015soc'] = {'yearrange': (1983, 2013), 'startyear': 1850, 'endyear': 2014}
-
-FN_STR_VAR = 'landuse-15crops_annual'
-"""fix filename part in input data"""
-
-CROP_NAME = dict()
-"""mapping of crop names"""
-CROP_NAME['mai'] = {'input': 'maize', 'fao': 'Maize', 'print': 'Maize'}
-CROP_NAME['ric'] = {'input': 'rice', 'fao': 'Rice, paddy', 'print': 'Rice'}
-CROP_NAME['whe'] = {'input': 'temperate_cereals', 'fao': 'Wheat', 'print': 'Wheat'}
-CROP_NAME['soy'] = {'input': 'oil_crops_soybean', 'fao': 'Soybeans', 'print': 'Soybeans'}
-
-CROP_NAME['ri1'] = {'input': 'rice', 'fao': 'Rice, paddy', 'print': 'Rice 1st season'}
-CROP_NAME['ri2'] = {'input': 'rice', 'fao': 'Rice, paddy', 'print': 'Rice 2nd season'}
-CROP_NAME['swh'] = {'input': 'temperate_cereals', 'fao': 'Wheat', 'print': 'Spring Wheat'}
-CROP_NAME['wwh'] = {'input': 'temperate_cereals', 'fao': 'Wheat', 'print': 'Winter Wheat'}
-CROP_NAME['cas'] = {'input': 'cassava', 'fao': 'Cassava, fresh', 'print': 'Cassava fresh'}
-
-
-"""mapping of irrigation parameter long names"""
-IRR_NAME = {'combined': {'name': 'combined'},
- 'noirr': {'name': 'rainfed'},
- 'firr': {'name': 'irrigated'},
- }
-
-"""Conversion factor weight [tons] to nutritional value [kcal].
-Based on Mueller et al. (2021), https://doi.org/10.1088/1748-9326/abd8fc :
-
-"For the aggregation of different crops, we compute total calories, assuming
-net water contents of 12% for maize, spring and winter wheat, 13% for rice and
-9% for soybean, according to Wirsenius (2000) and caloric contents of the
-“as purchased” biomass (i.e. including the water content) of 3.56kcal/g for maize,
-2.8kcal/g for rice, 3.35kcal/g for soybean and of 3.34kcal/g for spring and
-winter wheat, following FAO (2001).” (Müller et al., 2021)
-
-Based on https://www.retssa-ci.com/pages/Numero5/ADAYE/6-Tome-1.Retssa-Vol.3-N%C2%B05-Juin%202020.pdf :
-"The first excerpt (p. 2) states: "The pH of cassava is approximately 4, and minerals account for less than 1% of the ash content. The moisture content can exceed 50%. Cassava is an energy-dense food (over 300 Kcal/100g), but it is low in protein (1.1%) and fat (0.5%). The majority of its caloric value comes from carbohydrates (93% of the dry matter) and its high fiber content.
-Furthermore, the FAO document "Produce More with Less" specifies that the dry matter content of cassava roots is about 30%, which means that one ton of fresh roots contains approximately 300 kilograms of dry matter (p. 14)."
-
-Version 1: conversion factors for crop biomass "as purchased",
- here applied as default for FAO-normalized production:
- Production [kcal] = Production [t] * KCAL_PER_TON [kcal/t]
-"""
-
-KCAL_PER_TON = dict()
-KCAL_PER_TON['biomass'] = {'mai': 3.56e6,
- 'ric': 2.80e6,
- 'soy': 3.35e6,
- 'whe': 3.34e6,
- 'cas': 3.00e6,
- }
-"""
-Version 2: conversion factors for crop dry matter as simulated by most crop models,
- here applied as default for raw ISIMIP model yields and derived production values:
- Yield [kcal] = Yield [t] * KCAL_PER_TON [kcal/t] / (1-net_water_content_fraction)
-"""
-KCAL_PER_TON['drymatter'] = {'mai': 3.56e6 / (1-.12),
- 'ric': 2.80e6 / (1-.13),
- 'soy': 3.35e6 / (1-.09),
- 'whe': 3.34e6 / (1-.12),
- 'cas': 3.00e6 / (1-.07),
- }
-
-# Default folder structure for ISIMIP data:
-# deposit the landuse and FAO files in the directory:
-# {CONFIG.exposures.crop_production.local_data}/Input/Exposure
-# The FAO files need to be downloaded and renamed
-# FAO_FILE: contains producer prices per crop, country and year
-# (http://www.fao.org/faostat/en/#data/PP)
-# FAO_FILE2: contains production quantity per crop, country and year
-# (http://www.fao.org/faostat/en/#data/QC)
-DATA_DIR = CONFIG.exposures.crop_production.local_data.dir()
-INPUT_DIR = DATA_DIR.joinpath('Input', 'Exposure')
-FAO_FILE = "FAOSTAT_data_producer_prices.csv"
-FAO_FILE2 = "FAOSTAT_data_production_quantity.csv"
-
-YEARS_FAO = (2008, 2018)
-"""Default years from FAO used (data file contains values for 1991-2018)"""
-
-# default output directory: climada_python/data/ISIMIP_crop/Output/Exposure
-# by default the hist_mean files created by climada_python/hazard/crop_potential are saved in
-# climada_python/data/ISIMIP_crop/Output/hist_mean/
-HIST_MEAN_PATH = DATA_DIR.joinpath('Output', 'Hist_mean')
-OUTPUT_DIR = DATA_DIR.joinpath('Output')
-
-
-class CropProduction(Exposures):
- """Defines agriculture exposures from ISIMIP input data and FAO crop data
-
- geopandas GeoDataFrame with metadata and columns (pd.Series) defined in
- Attributes and Exposures.
-
- Attributes
- ----------
- crop : str
- crop typee.g., 'mai', 'ric', 'whe', 'soy','cas'
- """
-
- _metadata = Exposures._metadata + ['crop']
-
- def set_from_isimip_netcdf(self, *args, **kwargs):
- """This function is deprecated, use LitPop.from_isimip_netcdf instead."""
- LOGGER.warning("The use of LitPop.set_from_isimip_netcdf is deprecated."
- "Use LitPop.from_isimip_netcdf instead.")
- self.__dict__ = CropProduction.from_isimip_netcdf(*args, **kwargs).__dict__
-
- @classmethod
- def from_isimip_netcdf(cls, input_dir=None, filename=None, hist_mean=None,
- bbox=None, yearrange=None, cl_model=None, scenario=None,
- crop=None, irr=None, isimip_version=None,
- unit=None, fn_str_var=None):
-
- """Wrapper to fill exposure from NetCDF file from ISIMIP. Requires historical
- mean relative cropyield module as additional input.
-
- Parameters
- ----------
- input_dir : Path or str
- path to input data directory,
- default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
- filename : string
- name of the landuse data file to use,
- e.g. "histsoc_landuse-15crops_annual_1861_2005.nc""
- hist_mean : str or array
- historic mean crop yield per centroid (or path)
- bbox : list of four floats
- bounding box:
- [lon min, lat min, lon max, lat max]
- yearrange : int tuple
- year range for exposure set
- e.g., (1990, 2010)
- scenario : string
- climate change and socio economic scenario
- e.g., '1860soc', 'histsoc', '2005soc', 'rcp26soc','rcp60soc','2100rcp26soc'
- cl_model : string
- abbrev. climate model (only for future projections of lu data)
- e.g., 'gfdl-esm2m', 'hadgem2-es', 'ipsl-cm5a-lr','miroc5'
- crop : string
- crop type
- e.g., 'mai', 'ric', 'whe', 'soy','cas'
- irr : string
- irrigation type, default: 'combined'
- f.i 'firr' (full irrigation), 'noirr' (no irrigation) or 'combined'= firr+noirr
- isimip_version : str
- 'ISIMIP2' (default) or 'ISIMIP3'
- unit : string
- unit of the exposure (per year)
- f.i 't/y' (default), 'USD/y', or 'kcal/y'
- fn_str_var : string
- FileName STRing depending on VARiable and
- ISIMIP simuation round
-
- Returns
- -------
- Exposure
- """
- # parameters not provided in method call are set to default values:
- if irr is None:
- irr = 'combined'
- if not bbox:
- bbox = BBOX
- if not input_dir:
- input_dir = INPUT_DIR
- input_dir = Path(input_dir)
- if hist_mean is None:
- hist_mean = HIST_MEAN_PATH
- if isinstance(hist_mean, str):
- hist_mean = Path(hist_mean)
- if not fn_str_var:
- fn_str_var = FN_STR_VAR
- if (not isimip_version) or (isimip_version in ('ISIMIP2a', 'ISIMIP2b')):
- isimip_version = 'ISIMIP2'
- elif isimip_version in ('ISIMIP3a', 'ISIMIP3b'):
- isimip_version = 'ISIMIP3'
- if (not scenario) or (scenario in ('historical', 'hist')):
- scenario = 'histsoc'
- if yearrange is None:
- yearrange = YEARCHUNKS[isimip_version][scenario]['yearrange']
- if not unit:
- unit = 't/y'
-
- if isinstance(filename, Path): # if Path, extract pure filename as string
- if filename.is_file() and filename.parent.is_dir():
- LOGGER.info('input_dir is reset from %s to %s', input_dir, filename.parent)
- input_dir = filename.parent
- filename = filename.parts[-1]
-
- # The filename is set or other variables (cl_model, scenario) are extracted of the
- # specified filename
- if filename is None:
- yearchunk = YEARCHUNKS[isimip_version][scenario]
- # if scenario == 'histsoc' or scenario == '1860soc':
- if scenario in ('histsoc', '1860soc'):
- string = '{}_{}_{}_{}.nc'
- filepath = Path(input_dir, string.format(scenario, fn_str_var,
- yearchunk['startyear'],
- yearchunk['endyear']))
- else:
- string = '{}_{}_{}_{}_{}.nc'
- filepath = Path(input_dir, string.format(scenario, cl_model, fn_str_var,
- yearchunk['startyear'],
- yearchunk['endyear']))
- elif scenario == 'flexible':
- _, _, _, _, _, _, startyear, endyearnc = filename.split('_')
- endyear = endyearnc.split('.')[0]
- yearchunk = dict()
- yearchunk = {'yearrange': (int(startyear), int(endyear)),
- 'startyear': int(startyear), 'endyear': int(endyear)}
- filepath = Path(input_dir, filename)
- else:
- scenario, *_ = filename.split('_')
- yearchunk = YEARCHUNKS[isimip_version][scenario]
- filepath = Path(input_dir, filename)
-
- # Dataset is opened and data within the bbox extends is extracted
- data_set = xr.open_dataset(filepath, decode_times=False)
- [lonmin, latmin, lonmax, latmax] = bbox
- data = data_set.sel(lon=slice(lonmin, lonmax), lat=slice(latmax, latmin))
-
- # The latitude and longitude are set; the region_id is determined
- lon, lat = np.meshgrid(data.lon.values, data.lat.values)
- exp = cls()
- exp.gdf['latitude'] = lat.flatten()
- exp.gdf['longitude'] = lon.flatten()
- exp.gdf['region_id'] = u_coord.get_country_code(exp.gdf.latitude,
- exp.gdf.longitude,
- gridded=True)
-
- # The indeces of the yearrange to be extracted are determined
- time_idx = (int(yearrange[0] - yearchunk['startyear']),
- int(yearrange[1] - yearchunk['startyear']))
-
- # The area covered by a grid cell is calculated depending on the latitude
- area = u_coord.get_gridcellarea(lat, resolution=0.5)
-
- # The area covered by a crop is calculated as the product of the fraction and
- # the grid cell size
- if irr == 'combined':
- irr_types = ['firr', 'noirr']
- else:
- irr_types = [irr]
- area_crop = dict()
- for irr_var in irr_types:
- area_crop[irr_var] = (
- getattr(
- data, (CROP_NAME[crop])['input']+'_'+ (IRR_NAME[irr_var])['name']
- )[time_idx[0]:time_idx[1], :, :].mean(dim='time')*area
- ).values
- area_crop[irr_var] = np.nan_to_num(area_crop[irr_var]).flatten()
-
- # set historic mean, its latitude, and longitude:
- hist_mean_dict = dict()
- # if hist_mean is given as np.ndarray or dict,
- # code assumes it contains hist_mean as returned by relative_cropyield
- # however structured in dictionary as hist_mean_dict, with same
- # bbox extensions as the exposure:
- if isinstance(hist_mean, dict):
- if not ('firr' in hist_mean.keys() or 'noirr' in hist_mean.keys()):
- # as a dict hist_mean, needs to contain key 'firr' or 'noirr';
- # if irr=='combined', both 'firr' and 'noirr' are required.
- raise ValueError(f'Invalid hist_mean provided: {hist_mean}')
- hist_mean_dict = hist_mean
- lat_mean = exp.gdf.latitude.values
- elif isinstance(hist_mean, np.ndarray) or isinstance(hist_mean, list):
- hist_mean_dict[irr_types[0]] = np.array(hist_mean)
- lat_mean = exp.gdf.latitude.values
- elif Path(hist_mean).is_dir(): # else if hist_mean is given as path to directory
- # The adequate file from the directory (depending on crop and irrigation) is extracted
- # and the variables hist_mean, lat_mean and lon_mean are set accordingly
- for irr_var in irr_types:
- filename = str(Path(hist_mean, 'hist_mean_%s-%s_%i-%i.hdf5' %(
- crop, irr_var, yearrange[0], yearrange[1])))
- hist_mean_dict[irr_var] = (h5py.File(filename, 'r'))['mean'][()]
- lat_mean = (h5py.File(filename, 'r'))['lat'][()]
- lon_mean = (h5py.File(filename, 'r'))['lon'][()]
- elif Path(input_dir, hist_mean).is_file(): # file in input_dir
- # Hist_mean, lat_mean and lon_mean are extracted from the given file
- if len(irr_types) > 1:
- raise ValueError("For irr=='combined', hist_mean cannot be a single file.")
- hist_mean = h5py.File(str(Path(input_dir, hist_mean)), 'r')
- hist_mean_dict[irr_types[0]] = hist_mean['mean'][()]
- lat_mean = hist_mean['lat'][()]
- lon_mean = hist_mean['lon'][()]
- elif hist_mean.is_file(): # fall back: complete file path
- # Hist_mean, lat_mean and lon_mean are extracted from the given file
- if len(irr_types) > 1:
- raise ValueError("For irr=='combined', hist_mean can not be single file.")
- hist_mean = h5py.File(str(Path(input_dir, hist_mean)), 'r')
- hist_mean_dict[irr_types[0]] = hist_mean['mean'][()]
- lat_mean = hist_mean['lat'][()]
- lon_mean = hist_mean['lon'][()]
- else:
- raise ValueError(f"Invalid hist_mean provided: {hist_mean}")
-
- # The bbox is cut out of the hist_mean data file if needed
- if len(lat_mean) != len(exp.gdf.latitude.values):
- idx_mean = np.zeros(len(exp.gdf.latitude.values), dtype=int)
- for i in range(len(exp.gdf.latitude.values)):
- idx_mean[i] = np.where(
- (lat_mean == exp.gdf.latitude.values[i])
- & (lon_mean == exp.gdf.longitude.values[i])
- )[0][0]
- else:
- idx_mean = np.arange(0, len(lat_mean))
-
- # The exposure [t/y] is computed per grid cell as the product of the area covered
- # by a crop [ha] and its yield [t/ha/y]
- exp.gdf['value'] = np.squeeze(area_crop[irr_types[0]] * \
- hist_mean_dict[irr_types[0]][idx_mean])
- exp.gdf['value'] = np.nan_to_num(exp.gdf.value) # replace NaN by 0.0
- for irr_val in irr_types[1:]: # add other irrigation types if irr=='combined'
- value_tmp = np.squeeze(area_crop[irr_val]*hist_mean_dict[irr_val][idx_mean])
- value_tmp = np.nan_to_num(value_tmp) # replace NaN by 0.0
- exp.gdf['value'] += value_tmp
- exp.description=("Crop production exposure from ISIMIP"
- f" {CROP_NAME[crop]['print']} {irr}"
- f" {yearrange[0]} {yearrange[-1]}")
- exp.value_unit = 't/y' # input unit, will be reset below if required by user
- exp.crop = crop
- exp.ref_year = yearrange
- try:
- rows, cols, ras_trans = u_coord.pts_to_raster_meta(
- (exp.gdf.longitude.min(), exp.gdf.latitude.min(),
- exp.gdf.longitude.max(), exp.gdf.latitude.max()),
- u_coord.get_resolution(exp.gdf.longitude, exp.gdf.latitude))
- exp.meta = {
- 'width': cols,
- 'height': rows,
- 'crs': exp.crs,
- 'transform': ras_trans,
- }
- except ValueError:
- LOGGER.warning('Could not write attribute meta, because exposure'
- ' has only 1 data point')
- exp.meta = {}
-
- if 'USD' in unit:
- # set_value_to_usd() is called to compute the exposure in USD/y (country specific)
- exp.set_value_to_usd(input_dir=input_dir)
- elif 'kcal' in unit:
- # set_value_to_kcal() is called to compute the exposure in kcal/y
- # here, biomass=False because most crop models provide yield weight
- # for dry matter, not biomass:
- exp.set_value_to_kcal(biomass=False)
- exp.check()
- return exp
-
- def set_from_area_and_yield_nc4(self, *args, **kwargs):
- """This function is deprecated, use LitPop.from_area_and_yield_nc4 instead."""
- LOGGER.warning("The use of LitPop.set_from_area_and_yield_nc4 is deprecated."
- "Use LitPop.from_area_and_yield_nc4 instead.")
- self.__dict__ = CropProduction.from_area_and_yield_nc4(*args, **kwargs).__dict__
-
- @classmethod
- def from_area_and_yield_nc4(cls, crop_type, layer_yield, layer_area,
- filename_yield, filename_area, var_yield,
- var_area, bbox=BBOX, input_dir=INPUT_DIR):
- """
- Set crop_production exposure from cultivated area [ha] and
- yield [t/ha/year] provided in two netcdf files with the same grid.
-
- Both input files need to be netcdf format and come with dimensions
- 'lon', 'lat' and 'crop'. The information which crop type is saved in which
- crop layer in each input files needs to be provided manually via
- the parameters 'layer_*'.
-
- A convenience wrapper around this expert method is provided with
- from_spam_ray_mirca().
-
- Parameters
- ----------
- crop_type : str
- Crop type, e.g. 'mai' for maize, or 'ric', 'whe', 'soy','cas', etc.
- layer_yield : int
- crop layer in yield input data set. Index typically starts with 1.
- layer_area : int
- crop layer in area input data set. Index typically starts with 1.
- filename_yield : str
- Name of netcdf-file containing gridded yield data.
- Requires coordinates 'lon', 'lat', and 'crop'.
- filename_area : str
- Name of netcdf-file containing gridded cultivated area.
- Requires coordinates 'lon', 'lat', and 'crop'.
- var_yield : str
- variable name to be extracted from yield file, e.g. 'yield.rf',
- 'yield.ir', 'yield.tot', or depending on netcdf structure.
- var_area : str
- variable name to be extracted from area file,
- e.g. 'cultivated area rainfed', 'cultivated area irrigated',
- 'cultivated area all', or depending on netcdf structure.
- bbox (tuple of four floats): bounding box:
- bounding box to be extracted: (lon min, lat min, lon max, lat max).
- The default is (-180, -85, 180, 85).
- input_dir : Path, optional
- directory where input data is found. The default is
- {CONFIG.exposures.crop_production.local_data}/Input/Exposure.
-
- Returns
- -------
- CropProduction
- crop production exposure instance based on yield and area data
- """
- if isinstance(input_dir, str):
- input_dir = Path(input_dir)
- [lonmin, latmin, lonmax, latmax] = bbox
-
- # extract yield data to xarray.DataArray:
- data_set_tmp = xr.open_dataset(input_dir / filename_yield, decode_times=False)
- data_yield = data_set_tmp.sel(lon=slice(lonmin, lonmax),
- lat=slice(latmax, latmin),
- crop=layer_yield
- )[var_yield]
- # extract cultivated area data to xarray.DataArray:
- data_set_tmp = xr.open_dataset(input_dir / filename_area, decode_times=False)
- data_area = data_set_tmp.sel(lon=slice(lonmin, lonmax),
- lat=slice(latmax, latmin),
- crop=layer_area
- )[var_area]
- del data_set_tmp
-
- # The latitude and longitude are set; region_id is determined
- lon, lat = np.meshgrid(data_area.lon.values, data_area.lat.values)
-
- exp = cls()
- # initiate coordinates and values in GeoDatFrame:
- exp.gdf['latitude'] = lat.flatten()
- exp.gdf['longitude'] = lon.flatten()
- exp.gdf['region_id'] = u_coord.get_country_code(exp.gdf.latitude,
- exp.gdf.longitude, gridded=True)
- exp.gdf[INDICATOR_IMPF + DEF_HAZ_TYPE] = 1
- exp.gdf[INDICATOR_IMPF] = 1
- # calc annual crop production, [t/y] = [ha] * [t/ha/y]:
- exp.gdf['value'] = np.multiply(data_area.values, data_yield.values).flatten()
-
- exp.crop = crop_type
- exp.description=(f"Annual crop production from {var_area} and {var_yield} for"
- f" {exp.crop} from files {filename_area} and {filename_yield}")
- exp.value_unit = 't/y'
- try:
- rows, cols, ras_trans = u_coord.pts_to_raster_meta(
- (exp.gdf.longitude.min(), exp.gdf.latitude.min(),
- exp.gdf.longitude.max(), exp.gdf.latitude.max()),
- u_coord.get_resolution(exp.gdf.longitude, exp.gdf.latitude))
- exp.meta = {
- 'width': cols,
- 'height': rows,
- 'crs': exp.crs,
- 'transform': ras_trans,
- }
- except ValueError:
- LOGGER.warning('Could not write attribute meta, because exposure'
- ' has only 1 data point')
- exp.meta = {}
- return exp
-
- def set_from_spam_ray_mirca(self, *args, **kwargs):
- """This function is deprecated, use CropPoduction.from_spam_ray_mirca instead."""
- LOGGER.warning("The use of CropPoduction.set_from_spam_ray_mirca is deprecated."
- "Use CropPoduction.from_spam_ray_mirca instead.")
- self.__dict__ = CropProduction.from_spam_ray_mirca(*args, **kwargs).__dict__
-
- @classmethod
- def from_spam_ray_mirca(cls, crop_type, irrigation_type='all',
- bbox=BBOX, input_dir=INPUT_DIR):
- """
- Wrapper method around from_area_and_yield_nc4().
-
- Set crop_production exposure from cultivated area [ha] and
- yield [t/ha/year] provided in default input files.
- The default input files are based on the public yield data from
- SPAM2005 with gaps filled based on Ray et.al (2012); and cultivated area
- from MIRCA2000, both as post-processed by Jägermeyr et al. 2020; See
- https://doi.org/10.1073/pnas.1919049117 for more information and cite
- when using this data for publication.
-
- Parameters
- ----------
- crop_type : str
- Crop type, e.g. 'mai' for maize, or 'ric', 'whe', 'soy','cas' etc.
- irrigation_type : str, optional
- irrigation type to be extracted, the options are:
- 'all' : total crop production, i.e. irrigated + rainfed
- 'firr' : fully irrigated
- 'noirr' : not irrigated, i.e., rainfed
- The default is 'all'
- bbox (list of four floats): bounding box:
- [lon min, lat min, lon max, lat max]
- input_dir : Path, optional
- directory where input data is found. The default is
- {CONFIG.exposures.crop_production.local_data}/Input/Exposure.
-
- Returns
- ------
- Exposure
- Crop production exposure based on SPAM and MIRCA data set
- """
- filename_yield = 'spam_ray_yields.nc4'
- filename_area = 'cultivated_area_MIRCA_GGCMI.nc4'
-
- # crop layers and variable names in default input files:
- layers_yield = {'mai': 1, 'whe': 2, 'soy': 4, 'ric': 3,'cas': 5}
- layers_area = {'mai': 1, 'whe': 2, 'soy': 3, 'ric': 4,'cas': 5}
- # Note: layer numbers fo rice and soybean differ between input files.
- varnames_yield = {'noirr': 'yield.rf',
- 'firr': 'yield.ir',
- 'all': 'yield.tot'}
- varnames_area = {'noirr': 'cultivated area rainfed',
- 'firr': 'cultivated area irrigated',
- 'all': 'cultivated area all'}
-
- # return exposure from netcdf files:
- return cls.from_area_and_yield_nc4(crop_type, layers_yield[crop_type],
- layers_area[crop_type],
- filename_yield, filename_area,
- varnames_yield[irrigation_type],
- varnames_area[irrigation_type],
- bbox=bbox, input_dir=input_dir)
-
- def set_mean_of_several_isimip_models(self, *args, **kwargs):
- """This function is deprecated, use
- CropPoduction.from_mean_of_several_isimip_models instead."""
- LOGGER.warning("The use of CropPoduction.set_mean_of_several_isimip_models is deprecated."
- "Use CropPoduction.from_mean_of_several_isimip_models instead.")
- self.__dict__ = CropProduction.from_mean_of_several_isimip_models(*args, **kwargs).__dict__
-
- @classmethod
- def from_mean_of_several_isimip_models(cls, input_dir=None, hist_mean=None, bbox=None,
- yearrange=None, cl_model=None, scenario=None,
- crop=None, irr=None, isimip_version=None,
- unit=None, fn_str_var=None):
- """Wrapper to init exposure from several NetCDF files with crop yield data
- from ISIMIP.
-
- Parameters
- ----------
- input_dir : string
- path to input data directory
- hist_mean : array
- historic mean crop production per centroid
- bbox : list of four floats
- bounding box:
- [lon min, lat min, lon max, lat max]
- yearrange : int tuple
- year range for exposure set,e.g., (1976, 2005)
- scenario : string
- climate change and socio economic scenario
- e.g., 'histsoc' or 'rcp60soc'
- cl_model : string
- abbrev. climate model (only when landuse data
- is future projection)
- e.g., 'gfdl-esm2m' etc.
- crop : string
- crop type
- e.g., 'mai', 'ric', 'whe', 'soy','cas'
- irr : string
- irrigation type
- f.i 'rainfed', 'irrigated' or 'combined'= rainfed+irrigated
- isimip_version : str
- 'ISIMIP2' (default) or 'ISIMIP3'
- unit : string
- unit of the exposure (per year)
- f.i 't/y' (default), 'USD/y', or 'kcal/y'
- fn_str_var : string
- FileName STRing depending on VARiable and
- ISIMIP simuation round
-
- Returns
- -------
- Exposure
- """
- if not bbox:
- bbox = BBOX
- if (not isimip_version) or (isimip_version in ('ISIMIP2a', 'ISIMIP2b')):
- isimip_version = 'ISIMIP2'
- elif isimip_version in ('ISIMIP3a', 'ISIMIP3b'):
- isimip_version = 'ISIMIP3'
- if not input_dir:
- input_dir = INPUT_DIR
- input_dir = Path(input_dir)
- if not hist_mean:
- hist_mean = HIST_MEAN_PATH
- if yearrange is None:
- yearrange = YEARCHUNKS[isimip_version]['histsoc']['yearrange']
- if not unit:
- unit = 't/y'
- if not fn_str_var:
- fn_str_var = FN_STR_VAR
- filenames = dict()
- filenames['all'] = [f for f in Path(input_dir).iterdir()
- if f.is_file() and 'nc' in f.name and not f.name.startswith('.')]
-
- # If only files with a certain scenario and or cl_model shall be considered, they
- # are extracted from the original list of files
- filenames['subset'] = list()
- for name in filenames['all']:
- if cl_model is not None and scenario is not None:
- if cl_model in name or scenario in name:
- filenames['subset'].append(name)
- elif cl_model is not None and scenario is None:
- if cl_model in name:
- filenames['subset'].append(name)
- elif cl_model is None and scenario is not None:
- if scenario in name:
- filenames['subset'].append(name)
- else:
- filenames['subset'] = filenames['all']
-
- # The first exposure is calculate to determine its size
- # and initialize the combined exposure
- exp = cls.from_isimip_netcdf(input_dir, filename=filenames['subset'][0],
- hist_mean=hist_mean, bbox=bbox, yearrange=yearrange,
- crop=crop, irr=irr, isimip_version=isimip_version,
- unit=unit, fn_str_var=fn_str_var)
-
- combined_exp = np.zeros([exp.gdf.value.size, len(filenames['subset'])])
- combined_exp[:, 0] = exp.gdf.value
-
- # The calculations are repeated for all remaining exposures (starting from index 1 as
- # the first exposure has been saved in combined_exp[:, 0])
- for j, fname in enumerate(filenames['subset'][1:]):
- exp = cls.from_isimip_netcdf(input_dir, filename=fname, hist_mean=hist_mean,
- bbox=bbox, yearrange=yearrange,
- crop=crop, irr=irr, unit=unit,
- isimip_version=isimip_version)
- combined_exp[:, j+1] = exp.gdf.value
-
- exp.gdf.value = np.mean(combined_exp, 1)
- exp.gdf['crop'] = crop
- exp.check()
- return exp
-
-
- def set_value_to_kcal(self, *args, **kwargs):
- """This function is deprecated, use function value_to_kcal instead."""
- LOGGER.warning("The use of CropProduction.set_value_to_kcal is deprecated."
- "Use function value_to_kcal instead.")
- self.__dict__ = value_to_kcal(self, *args, **kwargs).__dict__
-
- def set_value_to_usd(self, *args, **kwargs):
- """This function is deprecated, use functiom value_to_usd instead."""
- LOGGER.warning("The use of CropProduction.set_value_to_usd is deprecated."
- "Use function value_to_usd instead.")
- self.__dict__ = value_to_usd(self, *args, **kwargs).__dict__
-
- def aggregate_countries(self):
- """Aggregate exposure data by country.
-
- Returns
- -------
- list_countries : list
- country codes (numerical ISO3)
- country_values : array
- aggregated exposure value
- """
-
- list_countries = np.unique(self.gdf.region_id)
- country_values = np.zeros(len(list_countries))
- for i, iso_nr in enumerate(list_countries):
- country_values[i] = self.gdf.loc[self.gdf.region_id == iso_nr].value.sum()
-
- return list_countries, country_values
-
-
-def value_to_kcal(exp_cp, biomass=True):
- """Converts the exposure value from tonnes to kcal per year using
- conversion factor per crop type.
-
- Parameters
- ----------
- exp_cp : CropProduction
- CropProduction exposure object with units tonnes per year ('t/y')
- biomass : bool, optional
- if true, KCAL_PER_TON['biomass'] is used (default,
- for FAO normalized crop production). If False, KCAL_PER_TON['drymatter']
- is used (best for crop model output in dry matter, default for
- raw crop model output).
- Default: True
-
- Returns
- -------
- new_exp : CropProduction
- CropProduction exposure object with unit 'kcal/y'
- """
- if exp_cp.value_unit != 't/y':
- LOGGER.warning('self.unit is not t/y.')
- # create deep copy of exposure
- new_exp = copy.deepcopy(exp_cp)
- new_exp.gdf['tonnes_per_year'] = exp_cp.gdf['value'].values
- if biomass:
- new_exp.gdf.value *= KCAL_PER_TON['biomass'][exp_cp.crop]
- else:
- new_exp.gdf.value *= KCAL_PER_TON['drymatter'][exp_cp.crop]
-
- new_exp.value_unit = 'kcal/y'
- return new_exp
-
-def value_to_usd(exp_cp, input_dir=None, yearrange=None):
- # to do: check api availability?; default yearrange for single year (e.g. 5a)
- """Calculates the exposure in USD per year using country and year specific
- data published by the FAO, requires crop production exposure with unit 't/y'
-
- Parameters
- ----------
- exp_cp : CropProduction
- CropProduction exposure object with units tonnes per year ('t/y')
- input_dir : Path or str, optional
- directory containing the input (FAO pricing) data,
- default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
- yearrange : np.array, optional
- year range for prices, can also be set to a single year
- Default is set to the arbitrary time range (2000, 2018)
- The data is available for the years 1991-2018
- crop : str
- crop type
- e.g., 'mai', 'ric', 'whe', 'soy', 'cas'
-
- Returns
- -------
- new_exp : CropProduction
- CropProduction exposure object with unit 'USD/y'
- """
- if not input_dir:
- input_dir = INPUT_DIR
- input_dir = Path(input_dir)
- if yearrange is None:
- yearrange = YEARS_FAO
- # create deep copy of exposure
- new_exp = copy.deepcopy(exp_cp)
-
- # account for the case of only specifying one year as yearrange
- if len(yearrange) == 1:
- yearrange = (yearrange[0], yearrange[0])
-
- # open both FAO files and extract needed variables
- # FAO_FILE: contains producer prices per crop, country and year
- fao = dict()
- fao['file'] = pd.read_csv(input_dir / FAO_FILE)
- fao['crops'] = fao['file'].Item.values
- fao['year'] = fao['file'].Year.values
- fao['price'] = fao['file'].Value.values
-
- fao_country = u_coord.country_faocode2iso(getattr(fao['file'], 'Area Code').values)
-
- # create deep copy of exposure
- new_exp = copy.deepcopy(exp_cp)
-
- # create a list of the countries contained in the exposure
- iso3num = list()
- new_exp.gdf.region_id[exp_cp.gdf.region_id == -99] = 0
- iso3num = np.asarray(u_coord.country_to_iso(
- new_exp.gdf.region_id, representation="numeric", fillvalue=999), dtype=object)
- list_countries = np.unique(iso3num)
-
- # iterate over all countries that are covered in the exposure, extract the according price
- # and calculate the crop production in USD/y
- area_price = np.zeros(new_exp.gdf.value.size)
- for country in list_countries:
- [idx_country] = (iso3num == country).nonzero()
- if country == 999:
- price = 0
- area_price[idx_country] = new_exp.gdf.value[idx_country] * price
- # zero means no country, 999 other country
- elif country != 0 and country != 999:
- idx_price = np.where((np.asarray(fao_country) == country) &
- (np.asarray(fao['crops']) == \
- (CROP_NAME[new_exp.crop])['fao']) &
- (fao['year'] >= yearrange[0]) &
- (fao['year'] <= yearrange[1]))
- # if no price can be determined for a specific yearrange and country, the world
- # average for that crop (in the specified yearrange) is used
- if idx_price[0].size != 0:
- price = np.mean(fao['price'][idx_price])
-
- else:
- idx_price = np.where((np.asarray(fao['crops']) == \
- (CROP_NAME[new_exp.crop])['fao']) &
- (fao['year'] >= yearrange[0]) &
- (fao['year'] <= yearrange[1]))
- price = np.mean(fao['price'][idx_price])
- area_price[idx_country] = new_exp.gdf.value[idx_country] * price
-
- # the exposure values in t/y is saved as 'tonnes_per_year'
- new_exp.gdf['tonnes_per_year'] = exp_cp.gdf['value'].values
- new_exp.gdf['value'] = area_price
- new_exp.value_unit = 'USD/y'
- new_exp.check()
- return new_exp
-
-def init_full_exp_set_isimip(input_dir=None, filename=None, hist_mean_dir=None,
- output_dir=None, bbox=None, yearrange=None, unit=None,
- isimip_version=None, return_data=False):
- """Generates CropProduction instances (exposure sets) for all files found in the
- input directory and saves them as hdf5 files in the output directory.
- Exposures are aggregated per crop and irrigation type.
-
- Parameters
- ----------
- input_dir : str or Path
- path to input data directory,
- default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
- filename : string
- if not specified differently, the file
- 'histsoc_landuse-15crops_annual_1861_2005.nc' will be used
- output_dir : string
- path to output data directory
- bbox : list of four floats
- bounding box:
- [lon min, lat min, lon max, lat max]
- yearrange : array
- year range for hazard set, e.g., (1976, 2005)
- isimip_version : str
- 'ISIMIP2' (default) or 'ISIMIP3'
- unit : str
- unit in which to return exposure (e.g., t/y or USD/y)
- return_data : boolean
- returned output
- False: returns list of filenames only, True: returns also list of data
-
- Returns
- -------
- filename_list : list
- all filenames of saved initiated exposure files
- output_list : list
- list containing all inisiated Exposure instances
- """
- if not bbox:
- bbox = BBOX
- if (not isimip_version) or (isimip_version in ('ISIMIP2a', 'ISIMIP2b')):
- isimip_version = 'ISIMIP2'
- elif isimip_version in ('ISIMIP3a', 'ISIMIP3b'):
- isimip_version = 'ISIMIP3'
- if not input_dir:
- input_dir = INPUT_DIR
- input_dir = Path(input_dir)
- if not hist_mean_dir:
- hist_mean_dir = HIST_MEAN_PATH
- hist_mean_dir = Path(hist_mean_dir)
- if not output_dir:
- output_dir = OUTPUT_DIR
- output_dir = Path(output_dir)
- if yearrange is None:
- yearrange = YEARCHUNKS[isimip_version]['histsoc']['yearrange']
- if not unit:
- unit = 't/y'
-
- filenames = [f.name for f in hist_mean_dir.iterdir()
- if f.is_file() and not f.name.startswith('.')]
-
- # generate output directory if it does not exist yet
- target_dir = output_dir / 'Exposure'
- target_dir.mkdir(exist_ok=True)
-
- # create exposures for all crop-irrigation combinations and save them
- filename_list = list()
- output_list = list()
- for file in filenames:
- _, _, crop_irr, *_ = file.split('_')
- crop, irr = crop_irr.split('-')
- crop_production = CropProduction.from_isimip_netcdf(input_dir=input_dir,
- filename=filename,
- hist_mean=hist_mean_dir, bbox=bbox,
- isimip_version=isimip_version,
- yearrange=yearrange, crop=crop, irr=irr, unit=unit)
- filename_expo = ('crop_production_' + crop + '-'+ irr + '_'
- + str(yearrange[0]) + '-' + str(yearrange[1]) + '.hdf5')
- filename_list.append(filename_expo)
- crop_production.write_hdf5(str(Path(target_dir, filename_expo)))
- if return_data:
- output_list.append(crop_production)
-
- return filename_list, output_list
-
-def normalize_with_fao_cp(exp_firr, exp_noirr, input_dir=None,
- yearrange=None, unit=None, return_data=True):
- """Normalize (i.e., bias correct) the given exposures countrywise with the mean
- crop production quantity documented by the FAO.
- Refer to the beginning of the script for guidance on where to download the
- required crop production data from FAO.Stat.
-
- Parameters
- ----------
- exp_firr : crop_production
- exposure under full irrigation
- exp_noirr : crop_production
- exposure under no irrigation
- input_dir : Path or str
- directory containing exposure input data,
- default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
- yearrange : array
- the mean crop production in this year range is used to normalize
- the exposure data
- Default is set to the arbitrary time range (2008, 2018)
- The data is available for the years 1961-2018
- unit : str
- unit in which to return exposure (t/y or USD/y)
- return_data : boolean
- returned output
- True: returns country list, ratio = FAO/ISIMIP, normalized exposures, crop production
- per country as documented by the FAO and calculated by the ISIMIP dataset
- False: country list, ratio = FAO/ISIMIP, normalized exposures
-
- Returns
- -------
- country_list : list
- List of country codes (numerical ISO3)
- ratio : list
- List of ratio of FAO crop production and aggregated exposure
- for each country
- exp_firr_norm : CropProduction
- Normalized CropProduction (full irrigation)
- exp_noirr_norm : CropProduction
- Normalized CropProduction (no irrigation)
- Returns : optional
- fao_crop_production : list
- FAO crop production value per country
- exp_tot_production : list
- Exposure crop production value per country
- (before normalization)
- """
- if not input_dir:
- input_dir = INPUT_DIR
- input_dir = Path(input_dir)
- if yearrange is None:
- yearrange = YEARS_FAO
- if not unit:
- unit = 't/y'
- # if the exposure unit is USD/y or kcal/y, temporarily reset the exposure to t/y
- # (stored in tonnes_per_year) in order to normalize with FAO crop production
- # values and then apply set_to_XXX() for the normalized exposure to restore the
- # initial exposure unit
- if exp_firr.value_unit == 'USD/y' or 'kcal' in exp_firr.value_unit:
- exp_firr.gdf.value = exp_firr.tonnes_per_year
- if exp_noirr.value_unit == 'USD/y' or 'kcal' in exp_noirr.value_unit:
- exp_noirr.gdf.value = exp_noirr.tonnes_per_year
-
- country_list, countries_firr = exp_firr.aggregate_countries()
- country_list, countries_noirr = exp_noirr.aggregate_countries()
-
- exp_tot_production = countries_firr + countries_noirr
-
- fao = pd.read_csv(input_dir / FAO_FILE2)
- fao_crops = fao.Item.values
- fao_year = fao.Year.values
- fao_values = fao.Value.values
- fao_code = getattr(fao, 'Area Code').values
-
- fao_country = u_coord.country_iso2faocode(country_list)
-
- fao_crop_production = np.zeros(len(country_list))
- ratio = np.ones(len(country_list))
- exp_firr_norm = exp_firr.copy(deep=True)
- exp_noirr_norm = exp_noirr.copy(deep=True)
-
- # loop over countries: compute ratio & apply normalization:
- for country, iso_nr in enumerate(country_list):
- idx = np.where((np.asarray(fao_code) == fao_country[country])
- & (np.asarray(fao_crops) == (CROP_NAME[exp_firr.crop])['fao'])
- & (fao_year >= yearrange[0]) & (fao_year <= yearrange[1]))
- if len(idx) >= 1:
- fao_crop_production[country] = np.mean(fao_values[idx])
-
- # if a country has no values in the exposure (e.g. Cyprus) the exposure value
- # is set to the FAO average value
- # in this case the ratio is left being 1 (as initiated)
- if exp_tot_production[country] == 0:
- exp_tot_production[country] = fao_crop_production[country]
- elif fao_crop_production[country] != np.nan and fao_crop_production[country] != 0:
- ratio[country] = fao_crop_production[country] / exp_tot_production[country]
-
- exp_firr_norm.gdf.value[exp_firr.gdf.region_id == iso_nr] = ratio[country] * \
- exp_firr.gdf.value[exp_firr.gdf.region_id == iso_nr]
- exp_noirr_norm.gdf.value[exp_firr.gdf.region_id == iso_nr] = ratio[country] * \
- exp_noirr.gdf.value[exp_noirr.gdf.region_id == iso_nr]
-
- if unit == 'USD/y' or exp_noirr.value_unit == 'USD/y':
- exp_noirr.set_value_to_usd(input_dir=input_dir)
- elif 'kcal' in unit or 'kcal' in exp_noirr.value_unit:
- exp_noirr.set_value_to_kcal(biomass=True)
- # FAO production is provided in biomass, not dry matter
- if unit == 'USD/y' or exp_firr.value_unit == 'USD/y':
- exp_firr.set_value_to_usd(input_dir=input_dir)
- elif 'kcal' in unit or 'kcal' in exp_firr.value_unit:
- exp_firr.set_value_to_kcal(biomass=True)
-
- exp_firr_norm.description = " ".join([exp_firr_norm.description or "", "normalized"])
- exp_noirr_norm.description = " ".join([exp_noirr_norm.description or "", "normalized"])
-
- if return_data:
- return country_list, ratio, exp_firr_norm, exp_noirr_norm, \
- fao_crop_production, exp_tot_production
- return country_list, ratio, exp_firr_norm, exp_noirr_norm
-
-def normalize_several_exp(input_dir=None, output_dir=None,
- yearrange=None, unit=None, return_data=True):
- """
- Multiple exposure sets saved as HDF5 files in input directory are normalized
- (i.e. bias corrected) against FAO statistics of crop production.
-
- Parameters
- ----------
- input_dir : Path or str
- directory containing exposure input data
- output_dir : Path or str
- directory containing exposure datasets (output of
- exposure creation)
- yearrange : array
- the mean crop production in this year range is used to normalize
- the exposure data (default 2008-2018)
- unit : str
- unit in which to return exposure (t/y or USD/y)
- return_data : boolean
- returned output
- True: lists containing data for each exposure file. Lists: crops, country list,
- ratio = FAO/ISIMIP, normalized exposures, crop production
- per country as documented by the FAO and calculated by the ISIMIP dataset
- False: lists containing data for each exposure file. Lists: crops, country list,
- ratio = FAO/ISIMIP, normalized exposures
-
- Returns
- -------
- crop_list : list
- List of crops
- country_list : list
- List of country codes (numerical ISO3)
- ratio : list
- List of ratio of FAO crop production and aggregated exposure
- for each country
- exp_firr_norm : list
- List of normalized CropProduction Exposures (full irrigation)
- exp_noirr_norm : list
- List of normalize CropProduction Exposures (no irrigation)
- fao_crop_production : list, optional
- FAO crop production value per country
- exp_tot_production : list, optional
- Exposure crop production value per country
- (before normalization)
- """
- if input_dir is None:
- input_dir = INPUT_DIR
- if output_dir is None:
- output_dir = OUTPUT_DIR
- output_dir = Path(output_dir)
- if not unit:
- unit = 't/y'
- if yearrange is None:
- yearrange = YEARS_FAO
-
- filenames_firr = [f.parts[-1] for f in (output_dir / 'Exposure').iterdir() if
- f.is_file() if not f.parts[-1].startswith('.') if
- 'firr' in f.parts[-1]]
-
- crop_list = list()
- countries_list = list()
- ratio_list = list()
- exp_firr_norm = list()
- exp_noirr_norm = list()
- fao_cp_list = list()
- exp_tot_cp_list = list()
-
- for file_firr in filenames_firr:
- _, _, crop_irr, years = file_firr.split('_')
- crop, _ = crop_irr.split('-')
- exp_firr = CropProduction.from_hdf5(str(Path(output_dir, 'Exposure', file_firr)))
-
- filename_noirr = 'crop_production_' + crop + '-' + 'noirr' + '_' + years
- exp_noirr = CropProduction.from_hdf5(str(Path(output_dir, 'Exposure', filename_noirr)))
-
- if return_data:
- countries, ratio, exp_firr2, exp_noirr2, fao_cp, \
- exp_tot_cp = normalize_with_fao_cp(exp_firr, exp_noirr, input_dir=input_dir,
- yearrange=yearrange, unit=unit)
- fao_cp_list.append(fao_cp)
- exp_tot_cp_list.append(exp_tot_cp)
- else:
- countries, ratio, exp_firr2, exp_noirr2 = normalize_with_fao_cp(
- exp_firr, exp_noirr, input_dir=input_dir,
- yearrange=yearrange, unit=unit, return_data=False)
-
- crop_list.append(crop)
- countries_list.append(countries)
- ratio_list.append(ratio)
- exp_firr_norm.append(exp_firr2)
- exp_noirr_norm.append(exp_noirr2)
-
- if return_data:
- return crop_list, countries_list, ratio_list, exp_firr_norm, exp_noirr_norm, \
- fao_cp_list, exp_tot_cp_list
- return crop_list, countries_list, ratio_list, exp_firr_norm, exp_noirr_norm
-
-def semilogplot_ratio(crop, countries, ratio, output_dir=None, save=True):
- """Plot ratio = FAO/ISIMIP against country codes.
-
- Parameters
- ----------
- crop : str
- crop to plot
- countries : list
- country codes of countries to plot
- ratio : array
- ratio = FAO/ISIMIP crop production data of countries to plot
- save : boolean
- True saves figure, else figure is not saved.
- output_dir : str
- directory to save figure
-
- Returns
- -------
- fig (plt figure handle)
- axes (plot axes handle)
- """
- if output_dir is None:
- output_dir = OUTPUT_DIR
- output_dir = Path(output_dir)
- fig = plt.figure()
- axes = plt.gca()
- axes.scatter(countries[ratio != 1], ratio[ratio != 1])
- axes.set_yscale('log')
- axes.set_ylabel('Ratio= FAO / ISIMIP')
- axes.set_xlabel('ISO3 country code')
- axes.set_ylim(np.nanmin(ratio), np.nanmax(ratio))
- plt.title(crop)
-
- if save:
- target_dir = output_dir / 'Exposure_norm_plots'
- target_dir.mkdir(exist_ok=True)
- plt.savefig(target_dir / 'fig_ratio_norm_' + crop)
- return fig, axes
From 67929925aaf092152b68e5ce73b6ae744a3c971f Mon Sep 17 00:00:00 2001
From: ABOLY PIERRE <59005341+abolfirst@users.noreply.github.com>
Date: Mon, 10 Mar 2025 12:35:18 +0000
Subject: [PATCH 3/3] Add files via upload
Add cassava culture in the list of crop production. @chapan and @dayoro as contributors
---
crop_production.py | 1185 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1185 insertions(+)
create mode 100644 crop_production.py
diff --git a/crop_production.py b/crop_production.py
new file mode 100644
index 000000000..b193aa880
--- /dev/null
+++ b/crop_production.py
@@ -0,0 +1,1185 @@
+"""
+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 .
+
+---
+
+Agriculture exposures from ISIMIP and FAO.
+"""
+import logging
+from pathlib import Path
+import copy
+
+import numpy as np
+import xarray as xr
+import pandas as pd
+import h5py
+from matplotlib import pyplot as plt
+
+from climada import CONFIG
+from climada.entity import Exposures, INDICATOR_IMPF
+import climada.util.coordinates as u_coord
+
+logging.root.setLevel(logging.DEBUG)
+LOGGER = logging.getLogger(__name__)
+
+DEF_HAZ_TYPE = 'RC'
+"""Default hazard type used in impact functions id."""
+
+BBOX = (-180, -85, 180, 85) # [Lon min, lat min, lon max, lat max]
+""""Default geographical bounding box of the total global agricultural land extent"""
+
+#ISIMIP input data specific global variables
+YEARCHUNKS = dict()
+"""start and end years per ISIMIP version and senario as in ISIMIP-filenames
+of landuse data containing harvest area per crop"""
+# two types of 1860soc (1661-2299 not implemented)
+YEARCHUNKS['ISIMIP2'] = dict()
+YEARCHUNKS['ISIMIP2']['1860soc'] = {'yearrange': (1800, 1860), 'startyear': 1661, 'endyear': 1860}
+YEARCHUNKS['ISIMIP2']['histsoc'] = {'yearrange': (1976, 2005), 'startyear': 1861, 'endyear': 2005}
+YEARCHUNKS['ISIMIP2']['2005soc'] = {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2299}
+YEARCHUNKS['ISIMIP2']['rcp26soc'] = {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2099}
+YEARCHUNKS['ISIMIP2']['rcp60soc'] = {'yearrange': (2006, 2099), 'startyear': 2006, 'endyear': 2099}
+YEARCHUNKS['ISIMIP2']['2100rcp26soc'] = {'yearrange': (2100, 2299), 'startyear': 2100,
+ 'endyear': 2299}
+YEARCHUNKS['ISIMIP3'] = dict()
+YEARCHUNKS['ISIMIP3']['histsoc'] = {'yearrange': (1983, 2013), 'startyear': 1850, 'endyear': 2014}
+YEARCHUNKS['ISIMIP3']['2015soc'] = {'yearrange': (1983, 2013), 'startyear': 1850, 'endyear': 2014}
+
+FN_STR_VAR = 'landuse-15crops_annual'
+"""fix filename part in input data"""
+
+CROP_NAME = dict()
+"""mapping of crop names"""
+CROP_NAME['mai'] = {'input': 'maize', 'fao': 'Maize', 'print': 'Maize'}
+CROP_NAME['ric'] = {'input': 'rice', 'fao': 'Rice, paddy', 'print': 'Rice'}
+CROP_NAME['whe'] = {'input': 'temperate_cereals', 'fao': 'Wheat', 'print': 'Wheat'}
+CROP_NAME['soy'] = {'input': 'oil_crops_soybean', 'fao': 'Soybeans', 'print': 'Soybeans'}
+
+CROP_NAME['ri1'] = {'input': 'rice', 'fao': 'Rice, paddy', 'print': 'Rice 1st season'}
+CROP_NAME['ri2'] = {'input': 'rice', 'fao': 'Rice, paddy', 'print': 'Rice 2nd season'}
+CROP_NAME['swh'] = {'input': 'temperate_cereals', 'fao': 'Wheat', 'print': 'Spring Wheat'}
+CROP_NAME['wwh'] = {'input': 'temperate_cereals', 'fao': 'Wheat', 'print': 'Winter Wheat'}
+CROP_NAME['cas'] = {'input': 'cassava', 'fao': 'Cassava, fresh', 'print': 'Cassava fresh'}
+
+
+"""mapping of irrigation parameter long names"""
+IRR_NAME = {'combined': {'name': 'combined'},
+ 'noirr': {'name': 'rainfed'},
+ 'firr': {'name': 'irrigated'},
+ }
+
+"""Conversion factor weight [tons] to nutritional value [kcal].
+Based on Mueller et al. (2021), https://doi.org/10.1088/1748-9326/abd8fc :
+
+"For the aggregation of different crops, we compute total calories, assuming
+net water contents of 12% for maize, spring and winter wheat, 13% for rice and
+9% for soybean, according to Wirsenius (2000) and caloric contents of the
+“as purchased” biomass (i.e. including the water content) of 3.56kcal/g for maize,
+2.8kcal/g for rice, 3.35kcal/g for soybean and of 3.34kcal/g for spring and
+winter wheat, following FAO (2001).” (Müller et al., 2021)
+
+Based on https://www.retssa-ci.com/pages/Numero5/ADAYE/6-Tome-1.Retssa-Vol.3-N%C2%B05-Juin%202020.pdf :
+"The first excerpt (p. 2) states: "The pH of cassava is approximately 4, and minerals account for less than 1% of the ash content. The moisture content can exceed 50%. Cassava is an energy-dense food (over 300 Kcal/100g), but it is low in protein (1.1%) and fat (0.5%). The majority of its caloric value comes from carbohydrates (93% of the dry matter) and its high fiber content.
+Furthermore, the FAO document "Produce More with Less" specifies that the dry matter content of cassava roots is about 30%, which means that one ton of fresh roots contains approximately 300 kilograms of dry matter (p. 14)."
+
+Version 1: conversion factors for crop biomass "as purchased",
+ here applied as default for FAO-normalized production:
+ Production [kcal] = Production [t] * KCAL_PER_TON [kcal/t]
+"""
+
+KCAL_PER_TON = dict()
+KCAL_PER_TON['biomass'] = {'mai': 3.56e6,
+ 'ric': 2.80e6,
+ 'soy': 3.35e6,
+ 'whe': 3.34e6,
+ 'cas': 3.00e6,
+ }
+"""
+Version 2: conversion factors for crop dry matter as simulated by most crop models,
+ here applied as default for raw ISIMIP model yields and derived production values:
+ Yield [kcal] = Yield [t] * KCAL_PER_TON [kcal/t] / (1-net_water_content_fraction)
+"""
+KCAL_PER_TON['drymatter'] = {'mai': 3.56e6 / (1-.12),
+ 'ric': 2.80e6 / (1-.13),
+ 'soy': 3.35e6 / (1-.09),
+ 'whe': 3.34e6 / (1-.12),
+ 'cas': 3.00e6 / (1-.07),
+ }
+
+# Default folder structure for ISIMIP data:
+# deposit the landuse and FAO files in the directory:
+# {CONFIG.exposures.crop_production.local_data}/Input/Exposure
+# The FAO files need to be downloaded and renamed
+# FAO_FILE: contains producer prices per crop, country and year
+# (http://www.fao.org/faostat/en/#data/PP)
+# FAO_FILE2: contains production quantity per crop, country and year
+# (http://www.fao.org/faostat/en/#data/QC)
+DATA_DIR = CONFIG.exposures.crop_production.local_data.dir()
+INPUT_DIR = DATA_DIR.joinpath('Input', 'Exposure')
+FAO_FILE = "FAOSTAT_data_producer_prices.csv"
+FAO_FILE2 = "FAOSTAT_data_production_quantity.csv"
+
+YEARS_FAO = (2008, 2018)
+"""Default years from FAO used (data file contains values for 1991-2018)"""
+
+# default output directory: climada_python/data/ISIMIP_crop/Output/Exposure
+# by default the hist_mean files created by climada_python/hazard/crop_potential are saved in
+# climada_python/data/ISIMIP_crop/Output/hist_mean/
+HIST_MEAN_PATH = DATA_DIR.joinpath('Output', 'Hist_mean')
+OUTPUT_DIR = DATA_DIR.joinpath('Output')
+
+
+class CropProduction(Exposures):
+ """Defines agriculture exposures from ISIMIP input data and FAO crop data
+
+ geopandas GeoDataFrame with metadata and columns (pd.Series) defined in
+ Attributes and Exposures.
+
+ Attributes
+ ----------
+ crop : str
+ crop typee.g., 'mai', 'ric', 'whe', 'soy','cas'
+ """
+
+ _metadata = Exposures._metadata + ['crop']
+
+ def set_from_isimip_netcdf(self, *args, **kwargs):
+ """This function is deprecated, use LitPop.from_isimip_netcdf instead."""
+ LOGGER.warning("The use of LitPop.set_from_isimip_netcdf is deprecated."
+ "Use LitPop.from_isimip_netcdf instead.")
+ self.__dict__ = CropProduction.from_isimip_netcdf(*args, **kwargs).__dict__
+
+ @classmethod
+ def from_isimip_netcdf(cls, input_dir=None, filename=None, hist_mean=None,
+ bbox=None, yearrange=None, cl_model=None, scenario=None,
+ crop=None, irr=None, isimip_version=None,
+ unit=None, fn_str_var=None):
+
+ """Wrapper to fill exposure from NetCDF file from ISIMIP. Requires historical
+ mean relative cropyield module as additional input.
+
+ Parameters
+ ----------
+ input_dir : Path or str
+ path to input data directory,
+ default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
+ filename : string
+ name of the landuse data file to use,
+ e.g. "histsoc_landuse-15crops_annual_1861_2005.nc""
+ hist_mean : str or array
+ historic mean crop yield per centroid (or path)
+ bbox : list of four floats
+ bounding box:
+ [lon min, lat min, lon max, lat max]
+ yearrange : int tuple
+ year range for exposure set
+ e.g., (1990, 2010)
+ scenario : string
+ climate change and socio economic scenario
+ e.g., '1860soc', 'histsoc', '2005soc', 'rcp26soc','rcp60soc','2100rcp26soc'
+ cl_model : string
+ abbrev. climate model (only for future projections of lu data)
+ e.g., 'gfdl-esm2m', 'hadgem2-es', 'ipsl-cm5a-lr','miroc5'
+ crop : string
+ crop type
+ e.g., 'mai', 'ric', 'whe', 'soy','cas'
+ irr : string
+ irrigation type, default: 'combined'
+ f.i 'firr' (full irrigation), 'noirr' (no irrigation) or 'combined'= firr+noirr
+ isimip_version : str
+ 'ISIMIP2' (default) or 'ISIMIP3'
+ unit : string
+ unit of the exposure (per year)
+ f.i 't/y' (default), 'USD/y', or 'kcal/y'
+ fn_str_var : string
+ FileName STRing depending on VARiable and
+ ISIMIP simuation round
+
+ Returns
+ -------
+ Exposure
+ """
+ # parameters not provided in method call are set to default values:
+ if irr is None:
+ irr = 'combined'
+ if not bbox:
+ bbox = BBOX
+ if not input_dir:
+ input_dir = INPUT_DIR
+ input_dir = Path(input_dir)
+ if hist_mean is None:
+ hist_mean = HIST_MEAN_PATH
+ if isinstance(hist_mean, str):
+ hist_mean = Path(hist_mean)
+ if not fn_str_var:
+ fn_str_var = FN_STR_VAR
+ if (not isimip_version) or (isimip_version in ('ISIMIP2a', 'ISIMIP2b')):
+ isimip_version = 'ISIMIP2'
+ elif isimip_version in ('ISIMIP3a', 'ISIMIP3b'):
+ isimip_version = 'ISIMIP3'
+ if (not scenario) or (scenario in ('historical', 'hist')):
+ scenario = 'histsoc'
+ if yearrange is None:
+ yearrange = YEARCHUNKS[isimip_version][scenario]['yearrange']
+ if not unit:
+ unit = 't/y'
+
+ if isinstance(filename, Path): # if Path, extract pure filename as string
+ if filename.is_file() and filename.parent.is_dir():
+ LOGGER.info('input_dir is reset from %s to %s', input_dir, filename.parent)
+ input_dir = filename.parent
+ filename = filename.parts[-1]
+
+ # The filename is set or other variables (cl_model, scenario) are extracted of the
+ # specified filename
+ if filename is None:
+ yearchunk = YEARCHUNKS[isimip_version][scenario]
+ # if scenario == 'histsoc' or scenario == '1860soc':
+ if scenario in ('histsoc', '1860soc'):
+ string = '{}_{}_{}_{}.nc'
+ filepath = Path(input_dir, string.format(scenario, fn_str_var,
+ yearchunk['startyear'],
+ yearchunk['endyear']))
+ else:
+ string = '{}_{}_{}_{}_{}.nc'
+ filepath = Path(input_dir, string.format(scenario, cl_model, fn_str_var,
+ yearchunk['startyear'],
+ yearchunk['endyear']))
+ elif scenario == 'flexible':
+ _, _, _, _, _, _, startyear, endyearnc = filename.split('_')
+ endyear = endyearnc.split('.')[0]
+ yearchunk = dict()
+ yearchunk = {'yearrange': (int(startyear), int(endyear)),
+ 'startyear': int(startyear), 'endyear': int(endyear)}
+ filepath = Path(input_dir, filename)
+ else:
+ scenario, *_ = filename.split('_')
+ yearchunk = YEARCHUNKS[isimip_version][scenario]
+ filepath = Path(input_dir, filename)
+
+ # Dataset is opened and data within the bbox extends is extracted
+ data_set = xr.open_dataset(filepath, decode_times=False)
+ [lonmin, latmin, lonmax, latmax] = bbox
+ data = data_set.sel(lon=slice(lonmin, lonmax), lat=slice(latmax, latmin))
+
+ # The latitude and longitude are set; the region_id is determined
+ lon, lat = np.meshgrid(data.lon.values, data.lat.values)
+ exp = cls()
+ exp.gdf['latitude'] = lat.flatten()
+ exp.gdf['longitude'] = lon.flatten()
+ exp.gdf['region_id'] = u_coord.get_country_code(exp.gdf.latitude,
+ exp.gdf.longitude,
+ gridded=True)
+
+ # The indeces of the yearrange to be extracted are determined
+ time_idx = (int(yearrange[0] - yearchunk['startyear']),
+ int(yearrange[1] - yearchunk['startyear']))
+
+ # The area covered by a grid cell is calculated depending on the latitude
+ area = u_coord.get_gridcellarea(lat, resolution=0.5)
+
+ # The area covered by a crop is calculated as the product of the fraction and
+ # the grid cell size
+ if irr == 'combined':
+ irr_types = ['firr', 'noirr']
+ else:
+ irr_types = [irr]
+ area_crop = dict()
+ for irr_var in irr_types:
+ area_crop[irr_var] = (
+ getattr(
+ data, (CROP_NAME[crop])['input']+'_'+ (IRR_NAME[irr_var])['name']
+ )[time_idx[0]:time_idx[1], :, :].mean(dim='time')*area
+ ).values
+ area_crop[irr_var] = np.nan_to_num(area_crop[irr_var]).flatten()
+
+ # set historic mean, its latitude, and longitude:
+ hist_mean_dict = dict()
+ # if hist_mean is given as np.ndarray or dict,
+ # code assumes it contains hist_mean as returned by relative_cropyield
+ # however structured in dictionary as hist_mean_dict, with same
+ # bbox extensions as the exposure:
+ if isinstance(hist_mean, dict):
+ if not ('firr' in hist_mean.keys() or 'noirr' in hist_mean.keys()):
+ # as a dict hist_mean, needs to contain key 'firr' or 'noirr';
+ # if irr=='combined', both 'firr' and 'noirr' are required.
+ raise ValueError(f'Invalid hist_mean provided: {hist_mean}')
+ hist_mean_dict = hist_mean
+ lat_mean = exp.gdf.latitude.values
+ elif isinstance(hist_mean, np.ndarray) or isinstance(hist_mean, list):
+ hist_mean_dict[irr_types[0]] = np.array(hist_mean)
+ lat_mean = exp.gdf.latitude.values
+ elif Path(hist_mean).is_dir(): # else if hist_mean is given as path to directory
+ # The adequate file from the directory (depending on crop and irrigation) is extracted
+ # and the variables hist_mean, lat_mean and lon_mean are set accordingly
+ for irr_var in irr_types:
+ filename = str(Path(hist_mean, 'hist_mean_%s-%s_%i-%i.hdf5' %(
+ crop, irr_var, yearrange[0], yearrange[1])))
+ hist_mean_dict[irr_var] = (h5py.File(filename, 'r'))['mean'][()]
+ lat_mean = (h5py.File(filename, 'r'))['lat'][()]
+ lon_mean = (h5py.File(filename, 'r'))['lon'][()]
+ elif Path(input_dir, hist_mean).is_file(): # file in input_dir
+ # Hist_mean, lat_mean and lon_mean are extracted from the given file
+ if len(irr_types) > 1:
+ raise ValueError("For irr=='combined', hist_mean cannot be a single file.")
+ hist_mean = h5py.File(str(Path(input_dir, hist_mean)), 'r')
+ hist_mean_dict[irr_types[0]] = hist_mean['mean'][()]
+ lat_mean = hist_mean['lat'][()]
+ lon_mean = hist_mean['lon'][()]
+ elif hist_mean.is_file(): # fall back: complete file path
+ # Hist_mean, lat_mean and lon_mean are extracted from the given file
+ if len(irr_types) > 1:
+ raise ValueError("For irr=='combined', hist_mean can not be single file.")
+ hist_mean = h5py.File(str(Path(input_dir, hist_mean)), 'r')
+ hist_mean_dict[irr_types[0]] = hist_mean['mean'][()]
+ lat_mean = hist_mean['lat'][()]
+ lon_mean = hist_mean['lon'][()]
+ else:
+ raise ValueError(f"Invalid hist_mean provided: {hist_mean}")
+
+ # The bbox is cut out of the hist_mean data file if needed
+ if len(lat_mean) != len(exp.gdf.latitude.values):
+ idx_mean = np.zeros(len(exp.gdf.latitude.values), dtype=int)
+ for i in range(len(exp.gdf.latitude.values)):
+ idx_mean[i] = np.where(
+ (lat_mean == exp.gdf.latitude.values[i])
+ & (lon_mean == exp.gdf.longitude.values[i])
+ )[0][0]
+ else:
+ idx_mean = np.arange(0, len(lat_mean))
+
+ # The exposure [t/y] is computed per grid cell as the product of the area covered
+ # by a crop [ha] and its yield [t/ha/y]
+ exp.gdf['value'] = np.squeeze(area_crop[irr_types[0]] * \
+ hist_mean_dict[irr_types[0]][idx_mean])
+ exp.gdf['value'] = np.nan_to_num(exp.gdf.value) # replace NaN by 0.0
+ for irr_val in irr_types[1:]: # add other irrigation types if irr=='combined'
+ value_tmp = np.squeeze(area_crop[irr_val]*hist_mean_dict[irr_val][idx_mean])
+ value_tmp = np.nan_to_num(value_tmp) # replace NaN by 0.0
+ exp.gdf['value'] += value_tmp
+ exp.description=("Crop production exposure from ISIMIP"
+ f" {CROP_NAME[crop]['print']} {irr}"
+ f" {yearrange[0]} {yearrange[-1]}")
+ exp.value_unit = 't/y' # input unit, will be reset below if required by user
+ exp.crop = crop
+ exp.ref_year = yearrange
+ try:
+ rows, cols, ras_trans = u_coord.pts_to_raster_meta(
+ (exp.gdf.longitude.min(), exp.gdf.latitude.min(),
+ exp.gdf.longitude.max(), exp.gdf.latitude.max()),
+ u_coord.get_resolution(exp.gdf.longitude, exp.gdf.latitude))
+ exp.meta = {
+ 'width': cols,
+ 'height': rows,
+ 'crs': exp.crs,
+ 'transform': ras_trans,
+ }
+ except ValueError:
+ LOGGER.warning('Could not write attribute meta, because exposure'
+ ' has only 1 data point')
+ exp.meta = {}
+
+ if 'USD' in unit:
+ # set_value_to_usd() is called to compute the exposure in USD/y (country specific)
+ exp.set_value_to_usd(input_dir=input_dir)
+ elif 'kcal' in unit:
+ # set_value_to_kcal() is called to compute the exposure in kcal/y
+ # here, biomass=False because most crop models provide yield weight
+ # for dry matter, not biomass:
+ exp.set_value_to_kcal(biomass=False)
+ exp.check()
+ return exp
+
+ def set_from_area_and_yield_nc4(self, *args, **kwargs):
+ """This function is deprecated, use LitPop.from_area_and_yield_nc4 instead."""
+ LOGGER.warning("The use of LitPop.set_from_area_and_yield_nc4 is deprecated."
+ "Use LitPop.from_area_and_yield_nc4 instead.")
+ self.__dict__ = CropProduction.from_area_and_yield_nc4(*args, **kwargs).__dict__
+
+ @classmethod
+ def from_area_and_yield_nc4(cls, crop_type, layer_yield, layer_area,
+ filename_yield, filename_area, var_yield,
+ var_area, bbox=BBOX, input_dir=INPUT_DIR):
+ """
+ Set crop_production exposure from cultivated area [ha] and
+ yield [t/ha/year] provided in two netcdf files with the same grid.
+
+ Both input files need to be netcdf format and come with dimensions
+ 'lon', 'lat' and 'crop'. The information which crop type is saved in which
+ crop layer in each input files needs to be provided manually via
+ the parameters 'layer_*'.
+
+ A convenience wrapper around this expert method is provided with
+ from_spam_ray_mirca().
+
+ Parameters
+ ----------
+ crop_type : str
+ Crop type, e.g. 'mai' for maize, or 'ric', 'whe', 'soy','cas', etc.
+ layer_yield : int
+ crop layer in yield input data set. Index typically starts with 1.
+ layer_area : int
+ crop layer in area input data set. Index typically starts with 1.
+ filename_yield : str
+ Name of netcdf-file containing gridded yield data.
+ Requires coordinates 'lon', 'lat', and 'crop'.
+ filename_area : str
+ Name of netcdf-file containing gridded cultivated area.
+ Requires coordinates 'lon', 'lat', and 'crop'.
+ var_yield : str
+ variable name to be extracted from yield file, e.g. 'yield.rf',
+ 'yield.ir', 'yield.tot', or depending on netcdf structure.
+ var_area : str
+ variable name to be extracted from area file,
+ e.g. 'cultivated area rainfed', 'cultivated area irrigated',
+ 'cultivated area all', or depending on netcdf structure.
+ bbox (tuple of four floats): bounding box:
+ bounding box to be extracted: (lon min, lat min, lon max, lat max).
+ The default is (-180, -85, 180, 85).
+ input_dir : Path, optional
+ directory where input data is found. The default is
+ {CONFIG.exposures.crop_production.local_data}/Input/Exposure.
+
+ Returns
+ -------
+ CropProduction
+ crop production exposure instance based on yield and area data
+ """
+ if isinstance(input_dir, str):
+ input_dir = Path(input_dir)
+ [lonmin, latmin, lonmax, latmax] = bbox
+
+ # extract yield data to xarray.DataArray:
+ data_set_tmp = xr.open_dataset(input_dir / filename_yield, decode_times=False)
+ data_yield = data_set_tmp.sel(lon=slice(lonmin, lonmax),
+ lat=slice(latmax, latmin),
+ crop=layer_yield
+ )[var_yield]
+ # extract cultivated area data to xarray.DataArray:
+ data_set_tmp = xr.open_dataset(input_dir / filename_area, decode_times=False)
+ data_area = data_set_tmp.sel(lon=slice(lonmin, lonmax),
+ lat=slice(latmax, latmin),
+ crop=layer_area
+ )[var_area]
+ del data_set_tmp
+
+ # The latitude and longitude are set; region_id is determined
+ lon, lat = np.meshgrid(data_area.lon.values, data_area.lat.values)
+
+ exp = cls()
+ # initiate coordinates and values in GeoDatFrame:
+ exp.gdf['latitude'] = lat.flatten()
+ exp.gdf['longitude'] = lon.flatten()
+ exp.gdf['region_id'] = u_coord.get_country_code(exp.gdf.latitude,
+ exp.gdf.longitude, gridded=True)
+ exp.gdf[INDICATOR_IMPF + DEF_HAZ_TYPE] = 1
+ exp.gdf[INDICATOR_IMPF] = 1
+ # calc annual crop production, [t/y] = [ha] * [t/ha/y]:
+ exp.gdf['value'] = np.multiply(data_area.values, data_yield.values).flatten()
+
+ exp.crop = crop_type
+ exp.description=(f"Annual crop production from {var_area} and {var_yield} for"
+ f" {exp.crop} from files {filename_area} and {filename_yield}")
+ exp.value_unit = 't/y'
+ try:
+ rows, cols, ras_trans = u_coord.pts_to_raster_meta(
+ (exp.gdf.longitude.min(), exp.gdf.latitude.min(),
+ exp.gdf.longitude.max(), exp.gdf.latitude.max()),
+ u_coord.get_resolution(exp.gdf.longitude, exp.gdf.latitude))
+ exp.meta = {
+ 'width': cols,
+ 'height': rows,
+ 'crs': exp.crs,
+ 'transform': ras_trans,
+ }
+ except ValueError:
+ LOGGER.warning('Could not write attribute meta, because exposure'
+ ' has only 1 data point')
+ exp.meta = {}
+ return exp
+
+ def set_from_spam_ray_mirca(self, *args, **kwargs):
+ """This function is deprecated, use CropPoduction.from_spam_ray_mirca instead."""
+ LOGGER.warning("The use of CropPoduction.set_from_spam_ray_mirca is deprecated."
+ "Use CropPoduction.from_spam_ray_mirca instead.")
+ self.__dict__ = CropProduction.from_spam_ray_mirca(*args, **kwargs).__dict__
+
+ @classmethod
+ def from_spam_ray_mirca(cls, crop_type, irrigation_type='all',
+ bbox=BBOX, input_dir=INPUT_DIR):
+ """
+ Wrapper method around from_area_and_yield_nc4().
+
+ Set crop_production exposure from cultivated area [ha] and
+ yield [t/ha/year] provided in default input files.
+ The default input files are based on the public yield data from
+ SPAM2005 with gaps filled based on Ray et.al (2012); and cultivated area
+ from MIRCA2000, both as post-processed by Jägermeyr et al. 2020; See
+ https://doi.org/10.1073/pnas.1919049117 for more information and cite
+ when using this data for publication.
+
+ Parameters
+ ----------
+ crop_type : str
+ Crop type, e.g. 'mai' for maize, or 'ric', 'whe', 'soy','cas' etc.
+ irrigation_type : str, optional
+ irrigation type to be extracted, the options are:
+ 'all' : total crop production, i.e. irrigated + rainfed
+ 'firr' : fully irrigated
+ 'noirr' : not irrigated, i.e., rainfed
+ The default is 'all'
+ bbox (list of four floats): bounding box:
+ [lon min, lat min, lon max, lat max]
+ input_dir : Path, optional
+ directory where input data is found. The default is
+ {CONFIG.exposures.crop_production.local_data}/Input/Exposure.
+
+ Returns
+ ------
+ Exposure
+ Crop production exposure based on SPAM and MIRCA data set
+ """
+ filename_yield = 'spam_ray_yields.nc4'
+ filename_area = 'cultivated_area_MIRCA_GGCMI.nc4'
+
+ # crop layers and variable names in default input files:
+ layers_yield = {'mai': 1, 'whe': 2, 'soy': 4, 'ric': 3,'cas': 5}
+ layers_area = {'mai': 1, 'whe': 2, 'soy': 3, 'ric': 4,'cas': 5}
+ # Note: layer numbers fo rice and soybean differ between input files.
+ varnames_yield = {'noirr': 'yield.rf',
+ 'firr': 'yield.ir',
+ 'all': 'yield.tot'}
+ varnames_area = {'noirr': 'cultivated area rainfed',
+ 'firr': 'cultivated area irrigated',
+ 'all': 'cultivated area all'}
+
+ # return exposure from netcdf files:
+ return cls.from_area_and_yield_nc4(crop_type, layers_yield[crop_type],
+ layers_area[crop_type],
+ filename_yield, filename_area,
+ varnames_yield[irrigation_type],
+ varnames_area[irrigation_type],
+ bbox=bbox, input_dir=input_dir)
+
+ def set_mean_of_several_isimip_models(self, *args, **kwargs):
+ """This function is deprecated, use
+ CropPoduction.from_mean_of_several_isimip_models instead."""
+ LOGGER.warning("The use of CropPoduction.set_mean_of_several_isimip_models is deprecated."
+ "Use CropPoduction.from_mean_of_several_isimip_models instead.")
+ self.__dict__ = CropProduction.from_mean_of_several_isimip_models(*args, **kwargs).__dict__
+
+ @classmethod
+ def from_mean_of_several_isimip_models(cls, input_dir=None, hist_mean=None, bbox=None,
+ yearrange=None, cl_model=None, scenario=None,
+ crop=None, irr=None, isimip_version=None,
+ unit=None, fn_str_var=None):
+ """Wrapper to init exposure from several NetCDF files with crop yield data
+ from ISIMIP.
+
+ Parameters
+ ----------
+ input_dir : string
+ path to input data directory
+ hist_mean : array
+ historic mean crop production per centroid
+ bbox : list of four floats
+ bounding box:
+ [lon min, lat min, lon max, lat max]
+ yearrange : int tuple
+ year range for exposure set,e.g., (1976, 2005)
+ scenario : string
+ climate change and socio economic scenario
+ e.g., 'histsoc' or 'rcp60soc'
+ cl_model : string
+ abbrev. climate model (only when landuse data
+ is future projection)
+ e.g., 'gfdl-esm2m' etc.
+ crop : string
+ crop type
+ e.g., 'mai', 'ric', 'whe', 'soy','cas'
+ irr : string
+ irrigation type
+ f.i 'rainfed', 'irrigated' or 'combined'= rainfed+irrigated
+ isimip_version : str
+ 'ISIMIP2' (default) or 'ISIMIP3'
+ unit : string
+ unit of the exposure (per year)
+ f.i 't/y' (default), 'USD/y', or 'kcal/y'
+ fn_str_var : string
+ FileName STRing depending on VARiable and
+ ISIMIP simuation round
+
+ Returns
+ -------
+ Exposure
+ """
+ if not bbox:
+ bbox = BBOX
+ if (not isimip_version) or (isimip_version in ('ISIMIP2a', 'ISIMIP2b')):
+ isimip_version = 'ISIMIP2'
+ elif isimip_version in ('ISIMIP3a', 'ISIMIP3b'):
+ isimip_version = 'ISIMIP3'
+ if not input_dir:
+ input_dir = INPUT_DIR
+ input_dir = Path(input_dir)
+ if not hist_mean:
+ hist_mean = HIST_MEAN_PATH
+ if yearrange is None:
+ yearrange = YEARCHUNKS[isimip_version]['histsoc']['yearrange']
+ if not unit:
+ unit = 't/y'
+ if not fn_str_var:
+ fn_str_var = FN_STR_VAR
+ filenames = dict()
+ filenames['all'] = [f for f in Path(input_dir).iterdir()
+ if f.is_file() and 'nc' in f.name and not f.name.startswith('.')]
+
+ # If only files with a certain scenario and or cl_model shall be considered, they
+ # are extracted from the original list of files
+ filenames['subset'] = list()
+ for name in filenames['all']:
+ if cl_model is not None and scenario is not None:
+ if cl_model in name or scenario in name:
+ filenames['subset'].append(name)
+ elif cl_model is not None and scenario is None:
+ if cl_model in name:
+ filenames['subset'].append(name)
+ elif cl_model is None and scenario is not None:
+ if scenario in name:
+ filenames['subset'].append(name)
+ else:
+ filenames['subset'] = filenames['all']
+
+ # The first exposure is calculate to determine its size
+ # and initialize the combined exposure
+ exp = cls.from_isimip_netcdf(input_dir, filename=filenames['subset'][0],
+ hist_mean=hist_mean, bbox=bbox, yearrange=yearrange,
+ crop=crop, irr=irr, isimip_version=isimip_version,
+ unit=unit, fn_str_var=fn_str_var)
+
+ combined_exp = np.zeros([exp.gdf.value.size, len(filenames['subset'])])
+ combined_exp[:, 0] = exp.gdf.value
+
+ # The calculations are repeated for all remaining exposures (starting from index 1 as
+ # the first exposure has been saved in combined_exp[:, 0])
+ for j, fname in enumerate(filenames['subset'][1:]):
+ exp = cls.from_isimip_netcdf(input_dir, filename=fname, hist_mean=hist_mean,
+ bbox=bbox, yearrange=yearrange,
+ crop=crop, irr=irr, unit=unit,
+ isimip_version=isimip_version)
+ combined_exp[:, j+1] = exp.gdf.value
+
+ exp.gdf.value = np.mean(combined_exp, 1)
+ exp.gdf['crop'] = crop
+ exp.check()
+ return exp
+
+
+ def set_value_to_kcal(self, *args, **kwargs):
+ """This function is deprecated, use function value_to_kcal instead."""
+ LOGGER.warning("The use of CropProduction.set_value_to_kcal is deprecated."
+ "Use function value_to_kcal instead.")
+ self.__dict__ = value_to_kcal(self, *args, **kwargs).__dict__
+
+ def set_value_to_usd(self, *args, **kwargs):
+ """This function is deprecated, use functiom value_to_usd instead."""
+ LOGGER.warning("The use of CropProduction.set_value_to_usd is deprecated."
+ "Use function value_to_usd instead.")
+ self.__dict__ = value_to_usd(self, *args, **kwargs).__dict__
+
+ def aggregate_countries(self):
+ """Aggregate exposure data by country.
+
+ Returns
+ -------
+ list_countries : list
+ country codes (numerical ISO3)
+ country_values : array
+ aggregated exposure value
+ """
+
+ list_countries = np.unique(self.gdf.region_id)
+ country_values = np.zeros(len(list_countries))
+ for i, iso_nr in enumerate(list_countries):
+ country_values[i] = self.gdf.loc[self.gdf.region_id == iso_nr].value.sum()
+
+ return list_countries, country_values
+
+
+def value_to_kcal(exp_cp, biomass=True):
+ """Converts the exposure value from tonnes to kcal per year using
+ conversion factor per crop type.
+
+ Parameters
+ ----------
+ exp_cp : CropProduction
+ CropProduction exposure object with units tonnes per year ('t/y')
+ biomass : bool, optional
+ if true, KCAL_PER_TON['biomass'] is used (default,
+ for FAO normalized crop production). If False, KCAL_PER_TON['drymatter']
+ is used (best for crop model output in dry matter, default for
+ raw crop model output).
+ Default: True
+
+ Returns
+ -------
+ new_exp : CropProduction
+ CropProduction exposure object with unit 'kcal/y'
+ """
+ if exp_cp.value_unit != 't/y':
+ LOGGER.warning('self.unit is not t/y.')
+ # create deep copy of exposure
+ new_exp = copy.deepcopy(exp_cp)
+ new_exp.gdf['tonnes_per_year'] = exp_cp.gdf['value'].values
+ if biomass:
+ new_exp.gdf.value *= KCAL_PER_TON['biomass'][exp_cp.crop]
+ else:
+ new_exp.gdf.value *= KCAL_PER_TON['drymatter'][exp_cp.crop]
+
+ new_exp.value_unit = 'kcal/y'
+ return new_exp
+
+def value_to_usd(exp_cp, input_dir=None, yearrange=None):
+ # to do: check api availability?; default yearrange for single year (e.g. 5a)
+ """Calculates the exposure in USD per year using country and year specific
+ data published by the FAO, requires crop production exposure with unit 't/y'
+
+ Parameters
+ ----------
+ exp_cp : CropProduction
+ CropProduction exposure object with units tonnes per year ('t/y')
+ input_dir : Path or str, optional
+ directory containing the input (FAO pricing) data,
+ default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
+ yearrange : np.array, optional
+ year range for prices, can also be set to a single year
+ Default is set to the arbitrary time range (2000, 2018)
+ The data is available for the years 1991-2018
+ crop : str
+ crop type
+ e.g., 'mai', 'ric', 'whe', 'soy', 'cas'
+
+ Returns
+ -------
+ new_exp : CropProduction
+ CropProduction exposure object with unit 'USD/y'
+ """
+ if not input_dir:
+ input_dir = INPUT_DIR
+ input_dir = Path(input_dir)
+ if yearrange is None:
+ yearrange = YEARS_FAO
+ # create deep copy of exposure
+ new_exp = copy.deepcopy(exp_cp)
+
+ # account for the case of only specifying one year as yearrange
+ if len(yearrange) == 1:
+ yearrange = (yearrange[0], yearrange[0])
+
+ # open both FAO files and extract needed variables
+ # FAO_FILE: contains producer prices per crop, country and year
+ fao = dict()
+ fao['file'] = pd.read_csv(input_dir / FAO_FILE)
+ fao['crops'] = fao['file'].Item.values
+ fao['year'] = fao['file'].Year.values
+ fao['price'] = fao['file'].Value.values
+
+ fao_country = u_coord.country_faocode2iso(getattr(fao['file'], 'Area Code').values)
+
+ # create deep copy of exposure
+ new_exp = copy.deepcopy(exp_cp)
+
+ # create a list of the countries contained in the exposure
+ iso3num = list()
+ new_exp.gdf.region_id[exp_cp.gdf.region_id == -99] = 0
+ iso3num = np.asarray(u_coord.country_to_iso(
+ new_exp.gdf.region_id, representation="numeric", fillvalue=999), dtype=object)
+ list_countries = np.unique(iso3num)
+
+ # iterate over all countries that are covered in the exposure, extract the according price
+ # and calculate the crop production in USD/y
+ area_price = np.zeros(new_exp.gdf.value.size)
+ for country in list_countries:
+ [idx_country] = (iso3num == country).nonzero()
+ if country == 999:
+ price = 0
+ area_price[idx_country] = new_exp.gdf.value[idx_country] * price
+ # zero means no country, 999 other country
+ elif country != 0 and country != 999:
+ idx_price = np.where((np.asarray(fao_country) == country) &
+ (np.asarray(fao['crops']) == \
+ (CROP_NAME[new_exp.crop])['fao']) &
+ (fao['year'] >= yearrange[0]) &
+ (fao['year'] <= yearrange[1]))
+ # if no price can be determined for a specific yearrange and country, the world
+ # average for that crop (in the specified yearrange) is used
+ if idx_price[0].size != 0:
+ price = np.mean(fao['price'][idx_price])
+
+ else:
+ idx_price = np.where((np.asarray(fao['crops']) == \
+ (CROP_NAME[new_exp.crop])['fao']) &
+ (fao['year'] >= yearrange[0]) &
+ (fao['year'] <= yearrange[1]))
+ price = np.mean(fao['price'][idx_price])
+ area_price[idx_country] = new_exp.gdf.value[idx_country] * price
+
+ # the exposure values in t/y is saved as 'tonnes_per_year'
+ new_exp.gdf['tonnes_per_year'] = exp_cp.gdf['value'].values
+ new_exp.gdf['value'] = area_price
+ new_exp.value_unit = 'USD/y'
+ new_exp.check()
+ return new_exp
+
+def init_full_exp_set_isimip(input_dir=None, filename=None, hist_mean_dir=None,
+ output_dir=None, bbox=None, yearrange=None, unit=None,
+ isimip_version=None, return_data=False):
+ """Generates CropProduction instances (exposure sets) for all files found in the
+ input directory and saves them as hdf5 files in the output directory.
+ Exposures are aggregated per crop and irrigation type.
+
+ Parameters
+ ----------
+ input_dir : str or Path
+ path to input data directory,
+ default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
+ filename : string
+ if not specified differently, the file
+ 'histsoc_landuse-15crops_annual_1861_2005.nc' will be used
+ output_dir : string
+ path to output data directory
+ bbox : list of four floats
+ bounding box:
+ [lon min, lat min, lon max, lat max]
+ yearrange : array
+ year range for hazard set, e.g., (1976, 2005)
+ isimip_version : str
+ 'ISIMIP2' (default) or 'ISIMIP3'
+ unit : str
+ unit in which to return exposure (e.g., t/y or USD/y)
+ return_data : boolean
+ returned output
+ False: returns list of filenames only, True: returns also list of data
+
+ Returns
+ -------
+ filename_list : list
+ all filenames of saved initiated exposure files
+ output_list : list
+ list containing all inisiated Exposure instances
+ """
+ if not bbox:
+ bbox = BBOX
+ if (not isimip_version) or (isimip_version in ('ISIMIP2a', 'ISIMIP2b')):
+ isimip_version = 'ISIMIP2'
+ elif isimip_version in ('ISIMIP3a', 'ISIMIP3b'):
+ isimip_version = 'ISIMIP3'
+ if not input_dir:
+ input_dir = INPUT_DIR
+ input_dir = Path(input_dir)
+ if not hist_mean_dir:
+ hist_mean_dir = HIST_MEAN_PATH
+ hist_mean_dir = Path(hist_mean_dir)
+ if not output_dir:
+ output_dir = OUTPUT_DIR
+ output_dir = Path(output_dir)
+ if yearrange is None:
+ yearrange = YEARCHUNKS[isimip_version]['histsoc']['yearrange']
+ if not unit:
+ unit = 't/y'
+
+ filenames = [f.name for f in hist_mean_dir.iterdir()
+ if f.is_file() and not f.name.startswith('.')]
+
+ # generate output directory if it does not exist yet
+ target_dir = output_dir / 'Exposure'
+ target_dir.mkdir(exist_ok=True)
+
+ # create exposures for all crop-irrigation combinations and save them
+ filename_list = list()
+ output_list = list()
+ for file in filenames:
+ _, _, crop_irr, *_ = file.split('_')
+ crop, irr = crop_irr.split('-')
+ crop_production = CropProduction.from_isimip_netcdf(input_dir=input_dir,
+ filename=filename,
+ hist_mean=hist_mean_dir, bbox=bbox,
+ isimip_version=isimip_version,
+ yearrange=yearrange, crop=crop, irr=irr, unit=unit)
+ filename_expo = ('crop_production_' + crop + '-'+ irr + '_'
+ + str(yearrange[0]) + '-' + str(yearrange[1]) + '.hdf5')
+ filename_list.append(filename_expo)
+ crop_production.write_hdf5(str(Path(target_dir, filename_expo)))
+ if return_data:
+ output_list.append(crop_production)
+
+ return filename_list, output_list
+
+def normalize_with_fao_cp(exp_firr, exp_noirr, input_dir=None,
+ yearrange=None, unit=None, return_data=True):
+ """Normalize (i.e., bias correct) the given exposures countrywise with the mean
+ crop production quantity documented by the FAO.
+ Refer to the beginning of the script for guidance on where to download the
+ required crop production data from FAO.Stat.
+
+ Parameters
+ ----------
+ exp_firr : crop_production
+ exposure under full irrigation
+ exp_noirr : crop_production
+ exposure under no irrigation
+ input_dir : Path or str
+ directory containing exposure input data,
+ default: {CONFIG.exposures.crop_production.local_data}/Input/Exposure
+ yearrange : array
+ the mean crop production in this year range is used to normalize
+ the exposure data
+ Default is set to the arbitrary time range (2008, 2018)
+ The data is available for the years 1961-2018
+ unit : str
+ unit in which to return exposure (t/y or USD/y)
+ return_data : boolean
+ returned output
+ True: returns country list, ratio = FAO/ISIMIP, normalized exposures, crop production
+ per country as documented by the FAO and calculated by the ISIMIP dataset
+ False: country list, ratio = FAO/ISIMIP, normalized exposures
+
+ Returns
+ -------
+ country_list : list
+ List of country codes (numerical ISO3)
+ ratio : list
+ List of ratio of FAO crop production and aggregated exposure
+ for each country
+ exp_firr_norm : CropProduction
+ Normalized CropProduction (full irrigation)
+ exp_noirr_norm : CropProduction
+ Normalized CropProduction (no irrigation)
+ Returns : optional
+ fao_crop_production : list
+ FAO crop production value per country
+ exp_tot_production : list
+ Exposure crop production value per country
+ (before normalization)
+ """
+ if not input_dir:
+ input_dir = INPUT_DIR
+ input_dir = Path(input_dir)
+ if yearrange is None:
+ yearrange = YEARS_FAO
+ if not unit:
+ unit = 't/y'
+ # if the exposure unit is USD/y or kcal/y, temporarily reset the exposure to t/y
+ # (stored in tonnes_per_year) in order to normalize with FAO crop production
+ # values and then apply set_to_XXX() for the normalized exposure to restore the
+ # initial exposure unit
+ if exp_firr.value_unit == 'USD/y' or 'kcal' in exp_firr.value_unit:
+ exp_firr.gdf.value = exp_firr.tonnes_per_year
+ if exp_noirr.value_unit == 'USD/y' or 'kcal' in exp_noirr.value_unit:
+ exp_noirr.gdf.value = exp_noirr.tonnes_per_year
+
+ country_list, countries_firr = exp_firr.aggregate_countries()
+ country_list, countries_noirr = exp_noirr.aggregate_countries()
+
+ exp_tot_production = countries_firr + countries_noirr
+
+ fao = pd.read_csv(input_dir / FAO_FILE2)
+ fao_crops = fao.Item.values
+ fao_year = fao.Year.values
+ fao_values = fao.Value.values
+ fao_code = getattr(fao, 'Area Code').values
+
+ fao_country = u_coord.country_iso2faocode(country_list)
+
+ fao_crop_production = np.zeros(len(country_list))
+ ratio = np.ones(len(country_list))
+ exp_firr_norm = exp_firr.copy(deep=True)
+ exp_noirr_norm = exp_noirr.copy(deep=True)
+
+ # loop over countries: compute ratio & apply normalization:
+ for country, iso_nr in enumerate(country_list):
+ idx = np.where((np.asarray(fao_code) == fao_country[country])
+ & (np.asarray(fao_crops) == (CROP_NAME[exp_firr.crop])['fao'])
+ & (fao_year >= yearrange[0]) & (fao_year <= yearrange[1]))
+ if len(idx) >= 1:
+ fao_crop_production[country] = np.mean(fao_values[idx])
+
+ # if a country has no values in the exposure (e.g. Cyprus) the exposure value
+ # is set to the FAO average value
+ # in this case the ratio is left being 1 (as initiated)
+ if exp_tot_production[country] == 0:
+ exp_tot_production[country] = fao_crop_production[country]
+ elif fao_crop_production[country] != np.nan and fao_crop_production[country] != 0:
+ ratio[country] = fao_crop_production[country] / exp_tot_production[country]
+
+ exp_firr_norm.gdf.value[exp_firr.gdf.region_id == iso_nr] = ratio[country] * \
+ exp_firr.gdf.value[exp_firr.gdf.region_id == iso_nr]
+ exp_noirr_norm.gdf.value[exp_firr.gdf.region_id == iso_nr] = ratio[country] * \
+ exp_noirr.gdf.value[exp_noirr.gdf.region_id == iso_nr]
+
+ if unit == 'USD/y' or exp_noirr.value_unit == 'USD/y':
+ exp_noirr.set_value_to_usd(input_dir=input_dir)
+ elif 'kcal' in unit or 'kcal' in exp_noirr.value_unit:
+ exp_noirr.set_value_to_kcal(biomass=True)
+ # FAO production is provided in biomass, not dry matter
+ if unit == 'USD/y' or exp_firr.value_unit == 'USD/y':
+ exp_firr.set_value_to_usd(input_dir=input_dir)
+ elif 'kcal' in unit or 'kcal' in exp_firr.value_unit:
+ exp_firr.set_value_to_kcal(biomass=True)
+
+ exp_firr_norm.description = " ".join([exp_firr_norm.description or "", "normalized"])
+ exp_noirr_norm.description = " ".join([exp_noirr_norm.description or "", "normalized"])
+
+ if return_data:
+ return country_list, ratio, exp_firr_norm, exp_noirr_norm, \
+ fao_crop_production, exp_tot_production
+ return country_list, ratio, exp_firr_norm, exp_noirr_norm
+
+def normalize_several_exp(input_dir=None, output_dir=None,
+ yearrange=None, unit=None, return_data=True):
+ """
+ Multiple exposure sets saved as HDF5 files in input directory are normalized
+ (i.e. bias corrected) against FAO statistics of crop production.
+
+ Parameters
+ ----------
+ input_dir : Path or str
+ directory containing exposure input data
+ output_dir : Path or str
+ directory containing exposure datasets (output of
+ exposure creation)
+ yearrange : array
+ the mean crop production in this year range is used to normalize
+ the exposure data (default 2008-2018)
+ unit : str
+ unit in which to return exposure (t/y or USD/y)
+ return_data : boolean
+ returned output
+ True: lists containing data for each exposure file. Lists: crops, country list,
+ ratio = FAO/ISIMIP, normalized exposures, crop production
+ per country as documented by the FAO and calculated by the ISIMIP dataset
+ False: lists containing data for each exposure file. Lists: crops, country list,
+ ratio = FAO/ISIMIP, normalized exposures
+
+ Returns
+ -------
+ crop_list : list
+ List of crops
+ country_list : list
+ List of country codes (numerical ISO3)
+ ratio : list
+ List of ratio of FAO crop production and aggregated exposure
+ for each country
+ exp_firr_norm : list
+ List of normalized CropProduction Exposures (full irrigation)
+ exp_noirr_norm : list
+ List of normalize CropProduction Exposures (no irrigation)
+ fao_crop_production : list, optional
+ FAO crop production value per country
+ exp_tot_production : list, optional
+ Exposure crop production value per country
+ (before normalization)
+ """
+ if input_dir is None:
+ input_dir = INPUT_DIR
+ if output_dir is None:
+ output_dir = OUTPUT_DIR
+ output_dir = Path(output_dir)
+ if not unit:
+ unit = 't/y'
+ if yearrange is None:
+ yearrange = YEARS_FAO
+
+ filenames_firr = [f.parts[-1] for f in (output_dir / 'Exposure').iterdir() if
+ f.is_file() if not f.parts[-1].startswith('.') if
+ 'firr' in f.parts[-1]]
+
+ crop_list = list()
+ countries_list = list()
+ ratio_list = list()
+ exp_firr_norm = list()
+ exp_noirr_norm = list()
+ fao_cp_list = list()
+ exp_tot_cp_list = list()
+
+ for file_firr in filenames_firr:
+ _, _, crop_irr, years = file_firr.split('_')
+ crop, _ = crop_irr.split('-')
+ exp_firr = CropProduction.from_hdf5(str(Path(output_dir, 'Exposure', file_firr)))
+
+ filename_noirr = 'crop_production_' + crop + '-' + 'noirr' + '_' + years
+ exp_noirr = CropProduction.from_hdf5(str(Path(output_dir, 'Exposure', filename_noirr)))
+
+ if return_data:
+ countries, ratio, exp_firr2, exp_noirr2, fao_cp, \
+ exp_tot_cp = normalize_with_fao_cp(exp_firr, exp_noirr, input_dir=input_dir,
+ yearrange=yearrange, unit=unit)
+ fao_cp_list.append(fao_cp)
+ exp_tot_cp_list.append(exp_tot_cp)
+ else:
+ countries, ratio, exp_firr2, exp_noirr2 = normalize_with_fao_cp(
+ exp_firr, exp_noirr, input_dir=input_dir,
+ yearrange=yearrange, unit=unit, return_data=False)
+
+ crop_list.append(crop)
+ countries_list.append(countries)
+ ratio_list.append(ratio)
+ exp_firr_norm.append(exp_firr2)
+ exp_noirr_norm.append(exp_noirr2)
+
+ if return_data:
+ return crop_list, countries_list, ratio_list, exp_firr_norm, exp_noirr_norm, \
+ fao_cp_list, exp_tot_cp_list
+ return crop_list, countries_list, ratio_list, exp_firr_norm, exp_noirr_norm
+
+def semilogplot_ratio(crop, countries, ratio, output_dir=None, save=True):
+ """Plot ratio = FAO/ISIMIP against country codes.
+
+ Parameters
+ ----------
+ crop : str
+ crop to plot
+ countries : list
+ country codes of countries to plot
+ ratio : array
+ ratio = FAO/ISIMIP crop production data of countries to plot
+ save : boolean
+ True saves figure, else figure is not saved.
+ output_dir : str
+ directory to save figure
+
+ Returns
+ -------
+ fig (plt figure handle)
+ axes (plot axes handle)
+ """
+ if output_dir is None:
+ output_dir = OUTPUT_DIR
+ output_dir = Path(output_dir)
+ fig = plt.figure()
+ axes = plt.gca()
+ axes.scatter(countries[ratio != 1], ratio[ratio != 1])
+ axes.set_yscale('log')
+ axes.set_ylabel('Ratio= FAO / ISIMIP')
+ axes.set_xlabel('ISO3 country code')
+ axes.set_ylim(np.nanmin(ratio), np.nanmax(ratio))
+ plt.title(crop)
+
+ if save:
+ target_dir = output_dir / 'Exposure_norm_plots'
+ target_dir.mkdir(exist_ok=True)
+ plt.savefig(target_dir / 'fig_ratio_norm_' + crop)
+ return fig, axes