This uses public data: daily Wikipedia pageviews for one article, as published in the Prophet repository's example datasets. It has no connection to any employer's systems or data.
Ad inventory is traffic. The impressions a publisher can sell next quarter depend on how many visits arrive, and direct-sales commitments need a forecast with an honest range around it. At Chegg I built seasonality-aware traffic and ad-inventory forecasts to support direct-sales commitments and revenue planning. This is a small, reproducible version of the same idea on public data.
The data
The series is daily pageviews of the "Peyton Manning" article on English Wikipedia, from December 10, 2007 to January 20, 2016. It is published on a log scale, so the analysis converts it back to daily views. It has everything that makes traffic forecasting hard: a strong weekly cycle, a yearly cycle tied to the NFL season, and news-driven spikes.
The setup
- Holdout: the last 365 days, January 21, 2015 to January 20, 2016. The model never sees them.
- Training window: the 3 years before that (1,092 days), recent enough to reflect current interest.
- Model: least squares on log views, with a linear trend, day-of-week effects, and 6 pairs of yearly Fourier terms. Working in logs turns multiplicative seasonality ("weekends are much lower") into additive terms and keeps spikes from dominating the fit.
- Interval: ±1.96 residual standard deviations on the log scale, converted back to views.
- Baseline: seasonal naive, the same weekday 52 weeks earlier. A model that cannot beat it is not adding anything.
Results
| Week of | Actual | Forecast | Interval |
|---|---|---|---|
| 2014-01-26 | 25,458 | — | — |
| 2014-02-02 | 85,766 | — | — |
| 2014-02-09 | 3,778 | — | — |
| 2014-02-16 | 2,444 | — | — |
| 2014-02-23 | 2,250 | — | — |
| 2014-03-02 | 2,192 | — | — |
| 2014-03-09 | 2,238 | — | — |
| 2014-03-16 | 1,885 | — | — |
| 2014-03-23 | 2,625 | — | — |
| 2014-03-30 | 1,717 | — | — |
| 2014-04-06 | 1,677 | — | — |
| 2014-04-13 | 1,634 | — | — |
| 2014-04-20 | 2,277 | — | — |
| 2014-04-27 | 1,964 | — | — |
| 2014-05-04 | 3,597 | — | — |
| 2014-05-11 | 2,204 | — | — |
| 2014-05-18 | 2,022 | — | — |
| 2014-05-25 | 2,256 | — | — |
| 2014-06-01 | 1,621 | — | — |
| 2014-06-08 | 1,598 | — | — |
| 2014-06-15 | 1,407 | — | — |
| 2014-06-22 | 1,290 | — | — |
| 2014-06-29 | 1,425 | — | — |
| 2014-07-06 | 1,505 | — | — |
| 2014-07-13 | 1,899 | — | — |
| 2014-07-20 | 2,047 | — | — |
| 2014-07-27 | 2,571 | — | — |
| 2014-08-03 | 2,567 | — | — |
| 2014-08-10 | 3,464 | — | — |
| 2014-08-17 | 3,127 | — | — |
| 2014-08-24 | 3,791 | — | — |
| 2014-08-31 | 4,389 | — | — |
| 2014-09-07 | 14,423 | — | — |
| 2014-09-14 | 10,307 | — | — |
| 2014-09-21 | 6,673 | — | — |
| 2014-09-28 | 4,395 | — | — |
| 2014-10-05 | 8,154 | — | — |
| 2014-10-12 | 6,165 | — | — |
| 2014-10-19 | 19,563 | — | — |
| 2014-10-26 | 6,150 | — | — |
| 2014-11-02 | 6,216 | — | — |
| 2014-11-09 | 4,126 | — | — |
| 2014-11-16 | 3,627 | — | — |
| 2014-11-23 | 3,905 | — | — |
| 2014-11-30 | 4,400 | — | — |
| 2014-12-07 | 3,816 | — | — |
| 2014-12-14 | 3,948 | — | — |
| 2014-12-21 | 4,001 | — | — |
| 2014-12-28 | 4,104 | — | — |
| 2015-01-04 | 5,558 | — | — |
| 2015-01-11 | 15,416 | — | — |
| 2015-01-18 | 7,219 | 8,474 | 2,769 to 25,933 |
| 2015-01-25 | 4,062 | 8,583 | 2,805 to 26,269 |
| 2015-02-01 | 10,130 | 6,242 | 2,039 to 19,102 |
| 2015-02-08 | 3,003 | 4,237 | 1,385 to 12,967 |
| 2015-02-15 | 2,593 | 2,978 | 973 to 9,114 |
| 2015-02-22 | 1,716 | 2,344 | 766 to 7,172 |
| 2015-03-01 | 2,272 | 2,113 | 690 to 6,467 |
| 2015-03-08 | 1,843 | 2,106 | 688 to 6,445 |
| 2015-03-15 | 1,439 | 2,164 | 707 to 6,623 |
| 2015-03-22 | 1,664 | 2,146 | 701 to 6,566 |
| 2015-03-29 | 1,456 | 1,986 | 649 to 6,077 |
| 2015-04-05 | 1,169 | 1,736 | 567 to 5,312 |
| 2015-04-12 | 1,322 | 1,500 | 490 to 4,590 |
| 2015-04-19 | 1,691 | 1,349 | 441 to 4,129 |
| 2015-04-26 | 1,650 | 1,301 | 425 to 3,982 |
| 2015-05-03 | 1,481 | 1,332 | 435 to 4,077 |
| 2015-05-10 | 1,463 | 1,387 | 453 to 4,245 |
| 2015-05-17 | 2,397 | 1,395 | 456 to 4,269 |
| 2015-05-24 | 1,300 | 1,311 | 428 to 4,013 |
| 2015-05-31 | 1,115 | 1,157 | 378 to 3,539 |
| 2015-06-07 | 979 | 997 | 326 to 3,051 |
| 2015-06-14 | 909 | 890 | 291 to 2,725 |
| 2015-06-21 | 1,391 | 864 | 282 to 2,645 |
| 2015-06-28 | 883 | 924 | 302 to 2,829 |
| 2015-07-05 | 1,017 | 1,060 | 347 to 3,245 |
| 2015-07-12 | 1,558 | 1,241 | 405 to 3,797 |
| 2015-07-19 | 1,481 | 1,412 | 461 to 4,320 |
| 2015-07-26 | 1,316 | 1,531 | 500 to 4,687 |
| 2015-08-02 | 1,485 | 1,611 | 527 to 4,931 |
| 2015-08-09 | 1,640 | 1,719 | 562 to 5,261 |
| 2015-08-16 | 1,941 | 1,948 | 637 to 5,963 |
| 2015-08-23 | 2,128 | 2,394 | 782 to 7,325 |
| 2015-08-30 | 2,493 | 3,119 | 1,019 to 9,544 |
| 2015-09-06 | 3,184 | 4,068 | 1,329 to 12,450 |
| 2015-09-13 | 6,761 | 4,963 | 1,622 to 15,189 |
| 2015-09-20 | 3,941 | 5,396 | 1,763 to 16,515 |
| 2015-09-27 | 4,919 | 5,190 | 1,696 to 15,882 |
| 2015-10-04 | 3,257 | 4,577 | 1,495 to 14,006 |
| 2015-10-11 | 2,727 | 3,942 | 1,288 to 12,063 |
| 2015-10-18 | 3,614 | 3,523 | 1,151 to 10,783 |
| 2015-10-25 | 3,087 | 3,368 | 1,101 to 10,309 |
| 2015-11-01 | 4,630 | 3,403 | 1,112 to 10,416 |
| 2015-11-08 | 3,829 | 3,483 | 1,138 to 10,659 |
| 2015-11-15 | 5,414 | 3,452 | 1,128 to 10,564 |
| 2015-11-22 | 3,591 | 3,251 | 1,062 to 9,949 |
| 2015-11-29 | 43,899 | 2,974 | 972 to 9,103 |
| 2015-12-06 | 3,029 | 2,795 | 913 to 8,555 |
| 2015-12-13 | 2,533 | 2,874 | 939 to 8,794 |
| 2015-12-20 | 2,418 | 3,344 | 1,093 to 10,232 |
| 2015-12-27 | 5,694 | 4,328 | 1,414 to 13,247 |
| 2016-01-03 | 4,944 | 5,800 | 1,895 to 17,749 |
| 2016-01-10 | 3,415 | 7,171 | 2,343 to 21,947 |
| 2016-01-17 | 14,467 | 8,558 | 2,796 to 26,190 |
| Daily error over the holdout year | Seasonal model | Seasonal naive |
|---|---|---|
| Mean absolute % error (MAPE) | 36.4% | 95.3% |
| Median absolute % error | 24.9% | 49.4% |
- The model roughly halves the baseline's typical error (median 24.9% vs 49.4%). Seasonal naive replays last year's spikes into weeks where they did not happen again: its forecast for February 1, 2015 copies the Super Bowl XLVIII surge of February 2, 2014, about 128,000 views against roughly 8,400 actual.
- The weekly cycle is large: weekend traffic runs about 41% below Mondays, which matters for any daily or weekly commitment.
- The interval is too wide. It covered 98.4% of holdout days, more than its nominal 95%, because spikes in the training years inflate the residual spread. For planning that errs on the safe side, but it gives up precision.
- Spikes are the main source of error. The largest holdout day, November 30, 2015, reached 189,032 views against a holdout average of 3,605. No seasonal model can see an event like that coming, which is why MAPE (pulled up by spikes) sits well above the median error.
Using a forecast like this for ad inventory
- Plan commitments against the low end of the range, not the point forecast, so a soft week does not break a direct-sales guarantee. Convert views to sellable impressions with ad-load and fill assumptions.
- Model known events explicitly. Launches, playoffs, and school terms belong in the model as regressors rather than being left for seasonality to absorb.
- Keep scoring it. Re-fit on a schedule and track holdout error over time; last year's good model drifts.
What I would do next
- Add event and holiday regressors, and handle spikes separately so they stop widening the interval.
- Try a damped trend; a straight line over three years can overshoot.
- Calibrate the interval (for example with quantile regression) so its coverage matches the nominal 95%.
Reproducibility: the analysis is a short Python script (numpy and pandas only) in this site's repository at analysis/forecast/forecast.py. It reads a pinned copy of the data, writes the chart data to a JSON file, and the site renders that file, so the numbers above are exactly the script's output.