ARIMA_PLUS vs TimesFM: anomaly detection on a Black Friday weekend

Written by
Simon Breton
Reading progress
Rocky shore monitoring camera

BigQuery offers two built-in functions for time-series anomaly detection. ML.DETECT_ANOMALIES scores your series against an ARIMA_PLUS model you train yourself. AI.DETECT_ANOMALIES runs on TimesFM, a pretrained model that doesn’t need training. Both are unsupervised, so neither needs labeled anomalies. In this article, I am interested in how useful these two functions are for detecting anomalies in web analytics data, especially anomalies caused by tracking issues. What most web analytics teams want to avoid, at the most basic level, is finding out three weeks too late that their purchase events stopped firing. In practice, this means that you want an alert when there is a 90 percent drop in daily purchases. On the other hand, you expect a big drop after a Black Friday weekend, and you don’t want to receive a notification for that. Given this context, I will run an experiment to see how these two functions behave on a Black Friday weekend, and which one is better suited to this kind of monitoring.

Hypothesis

ML.DETECT_ANOMALIES scores your series against a model you train yourself, in this case ARIMA_PLUS, which learns trend and seasonality from the history you provide. AI.DETECT_ANOMALIES uses TimesFM, a built-in foundation model that forecasts from the same series directly, with no training step. Because it comes back every tear, Black Friday is a seasonal pattern. My guess is that, ML.DETECT_ANOMALIES will not flag the next Black Friday as an anomaly, since it trains on data containing a few Black Friday weekends. AI.DETECT_ANOMALIES will.

The experiment

In this experiment, I created a synthetic dataset to simulate three years of web analytics activity, including three Black Friday weekends. I tested ML.DETECT_ANOMALIES and AI.DETECT_ANOMALIES on the third Black Friday weekend to determine whether either of them identified it as an anomaly.

The dataset

A single query generates the dataset. It consists of a daily series of event counts covering three years of data, from 1 January 2023 to 31 December 2025. The series averages about 10,500 events a day. The query places Black Friday weekends on their real calendar dates from the last three years (24 to 26 November 2023, 29 November to 1 December 2024, and 28 to 30 November 2025), and built with a 148 percent increase of events compared to ordinary days.

The line chart looks like this:

Line chart of daily events, 2023 to 2025, with three tall Black Friday spikes.
Each Black Friday weekend more than doubles a normal day. The spike on the right is the 2025 test window.

Here is the query:

-- One row per day, 2023-01-01 to 2025-12-31.
-- Each value is a base of 10,000 events multiplied by four factors,
-- then rounded and floored at zero.
CREATE OR REPLACE TABLE `anomaly_demo.series_bf_d` AS
WITH dates AS (
  SELECT d AS date, DATE_DIFF(d, DATE '2023-01-01', DAY) AS t,
         EXTRACT(DAYOFWEEK FROM d) AS dow, EXTRACT(DAYOFYEAR FROM d) AS doy
  FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2023-01-01', DATE '2025-12-31')) AS d
)
SELECT
  date,
  GREATEST(0, ROUND(
      10000
    -- TREND. Linear, not compounding: t counts days since 2023-01-01, so this
    -- adds about 27% across the three years.
    * (1 + 0.00025 * t)
    -- WEEKDAY. This is the sawtooth. dow 1 = Sunday, 6 = Friday, 7 = Saturday,
    -- everything else Monday to Thursday.
    * CASE dow WHEN 1 THEN 0.65 WHEN 7 THEN 0.70
               WHEN 6 THEN 0.92 ELSE 1.05 END
    -- SEASONAL. A gentle yearly cycle. On the nine Black Friday dates a further
    -- 1.40 is added to this same factor, which is what produces the 98% uplift.
    * (1 + 0.10 * SIN(2 * ACOS(-1) * doy / 365.0)
         + CASE WHEN date IN (DATE '2023-11-24', DATE '2023-11-25', DATE '2023-11-26',
                              DATE '2024-11-29', DATE '2024-11-30', DATE '2024-12-01',
                              DATE '2025-11-28', DATE '2025-11-29', DATE '2025-11-30')
                THEN 1.40 ELSE 0 END)
    -- NOISE. u falls between -1 and 1 and comes from a hash of the date rather
    -- than a random number. A hash takes no seed and holds no state, so every
    -- rerun returns the same table.
    * (1 + 0.03 * (MOD(ABS(FARM_FINGERPRINT(FORMAT_DATE('%F', date))), 1000000)
                   / 1000000.0 * 2 - 1))
  )) AS value
FROM dates

The detection

Even though I am comparing TimesFM and ARIMA_PLUS, this experiment actually compares six anomaly detection configurations. TimesFM is straightforward and offers no training parameters, while ARIMA_PLUS offers several parameters that I wanted to tweak and compare against each other:

  • ARIMA default: clean_spikes_and_dips and adjust_step_changes are both TRUE, and no holiday calendar set.
  • ARIMA No spike cleaning: clean_spikes_and_dips = FALSE. ARIMA_PLUS no longer replaces spikes and dips with locally interpolated values during training, so the 2023 and 2024 Black Fridays stay in the training data as they are.
  • ARIMA No step adjustment: adjust_step_changes = FALSE. ARIMA_PLUS no longer detects and corrects abrupt level shifts.
  • ARIMA both off: no spike cleaning and no step adjustment, the two above combined.
  • ARIMA with US calendar: holiday_region = 'US'. ARIMA_PLUS loads the US holiday calendar and models each holiday’s impact on the series. Even though Black Friday is not in that calendar, the US_Thanksgiving window coverts it, which runs from three days before the holiday to five days after. The Google team behind ARIMA_PLUS write that “we curate the start and end of the effect window and that means holidays such as Thanksgiving will cover Black Friday and Cyber Monday” (Cheng et al., 2025, section 3.2).
  • TimesFM zero-shot: no training step, and therefore no training options. The history is passed as context at inference time.

Here are the anomaly detection queries:

-- Create model for ARIMA_PLUS
CREATE OR REPLACE MODEL `anomaly_demo.arima_bf_d`
OPTIONS(
  model_type                = 'ARIMA_PLUS',
  time_series_timestamp_col = 'date',
  time_series_data_col      = 'value',
  data_frequency            = 'DAILY',
  decompose_time_series     = TRUE,
  -- the levers this experiment varies, shown here at their defaults
  clean_spikes_and_dips     = TRUE,
  adjust_step_changes       = TRUE
  -- holiday_region         = 'US'  -- no default; switched on in one configuration
) AS
SELECT date, value
FROM `anomaly_demo.series_bf_d`
WHERE date < DATE '2025-11-28'   -- blind to the 2025 Black Friday weekend

-- trained ARIMA_PLUS
SELECT DATE(date) AS date, value, lower_bound, upper_bound, is_anomaly, anomaly_probability
FROM ML.DETECT_ANOMALIES(
  MODEL `anomaly_demo.arima_bf_d`,
  STRUCT(0.9 AS anomaly_prob_threshold),
  (SELECT date, value FROM `anomaly_demo.series_bf_d`
   WHERE date BETWEEN DATE '2025-11-28' AND DATE '2025-11-30'))

-- zero-shot TimesFM
SELECT DATE(time_series_timestamp) AS date, time_series_data AS value,
       lower_bound, upper_bound, is_anomaly, anomaly_probability
FROM AI.DETECT_ANOMALIES(
  (SELECT date, value FROM `anomaly_demo.series_bf_d` WHERE date < DATE '2025-11-28'),
  (SELECT date, value FROM `anomaly_demo.series_bf_d`
   WHERE date BETWEEN DATE '2025-11-28' AND DATE '2025-11-30'),
  data_col => 'value', timestamp_col => 'date', anomaly_prob_threshold => 0.9)

Notebook

Everything above runs from one Jupyter notebook, blackfriday_3day_forecast_arima_vs_timesfm.ipynb, which produced every number and both charts in this article. It builds the series, trains each model, runs both detection functions, and prints the results, showing each SQL statement before it executes.

Results presentation

Here is the result of the experiment, plotted:

Line chart comparing six forecasts against the actual Black Friday spike over three days.
Only ARIMA with the US calendar, in blue, follows the actual spike. The other ARIMA configurations peak a day late, and TimesFM, in orange, predicts a fall.

The greyed zone marks the three days of the third Black Friday weekend. The three red dots are the daily event counts the dataset recorded for those days. TimesFM, in orange, does not predict the spike at all, which is what I expected from a model with no training step. The four ARIMA configurations, in grey, fall about 42 percent below the actual event counts. They do produce a spike, but it does not follow the shape of the synthetic dataset. ARIMA with the calendar, in blue, lands within about 8 percent of each red dot. It is the only configuration that rises and falls with the actual events acount.

Interpretation and analysis

ARIMA with the calendar is the clear winner here. The other predictions fall outside of what’s really happened. They would all flag this third Black Friday weekend as an anomaly. Turning off clean_spikes_and_dips and adjust_step_changes does not change anything. ARIMA_PLUS being less wrong than TimesFM changes nothing operationally. The holiday_region calendar makes all the difference. This is the only parameter that stops ARIMA_PLUS from flagging the third Black Friday weekend as an anomaly.

Learning

As I investigated why the holiday_region parameter was needed, I discovered that Black Friday does not fall within the definition of seasonality the Google team behind ARIMA_PLUS uses:

Seasonality is a repeated, calendar-based pattern in a time series. For example, when dealing with retail sales, it is likely the case that different days of the week have a strong effect on overall sales (weekly seasonality, such as weekday vs. weekend). Additionally, the time of the year also has a clear effect on retail sales (yearly seasonality). Compared with holidays and events, seasonality results in effects that are typically more gradual and smooth. (Cheng et al., 2025)

A seasonal effect is tied to a repeating slot in a calendar cycle. That would be an event happening every one day of the week over week or every week month over month etc… Black Friday happens after the fourth Thursday of November. Its doesnt fit any calendar cycle. Seasonal effects are gradual and smooth, while holiday effects are sudden and short. According to the ARIMA_PLUS team definition, Black Friday is a holiday effect rather than a seasonal effect. That is why holiday_region is the only setting that allows the model to predict it correctly.

Conclusion

  • Without the holiday_region calendar, ARIMA_PLUS and TimesFM perform the same. Training without a specified holiday region was not useful for this experiment, at least operationally, since both would raise a false positive alert on Black Friday activity.
  • Configure holiday_region parameter For an ARIMA_PLUS model, holiday_region is a great way to reduce the noise that comes with anomaly detection.
  • Context is key. As always when working with data, the data alone doesn’t tell you much. An anomaly is neither good nor bad. It is simply a variation in your series. How much that variation matters, and whether it is worth investigating or alerting on, depends on the business context.
  • AI agents filtering vs holiday_region. An agent could add a layer of context and filtering on top of the alerts anomaly detection generates. I’m wondering whether holiday_region is just one small piece of that context, one an agent could handle with more flexibility and coverage.

Read more about our Anomaly Detection Module

Photo by Yanping Ma on Unsplash

Simon Breton

Published at August 23, 2026

Continue Reading