Source code for ctdcal.ctd_plots

"""
A module for plotting and visualizing the CTDCAL data products.
"""
import logging
from pathlib import Path

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
import pandas as pd

log = logging.getLogger(__name__)


def _apply_default_fmt(xlim, ylim, xlabel, ylabel, title, grid, fontsize=12):
    plt.xlim(xlim)
    plt.xticks(rotation=45)
    plt.ylim(ylim)
    plt.xlabel(xlabel, fontsize=fontsize)
    plt.ylabel(ylabel, fontsize=fontsize)
    plt.title(title, fontsize=fontsize)
    plt.grid(grid)
    plt.tight_layout()


def _save_fig(ax, f_out):
    if f_out is not None:
        f_parent = Path(f_out).parent
        if not f_parent.exists():
            log.info(f"Parent folder '{f_parent.as_posix()}' doesn't exist... creating")
            Path(f_out).parent.mkdir(parents=True)
        plt.savefig(f_out)
        plt.close()
    else:
        return ax


[docs] def residual_vs_pressure( param, ref, prs, stn=None, xlim=(-0.02, 0.02), ylim=(6000, 0), xlabel="Residual", ylabel="Pressure (dbar)", auto_title=True, grid=False, deep=False, f_out=None, ): """ Plot residuals (ref - param) as a function of pressure. Parameters ---------- param : pd.Series or array-like Input variable ref : pd.Series or array-like Reference variable prs : pd.Series or array-like Pressure variable stn : pd.Series or array-like, optional Station variable xlim : tuple, optional Lower and upper x-limits ylim : tuple, optional Lower and upper y-limits xlabel : str, optional Label for the x-axis ylabel : str, optional Label for the y-axis auto_title : bool, optional Generate title from input attributes (iff dtype is pd.Series) grid : bool, optional Overlay grid on figure deep : bool, optional Whether to plot only values >2000 dbar f_out : path-like, optional Path to save figure Returns ------- ax : matplotlib.axes, optional Formatted scatter plot (if f_out is not given) """ diff = ref - param deep_text = " (>2000 dbar)" if deep else "" if deep: deep_rows = prs > 2000 diff, prs, stn = diff[deep_rows], prs[deep_rows], stn[deep_rows] # initialize figure plt.figure(figsize=(7, 6)) ax = plt.axes() # color scatter by stations if given if stn is not None: idx, uniques = pd.factorize(stn) # find unique stations #s and index them sc = ax.scatter(diff, prs, c=idx, marker="+") cbar = plt.colorbar(sc, ax=ax, pad=0.1) # set cbar ticks to station names tick_inds = cbar.get_ticks().astype(int) cbar.ax.yaxis.set_major_locator(ticker.FixedLocator(tick_inds)) cbar.ax.set_yticklabels(uniques[tick_inds]) cbar.ax.set_title("Station") else: sc = ax.scatter(diff, prs, marker="+") # formatting title = None if auto_title: try: title = f"{ref.name}-{param.name}{deep_text} vs. {prs.name}" except AttributeError: log.warning( "Failed to set title from variable names (requires dtype pd.Series)" ) log.info("Set afterward using 'ax.set_title(\"title\")'") _apply_default_fmt(xlim, ylim, xlabel, ylabel, title, grid) # save to path or return axis return _save_fig(ax, f_out)
[docs] def residual_vs_station( param, ref, prs, stn, ylim=(-0.02, 0.02), xlabel="Station Number", ylabel="Residual", grid=False, deep=False, f_out=None, ): """ Plot residuals (ref - param) as a function of station number. Parameters ---------- param : pd.Series or array-like Input variable ref : pd.Series or array-like Reference variable prs : pd.Series or array-like Pressure variable stn : pd.Series or array-like, optional Station variable ylim : tuple, optional Lower and upper y-limits xlabel : str, optional Label for the x-axis ylabel : str, optional Label for the y-axis grid : bool, optional Overlay grid on figure deep : bool, optional Whether to plot only values >2000 dbar f_out : path-like, optional Path to save figure Returns ------- ax : matplotlib.axes, optional Formatted scatter plot (if f_out is not given) """ diff = ref - param deep_text = " (>2000 dbar)" if deep else "" if deep: deep_rows = prs > 2000 diff, prs, stn = diff[deep_rows], prs[deep_rows], stn[deep_rows] plt.figure(figsize=(7, 6)) ax = plt.axes() sc = ax.scatter(stn, diff, c=prs, marker="+") cbar = plt.colorbar(sc, pad=0.1) cbar.set_label("Pressure (dbar)") # formatting try: title = f"{ref.name}-{param.name}{deep_text} vs. {stn.name}" except AttributeError: title = None log.warning( "Failed to set title from variable names (requires dtype pd.Series)" ) log.info('Set afterward using \'ax.set_title("title")`') _apply_default_fmt(None, ylim, xlabel, ylabel, title, grid) # save to path or return axis return _save_fig(ax, f_out)
def _intermediate_residual_plot( diff, prs, stn, xlim=(-0.02, 0.02), xlabel="Residual", show_thresh=False, f_out=None, ): """ Internal function to make figures at intermediate processing stages for debugging. """ ax = residual_vs_pressure( 0, diff, prs, stn=stn, xlim=xlim, xlabel=xlabel, auto_title=False, grid=True ) if show_thresh: thresh = np.array([0.002, 0.005, 0.010, 0.020]) p_range = np.array([6000, 2000, 1000, 500]) thresh = np.append(thresh, thresh[-1]) # this should still work fine even when p_range = np.append(p_range, 0) # thresh/p_range are defined elsewhere plt.step(thresh, p_range, ":k") plt.step(-thresh, p_range, ":k") mean = np.round(np.nanmean(diff), 4) stdev = np.round(np.nanstd(diff), 4) ax.set_title(f"Mean: {mean} / Stdev: {stdev}") # save to path or return axis (primarily for testing) return _save_fig(ax, f_out) # TODO: more plots! what does ODV have? # section plots (w/ PyDiva) # parameter-parameter plots