package com.github.servicenow.ds.stats.stl;
/**
* StlFitStats analyzes an STL decomposition, computing basic statistics for the original data and the decomposition.
*
* Created by Jim Crotinger on 1-Jun-2016
*/
@SuppressWarnings("WeakerAccess")
public class StlFitStats {
private final int fSampleSize;
private final double fDataMean;
private final double fDataVariance;
private final double fTrendMean;
private final double fTrendRange;
private final double fSeasonalMean;
private final double fSeasonalVariance;
private final double fResidualMean;
private final double fResidualVariance;
private final double fResidualLogLikelihood;
private final double fDeSeasonalMean;
private final double fDeSeasonalVariance;
private final double fDeTrendMean;
private final double fDeTrendVariance;
private final double fSeasonalRange;
private final double fResidualVarMLE;
/**
* Analyze the STL decomposition, computing basic statistics for the original data and the decomposition.
*
* @param stl a SeasonalTrendLoess object after the decomposition is performed
*/
public StlFitStats(SeasonalTrendLoess.Decomposition stl) {
int length = stl.getData().length;
// Unnecessary since STL guarantees this, so it can't be tested:
// Preconditions.checkArgument(length >= 4, "STL Decomposition must have at least 4 data points");
final double[] data = stl.getData();
final double[] trend = stl.getTrend();
final double[] seasonal = stl.getSeasonal();
final double[] residuals = stl.getResidual();
double dataSum = 0.0;
double dataSqSum = 0.0;
double trendSum = 0.0;
double trendMax = -1.0e100;
double trendMin = 1.0e100;
double seasonalSum = 0.0;
double seasonalSqSum = 0.0;
double seasonalMax = -1.0e100;
double seasonalMin = 1.0e100;
double residualSum = 0.0;
double residualSqSum = 0.0;
double deSeasonalSum = 0.0;
double deSeasonalSqSum = 0.0;
double deTrendSum = 0.0;
double deTrendSqSum = 0.0;
for (int i = 0; i < length; ++i) {
double d = data[i];
double t = trend[i];
double s = seasonal[i];
double r = residuals[i];
double f = d - s;
double dt = d - t;
dataSum += d;
dataSqSum += d * d;
trendSum += t;
if (t > trendMax)
trendMax = t;
if (t < trendMin)
trendMin = t;
seasonalSum += s;
seasonalSqSum += s * s;
if (s > seasonalMax)
seasonalMax = s;
if (s < seasonalMin)
seasonalMin = s;
residualSum += r;
residualSqSum += r * r;
deSeasonalSum += f;
deSeasonalSqSum += f * f;
deTrendSum += dt;
deTrendSqSum += dt * dt;
}
double denom = 1.0 / length;
fDataMean = dataSum * denom;
fTrendMean = trendSum * denom;
fSeasonalMean = seasonalSum * denom;
fResidualMean = residualSum * denom;
fDeSeasonalMean = deSeasonalSum * denom;
fDeTrendMean = deTrendSum * denom;
// The data is from a valid STL decomposition, so length = 4 at minimum.
double corrBC = length / (length - 1.0); // Bessel's correction
double denomBC = 1.0 / (length - 1.0);
fDataVariance = dataSqSum * denomBC - fDataMean * fDataMean * corrBC;
fTrendRange = trendMax - trendMin;
fSeasonalVariance = seasonalSqSum * denomBC - fSeasonalMean * fSeasonalMean * corrBC;
fSeasonalRange = seasonalMax - seasonalMin;
fResidualVariance = residualSqSum * denomBC - fResidualMean * fResidualMean * corrBC;
fDeSeasonalVariance = deSeasonalSqSum * denomBC - fDeSeasonalMean * fDeSeasonalMean * corrBC;
fDeTrendVariance = deTrendSqSum * denomBC - fDeTrendMean * fDeTrendMean * corrBC;
fResidualVarMLE = denom * residualSqSum;
fResidualLogLikelihood = - 0.5 * length * (1 + Math.log(2 * Math.PI * fResidualVarMLE));
fSampleSize = length;
}
/**
* Get the mean of the trend.
*
* @return the mean value of the trend
*/
public double getTrendMean() {
return fTrendMean;
}
/**
* Get the range (max - min) of the trend.
*
* @return the range of the trend
*/
public double getTrendRange() {
return fTrendRange;
}
/**
* Get the mean of the data.
*
* @return the mean value of the data
*/
public double getDataMean() {
return fDataMean;
}
/**
* Get the variance of the data.
*
* @return the variance of the data
*/
public double getDataVariance() {
return fDataVariance;
}
/**
* Get the standard deviation of the data.
*
* @return the standard deviation of the data
*/
public double getDataStdDev() {
return Math.sqrt(fDataVariance);
}
/**
* Get the mean of the seasonal component. Should be near zero if the length is an even multiple of the period.
*
* @return the mean value of the seasonal component
*/
public double getSeasonalMean() {
return fSeasonalMean;
}
/**
* Get the variance of the seasonal component.
*
* @return the variance of the seasonal component
*/
public double getSeasonalVariance() {
return fSeasonalVariance;
}
/**
* Get the standard deviation of the seasonal component.
*
* @return the standard deviation of the seasonal component
*/
public double getSeasonalStdDev() {
return Math.sqrt(fSeasonalVariance);
}
/**
* Get the range (max - min) of the seasonal component.
*
* @return the range of the seasonal component
*/
public double getSeasonalRange() {
return fSeasonalRange;
}
/**
* Get the mean of the residual. Should be near zero.
*
* @return the mean of the residual
*/
public double getResidualMean() {
return fResidualMean;
}
/**
* Get the variance of the residual.
*
* @return the variance of the residual
*/
public double getResidualVariance() {
return fResidualVariance;
}
/**
* Get the standard deviation of the residual.
*
* @return the standard deviation of the residual
*/
public double getResidualStdDev() {
return Math.sqrt(fResidualVariance);
}
/**
* Get the deseasonalized mean.
*
* @return the mean of the data after the seasonal component is removed.
*/
public double getDeSeasonalMean() {
return fDeSeasonalMean;
}
/**
* Get the deseasonalized variance. This is the same as the trend-free variance since if the trend is constant it
* will drop out of the variance.
*
* @return the variance of the data after the seasonal component is removed.
*/
public double getDeSeasonalVariance() {
return fDeSeasonalVariance;
}
/**
* Get the deseasonalized mean.
*
* @return the mean of the data after the seasonal component is removed.
*/
public double getDeTrendMean() {
return fDeTrendMean;
}
/**
* Get the deseasonalized variance. This is the same as the trend-free variance since if the trend is constant it
* will drop out of the variance.
*
* @return the variance of the data after the seasonal component is removed.
*/
public double getDeTrendVariance() {
return fDeTrendVariance;
}
/**
* Get the estimated variance of the residual sample variance. Based on the assumption that the residuals are
* normal.
*
* @return the estimate of the variance of the residual sample variance.
*/
public double getEstimatedVarianceOfResidualSampleVariance() {
double v = getResidualVariance();
return 2 * v * v / (fSampleSize - 1);
}
/**
* Get a Z-Score for the residual variance with no trend relative to the statistics from the STL residual.
*
* @return the 'trend Z-Score'
*/
public double getTrendinessZScore() {
double resVarVar = getEstimatedVarianceOfResidualSampleVariance();
return (fDeSeasonalVariance - fResidualVariance) / Math.sqrt(Math.max(1.0e-12, resVarVar));
}
/**
* Get a Z-Score for the residual variance with no seasonality relative to the statistics from the STL residual.
*
* @return the 'seasonal Z-Score'
*/
public double getSeasonalZScore() {
double resVarVar = getEstimatedVarianceOfResidualSampleVariance();
return (fDeTrendVariance - fResidualVariance) / Math.sqrt(Math.max(1.0e-12, resVarVar));
}
/**
* Get the residual log likelihood, evaluated when the variance is the MLE variance of the residual
* given that the expected residual mean is zero.
*
* @return log-likelihood
*/
public double getResidualLogLikelihood() {
return fResidualLogLikelihood;
}
/**
* Compute the residual log likelihood of the residual with the specified standard deviation.
*
* @param sigma standard deviation
* @return residual log-likelihood
*/
public double getResidualLogLikelihood(double sigma) {
double var = sigma * sigma;
return - 0.5 * fSampleSize * (fResidualVarMLE / var + Math.log(2 * Math.PI * var));
}
@Override
public String toString() {
return String.format(
"Data Mean = %10f\n" +
"Data Variance = %10f\n" +
"Trend Mean = %10f\n" +
"Trend Range = %10f\n" +
"Seasonal Mean = %10f\n" +
"Seasonal Variance = %10f\n" +
"Seasonal Range = %10f\n" +
"De-Seasonal Mean = %10f\n" +
"De-Seasonal Variance = %10f\n" +
"De-Trend Mean = %10f\n" +
"De-Trend Variance = %10f\n" +
"Residual Mean = %10f\n" +
"Residual Variance = %10f\n" +
"Var(ResSampleVar) = %10f\n" +
"Trend Test ZScore = %10f\n" +
"Seasonal Test ZScore = %10f\n" +
"SeasonalVar/ResidVar = %10f",
getDataMean(), getDataVariance(),
getTrendMean(), getTrendRange(),
getSeasonalMean(), getSeasonalVariance(), getSeasonalRange(),
getDeSeasonalMean(), getDeSeasonalVariance(),
getDeTrendMean(), getDeTrendVariance(),
getResidualMean(), getResidualVariance(),
getEstimatedVarianceOfResidualSampleVariance(),
getTrendinessZScore(), getSeasonalZScore(),
getSeasonalVariance() / getResidualVariance());
}
}
Ads help maintain this website.