Module gdess.recipes.surface_trends

Produce a plot of multidecadal trends of atmospheric CO2

Note

This function parses: - observational data from Globalview+ surface stations - model output from CMIP6

Expand source code
"""Produce a plot of multidecadal trends of atmospheric CO2

Note
----
This function parses:
 - observational data from Globalview+ surface stations
 - model output from CMIP6
"""
import logging
import argparse
from typing import Union

import matplotlib.pyplot as plt
from dask.diagnostics import ProgressBar

from gdess import set_verbose, benchmark_recipe
from gdess.graphics.utils import aesthetic_grid_no_spines, mysavefig, limits_with_zero
from gdess.recipe_parsers import parse_recipe_options, add_surface_trends_args_to_parser
from gdess.recipes.recipe_utils import populate_station_list
from gdess.operations.Confrontation import Confrontation, load_cmip_model_output
from gdess.formatters import append_before_extension

_logger = logging.getLogger(__name__)


@benchmark_recipe
def surface_trends(options: Union[dict, argparse.Namespace],
                   verbose: Union[bool, str] = False,
                   ) -> dict:
    """Execute a series of preprocessing steps and generate a diagnostic result.

    Relevant gdess collections are instantiated and processed.

    Parameters
    ----------
    options : Union[dict, argparse.Namespace]
        Recipe options specified as key:value pairs. It can contain the following keys:
            ref_data : str
                (required) directory containing the NOAA Obspack NetCDF files
            model_name : str, default 'CMIP.NOAA-GFDL.GFDL-ESM4.esm-hist.Amon.gr1'
            cmip_load_method : str, default 'pangeo'
                either 'pangeo' (which uses a stored url),
                or 'local' (which uses the path defined in config file)
            start_yr : str, default '1960'
            end_yr : str, default '2015'
            figure_savepath : str, default None
            difference : str, default None
            globalmean : str
                either 'station', which requires specifying the <station_code> parameter,
                or 'global', which will calculate a global mean
            station_list : str, default 'mlo'
                a sequence of three letter codes (space-delimited) to specify
                the desired surface observing station
    verbose : Union[bool, str]
        can be either True, False, or a string for level such as "INFO, DEBUG, etc."

    Returns
    -------
    dict
        The data that were plotted.
    """
    set_verbose(_logger, verbose)
    if verbose:
        ProgressBar().register()
    _logger.debug("Parsing diagnostic parameters...")
    opts = parse_recipe_options(options, add_surface_trends_args_to_parser)

    stations_to_analyze = populate_station_list(run_all_stations=False, station_list=opts.station_list)

    # --- Load CMIP model output ---
    compare_against_model, ds_mdl = load_cmip_model_output(opts.model_name, opts.cmip_load_method, verbose=verbose)

    conf = Confrontation(compare_against_model, ds_mdl, opts, stations_to_analyze, verbose)
    cycles_of_each_station, concatenated_dfs, df_station_metadata, \
        xdata_obs, xdata_mdl, ydata_obs, ydata_mdl, \
        rmse_y_true, rmse_y_pred = conf.looper(how='trend')

    # --- Create Graphic ---
    fig, ax = plt.subplots(1, 1, figsize=(6, 4))
    diffs = {}
    if opts.difference:
        # Values at the same time
        for station in stations_to_analyze:
            merged = rmse_y_pred.loc[:, ['time', station]].merge(rmse_y_true.loc[:, ['time', station]],
                                                                 on='time', suffixes=("_pred", "_true"),)
            merged['diff'] = merged[station + '_pred'] - merged[station + '_true']
            # Plot
            ax.plot(merged['time'], merged['diff'],
                    label=f"model - obs [{station}]",
                    marker='.', linestyle='none')
            diffs[station] = merged['diff']
        #
        ax.set_ylim(limits_with_zero(ax.get_ylim()))
        #
        data_output = {'model': rmse_y_pred, 'obs': rmse_y_true, 'diff': diffs}

    else:
        data_output = {'model': ydata_mdl, 'obs': ydata_obs}
        #
        for station in stations_to_analyze:
            # Plot
            ax.plot(concatenated_dfs['ref']['time'], concatenated_dfs['ref'][station],
                    label=f"Obs [{station}]",
                    color='k')
            ax.plot(concatenated_dfs['mdl']['time'], concatenated_dfs['mdl'][station],
                    label=f'Model [{opts.model_name}]',
                    color='r', linestyle='-')

    ax.set_ylabel('$CO_2$ (ppm)')
    aesthetic_grid_no_spines(ax)
    #
    plt.legend()
    #
    plt.tight_layout()
    #
    if opts.figure_savepath:
        savepath = append_before_extension(opts.figure_savepath, 'trend')
        mysavefig(fig=fig, plot_save_name=savepath,
                  bbox_inches='tight')
        _logger.info("Saved at <%s>" % savepath)
    return data_output