@Throws(WarpScriptException::class){ fun entropyHybridTest( gts: GeoTimeSerie, buckets_per_period: Int, periods_per_piece: Int, k: Int, alpha: Double ): List<Long>? { doubleCheck(gts) val anomalous_ticks: MutableList<Long> = ArrayList() if (!GTSHelper.isBucketized(gts)) { throw WarpScriptException("GTS must be bucketized") } if (k >= periods_per_piece * buckets_per_period / 2) { throw WarpScriptException("Upper bound of number of outliers must be less than half of the number of observations per piece") } var subgts: GeoTimeSerie? = null var subsubgts: GeoTimeSerie? = null var seasonal: GeoTimeSerie? = null val pieces: Long = gts.bucketcount / buckets_per_period / periods_per_piece val bpp = periods_per_piece * buckets_per_period val lb: Long = gts.lastbucket val bs: Long = gts.bucketspan for (u in 0 until pieces) { val start = lb - bs * ((pieces - u) * bpp - 1) val stop = lb - bs * (pieces - u - 1) * bpp subgts = GTSHelper.subSerie(gts, start, stop, false, false, subgts) subgts.lastbucket = stop subgts.bucketcount = bpp subgts.bucketspan = bs if (null == seasonal) { seasonal = GeoTimeSerie(bpp) seasonal.doubleValues = DoubleArray(bpp) seasonal.ticks = LongArray(bpp) } else { GTSHelper.reset(seasonal) } seasonal.type = TYPE.DOUBLE for (v in 0 until buckets_per_period) { subsubgts = GTSHelper.subCycleSerie( subgts, stop - v * bs, buckets_per_period, true, subsubgts ) val madsigma: DoubleArray = madsigma(subsubgts, true) val median = madsigma[0] val mad = madsigma[1] var sum = 0.0 for (w in 0 until subsubgts.values) { subsubgts.doubleValues.get(w) = if (0.0 != mad) Math.abs((subsubgts.doubleValues.get(w) - median) / mad) else 1.0
sum += subsubgts.doubleValues.get(w) } var entropy = 0.0 for (w in 0 until subsubgts.values) { subsubgts.doubleValues.get(w) /= sum val tmp: Double = subsubgts.doubleValues.get(w) if (0.0 != tmp) { entropy -= tmp * Math.log(tmp) } } if (0.0 != entropy) { entropy /= Math.log(subsubgts.values) } else { entropy = 1.0 } for (w in 0 until subsubgts.values) { GTSHelper.setValue( seasonal, subsubgts.ticks.get(w), entropy * subsubgts.doubleValues.get(w) ) } } GTSHelper.sort(seasonal) val m: Double = median(seasonal) var idx = 0 for (i in 0 until subgts.values) { idx = Arrays.binarySearch(seasonal.ticks, idx, seasonal.values, subgts.ticks.get(i)) if (idx < 0) { throw WarpScriptException( "Internal bug method entropyHybridTest: can't find tick " + subgts.ticks.get( i ).toString() + " in seasonal.ticks" ) } else { subgts.doubleValues.get(i) -= seasonal.doubleValues.get(idx) + m } } anomalous_ticks.addAll(ESDTest(subgts, k, true, alpha)) } return anomalous_ticks }
Applying Seasonal Entropy Hybrid test This test is based on piecewise decomposition where trend components are approximated by median and seasonal components by entropy of the cycle sub-series . An ESD test is passed upon the residuals . It differs from hybridTest by approximating seasonal component instead of using STL . But in many cases this approximation is more useful than estimation of STL .