In this example we fit seasonal curves to repeating daily and weekly patterns, and then subtract these patterns from the data. The remaining signal can be used as an input to a simple anomaly detector, without giving false-alarms for expected changes (like decreased levels over the weekend, or increases during daylight hours).
import 'https://gist.githubusercontent.com/welch/b18d75bba184c441253c/raw/6b5b56d4677e416ef4a085fa6004f6f001ed1af9/sources.juttle' as sources; | |
import 'https://gist.githubusercontent.com/welch/6f2053f2bfc7d9e7b4d9/raw/b1e46167d05c7e49233db4613437283dcb1a3c4e/trend.juttle' as trend; | |
const start = :2014-01-01: ; | |
const dt = :60s: ; | |
sources.ripple_cpu -from start -to start + :1h: | |
| trend.change -in 'cpu' -dt dt -t0 start -out 'change' | |
| split | |
| @timechart -title "60s change" -display.dataDensity 0 |
One-step-ahead predictions for timeseries using exponential smoothing
Forecast threads a smooth curve through a noisy timeseries in a way that lets you visualize trends, cycles, and anomalies. It can be used as part of an automatic anomaly detection system for metric timeseries (that is, sequences of timestamped numerical values).
It accomplishes this using a variation of Holt-Winters forecasting -- more generally known as exponential smoothing. Forecast decomposes a noisy signal into level, trend, repetitive "seasonal" effects, and unexplained variation or noise. The result is a smoothed version of the signal which may be used to forecast future values or detect unexpected variation. This approach has been successfully used for network anomaly detection with other monitoring tools. Here we implement the meth
[ | |
{"time":"2015-01-01T00:00:00.000Z", "event":"create", "cust_id":1, "email":"lou@grainger.com"}, | |
{"time":"2015-01-01T00:00:01.000Z", "event":"create", "cust_id":2, "email":"bubba@nytimes.com"}, | |
{"time":"2015-01-01T00:00:02.000Z", "event":"purchase", "cust_id":1, "purchase_id":1}, | |
{"time":"2015-01-01T00:00:03.000Z", "event":"purchase", "cust_id":2, "purchase_id":2}, | |
{"time":"2015-01-01T00:00:04.000Z", "event":"purchase", "cust_id":1, "purchase_id":3}, | |
{"time":"2015-01-01T00:00:05.000Z", "event":"update", "cust_id":1, "email":"louise@grainger.com"}, | |
{"time":"2015-01-01T00:00:06.000Z", "event":"purchase", "cust_id":1, "purchase_id":4}, | |
{"time":"2015-01-01T00:00:07.000Z", "event":"update", "cust_id":1, "email":"larry@grainger.com"}, | |
{"time":"2015-01-01T00:00:08.000Z", "event":"purchase", "cust_id":2, "purchase_id":5}, |
[{"humidity":37.19,"irlight":0.0,"light":0.0,"pressure":30.021185563,"temp":53.24,"temp2":51.998,"uvlight":0.0,"time":"2015-03-23T16:33:57.712Z"}, | |
{"humidity":37.2,"irlight":0.0,"light":0.0,"pressure":30.022957369,"temp":53.06,"temp2":51.98,"uvlight":0.0,"time":"2015-03-23T16:34:08.069Z"}, | |
{"humidity":37.18,"irlight":0.0,"light":0.0,"pressure":30.019413757,"temp":53.24,"temp2":51.944,"uvlight":0.0,"time":"2015-03-23T16:34:18.389Z"}, | |
{"humidity":37.18,"irlight":0.0,"light":0.0,"pressure":30.020594961,"temp":53.24,"temp2":51.962,"uvlight":0.0,"time":"2015-03-23T16:34:28.973Z"}, | |
{"humidity":37.16,"irlight":0.0,"light":0.0,"pressure":30.021776165,"temp":53.24,"temp2":51.926,"uvlight":0.0,"time":"2015-03-23T16:34:39.467Z"}, | |
{"humidity":37.17,"irlight":0.0,"light":0.0,"pressure":30.022071466,"temp":53.24,"temp2":51.98,"uvlight":0.0,"time":"2015-03-23T16:34:49.783Z"}, | |
{"humidity":37.17,"irlight":0.0,"light":0.0,"pressure":30.02325267,"temp":53.24,"temp2":51.944,"uvlight":0.0,"time":"2015-03-23T16:35:00.093Z"}, | |
{"hum |
[{"mag_x":713.66,"mag_y":-585.03,"mag_z":2321.59,"time":"2014-11-30T12:35:53.880Z"}, | |
{"mag_x":713.03,"mag_y":-585.72,"mag_z":2321.40,"time":"2014-11-30T12:36:24.659Z"}, | |
{"mag_x":711.83,"mag_y":-584.98,"mag_z":2317.33,"time":"2014-11-30T12:36:55.445Z"}, | |
{"mag_x":712.82,"mag_y":-585.06,"mag_z":2318.68,"time":"2014-11-30T12:37:26.244Z"}, | |
{"mag_x":715.49,"mag_y":-584.02,"mag_z":2322.56,"time":"2014-11-30T12:37:57.034Z"}, | |
{"mag_x":714.46,"mag_y":-581.64,"mag_z":2317.70,"time":"2014-11-30T12:38:27.816Z"}, | |
{"mag_x":712.50,"mag_y":-582.39,"mag_z":2314.87,"time":"2014-11-30T12:38:58.610Z"}, | |
{"mag_x":711.84,"mag_y":-582.86,"mag_z":2313.77,"time":"2014-11-30T12:39:29.464Z"}, | |
{"mag_x":712.57,"mag_y":-584.40,"mag_z":2314.67,"time":"2014-11-30T12:40:00.273Z"}, | |
{"mag_x":713.13,"mag_y":-584.41,"mag_z":2316.14,"time":"2014-11-30T12:40:31.055Z"}, |
[{"distanceInCm":0,"time":"2014-08-18T05:00:12.237Z"}, | |
{"distanceInCm":0,"time":"2014-08-18T05:05:09.803Z"}, | |
{"distanceInCm":0,"time":"2014-08-18T05:10:10.060Z"}, | |
{"distanceInCm":0,"time":"2014-08-18T05:15:10.300Z"}, | |
{"distanceInCm":0,"time":"2014-08-18T05:20:10.545Z"}, | |
{"distanceInCm":0,"time":"2014-08-18T05:25:11.042Z"}, | |
{"distanceInCm":0,"time":"2014-08-18T05:30:11.033Z"}, | |
{"distanceInCm":0,"time":"2014-08-18T05:35:11.279Z"}, | |
{"distanceInCm":170,"time":"2014-08-18T05:40:11.577Z"}, | |
{"distanceInCm":0,"time":"2014-08-18T05:45:11.777Z"}, |
Juttle examples applying forecasting and anomaly detection to jx3 streams.
reducer normal(mean, sigma) { | |
// return a Box-Muller approximate normal with given mean and stddev. | |
// this is written as a custom "reducer" because Box-Muller values come | |
// in pairs, and we want to save one for next time. | |
// | |
var leftover = null; | |
function update() {} | |
function result() { | |
if (leftover != null) { | |
var result = mean + sigma * leftover; |