Source code for bouts.boutsnls

"""BoutsNLS class

"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter
from statsmodels.distributions.empirical_distribution import ECDF
from . import bouts


[docs] class BoutsNLS(bouts.Bouts): """Nonlinear Least Squares fitting for models of Poisson process mixtures Methods for modelling log-frequency data as a mixture of Poisson processes via nonlinear least squares [1]_. References ---------- .. [1] Sibly, R.; Nott, H. and Fletcher, D. (1990) Splitting behaviour into bouts Animal Behaviour 39, 63-69. Examples -------- Draw 1000 samples from a mixture where the first process occurs with :math:`p < 0.7` and the second process occurs with the remaining probability. >>> from skdiveMove.tests import random_mixexp >>> rng = np.random.default_rng(123) >>> x2 = random_mixexp(1000, p=0.7, lda=np.array([0.05, 0.005]), ... rng=rng) >>> xbouts2 = BoutsNLS(x2, bw=5) >>> init_pars = xbouts2.init_pars([80], plot=False) Fit the model and retrieve coefficients: >>> coefs, pcov = xbouts2.fit(init_pars) >>> print(np.round(coefs, 4)) (2.519, 80.0] (80.0, 1297.52] a 3648.8547 1103.4423 lambda 0.0388 0.0032 Calculate bout-ending criterion (returns array): >>> print(np.round(xbouts2.bec(coefs), 4)) [103.8648] Plot observed and predicted data: >>> xbouts2.plot_fit(coefs) # doctest: +ELLIPSIS <Axes: ...> Plot ECDF: >>> xbouts2.plot_ecdf(coefs) # doctest: +ELLIPSIS <Axes: ...> """
[docs] def fit(self, start, **kwargs): """Fit non-linear least squares model to log frequencies The metaclass :class:`bouts.Bouts` implements this method. Parameters ---------- start : pandas.DataFrame DataFrame with coefficients for each process in columns. **kwargs Optional keyword arguments passed to :func:`scipy.optimize.curve_fit`. Returns ------- coefs : pandas.DataFrame Coefficients of the model. pcov : 2D array Covariance of coefs. """ return bouts.Bouts.fit(self, start, **kwargs)
[docs] def bec(self, coefs): """Calculate bout ending criteria from model coefficients The metaclass :class:`bouts.Bouts` implements this method. Parameters ---------- coefs : pandas.DataFrame DataFrame with model coefficients in columns. Returns ------- out : numpy.ndarray, shape (n,) 1-D array with BECs implied by `coefs`. Length is ``coefs.shape[1]`` """ # The metaclass implements this method return bouts.Bouts.bec(self, coefs)
[docs] def plot_ecdf(self, coefs, ax=None, **kwargs): """Plot observed and modelled empirical cumulative frequencies Parameters ---------- coefs : pandas.DataFrame DataFrame with model coefficients in columns. ax : matplotlib.axes.Axes instance An Axes instance to use as target. **kwargs Optional keyword arguments passed to :func:`matplotlib.pyplot.gca`. Returns ------- ax : :class:`~matplotlib.axes.Axes` instances. """ x = self.x xx = np.log1p(x) x_ecdf = ECDF(xx) x_pred = np.linspace(0, xx.max(), num=101) x_pred_expm1 = np.expm1(x_pred) y_pred = x_ecdf(x_pred) if ax is None: ax = plt.gca(**kwargs) # Plot ECDF of data ax.step(x_pred_expm1, y_pred, label="observed") ax.set_xscale("log") ax.xaxis.set_major_formatter(ScalarFormatter()) ax.set_xlim(np.exp(xx).min(), np.exp(xx).max()) # Plot estimated CDF p, lambdas = bouts.calc_p(coefs) y_mod = bouts.ecdf(x_pred_expm1, p, lambdas) ax.plot(x_pred_expm1, y_mod, label="model") # Add a little offset to ylim for visibility yoffset = (0.05, 1.05) ax.set_ylim(*yoffset) # add some spacing # Plot BEC bec_x = self.bec(coefs) bec_y = bouts.ecdf(bec_x, p=p, lambdas=lambdas) bouts._plot_bec(bec_x, bec_y=bec_y, ax=ax, xytext=(-5, 5), horizontalalignment="right") ax.legend(loc="upper left") ax.set_xlabel("x") ax.set_ylabel("ECDF [x]") return ax
if __name__ == '__main__': from skdiveMove.tests import diveMove2skd import pandas as pd tdrX = diveMove2skd() pars = {"offset_zoc": 3, "dry_thr": 70, "wet_thr": 3610, "dive_thr": 3, "dive_model": "unimodal", "smooth_par": 0.1, "knot_factor": 20, "descent_crit_q": 0.01, "ascent_crit_q": 0} tdrX.calibrate(zoc_method="offset", offset=pars["offset_zoc"], dry_thr=pars["dry_thr"], wet_thr=pars["dry_thr"], dive_thr=pars["dive_thr"], dive_model=pars["dive_model"], smooth_par=pars["smooth_par"], knot_factor=pars["knot_factor"], descent_crit_q=pars["descent_crit_q"], ascent_crit_q=pars["ascent_crit_q"]) stats = tdrX.dive_stats() stamps = tdrX.stamp_dives(ignore_z=True) stats_tab = pd.concat((stamps, stats), axis=1) # 2=4 here postdives = stats_tab["postdive_dur"][stats_tab["phase_id"] == 4] postdives_diff = postdives.dt.total_seconds().diff()[1:].abs() # Remove isolated dives postdives_diff = postdives_diff[postdives_diff < 2000] # Set up instance bouts_postdive = BoutsNLS(postdives_diff, 0.1) # Get init parameters bout_init_pars = bouts_postdive.init_pars([50], plot=False) nls_coefs, _ = bouts_postdive.fit(bout_init_pars) # BEC bouts_postdive.bec(nls_coefs) bouts_postdive.plot_fit(nls_coefs) # ECDF fig1, ax1 = bouts_postdive.plot_ecdf(nls_coefs) # Try 3 processes # Get init parameters bout_init_pars = bouts_postdive.init_pars([50, 550], plot=False) nls_coefs, _ = bouts_postdive.fit(bout_init_pars) # BEC bouts_postdive.bec(nls_coefs) bouts_postdive.plot_fit(nls_coefs) # ECDF fig2, ax2 = bouts_postdive.plot_ecdf(nls_coefs)