"""This module contains all the plotting methods for Pastas Models.
"""
import logging
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_pdf import PdfPages
from matplotlib.ticker import MultipleLocator, LogFormatter
from pandas import concat, Series
from .decorators import model_tmin_tmax
from .plots import series, diagnostics, cum_frequency, \
_table_formatter_params, _table_formatter_stderr
logger = logging.getLogger(__name__)
[docs]class Plotting:
"""Class that contains all plotting methods for Pastas models.
Pastas models come with a number of predefined plotting methods to quickly
visualize a Model. All of these methods are contained in the `plot`
attribute of a model. For example, if we stored a
:class:`pastas.model.Model` instance in the variable `ml`, the plot
methods are available as follows::
>>> ml.plot.results()
"""
[docs] def __init__(self, ml):
self.ml = ml # Store a reference to the model class
def __repr__(self):
msg = "This module contains all the built-in plotting options that " \
"are available."
return msg
[docs] @model_tmin_tmax
def plot(self, tmin=None, tmax=None, oseries=True, simulation=True,
ax=None, figsize=None, legend=True, **kwargs):
"""Make a plot of the observed and simulated series.
Parameters
----------
tmin: str or pandas.Timestamp, optional
tmax: str or pandas.Timestamp, optional
oseries: bool, optional
True to plot the observed time series.
simulation: bool, optional
True to plot the simulated time series.
ax: Matplotlib.axes instance, optional
Axes to add the plot to.
figsize: tuple, optional
Tuple with the height and width of the figure in inches.
legend: bool, optional
Boolean to determine to show the legend (True) or not (False).
Returns
-------
ax: matplotlib.axes.Axes
matplotlib axes with the simulated and optionally the observed
timeseries.
Examples
--------
>>> ml.plot()
"""
if ax is None:
_, ax = plt.subplots(figsize=figsize, **kwargs)
if oseries:
o = self.ml.observations(tmin=tmin, tmax=tmax)
o_nu = self.ml.oseries.series.drop(o.index).loc[
o.index.min():o.index.max()]
if not o_nu.empty:
# plot parts of the oseries that are not used in grey
o_nu.plot(linestyle='', marker='.', color='0.5', label='',
ax=ax)
o.plot(linestyle='', marker='.', color='k', ax=ax)
if simulation:
sim = self.ml.simulate(tmin=tmin, tmax=tmax)
r2 = round(self.ml.stats.rsq(tmin=tmin, tmax=tmax) * 100, 1)
sim.plot(ax=ax, label=f'{sim.name} ($R^2$ = {r2}%)')
# Dress up the plot
ax.set_xlim(tmin, tmax)
ax.set_ylabel("Groundwater levels [meter]")
ax.set_title("Results of {}".format(self.ml.name))
if legend:
ax.legend(ncol=2, numpoints=3)
plt.tight_layout()
return ax
[docs] @model_tmin_tmax
def results(self, tmin=None, tmax=None, figsize=(10, 8), split=False,
adjust_height=True, return_warmup=False, block_or_step='step',
fig=None, **kwargs):
"""Plot different results in one window to get a quick overview.
Parameters
----------
tmin: str or pandas.Timestamp, optional
tmax: str or pandas.Timestamp, optional
figsize: tuple, optional
tuple of size 2 to determine the figure size in inches.
split: bool, optional
Split the stresses in multiple stresses when possible. Default is
False.
adjust_height: bool, optional
Adjust the height of the graphs, so that the vertical scale of all
the subplots on the left is equal. Default is True.
return_warmup: bool, optional
Show the warmup-period. Default is false.
block_or_step: str, optional
Plot the block- or step-response on the right. Default is 'step'.
fig: Matplotib.Figure instance, optional
Optionally provide a Matplotib.Figure instance to plot onto.
**kwargs: dict, optional
Optional arguments, passed on to the plt.figure method.
Returns
-------
list of matplotlib.axes.Axes
Examples
--------
>>> ml.plots.results()
"""
# Number of rows to make the figure with
o = self.ml.observations(tmin=tmin, tmax=tmax)
o_nu = self.ml.oseries.series.drop(o.index)
if return_warmup:
o_nu = o_nu[tmin - self.ml.settings['warmup']: tmax]
else:
o_nu = o_nu[tmin: tmax]
sim = self.ml.simulate(tmin=tmin, tmax=tmax,
return_warmup=return_warmup)
res = self.ml.residuals(tmin=tmin, tmax=tmax)
contribs = self.ml.get_contributions(split=split, tmin=tmin,
tmax=tmax,
return_warmup=return_warmup)
ylims = [(min([sim.min(), o[tmin:tmax].min()]),
max([sim.max(), o[tmin:tmax].max()])),
(res.min(), res.max())] # residuals are bigger than noise
if adjust_height:
for contrib in contribs:
hs = contrib.loc[tmin:tmax]
if hs.empty:
if contrib.empty:
ylims.append((0.0, 0.0))
else:
ylims.append((contrib.min(), hs.max()))
else:
ylims.append((hs.min(), hs.max()))
hrs = _get_height_ratios(ylims)
else:
hrs = [2] + [1] * (len(contribs) + 1)
# Make main Figure
if fig is None:
fig = plt.figure(figsize=figsize, **kwargs)
gs = fig.add_gridspec(ncols=2, nrows=len(contribs) + 2,
width_ratios=[2, 1], height_ratios=hrs)
# Main frame
ax1 = fig.add_subplot(gs[0, 0])
o.plot(ax=ax1, linestyle='', marker='.', color='k', x_compat=True)
if not o_nu.empty:
# plot parts of the oseries that are not used in grey
o_nu.plot(ax=ax1, linestyle='', marker='.', color='0.5', label='',
x_compat=True, zorder=-1)
# add rsq to simulation
r2 = self.ml.stats.rsq(tmin=tmin, tmax=tmax)
sim.plot(ax=ax1, x_compat=True, label=f'{sim.name} ($R^2$={r2:.2%})')
ax1.legend(loc=(0, 1), ncol=3, frameon=False, numpoints=3)
ax1.set_ylim(ylims[0])
# Residuals and noise
ax2 = fig.add_subplot(gs[1, 0], sharex=ax1)
res.plot(ax=ax2, color='k', x_compat=True)
if self.ml.settings["noise"] and self.ml.noisemodel:
noise = self.ml.noise(tmin=tmin, tmax=tmax)
noise.plot(ax=ax2, x_compat=True)
ax2.axhline(0.0, color='k', linestyle='--', zorder=0)
ax2.legend(loc=(0, 1), ncol=3, frameon=False)
# Add a row for each stressmodel
rmin = 0 # tmin of the response
rmax = 0 # tmax of the response
axb = None
i = 0
for sm_name, sm in self.ml.stressmodels.items():
# plot the contribution
nsplit = sm.get_nsplit()
if split and nsplit > 1:
for _ in range(nsplit):
ax = fig.add_subplot(gs[i + 2, 0], sharex=ax1)
contribs[i].plot(ax=ax, x_compat=True)
ax.legend(loc=(0, 1), ncol=3, frameon=False)
if adjust_height:
ax.set_ylim(ylims[i + 2])
i = i + 1
else:
ax = fig.add_subplot(gs[i + 2, 0], sharex=ax1)
contribs[i].plot(ax=ax, x_compat=True)
title = [stress.name for stress in sm.stress]
if len(title) > 3:
title = title[:3] + ["..."]
ax.set_title(f"Stresses: {title}", loc="right",
fontsize=plt.rcParams['legend.fontsize'])
ax.legend(loc=(0, 1), ncol=3, frameon=False)
if adjust_height:
ax.set_ylim(ylims[i + 2])
i = i + 1
# plot the step response
response = self.ml._get_response(block_or_step=block_or_step,
name=sm_name, add_0=True)
if response is not None:
rmax = max(rmax, response.index.max())
axb = fig.add_subplot(gs[i + 1, 1], sharex=axb)
response.plot(ax=axb)
if block_or_step == 'block':
title = 'Block response'
rmin = response.index[1]
axb.set_xscale('log')
axb.xaxis.set_major_formatter(LogFormatter())
else:
title = 'Step response'
axb.set_title(title, fontsize=plt.rcParams['legend.fontsize'])
if axb is not None:
axb.set_xlim(rmin, rmax)
# xlim sets minorticks back after plots:
ax1.minorticks_off()
if return_warmup:
ax1.set_xlim(tmin - self.ml.settings['warmup'], tmax)
else:
ax1.set_xlim(tmin, tmax)
# sometimes, ticks suddenly appear on top plot, turn off just in case
plt.setp(ax1.get_xticklabels(), visible=False)
for ax in fig.axes:
ax.grid(True)
if isinstance(fig, plt.Figure):
fig.tight_layout(pad=0.0) # Before making the table
# Draw parameters table
ax3 = fig.add_subplot(gs[0:2, 1])
n_free = self.ml.parameters.vary.sum()
ax3.set_title(f'Model Parameters ($n_c$={n_free})', loc='left',
fontsize=plt.rcParams['legend.fontsize'])
p = self.ml.parameters.copy().loc[:, ["name", "optimal", "stderr"]]
p.loc[:, "name"] = p.index
stderr = p.loc[:, "stderr"] / p.loc[:, "optimal"]
p.loc[:, "optimal"] = p.loc[:, "optimal"].apply(
_table_formatter_params)
p.loc[:, "stderr"] = stderr.abs().apply(_table_formatter_stderr)
ax3.axis('off')
ax3.table(bbox=(0., 0., 1.0, 1.0), cellText=p.values,
colWidths=[0.5, 0.25, 0.25], colLabels=p.columns)
return fig.axes
[docs] @model_tmin_tmax
def decomposition(self, tmin=None, tmax=None, ytick_base=True, split=True,
figsize=(10, 8), axes=None, name=None,
return_warmup=False, min_ylim_diff=None, **kwargs):
"""Plot the decomposition of a time-series in the different stresses.
Parameters
----------
tmin: str or pandas.Timestamp, optional
tmax: str or pandas.Timestamp, optional
ytick_base: Boolean or float, optional
Make the ytick-base constant if True, set this base to float if
float.
split: bool, optional
Split the stresses in multiple stresses when possible. Default is
True.
axes: matplotlib.axes.Axes instance, optional
Matplotlib Axes instance to plot the figure on to.
figsize: tuple, optional
tuple of size 2 to determine the figure size in inches.
name: str, optional
Name to give the simulated time series in the legend.
return_warmup: bool, optional
Show the warmup-period. Default is false.
min_ylim_diff: float, optional
Float with the difference in the ylimits. Default is None
**kwargs: dict, optional
Optional arguments, passed on to the plt.subplots method.
Returns
-------
axes: list of matplotlib.axes.Axes
"""
o = self.ml.observations(tmin=tmin, tmax=tmax)
# determine the simulation
sim = self.ml.simulate(tmin=tmin, tmax=tmax,
return_warmup=return_warmup)
if name is not None:
sim.name = name
# determine the influence of the different stresses
contribs = self.ml.get_contributions(split=split, tmin=tmin, tmax=tmax,
return_warmup=return_warmup)
names = [s.name for s in contribs]
if self.ml.transform:
contrib = self.ml.get_transform_contribution(tmin=tmin, tmax=tmax)
contribs.append(contrib)
names.append(self.ml.transform.name)
# determine ylim for every graph, to scale the height
ylims = [(min([sim.min(), o[tmin:tmax].min()]),
max([sim.max(), o[tmin:tmax].max()]))]
for contrib in contribs:
hs = contrib[tmin:tmax]
if hs.empty:
if contrib.empty:
ylims.append((0.0, 0.0))
else:
ylims.append((contrib.min(), hs.max()))
else:
ylims.append((hs.min(), hs.max()))
if min_ylim_diff is not None:
for i, ylim in enumerate(ylims):
if np.diff(ylim) < min_ylim_diff:
ylims[i] = (np.mean(ylim) - min_ylim_diff / 2,
np.mean(ylim) + min_ylim_diff / 2)
# determine height ratios
height_ratios = _get_height_ratios(ylims)
nrows = len(contribs) + 1
if axes is None:
# open a new figure
gridspec_kw = {'height_ratios': height_ratios}
fig, axes = plt.subplots(nrows, sharex=True, figsize=figsize,
gridspec_kw=gridspec_kw, **kwargs)
axes = np.atleast_1d(axes)
o_label = o.name
set_axes_properties = True
else:
if len(axes) != nrows:
msg = 'Makes sure the number of axes equals the number of ' \
'series'
raise Exception(msg)
fig = axes[0].figure
o_label = ''
set_axes_properties = False
# plot simulation and observations in top graph
o_nu = self.ml.oseries.series.drop(o.index)
if not o_nu.empty:
# plot parts of the oseries that are not used in grey
o_nu.plot(linestyle='', marker='.', color='0.5', label='',
markersize=2, ax=axes[0], x_compat=True)
o.plot(linestyle='', marker='.', color='k', label=o_label,
markersize=3, ax=axes[0], x_compat=True)
sim.plot(ax=axes[0], x_compat=True)
if set_axes_properties:
axes[0].set_title('observations vs. simulation')
axes[0].set_ylim(ylims[0])
axes[0].grid(True)
axes[0].legend(ncol=3, frameon=False, numpoints=3)
if ytick_base and set_axes_properties:
if isinstance(ytick_base, bool):
# determine the ytick-spacing of the top graph
yticks = axes[0].yaxis.get_ticklocs()
if len(yticks) > 1:
ytick_base = yticks[1] - yticks[0]
else:
ytick_base = None
axes[0].yaxis.set_major_locator(
MultipleLocator(base=ytick_base))
# plot the influence of the stresses
for i, contrib in enumerate(contribs):
ax = axes[i + 1]
contrib.plot(ax=ax, x_compat=True)
if set_axes_properties:
if ytick_base:
# set the ytick-spacing equal to the top graph
locator = MultipleLocator(base=ytick_base)
ax.yaxis.set_major_locator(locator)
ax.set_title(names[i])
ax.set_ylim(ylims[i + 1])
ax.grid(True)
ax.minorticks_off()
if set_axes_properties:
axes[0].set_xlim(tmin, tmax)
fig.tight_layout(pad=0.0)
return axes
[docs] @model_tmin_tmax
def diagnostics(self, tmin=None, tmax=None, figsize=(10, 5), bins=50,
acf_options=None, fig=None, alpha=0.05, **kwargs):
"""Plot a window that helps in diagnosing basic model assumptions.
Parameters
----------
tmin: str or pandas.Timestamp, optional
start time for which to calculate the residuals.
tmax: str or pandas.Timestamp, optional
end time for which to calculate the residuals.
figsize: tuple, optional
Tuple with the height and width of the figure in inches.
bins: int optional
number of bins used for the histogram. 50 is default.
acf_options: dict, optional
dictionary with keyword arguments that are passed on to
pastas.stats.acf.
fig: Matplotib.Figure instance, optional
Optionally provide a Matplotib.Figure instance to plot onto.
alpha: float, optional
Significance level to calculate the (1-alpha)-confidence intervals.
**kwargs: dict, optional
Optional keyword arguments, passed on to plt.figure.
Returns
-------
axes: list of matplotlib.axes.Axes
Examples
--------
>>> axes = ml.plots.diagnostics()
Note
----
This plot assumed that the noise or residuals follow a Normal
distribution.
See Also
--------
pastas.stats.acf
Method that computes the autocorrelation.
scipy.stats.probplot
Method use to plot the probability plot.
"""
if self.ml.settings["noise"]:
res = self.ml.noise(tmin=tmin, tmax=tmax).iloc[1:]
else:
res = self.ml.residuals(tmin=tmin, tmax=tmax)
sim = self.ml.simulate(tmin=tmin, tmax=tmax)
if self.ml.interpolate_simulation:
sim_interpolated = np.interp(res.index.asi8,
sim.index.asi8,
sim.values)
sim = Series(index=res.index, data=sim_interpolated)
return diagnostics(series=res, sim=sim, figsize=figsize, bins=bins,
fig=fig, acf_options=acf_options, alpha=alpha,
**kwargs)
[docs] @model_tmin_tmax
def cum_frequency(self, tmin=None, tmax=None, ax=None, figsize=(5, 2),
**kwargs):
"""Plot the cumulative frequency for the observations and simulation.
Parameters
----------
Parameters
----------
tmin: str or pandas.Timestamp, optional
tmax: str or pandas.Timestamp, optional
ax: Matplotlib.axes instance, optional
Axes to add the plot to.
figsize: tuple, optional
Tuple with the height and width of the figure in inches.
**kwargs:
Passed on to plot_cum_frequency
Returns
-------
ax: matplotlib.axes.Axes
See Also
--------
ps.stats.plot_cum_frequency
"""
sim = self.ml.simulate(tmin=tmin, tmax=tmax)
obs = self.ml.observations(tmin=tmin, tmax=tmax)
return cum_frequency(obs, sim, ax=ax, figsize=figsize, **kwargs)
[docs] def block_response(self, stressmodels=None, ax=None, figsize=None,
**kwargs):
"""Plot the block response for a specific stressmodels.
Parameters
----------
stressmodels: list, optional
List with the stressmodels to plot the block response for.
ax: Matplotlib.axes instance, optional
Axes to add the plot to.
figsize: tuple, optional
Tuple with the height and width of the figure in inches.
Returns
-------
matplotlib.axes.Axes
matplotlib axes instance.
"""
if ax is None:
_, ax = plt.subplots(figsize=figsize, **kwargs)
if not stressmodels:
stressmodels = self.ml.stressmodels.keys()
legend = []
for name in stressmodels:
if hasattr(self.ml.stressmodels[name], 'rfunc'):
self.ml.get_block_response(name).plot(ax=ax)
legend.append(name)
else:
logger.warning("Stressmodel %s not in stressmodels list.",
name)
plt.xlim(0)
plt.xlabel("Time [days]")
plt.legend(legend)
return ax
[docs] def step_response(self, stressmodels=None, ax=None, figsize=None,
**kwargs):
"""Plot the step response for a specific stressmodels.
Parameters
----------
stressmodels: list, optional
List with the stressmodels to plot the block response for.
Returns
-------
matplotlib.axes.Axes
matplotlib axes instance.
"""
if ax is None:
_, ax = plt.subplots(figsize=figsize, **kwargs)
if not stressmodels:
stressmodels = self.ml.stressmodels.keys()
legend = []
for name in stressmodels:
if hasattr(self.ml.stressmodels[name], 'rfunc'):
self.ml.get_step_response(name).plot(ax=ax)
legend.append(name)
else:
logger.warning("Stressmodel %s not in stressmodels list.",
name)
plt.xlim(0)
plt.xlabel("Time [days]")
plt.legend(legend)
return ax
[docs] @model_tmin_tmax
def stresses(self, tmin=None, tmax=None, cols=1, split=True, sharex=True,
figsize=(10, 8), **kwargs):
"""This method creates a graph with all the stresses used in the model.
Parameters
----------
tmin
tmax
cols: int
number of columns used for plotting.
split: bool, optional
Split the stress
sharex: bool, optional
Sharex the x-axis.
figsize: tuple, optional
Tuple with the height and width of the figure in inches.
Returns
-------
axes: matplotlib.axes
matplotlib axes instance.
"""
stresses = _get_stress_series(self.ml, split=split)
rows = len(stresses)
rows = -(-rows // cols) # round up with out additional import
fig, axes = plt.subplots(rows, cols, sharex=sharex, figsize=figsize,
**kwargs)
if hasattr(axes, "flatten"):
axes = axes.flatten()
else:
axes = [axes]
for ax, stress in zip(axes, stresses):
stress.plot(ax=ax)
ax.legend([stress.name], loc=2)
plt.xlim(tmin, tmax)
fig.tight_layout(pad=0.0)
return axes
[docs] @model_tmin_tmax
def contributions_pie(self, tmin=None, tmax=None, ax=None,
figsize=None, split=True, partition='std',
wedgeprops=None, startangle=90,
autopct='%1.1f%%', **kwargs):
"""Make a pie chart of the contributions. This plot is based on the TNO
Groundwatertoolbox.
Parameters
----------
tmin: str or pandas.Timestamp, optional.
tmax: str or pandas.Timestamp, optional.
ax: matplotlib.axes, optional
Axes to plot the pie chart on. A new figure and axes will be
created of not providided.
figsize: tuple, optional
tuple of size 2 to determine the figure size in inches.
split: bool, optional
Split the stresses in multiple stresses when possible.
partition : str
statistic to use to determine contribution of stress, either
'sum' or 'std' (default).
wedgeprops: dict, optional, default None
dict containing pie chart wedge properties, default is None,
which sets edgecolor to white.
startangle: float
at which angle to start drawing wedges
autopct: str
format string to add percentages to pie chart
kwargs: dict, optional
The keyword arguments are passed on to plt.pie.
Returns
-------
ax: matplotlib.axes
"""
if ax is None:
_, ax = plt.subplots(figsize=figsize)
contribs = self.ml.get_contributions(split=split, tmin=tmin, tmax=tmax)
if partition == 'sum':
# the part of each pie is determined by the sum of the contribution
frac = [np.abs(contrib).sum() for contrib in contribs]
elif partition == 'std':
# the part of each pie is determined by the std of the contribution
frac = [contrib.std() for contrib in contribs]
else:
msg = 'Unknown value for partition: {}'.format(partition)
raise (Exception(msg))
# make sure the unexplained part is 100 - evp %
evp = self.ml.stats.evp(tmin=tmin, tmax=tmax) / 100
frac = np.array(frac) / sum(frac) * evp
frac = np.append(frac, 1 - evp)
if 'labels' not in kwargs:
labels = [contrib.name for contrib in contribs]
labels.append("Unexplained")
kwargs['labels'] = labels
if wedgeprops is None:
wedgeprops = {'edgecolor': 'w'}
ax.pie(frac, wedgeprops=wedgeprops, startangle=startangle,
autopct=autopct, **kwargs)
ax.axis('equal')
return ax
[docs] @model_tmin_tmax
def stacked_results(self, tmin=None, tmax=None, figsize=(10, 8),
stacklegend=False, stacklegend_kws=None, **kwargs):
"""Create a results plot, similar to `ml.plots.results()`, in which the
individual contributions of stresses (in stressmodels with multiple
stresses) are stacked.
Note: does not plot the individual contributions of StressModel2
Parameters
----------
tmin : str or pandas.Timestamp, optional
tmax : str or pandas.Timestamp, optional
figsize : tuple, optional
stacklegend : bool, optional
Add legend to the stacked plot.
Returns
-------
axes: list of axes objects
"""
# %% Contribution per stress on model results plot
def custom_sort(t):
"""Sort by mean contribution."""
return t[1].mean()
# Create standard results plot
axes = self.ml.plots.results(tmin=tmin, tmax=tmax, figsize=figsize,
**kwargs)
nsm = len(self.ml.stressmodels)
# loop over axes showing stressmodel contributions
for i, sm in zip(range(3, 3 + 2 * nsm, 2),
self.ml.stressmodels.keys()):
# Get the contributions for StressModels with multiple stresses
contributions = []
sml = self.ml.stressmodels[sm]
if (len(sml.stress) > 0) and (sml._name == "WellModel"):
nsplit = sml.get_nsplit()
if nsplit > 1:
for istress in range(len(sml.stress)):
h = self.ml.get_contribution(sm, istress=istress,
tmin=tmin, tmax=tmax)
name = sml.stress[istress].name
if name is None:
name = sm
contributions.append((name, h))
else:
h = self.ml.get_contribution(sm, tmin=tmin, tmax=tmax)
name = sm
contributions.append((name, h))
contributions.sort(key=custom_sort)
# add stacked plot to correct axes
ax = axes[i - 1]
del ax.lines[0] # delete existing line
contrib = [c[1] for c in contributions] # get timeseries
vstack = concat(contrib, axis=1, sort=False)
names = [c[0] for c in contributions] # get names
ax.stackplot(vstack.index, vstack.values.T, labels=names)
if stacklegend:
if stacklegend_kws is None:
stacklegend_kws = {}
else:
ncol = stacklegend_kws.pop("ncol", 5)
fontsize = stacklegend_kws.pop("fontsize", 6)
loc = stacklegend_kws.pop("loc", "best")
ax.legend(loc=loc, ncol=ncol, fontsize=fontsize,
**stacklegend_kws)
# y-scale does not show 0
ylower, yupper = ax.get_ylim()
if (ylower < 0) and (yupper < 0):
ax.set_ylim(top=0)
elif (ylower > 0) and (yupper > 0):
ax.set_ylim(bottom=0)
return axes
[docs] @model_tmin_tmax
def series(self, tmin=None, tmax=None, split=True, **kwargs):
"""Method to plot all the time series going into a Pastas Model.
Parameters
----------
tmin: str or pd.Timestamp
tmax: str or pd.Timestamp
split: bool, optional
Split the stresses in multiple stresses when possible.
hist: bool
Histogram for the Series. Returns the number of observations, mean,
skew and kurtosis as well. For the head series the result of the
shapiro-wilk test (p > 0.05) for normality is reported.
bins: float
Number of bins in the histogram plot.
titles: bool
Set the titles or not. Taken from the name attribute of the Series.
labels: List of str
List with the labels for each subplot.
figsize: tuple
Set the size of the figure.
Returns
-------
matplotlib.Axes
"""
obs = self.ml.observations(tmin=tmin, tmax=tmax)
stresses = _get_stress_series(self.ml, split=split)
axes = series(obs, stresses=stresses, **kwargs)
return axes
[docs] @model_tmin_tmax
def summary_pdf(self, tmin=None, tmax=None, fname=None, dpi=150,
results_kwargs={}, diagnostics_kwargs={}):
"""Create a PDF file (A4) with the results and diagnostics plot.
Parameters
----------
tmin: str or pd.Timestamp, optional
tmax: str or pd.Timestamp, optional
fname: str, optional
string with the file name / path to store the PDF file.
dpi: int, optional
dpi to save the figure with.
results_kwargs: dict, optional
dictionary passed on to ml.plots.results method.
diagnostics_kwargs: dict, optional
dictionary passed on to ml.plots.diagnostics method.
Returns
-------
fig: matplotlib.Figure instance
"""
if fname is None:
fname = "{}.pdf".format(self.ml.name)
pdf = PdfPages(fname)
fig = plt.figure(figsize=(8.27, 11.69), dpi=50)
fig1, fig2 = fig.subfigures(2, 1, height_ratios=[1.25, 1.])
self.results(fig=fig1, tmin=tmin, tmax=tmax, **results_kwargs)
self.diagnostics(fig=fig2, tmin=tmin, tmax=tmax, **diagnostics_kwargs)
fig2.subplots_adjust(wspace=0.2)
fig1.suptitle("Model Results", fontweight="bold")
fig2.suptitle("Model Diagnostics", fontweight="bold")
plt.subplots_adjust(left=0.1, top=0.9, right=0.95, bottom=0.1)
pdf.savefig(fig, orientation="portrait", dpi=dpi)
pdf.close()
return fig
def _get_height_ratios(ylims):
height_ratios = []
for ylim in ylims:
hr = ylim[1] - ylim[0]
if np.isnan(hr):
hr = 0.0
height_ratios.append(hr)
return height_ratios
def _get_stress_series(ml, split=True):
stresses = []
for name in ml.stressmodels.keys():
nstress = len(ml.stressmodels[name].stress)
if split and nstress > 1:
for istress in range(nstress):
stress = ml.get_stress(name, istress=istress)
stresses.append(stress)
else:
stress = ml.get_stress(name)
if isinstance(stress, list):
stresses.extend(stress)
else:
stresses.append(stress)
return stresses