Source code for esmvalcore.preprocessor._bias

"""Preprocessor functions to calculate biases from data."""
import logging

import dask.array as da
import iris.cube

from ._io import concatenate

logger = logging.getLogger(__name__)


[docs]def bias(products, bias_type='absolute', denominator_mask_threshold=1e-3, keep_reference_dataset=False): """Calculate biases. Notes ----- This preprocessor requires a reference dataset. For this, exactly one input dataset needs to have the facet ``reference_for_bias: true`` defined in the recipe. In addition, all input datasets need to have identical dimensional coordinates. This can for example be ensured with the preprocessors :func:`esmvalcore.preprocessor.regrid` and/or :func:`esmvalcore.preprocessor.regrid_time`. Parameters ---------- products: set of esmvalcore.preprocessor.PreprocessorFile Input datasets. Exactly one datasets needs the facet ``reference_for_bias: true``. bias_type: str, optional (default: 'absolute') Bias type that is calculated. Must be one of ``'absolute'`` (dataset - ref) or ``'relative'`` ((dataset - ref) / ref). denominator_mask_threshold: float, optional (default: 1e-3) Threshold to mask values close to zero in the denominator (i.e., the reference dataset) during the calculation of relative biases. All values in the reference dataset with absolute value less than the given threshold are masked out. This setting is ignored when ``bias_type`` is set to ``'absolute'``. Please note that for some variables with very small absolute values (e.g., carbon cycle fluxes, which are usually :math:`< 10^{-6}` kg m :math:`^{-2}` s :math:`^{-1}`) it is absolutely essential to change the default value in order to get reasonable results. keep_reference_dataset: bool, optional (default: False) If ``True``, keep the reference dataset in the output. If ``False``, drop the reference dataset. Returns ------- set of esmvalcore.preprocessor.PreprocessorFile Output datasets. Raises ------ ValueError Not exactly one input datasets contains the facet ``reference_for_bias: true``; ``bias_type`` is not one of ``'absolute'`` or ``'relative'``. """ # Get reference product reference_product = [] for product in products: if product.attributes.get('reference_for_bias', False): reference_product.append(product) if len(reference_product) != 1: raise ValueError( f"Expected exactly 1 dataset with 'reference_for_bias: true', " f"found {len(reference_product):d}") reference_product = reference_product[0] # Extract reference cube # Note: For technical reasons, product objects contain the member # ``cubes``, which is a list of cubes. However, this is expected to be a # list with exactly one element due to the call of concatenate earlier in # the preprocessing chain of ESMValTool. To make sure that this # preprocessor can also be used outside the ESMValTool preprocessing chain, # an additional concatenate call is added here. ref_cube = concatenate(reference_product.cubes) if bias_type == 'relative': ref_cube = ref_cube.copy() ref_cube.data = da.ma.masked_inside(ref_cube.core_data(), -denominator_mask_threshold, denominator_mask_threshold) # Iterate over all input datasets and calculate bias output_products = set() for product in products: if product == reference_product: continue cube = concatenate(product.cubes) cube_metadata = cube.metadata # Calculate bias if bias_type == 'absolute': cube = cube - ref_cube new_units = str(cube.units) elif bias_type == 'relative': cube = (cube - ref_cube) / ref_cube new_units = '1' else: raise ValueError( f"Expected one of ['absolute', 'relative'] for bias_type, got " f"'{bias_type}'") # Adapt cube metadata and provenance information cube.metadata = cube_metadata cube.units = new_units product.attributes['units'] = new_units product.wasderivedfrom(reference_product) product.cubes = iris.cube.CubeList([cube]) output_products.add(product) # Add reference dataset to output if desired if keep_reference_dataset: output_products.add(reference_product) return output_products