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