From cde228833590c0698f381d9660a1e380dbebe63d Mon Sep 17 00:00:00 2001 From: wsavran <35315438+wsavran@users.noreply.github.com> Date: Mon, 15 Nov 2021 08:46:35 -0800 Subject: [PATCH] updated catalog forecast evaluation plots with better plot-args (#154) * updated catalog forecast evaluation plots with better plot-args * added plotting functions to api-reference in docs * added option to skip drawing colorbar on spatial plot --- csep/utils/plots.py | 241 +++++++++++++++++++++++-------- docs/reference/api_reference.rst | 7 +- requirements.txt | 2 +- 3 files changed, 183 insertions(+), 67 deletions(-) diff --git a/csep/utils/plots.py b/csep/utils/plots.py index f78bb8bb..f4308202 100644 --- a/csep/utils/plots.py +++ b/csep/utils/plots.py @@ -389,7 +389,7 @@ def plot_histogram(simulated, observation, bins='fd', percentile=None, pyplot.show() return ax -def plot_ecdf(x, ecdf, axes=None, xv=None, show=False, plot_args = None): +def plot_ecdf(x, ecdf, axes=None, xv=None, show=False, plot_args=None): """ Plots empirical cumulative distribution function. """ plot_args = plot_args or {} # get values from plotting args @@ -883,6 +883,7 @@ def plot_spatial_dataset(gridded, region, ax=None, show=False, extent=None, set_ linecolor = plot_args.get('linecolor', 'black') region_border = plot_args.get('region_border', True) # color bar properties + include_cbar = plot_args.get('include_cbar', True) cmap = plot_args.get('cmap', None) clim = plot_args.get('clim', None) clabel = plot_args.get('clabel', None) @@ -937,11 +938,12 @@ def plot_spatial_dataset(gridded, region, ax=None, show=False, extent=None, set_ # Colorbar options # create an axes on the right side of ax. The width of cax will be 5% # of ax and the padding between cax and ax will be fixed at 0.05 inch. - cax = fig.add_axes([ax.get_position().x1 + 0.01, ax.get_position().y0, 0.025, ax.get_position().height], - label='Colorbar') - cbar = fig.colorbar(im, ax=ax, cax=cax) - cbar.set_label(clabel, fontsize=clabel_fontsize) - cbar.ax.tick_params(labelsize=cticks_fontsize) + if include_cbar: + cax = fig.add_axes([ax.get_position().x1 + 0.01, ax.get_position().y0, 0.025, ax.get_position().height], + label='Colorbar') + cbar = fig.colorbar(im, ax=ax, cax=cax) + cbar.set_label(clabel, fontsize=clabel_fontsize) + cbar.ax.tick_params(labelsize=cticks_fontsize) # Gridline options if grid: @@ -972,32 +974,56 @@ def plot_number_test(evaluation_result, axes=None, show=True, plot_args=None): Takes result from evaluation and generates a specific histogram plot to show the results of the statistical evaluation for the n-test. - Args: evaluation_result: object-like var that implements the interface of the above EvaluationResult + axes (matplotlib.Axes): axes object used to chain this plot + show (bool): if true, call pyplot.show() + plot_args(dict): optional argument containing a dictionary of plotting arguments, with keys as strings and items as described below + + Optional plotting arguments: + * figsize: (:class:`list`/:class:`tuple`) - default: [6.4, 4.8] + * title: (:class:`str`) - default: name of the first evaluation result type + * title_fontsize: (:class:`float`) Fontsize of the plot title - default: 10 + * xlabel: (:class:`str`) - default: 'X' + * xlabel_fontsize: (:class:`float`) - default: 10 + * xticks_fontsize: (:class:`float`) - default: 10 + * ylabel_fontsize: (:class:`float`) - default: 10 + * text_fontsize: (:class:`float`) - default: 14 + * tight_layout: (:class:`bool`) Set matplotlib.figure.tight_layout to remove excess blank space in the plot - default: True + * percentile (:class:`float`) Critial region to shade on histogram - default: 95 + * bins: (:class:`str`) - Set binning type. see matplotlib.hist for more info - default: 'auto' + * xy: (:class:`list`/:class:`tuple`) - default: (0.55, 0.3) Returns: ax (matplotlib.axes.Axes): can be used to modify the figure """ - plot_args = plot_args or {} - # handle plotting + + # chain plotting axes if requested if axes: chained = True else: chained = False - # supply fixed arguments to plots - # might want to add other defaults here - filename = plot_args.get('filename', None) - xlabel = plot_args.get('xlabel', 'Event count of catalog') + + # default plotting arguments + plot_args = plot_args or {} + title = plot_args.get('title', 'Number Test') + title_fontsize = plot_args.get('title_fontsize', None) + xlabel = plot_args.get('xlabel', 'Event count of catalogs') + xlabel_fontsize = plot_args.get('xlabel_fontsize', None) ylabel = plot_args.get('ylabel', 'Number of catalogs') + ylabel_fontsize = plot_args.get('ylabel_fontsize', None) + text_fontsize = plot_args.get('text_fontsize', 14) + tight_layout = plot_args.get('tight_layout', True) + percentile = plot_args.get('percentile', 95) + filename = plot_args.get('filename', None) + bins = plot_args.get('bins', 'auto') xy = plot_args.get('xy', (0.5, 0.3)) + # set default plotting arguments fixed_plot_args = {'obs_label': evaluation_result.obs_name, 'sim_label': evaluation_result.sim_name} plot_args.update(fixed_plot_args) - bins = plot_args.get('mag_bins', 'auto') - percentile = plot_args.get('percentile', 95) ax = plot_histogram(evaluation_result.test_distribution, evaluation_result.observed_statistic, catalog=evaluation_result.obs_catalog_repr, plot_args=plot_args, @@ -1012,18 +1038,20 @@ def plot_number_test(evaluation_result, axes=None, show=True, plot_args=None): .format(*evaluation_result.quantile, evaluation_result.observed_statistic), xycoords='axes fraction', xy=xy, - fontsize=14) + fontsize=text_fontsize) except: ax.annotate('$\gamma = P(X \leq x) = {:.2f}$\n$\omega = {:.2f}$' .format(evaluation_result.quantile, evaluation_result.observed_statistic), xycoords='axes fraction', xy=xy, - fontsize=14) + fontsize=text_fontsize) - title = plot_args.get('title', evaluation_result.name) - ax.set_title(title, fontsize=14) - ax.set_xlabel(xlabel) - ax.set_ylabel(ylabel) + ax.set_title(title, fontsize=title_fontsize) + ax.set_xlabel(xlabel, fontsize=xlabel_fontsize) + ax.set_ylabel(ylabel, fontsize=ylabel_fontsize) + + if tight_layout: + ax.figure.tight_layout() if filename is not None: ax.figure.savefig(filename + '.pdf') @@ -1042,31 +1070,54 @@ def plot_magnitude_test(evaluation_result, axes=None, show=True, plot_args=None) Takes result from evaluation and generates a specific histogram plot to show the results of the statistical evaluation for the M-test. - Args: - evaluation_result: object that implements the interface of EvaluationResult + evaluation_result: object-like var that implements the interface of the above EvaluationResult + axes (matplotlib.Axes): axes object used to chain this plot + show (bool): if true, call pyplot.show() + plot_args(dict): optional argument containing a dictionary of plotting arguments, with keys as strings and items as described below + + Optional plotting arguments: + * figsize: (:class:`list`/:class:`tuple`) - default: [6.4, 4.8] + * title: (:class:`str`) - default: name of the first evaluation result type + * title_fontsize: (:class:`float`) Fontsize of the plot title - default: 10 + * xlabel: (:class:`str`) - default: 'X' + * xlabel_fontsize: (:class:`float`) - default: 10 + * xticks_fontsize: (:class:`float`) - default: 10 + * ylabel_fontsize: (:class:`float`) - default: 10 + * tight_layout: (:class:`bool`) Set matplotlib.figure.tight_layout to remove excess blank space in the plot - default: True + * percentile (:class:`float`) Critial region to shade on histogram - default: 95 + * bins: (:class:`str`) - Set binning type. see matplotlib.hist for more info - default: 'auto' + * xy: (:class:`list`/:class:`tuple`) - default: (0.55, 0.6) Returns: - ax (matplotlib.axes.Axes): can be used to modify the figure + ax (matplotlib.Axes): containing the new plot """ plot_args = plot_args or {} + title = plot_args.get('title', 'Magnitude Test') + title_fontsize = plot_args.get('title_fontsize', None) + xlabel = plot_args.get('xlabel', 'D* Statistic') + xlabel_fontsize = plot_args.get('xlabel_fontsize', None) + ylabel = plot_args.get('ylabel', 'Number of catalogs') + ylabel_fontsize = plot_args.get('ylabel_fontsize', None) + tight_layout = plot_args.get('tight_layout', True) + percentile = plot_args.get('percentile', 95) + text_fontsize = plot_args.get('text_fontsize', 14) + filename = plot_args.get('filename', None) + bins = plot_args.get('bins', 'auto') + xy = plot_args.get('xy', (0.55, 0.6)) + # handle plotting if axes: chained = True else: chained = False + # supply fixed arguments to plots # might want to add other defaults here - filename = plot_args.get('filename', None) - xy = plot_args.get('xy', (0.55, 0.6)) - fixed_plot_args = {'xlabel': 'D* Statistic', - 'ylabel': 'Number of Catalogs', - 'obs_label': evaluation_result.obs_name, + fixed_plot_args = {'obs_label': evaluation_result.obs_name, 'sim_label': evaluation_result.sim_name} plot_args.update(fixed_plot_args) - bins = plot_args.get('bins', 'auto') - percentile = plot_args.get('percentile', 95) ax = plot_histogram(evaluation_result.test_distribution, evaluation_result.observed_statistic, catalog=evaluation_result.obs_catalog_repr, plot_args=plot_args, @@ -1081,17 +1132,22 @@ def plot_magnitude_test(evaluation_result, axes=None, show=True, plot_args=None) .format(evaluation_result.quantile, evaluation_result.observed_statistic), xycoords='axes fraction', xy=xy, - fontsize=14) + fontsize=text_fontsize) except TypeError: # if both quantiles are provided, we want to plot the greater-equal quantile ax.annotate('$\gamma = P(X \geq x) = {:.2f}$\n$\omega = {:.2f}$' .format(evaluation_result.quantile[0], evaluation_result.observed_statistic), xycoords='axes fraction', xy=xy, - fontsize=14) + fontsize=text_fontsize) - title = plot_args.get('title', 'Magnitude Test') - ax.set_title(title, fontsize=14) + ax.set_title(title, fontsize=title_fontsize) + ax.set_xlabel(xlabel, fontsize=xlabel_fontsize) + ax.set_ylabel(ylabel, fontsize=ylabel_fontsize) + + if tight_layout: + var = ax.get_figure().tight_layout + () if filename is not None: ax.figure.savefig(filename + '.pdf') @@ -1151,7 +1207,6 @@ def plot_distribution_test(evaluation_result, axes=None, show=True, plot_args=No title = plot_args.get('title', evaluation_result.name) ax.set_title(title, fontsize=14) - ax.set_title(title, fontsize=14) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) @@ -1172,15 +1227,43 @@ def plot_likelihood_test(evaluation_result, axes=None, show=True, plot_args=None Takes result from evaluation and generates a specific histogram plot to show the results of the statistical evaluation for the L-test. - Args: evaluation_result: object-like var that implements the interface of the above EvaluationResult + axes (matplotlib.Axes): axes object used to chain this plot + show (bool): if true, call pyplot.show() + plot_args(dict): optional argument containing a dictionary of plotting arguments, with keys as strings and items as described below + + Optional plotting arguments: + * figsize: (:class:`list`/:class:`tuple`) - default: [6.4, 4.8] + * title: (:class:`str`) - default: name of the first evaluation result type + * title_fontsize: (:class:`float`) Fontsize of the plot title - default: 10 + * xlabel: (:class:`str`) - default: 'X' + * xlabel_fontsize: (:class:`float`) - default: 10 + * xticks_fontsize: (:class:`float`) - default: 10 + * ylabel_fontsize: (:class:`float`) - default: 10 + * text_fontsize: (:class:`float`) - default: 14 + * tight_layout: (:class:`bool`) Set matplotlib.figure.tight_layout to remove excess blank space in the plot - default: True + * percentile (:class:`float`) Critial region to shade on histogram - default: 95 + * bins: (:class:`str`) - Set binning type. see matplotlib.hist for more info - default: 'auto' + * xy: (:class:`list`/:class:`tuple`) - default: (0.55, 0.3) Returns: ax (matplotlib.axes.Axes): can be used to modify the figure - """ plot_args = plot_args or {} + title = plot_args.get('title', 'Pseudo-likelihood Test') + title_fontsize = plot_args.get('title_fontsize', None) + xlabel = plot_args.get('xlabel', 'Pseudo likelihood') + xlabel_fontsize = plot_args.get('xlabel_fontsize', None) + ylabel = plot_args.get('ylabel', 'Number of catalogs') + ylabel_fontsize = plot_args.get('ylabel_fontsize', None) + text_fontsize = plot_args.get('text_fontsize', 14) + tight_layout = plot_args.get('tight_layout', True) + percentile = plot_args.get('percentile', 95) + filename = plot_args.get('filename', None) + bins = plot_args.get('bins', 'auto') + xy = plot_args.get('xy', (0.55, 0.3)) + # handle plotting if axes: chained = True @@ -1188,14 +1271,9 @@ def plot_likelihood_test(evaluation_result, axes=None, show=True, plot_args=None chained = False # supply fixed arguments to plots # might want to add other defaults here - filename = plot_args.get('filename', None) - fixed_plot_args = {'xlabel': 'Pseudo likelihood', - 'ylabel': 'Number of catalogs', - 'obs_label': evaluation_result.obs_name, + fixed_plot_args = {'obs_label': evaluation_result.obs_name, 'sim_label': evaluation_result.sim_name} plot_args.update(fixed_plot_args) - bins = plot_args.get('bins', 'auto') - percentile = plot_args.get('percentile', 95) ax = plot_histogram(evaluation_result.test_distribution, evaluation_result.observed_statistic, catalog=evaluation_result.obs_catalog_repr, plot_args=plot_args, @@ -1209,19 +1287,22 @@ def plot_likelihood_test(evaluation_result, axes=None, show=True, plot_args=None ax.annotate('$\gamma = P(X \leq x) = {:.2f}$\n$\omega = {:.2f}$' .format(evaluation_result.quantile, evaluation_result.observed_statistic), xycoords='axes fraction', - xy=(0.55, 0.3), - fontsize=14) + xy=xy, + fontsize=text_fontsize) except TypeError: # if both quantiles are provided, we want to plot the greater-equal quantile ax.annotate('$\gamma = P(X \leq x) = {:.2f}$\n$\omega = {:.2f}$' .format(evaluation_result.quantile[1], evaluation_result.observed_statistic), xycoords='axes fraction', - xy=(0.55, 0.3), - fontsize=14) + xy=xy, + fontsize=text_fontsize) + ax.set_title(title, fontsize=title_fontsize) + ax.set_xlabel(xlabel, fontsize=xlabel_fontsize) + ax.set_ylabel(ylabel, fontsize=ylabel_fontsize) - title = plot_args.get('title', 'Likelihood Test') - ax.set_title(title, fontsize=14) + if tight_layout: + ax.figure.tight_layout() if filename is not None: ax.figure.savefig(filename + '.pdf') @@ -1239,31 +1320,59 @@ def plot_spatial_test(evaluation_result, axes=None, plot_args=None, show=True): Plot spatial test result from catalog based forecast Args: - evaluation_result: + evaluation_result: object-like var that implements the interface of the above EvaluationResult + axes (matplotlib.Axes): axes object used to chain this plot + show (bool): if true, call pyplot.show() + plot_args(dict): optional argument containing a dictionary of plotting arguments, with keys as strings and items as described below - Returns: + Optional plotting arguments: + * figsize: (:class:`list`/:class:`tuple`) - default: [6.4, 4.8] + * title: (:class:`str`) - default: name of the first evaluation result type + * title_fontsize: (:class:`float`) Fontsize of the plot title - default: 10 + * xlabel: (:class:`str`) - default: 'X' + * xlabel_fontsize: (:class:`float`) - default: 10 + * xticks_fontsize: (:class:`float`) - default: 10 + * ylabel_fontsize: (:class:`float`) - default: 10 + * text_fontsize: (:class:`float`) - default: 14 + * tight_layout: (:class:`bool`) Set matplotlib.figure.tight_layout to remove excess blank space in the plot - default: True + * percentile (:class:`float`) Critial region to shade on histogram - default: 95 + * bins: (:class:`str`) - Set binning type. see matplotlib.hist for more info - default: 'auto' + * xy: (:class:`list`/:class:`tuple`) - default: (0.2, 0.6) + Returns: + ax (matplotlib.axes.Axes): can be used to modify the figure """ + plot_args = plot_args or {} + title = plot_args.get('title', 'Spatial Test') + title_fontsize = plot_args.get('title_fontsize', None) + xlabel = plot_args.get('xlabel', 'Normalized pseudo-likelihood') + xlabel_fontsize = plot_args.get('xlabel_fontsize', None) + ylabel = plot_args.get('ylabel', 'Number of catalogs') + ylabel_fontsize = plot_args.get('ylabel_fontsize', None) + text_fontsize = plot_args.get('text_fontsize', 14) + tight_layout = plot_args.get('tight_layout', True) + percentile = plot_args.get('percentile', 95) + filename = plot_args.get('filename', None) + bins = plot_args.get('bins', 'auto') + xy = plot_args.get('xy', (0.2, 0.6)) + # handle plotting if axes: chained = True else: chained = False + # supply fixed arguments to plots # might want to add other defaults here - filename = plot_args.get('filename', None) fixed_plot_args = {'obs_label': evaluation_result.obs_name, - 'sim_label': evaluation_result.sim_name, - 'xlabel': 'Normalized pseudo likelihood', - 'ylabel': 'Number of catalogs'} + 'sim_label': evaluation_result.sim_name} plot_args.update(fixed_plot_args) - title = plot_args.get('title', 'Spatial Test') - percentile = plot_args.get('percentile', 95) + ax = plot_histogram(evaluation_result.test_distribution, evaluation_result.observed_statistic, catalog=evaluation_result.obs_catalog_repr, plot_args=plot_args, - bins='fd', + bins=bins, axes=axes, percentile=percentile) @@ -1273,18 +1382,22 @@ def plot_spatial_test(evaluation_result, axes=None, plot_args=None, show=True): ax.annotate('$\gamma = P(X \leq x) = {:.2f}$\n$\omega = {:.2f}$' .format(evaluation_result.quantile, evaluation_result.observed_statistic), xycoords='axes fraction', - xy=(0.2, 0.6), - fontsize=14) + xy=xy, + fontsize=text_fontsize) except TypeError: # if both quantiles are provided, we want to plot the greater-equal quantile ax.annotate('$\gamma = P(X \leq x) = {:.2f}$\n$\omega = {:.2f}$' .format(evaluation_result.quantile[1], evaluation_result.observed_statistic), xycoords='axes fraction', - xy=(0.2, 0.6), - fontsize=14) + xy=xy, + fontsize=text_fontsize) + ax.set_title(title, fontsize=title_fontsize) + ax.set_xlabel(xlabel, fontsize=xlabel_fontsize) + ax.set_ylabel(ylabel, fontsize=ylabel_fontsize) - ax.set_title(title, fontsize=14) + if tight_layout: + ax.figure.tight_layout() if filename is not None: ax.figure.savefig(filename + '.pdf') diff --git a/docs/reference/api_reference.rst b/docs/reference/api_reference.rst index c9feb302..9f8a6b36 100644 --- a/docs/reference/api_reference.rst +++ b/docs/reference/api_reference.rst @@ -211,7 +211,7 @@ Region utilities: increase_grid_resolution masked_region generate_aftershock_region - Polygon + california_relm_region Plotting @@ -227,6 +227,8 @@ General plotting: plot_histogram plot_ecdf plot_basemap + plot_spatial_dataset + add_labels_for_publication Plotting from catalogs: @@ -236,7 +238,7 @@ Plotting from catalogs: plot_magnitude_versus_time plot_catalog -Plotting from stochastic event sets: +Plotting stochastic event sets and evaluations: .. autosummary:: :toctree: generated @@ -248,6 +250,7 @@ Plotting from stochastic event sets: plot_distribution_test plot_likelihood_test plot_spatial_test + plot_calibration_test Plotting gridded forecasts and evaluations: diff --git a/requirements.txt b/requirements.txt index cd90dbf1..a9921178 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ python-dateutil pytest vcrpy pytest-cov -sphinx == 4.2.0 +sphinx sphinx-gallery sphinx-rtd-theme pillow