.. index:: single: moving window statistics single: statistics, moving window single: online statistics ************************ Moving Window Statistics ************************ This chapter describes routines for computing *moving window statistics* (also called rolling statistics and running statistics), using a window around a sample which is used to calculate various local statistical properties of an input data stream. The window is then slid forward by one sample to process the next data point and so on. The functions described in this chapter are declared in the header file :file:`gsl_movstat.h`. Introduction ============ This chapter is concerned with calculating various statistics from subsets of a given dataset. The main idea is to compute statistics in the vicinity of a given data sample by defining a *window* which includes the sample itself as well as some specified number of samples before and after the sample in question. For a sample :math:`x_i`, we define a window :math:`W_i^{H,J}` as .. only:: not texinfo .. math:: W_i^{H,J} = \left\{ x_{i-H}, \dots, x_i, \dots, x_{i+J} \right\} .. only:: texinfo :: W_i^{H,J} = {x_{i-H},...,x_i,...,x_{i+J}} The parameters :math:`H` and :math:`J` are non-negative integers specifying the number of samples to include before and after the sample :math:`x_i`. Statistics such as the mean and standard deviation of the window :math:`W_i^{H,J}` may be computed, and then the window is shifted forward by one sample to focus on :math:`x_{i+1}`. The total number of samples in the window is :math:`K = H + J + 1`. To define a symmetric window centered on :math:`x_i`, one would set :math:`H = J = \left\lfloor K / 2 \right\rfloor`. Handling Endpoints ================== When processing samples near the ends of the input signal, there will not be enough samples to fill the window :math:`W_i^{H,J}` defined above. Therefore the user must specify how to construct the windows near the end points. This is done by passing an input argument of type :type:`gsl_movstat_end_t`: .. type:: gsl_movstat_end_t This data type specifies how to construct windows near end points and can be selected from the following choices: .. macro:: GSL_MOVSTAT_END_PADZERO With this option, a full window of length :math:`K` will be constructed by inserting zeros into the window near the signal end points. Effectively, the input signal is modified to .. only:: not texinfo .. math:: \tilde{x} = \{ \underbrace{0, \dots, 0}_{H \textrm{ zeros}}, x_1, x_2, \dots, x_{n-1}, x_n, \underbrace{0, \dots, 0}_{J \textrm{ zeros} } \} .. only:: texinfo :: x~ = {0, ..., 0, x_1, x_2, ..., x_{n-1}, x_n, 0, ..., 0} to ensure a well-defined window for all :math:`x_i`. .. macro:: GSL_MOVSTAT_END_PADVALUE With this option, a full window of length :math:`K` will be constructed by padding the window with the first and last sample in the input signal. Effectively, the input signal is modified to .. only:: not texinfo .. math:: \tilde{x} = \{ \underbrace{x_1, \dots, x_1}_{H}, x_1, x_2, \dots, x_{n-1}, x_n, \underbrace{x_n, \dots, x_n}_{J} \} .. only:: texinfo :: x~ = {x_1, ..., x_1, x_1, x_2, ..., x_{n-1}, x_n, x_n, ..., x_n} .. macro:: GSL_MOVSTAT_END_TRUNCATE With this option, no padding is performed, and the windows are simply truncated as the end points are approached. .. index:: single: moving window, allocation Allocation for Moving Window Statistics ======================================= .. type:: gsl_movstat_workspace The moving window statistical routines use a common workspace. .. function:: gsl_movstat_workspace * gsl_movstat_alloc(const size_t K) This function allocates a workspace for computing symmetric, centered moving statistics with a window length of :math:`K` samples. In this case, :math:`H = J = \left\lfloor K/2 \right\rfloor`. The size of the workspace is :math:`O(7K)`. .. function:: gsl_movstat_workspace * gsl_movstat_alloc2(const size_t H, const size_t J) This function allocates a workspace for computing moving statistics using a window with :math:`H` samples prior to the current sample, and :math:`J` samples after the current sample. The total window size is :math:`K = H + J + 1`. The size of the workspace is :math:`O(7K)`. .. function:: void * gsl_movstat_free(gsl_movstat_workspace * w) This function frees the memory associated with :data:`w`. .. index:: single: moving mean single: rolling mean Moving Mean =========== The moving window mean calculates the mean of the values of each window :math:`W_i^{H,J}`. .. only:: not texinfo .. math:: \hat{\mu}_i = \frac{1}{\left| W_i^{H,J} \right|} \sum_{x_m \in W_i^{H,J}} x_m .. only:: texinfo :: \hat{\mu}_i = 1/| W_i^{H,J} | \sum_{x_m \in W_i^{H,J}} x_m Here, :math:`\left| W_i^{H,J} \right|` represents the number of elements in the window :math:`W_i^{H,J}`. This will normally be :math:`K`, unless the :macro:`GSL_MOVSTAT_END_TRUNCATE` option is selected, in which case it could be less than :math:`K` near the signal end points. .. function:: int gsl_movstat_mean(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * y, gsl_movstat_workspace * w) This function computes the moving window mean of the input vector :data:`x`, storing the output in :data:`y`. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. It is allowed to have :data:`x` = :data:`y` for an in-place moving mean. .. index:: single: moving variance single: moving standard deviation single: rolling variance single: rolling standard deviation Moving Variance and Standard Deviation ====================================== The moving window variance calculates the *sample variance* of the values of each window :math:`W_i^{H,J}`, defined by .. only:: not texinfo .. math:: \hat{\sigma}_i^2 = \frac{1}{\left( \left| W_i^{H,J} \right| - 1 \right)} \sum_{x_m \in W_i^{H,J}} \left( x_m - \hat{\mu}_i \right)^2 .. only:: texinfo :: \hat{\sigma}_i^2 = 1/(|W_i^{H,J}| - 1) \sum_{x_m \in W_i^{H,J}} ( x_m - \hat{\mu}_i )^2 where :math:`\hat{\mu}_i` is the mean of :math:`W_i^{H,J}` defined above. The standard deviation :math:`\hat{\sigma}_i` is the square root of the variance. .. function:: int gsl_movstat_variance(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * y, gsl_movstat_workspace * w) This function computes the moving window variance of the input vector :data:`x`, storing the output in :data:`y`. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. It is allowed to have :data:`x` = :data:`y` for an in-place moving variance. .. function:: int gsl_movstat_sd(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * y, gsl_movstat_workspace * w) This function computes the moving window standard deviation of the input vector :data:`x`, storing the output in :data:`y`. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. It is allowed to have :data:`x` = :data:`y` for an in-place moving standard deviation. .. index:: single: moving minimum single: moving maximum single: rolling minimum single: rolling maximum Moving Minimum and Maximum ========================== The moving minimum/maximum calculates the minimum and maximum values of each window :math:`W_i^{H,J}`. .. only:: not texinfo .. math:: y_i^{min} &= \min \left( W_i^{H,J} \right) \\ y_i^{max} &= \max \left( W_i^{H,J} \right) .. only:: texinfo :: y_i^{min} = \min W_i^{H,J} y_i^{max} = \max W_i^{H,J} .. function:: int gsl_movstat_min(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * y, gsl_movstat_workspace * w) This function computes the moving minimum of the input vector :data:`x`, storing the result in :data:`y`. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. It is allowed to have :data:`x` = :data:`y` for an in-place moving minimum. .. function:: int gsl_movstat_max(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * y, gsl_movstat_workspace * w) This function computes the moving maximum of the input vector :data:`x`, storing the result in :data:`y`. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. It is allowed to have :data:`x` = :data:`y` for an in-place moving maximum. .. function:: int gsl_movstat_minmax(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * y_min, gsl_vector * y_max, gsl_movstat_workspace * w) This function computes the moving minimum and maximum of the input vector :data:`x`, storing the window minimums in :data:`y_min` and the window maximums in :data:`y_max`. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. .. index:: single: moving sum single: rolling sum Moving Sum ========== The moving window sum calculates the sum of the values of each window :math:`W_i^{H,J}`. .. math:: y_i = \sum_{x_m \in W_i^{H,J}} x_m .. function:: int gsl_movstat_sum(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * y, gsl_movstat_workspace * w) This function computes the moving window sum of the input vector :data:`x`, storing the output in :data:`y`. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. It is allowed to have :data:`x` = :data:`y` for an in-place moving sum. .. index:: single: moving median single: rolling median Moving Median ============= The moving median calculates the median of the window :math:`W_i^{H,J}` for each sample :math:`x_i`: .. only:: not texinfo .. math:: y_i = \textrm{median} \left( W_i^{H,J} \right) .. only:: texinfo :: y_i = median(W_i^{H,J}) .. function:: int gsl_movstat_median(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * y, gsl_movstat_workspace * w) This function computes the moving median of the input vector :data:`x`, storing the output in :data:`y`. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. It is allowed for :data:`x` = :data:`y` for an in-place moving window median. Robust Scale Estimation ======================= A common problem in statistics is to quantify the dispersion (also known as the variability, scatter, and spread) of a set of data. Often this is done by calculating the variance or standard deviation. However these statistics are strongly influenced by outliers, and can often provide erroneous results when even a small number of outliers are present. Several useful statistics have emerged to provide robust estimates of scale which are not as susceptible to data outliers. A few of these statistical scale estimators are described below. .. index:: single: moving median absolute deviation single: rolling median absolute deviation Moving MAD ---------- The median absolute deviation (MAD) for the window :math:`W_i^{H,J}` is defined to be the median of the absolute deviations from the window's median: .. only:: not texinfo .. math:: MAD_i = 1.4826 \times \textrm{median} \left( \left| W_i^{H,J} - \textrm{median} \left( W_i^{H,J} \right) \right| \right) .. only:: texinfo :: MAD_i = 1.4826 * median[ |W_i^{H,J} - median(W_i^{H,J})| ] The factor of :math:`1.4826` makes the MAD an unbiased estimator of the standard deviation for Gaussian data. The MAD has an efficiency of 37%. See :ref:`here ` for more information. .. function:: int gsl_movstat_mad0(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * xmedian, gsl_vector * xmad, gsl_movstat_workspace * w) int gsl_movstat_mad(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * xmedian, gsl_vector * xmad, gsl_movstat_workspace * w) These functions compute the moving MAD of the input vector :data:`x` and store the result in :data:`xmad`. The medians of each window :math:`W_i^{H,J}` are stored in :data:`xmedian` on output. The inputs :data:`x`, :data:`xmedian`, and :data:`xmad` must all be the same length. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. The function :code:`mad0` does not include the scale factor of :math:`1.4826`, while the function :code:`mad` does include this factor. .. index:: single: moving quantile range single: rolling quantile range Moving QQR ---------- The q-quantile range (QQR) is the difference between the :math:`(1-q)` and :math:`q` quantiles of a set of data, .. math:: QQR = Q_{1-q} - Q_q The case :math:`q = 0.25` corresponds to the well-known *interquartile range (IQR)*, which is the difference between the 75th and 25th percentiles of a set of data. The QQR is a *trimmed estimator*, the main idea being to discard the largest and smallest values in a data window and compute a scale estimate from the remaining middle values. In the case of the IQR, the largest and smallest 25% of the data are discarded and the scale is estimated from the remaining (middle) 50%. .. function:: int gsl_movstat_qqr(const gsl_movstat_end_t endtype, const gsl_vector * x, const double q, gsl_vector * xqqr, gsl_movstat_workspace * w) This function computes the moving QQR of the input vector :data:`x` and stores the q-quantile ranges of each window :math:`W_i^{H,J}` in :data:`xqqr`. The quantile parameter :data:`q` must be between :math:`0` and :math:`0.5`. The input :math:`q = 0.25` corresponds to the IQR. The inputs :data:`x` and :data:`xqqr` must be the same length. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. Moving :math:`S_n` ------------------ The :math:`S_n` statistic proposed by Croux and Rousseeuw is based on pairwise differences between all samples in the window. It has an efficiency of 58%, significantly higher than the MAD. See :ref:`here ` for more information. .. function:: int gsl_movstat_Sn(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * xscale, gsl_movstat_workspace * w) This function computes the moving :math:`S_n` of the input vector :data:`x` and stores the output in :data:`xscale`. The inputs :data:`x` and :data:`xscale` must be the same length. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. It is allowed for :data:`x` = :data:`xscale` for an in-place moving window :math:`S_n`. Moving :math:`Q_n` ------------------ The :math:`Q_n` statistic proposed by Croux and Rousseeuw is loosely based on the Hodges-Lehmann location estimator. It has a relatively high efficiency of 82%. See :ref:`here ` for more information. .. function:: int gsl_movstat_Qn(const gsl_movstat_end_t endtype, const gsl_vector * x, gsl_vector * xscale, gsl_movstat_workspace * w) This function computes the moving :math:`Q_n` of the input vector :data:`x` and stores the output in :data:`xscale`. The inputs :data:`x` and :data:`xscale` must be the same length. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. It is allowed for :data:`x` = :data:`xscale` for an in-place moving window :math:`Q_n`. .. index:: single: moving window accumulators single: rolling window accumulators User-defined Moving Statistics ============================== GSL offers an interface for users to define their own moving window statistics functions, without needing to implement the edge-handling and accumulator machinery. This can be done by explicitly constructing the windows :math:`W_i^{H,J}` for a given input signal (:func:`gsl_movstat_fill`), or by calculating a user-defined function for each window automatically. In order to apply a user-defined function to each window, users must define a variable of type :type:`gsl_movstat_function` to pass into :func:`gsl_movstat_apply`. This structure is defined as follows. .. type:: gsl_movstat_function Structure specifying user-defined moving window statistical function:: typedef struct { double (* function) (const size_t n, double x[], void * params); void * params; } gsl_movstat_function; This structure contains a pointer to the user-defined function as well as possible parameters to pass to the function. .. member:: double (* function) (const size_t n, double x[], void * params) This function returns the user-defined statistic of the array :data:`x` of length :data:`n`. User-specified parameters are passed in via :data:`params`. It is allowed to modify the array :data:`x`. .. member:: void * params User-specified parameters to be passed into the function. .. function:: int gsl_movstat_apply(const gsl_movstat_end_t endtype, const gsl_movstat_function * F, const gsl_vector * x, gsl_vector * y, gsl_movstat_workspace * w) This function applies the user-defined moving window statistic specified in :data:`F` to the input vector :data:`x`, storing the output in :data:`y`. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. It is allowed for :data:`x` = :data:`y` for an in-place moving window calculation. .. function:: size_t gsl_movstat_fill(const gsl_movstat_end_t endtype, const gsl_vector * x, const size_t idx, const size_t H, const size_t J, double * window) This function explicitly constructs the sliding window for the input vector :data:`x` which is centered on the sample :data:`idx`. On output, the array :data:`window` will contain :math:`W_{idx}^{H,J}`. The number of samples to the left and right of the sample :data:`idx` are specified by :data:`H` and :data:`J` respectively. The parameter :data:`endtype` specifies how windows near the ends of the input should be handled. The function returns the size of the window. Accumulators ============ Many of the algorithms of this chapter are based on an accumulator design, which process the input vector one sample at a time, updating calculations of the desired statistic for the current window. Each accumulator is stored in the following structure: .. type:: gsl_movstat_accum Structure specifying accumulator for moving window statistics:: typedef struct { size_t (* size) (const size_t n); int (* init) (const size_t n, void * vstate); int (* insert) (const double x, void * vstate); int (* delete) (void * vstate); int (* get) (void * params, double * result, const void * vstate); } gsl_movstat_accum; The structure contains function pointers responsible for performing different tasks for the accumulator. .. member:: size_t (* size) (const size_t n) This function returns the size of the workspace (in bytes) needed by the accumulator for a moving window of length :data:`n`. .. member:: int (* init) (const size_t n, void * vstate) This function initializes the workspace :data:`vstate` for a moving window of length :data:`n`. .. member:: int (* insert) (const double x, void * vstate) This function inserts a single sample :data:`x` into the accumulator, updating internal calculations of the desired statistic. If the accumulator is full (i.e. :math:`n` samples have already been inserted), then the oldest sample is deleted from the accumulator. .. member:: int (* delete) (void * vstate) This function deletes the oldest sample from the accumulator, updating internal calculations of the desired statistic. .. member:: int (* get) (void * params, double * result, const void * vstate) This function stores the desired statistic for the current window in :data:`result`. The input :data:`params` specifies optional parameters for calculating the statistic. The following accumulators of type :type:`gsl_movstat_accum` are defined by GSL to perform moving window statistics calculations. .. var:: gsl_movstat_accum * gsl_movstat_accum_min gsl_movstat_accum * gsl_movstat_accum_max gsl_movstat_accum * gsl_movstat_accum_minmax These accumulators calculate moving window minimum/maximums efficiently, using the algorithm of D. Lemire. .. var:: gsl_movstat_accum * gsl_movstat_accum_mean gsl_movstat_accum * gsl_movstat_accum_sd gsl_movstat_accum * gsl_movstat_accum_variance These accumulators calculate the moving window mean, standard deviation, and variance, using the algorithm of B. P. Welford. .. var:: gsl_movstat_accum * gsl_movstat_accum_median This accumulator calculates the moving window median using the min/max heap algorithm of Härdle and Steiger. .. var:: gsl_movstat_accum * gsl_movstat_accum_Sn gsl_movstat_accum * gsl_movstat_accum_Qn These accumulators calculate the moving window :math:`S_n` and :math:`Q_n` statistics developed by Croux and Rousseeuw. .. var:: gsl_movstat_accum * gsl_movstat_accum_sum This accumulator calculates the moving window sum. .. var:: gsl_movstat_accum * gsl_movstat_accum_qqr This accumulator calculates the moving window q-quantile range. Examples ======== Example 1 --------- The following example program computes the moving mean, minimum and maximum of a noisy sinusoid signal of length :math:`N = 500` with a symmetric moving window of size :math:`K = 11`. .. _fig_movstat1: .. figure:: /images/movstat1.png :scale: 60% Original signal time series (gray) with moving mean (green), moving minimum (blue), and moving maximum (orange). The program is given below. .. include:: examples/movstat1.c :code: Example 2: Robust Scale ----------------------- The following example program analyzes a time series of length :math:`N = 1000` composed of Gaussian random variates with zero mean whose standard deviation changes in a piecewise constant fashion as shown in the table below. ============ ============== Sample Range :math:`\sigma` ============ ============== 1-200 1.0 201-450 5.0 451-600 1.0 601-850 3.0 851-1000 5.0 ============ ============== Additionally, about 1% of the samples are perturbed to represent outliers by adding :math:`\pm 15` to the random Gaussian variate. The program calculates the moving statistics MAD, IQR, :math:`S_n`, :math:`Q_n`, and the standard deviation using a symmetric moving window of length :math:`K = 41`. The results are shown in :numref:`fig_movstat2`. .. _fig_movstat2: .. figure:: /images/movstat2.png :scale: 60% Top: time series of piecewise constant variance. Bottom: scale estimates using a moving window; the true sigma value is in light blue, MAD in green, IQR in red, :math:`S_n` in yellow, and :math:`Q_n` in dark blue. The moving standard deviation is shown in gray. The robust statistics follow the true standard deviation piecewise changes well, without being influenced by the outliers. The moving standard deviation (gray curve) is heavily influenced by the presence of the outliers. The program is given below. .. include:: examples/movstat2.c :code: Example 3: User-defined Moving Window ------------------------------------- This example program illustrates how a user can define their own moving window function to apply to an input vector. It constructs a random noisy time series of length :math:`N = 1000` with some outliers added. Then it applies a moving window trimmed mean to the time series with trim parameter :math:`\alpha = 0.1`. The length of the moving window is :math:`K = 11`, so the smallest and largest sample of each window is discarded prior to computing the mean. The results are shown in :numref:`fig_movstat3`. .. _fig_movstat3: .. figure:: /images/movstat3.png :scale: 60% Noisy time series data (black) with moving window trimmed mean (red) The program is given below. .. include:: examples/movstat3.c :code: References and Further Reading ============================== The following publications are relevant to the algorithms described in this chapter, * W.Hardle and W. Steiger, *Optimal Median Smoothing*, Appl. Statist., 44 (2), 1995. * D. Lemire, *Streaming Maximum-Minimum Filter Using No More than Three Comparisons per Element*, Nordic Journal of Computing, 13 (4), 2006 (https://arxiv.org/abs/cs/0610046). * B. P. Welford, *Note on a method for calculating corrected sums of squares and products*, Technometrics, 4 (3), 1962.