{"id":727964,"date":"2026-05-17T20:10:18","date_gmt":"2026-05-17T17:10:18","guid":{"rendered":"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/"},"modified":"2026-05-17T20:10:18","modified_gmt":"2026-05-17T17:10:18","slug":"how-to-model-non-linear-seo-seasonality-with-prophet","status":"publish","type":"post","link":"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/","title":{"rendered":"How to model non-linear SEO seasonality with Prophet"},"content":{"rendered":"<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_85 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<label for=\"ez-toc-cssicon-toggle-item-6a383e8294a43\" class=\"ez-toc-cssicon-toggle-label\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #dd3333;color:#dd3333\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #dd3333;color:#dd3333\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/label><input type=\"checkbox\"  id=\"ez-toc-cssicon-toggle-item-6a383e8294a43\" checked aria-label=\"Toggle\" \/><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#Traditional_SEO_forecasts_often_miss_seasonality_and_volatility_Learn_how_to_model_non-stationary_search_behavior_with_Prophet_and_Python\" >Traditional SEO forecasts often miss seasonality and volatility. Learn how to model non-stationary search behavior with Prophet and Python.<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#SEO_forecasting_pays_the_bills_but_doesnt_add_much_value\" >SEO forecasting pays the bills, but doesn\u2019t add much value<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#Why_LLMs_arent_the_answer_to_SEO_forecasting\" >Why LLMs aren\u2019t the answer to SEO forecasting<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#They_assume_data_behaves_linearly\" >They assume data behaves linearly<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#They_optimize_for_plausibility_not_statistical_accuracy\" >They optimize for plausibility, not statistical accuracy<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#How_to_do_an_SEO_forecast_that_accounts_for_seasonal_effects\" >How to do an SEO forecast that accounts for seasonal effects<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#Retrieving_and_preprocessing_seasonal_fluctuations\" >Retrieving and preprocessing seasonal fluctuations<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#Handling_seasonality_with_SEO_forecast\" >Handling seasonality with SEO forecast\u00a0<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#Plotting_a_non-stationary_SEO_forecast\" >Plotting a non-stationary SEO forecast<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#SEO_forecasting_isnt_usually_linear\" >SEO forecasting isn\u2019t usually linear<\/a><ul class='ez-toc-list-level-5' ><li class='ez-toc-heading-level-5'><ul class='ez-toc-list-level-5' ><li class='ez-toc-heading-level-5'><ul class='ez-toc-list-level-5' ><li class='ez-toc-heading-level-5'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/buradabiliyorum.com\/en\/how-to-model-non-linear-seo-seasonality-with-prophet\/#Topics_on_this_page\" >Topics on this page<\/a><\/li><\/ul><\/li><\/ul><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n<h2 class=\"subhead\" itemprop=\"alternativeHeadline\"><span class=\"ez-toc-section\" id=\"Traditional_SEO_forecasts_often_miss_seasonality_and_volatility_Learn_how_to_model_non-stationary_search_behavior_with_Prophet_and_Python\"><\/span>Traditional SEO forecasts often miss seasonality and volatility. Learn how to model non-stationary search behavior with Prophet and Python.<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><\/p>\n<div class=\"bialty-container\">\n<p>Forecasting SEO performance means estimating future outcomes from historical data. But search behavior rarely follows stable or linear patterns.<\/p>\n<p>Seasonal demand, anomalies, SERP changes, and measurement issues can all distort your data and lead to unreliable forecasts.<\/p>\n<p>That makes forecasting more complex than running linear regression, exponential smoothing, or asking an LLM to project trends from historical performance.<\/p>\n<p>Here\u2019s how to account for seasonality, detect anomalies, and build more reliable SEO forecasts in Python using models designed for non-linear search data.<\/p>\n<h2 id=\"seo-forecasting-pays-the-bills-but-doesnt-add-much-value\" class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"SEO_forecasting_pays_the_bills_but_doesnt_add_much_value\"><\/span>SEO forecasting pays the bills, but doesn\u2019t add much value<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Decision-makers rely on forecasts to justify investments and align expectations across digital teams. Stakeholders want forward-looking estimates, finance needs revenue projections, and roadmaps require a clear view of expected returns. However, the value of forecasting has diminished today.<\/p>\n<p>AI Mode and AI Overviews created a major disconnect between clicks and impressions as LLM-driven scrapers increased bot activity and inflated impression data in reporting tools.<\/p>\n<p>Additionally, Google reported a logging issue affecting Search Console impression data since May 2025. As a result, many forecasts end up serving as reassurance rather than guidance. They shield decision-makers from scrutiny while failing to reflect the business\u2019s actual operating context.<\/p>\n<p>From a data analytics perspective, if search performance followed a normal distribution, you could rely on linear regression, exponential smoothing, or even a simple moving average (SMA) with confidence.<\/p>\n<p>However, the average SEO forecast still relies on assumptions that don\u2019t hold in organic search:<\/p>\n<ul class=\"wp-block-list\">\n<li>Stable trends.<\/li>\n<li>Normal distributions.<\/li>\n<li>Consistent relationships between inputs and outputs.<\/li>\n<\/ul>\n<figure class=\"wp-block-table\">\n<table>\n<tbody>\n<tr>\n<td><strong>Technique<\/strong><\/td>\n<td><strong>De<a href=\"https:\/\/buradabiliyorum.com\/en\/category\/download-scripts-themes-apps\/\" data-internallinksmanager029f6b8e52c=\"9\" title=\"Download Scripts &amp; Themes &amp; Apps\" target=\"_blank\" rel=\"noopener\">script<\/a>ion<\/strong><\/td>\n<td><strong>When to use<\/strong><\/td>\n<td><strong>When not to use<\/strong><\/td>\n<\/tr>\n<tr>\n<td>Linear regression<\/td>\n<td>Fits a straight line through historical data to model long-term trends and project future performance.<\/td>\n<td>When traffic or rankings show a consistent upward or downward trend with relatively low volatility. Useful for baseline forecasting and directional planning.<\/td>\n<td>When data is highly volatile, seasonal, or affected by frequent algorithm updates, migrations, or campaign spikes.<\/td>\n<\/tr>\n<tr>\n<td>Exponential smoothing<\/td>\n<td>Applies weighted averages where recent data points have more influence than older ones. Can adapt to short-term changes.<\/td>\n<td>When recent performance is more indicative of future outcomes, such as after site changes, migrations, or content updates. Useful for short-term forecasting.<\/td>\n<td>When long-term trends matter more than recency, or when sharp anomalies may distort recent weighting.<\/td>\n<\/tr>\n<tr>\n<td>Simple moving average (SMA)<\/td>\n<td>Averages values over a fixed window to smooth noise and highlight underlying trends.<\/td>\n<td>When you need to understand data direction, such as smoothing daily traffic for reporting.<\/td>\n<td>When forecasting future performance because predictions rely on aggregated historical averages and may miss turning points.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Today\u2019s AI landscape forces a rethink of forecasting as search shifts toward highly volatile and probabilistic outcomes. In other words, today, a 10% increase in effort doesn\u2019t translate into a proportional 10% increase in traffic.<\/p>\n<p>Several structural factors are at play:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>Long-tail traffic distribution:<\/strong> A small number of pages typically generate most traffic, while most pages contribute very little.<\/li>\n<li><strong>Binary user behavior:<\/strong> Many core SEO metrics, such as CTR, are driven by yes\/no interactions (click versus no click) that diverge from normally distributed patterns.<\/li>\n<li><strong>Zero-click search impact:<\/strong> High rankings don\u2019t guarantee traffic \u2014 more queries are resolved directly in the SERP, inflating visibility without corresponding clicks.<\/li>\n<\/ul>\n<p>If you have to forecast, do it properly. Baseline models still have a role:<\/p>\n<ul class=\"wp-block-list\">\n<li>Linear regression for directional trends.<\/li>\n<li>Exponential smoothing for short-term adjustments.<\/li>\n<li>Moving averages for noise reduction.<\/li>\n<\/ul>\n<p>There are ways to apply these techniques in Google Sheets. However, they should be treated as descriptive tools, not decision-making systems. To make forecasting useful, you need to move beyond them.<\/p>\n<div style=\"background: radial-gradient(circle at 30% 40%, rgba(184, 111, 255, 0.15), rgba(0, 169, 255, 0.15) 40%, #CDE8FD 70%); padding: 30px; width: 100%; max-width: 802px; color: #000000 !important; font-family: Arial, sans-serif; margin: 25px 0 30px 0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); position: relative; box-sizing: border-box;\">\n<div style=\"width: 100%; max-width: 100%; margin-bottom: 20px; text-align: left; padding-right: 20px; box-sizing: border-box;\">\n<div id=\"semrush-one-headline\" class=\"headline-responsive\" style=\"font-family: Oswald, sans-serif; font-size: 30px; font-weight: normal; margin: 0; color: #000000 !important; line-height: 1.2;\">\n        Your customers search everywhere. Make sure your brand <span style=\"background: linear-gradient(90deg, #D56EFE 0%, #068EF8 51%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;\">shows up<\/span>.\n      <\/div>\n<p id=\"semrush-one-subhead\" style=\"font-family: Roboto, sans-serif; font-size: 18px; font-weight: 300; line-height: 25px; margin: 12px 0 0 0; color: #000000 !important;\">\n        The SEO toolkit you know, plus the AI visibility data you need.\n      <\/p>\n<\/p><\/div>\n<div style=\"margin-bottom: 15px;\">\n      <span id=\"semrush-one-cta\" style=\"display: inline-block; background-color: #FF642D; color: white; height: 44px; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; padding: 0 24px; font-weight: bold; white-space: nowrap; box-sizing: border-box; text-decoration: none; line-height: 44px;\">Start Free Trial<\/span>\n    <\/div>\n<div style=\"font-size: 12px;\">\n<div style=\"font-family: Roboto, sans-serif; font-weight: 300; color: #000000; margin-bottom: 4px;\">Get started with<\/div>\n<p>      <img loading=\"lazy\" width=\"400\" height=\"52\" decoding=\"async\" http: alt=\"Semrush One Logo\" style=\"height: 16px; width: auto; display: block;\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2025\/11\/semrush-one.webp\"><img loading=\"lazy\" width=\"400\" height=\"52\" decoding=\"async\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2025\/11\/semrush-one.webp\" alt=\"Semrush One Logo\" style=\"height: 16px; width: auto; display: block;\">\n    <\/div>\n<\/p><\/div>\n<style>\n  @media (max-width: 768px) {\n    .headline-responsive {\n      font-size: 30px !important;\n      line-height: 1.3 !important;\n    }\n  }\n<\/style>\n<\/p>\n<h2 id=\"why-llms-arent-the-answer-to-seo-forecasting\" class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Why_LLMs_arent_the_answer_to_SEO_forecasting\"><\/span>Why LLMs aren\u2019t the answer to SEO forecasting<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>LLMs and MCP connections only compound the inefficiencies listed above. There are two structural problems with this approach.<\/p>\n<h3 class=\"wp-block-heading\" id=\"h-they-assume-data-behaves-linearly\"><span class=\"ez-toc-section\" id=\"They_assume_data_behaves_linearly\"><\/span>They assume data behaves linearly<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Pre-configured prompts or skills implicitly assume the data follows a linear distribution. This is misleading because SEO data is dominated by seasonality, cyclical demand, and structural breaks. Any system that treats it as smooth or continuous will systematically misrepresent future performance.<\/p>\n<h3 class=\"wp-block-heading\" id=\"h-they-optimize-for-plausibility-not-statistical-accuracy\"><span class=\"ez-toc-section\" id=\"They_optimize_for_plausibility_not_statistical_accuracy\"><\/span>They optimize for plausibility, not statistical accuracy<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>LLMs aren\u2019t forecasting models. They\u2019re probabilistic text generation systems<strong>. <\/strong>They assign probability scores to predict token sequences based on patterns observed during training. They\u2019re trained to reward your thinking, not challenge it.<\/p>\n<p>As a result, they can produce confident but ungrounded outputs that lack the business and domain context required to interpret anomalies.<\/p>\n<p>No matter how well engineered the prompt is, the system can still hallucinate \u2013 not because it\u2019s \u201cwrong,\u201d but because it\u2019s optimizing for linguistic plausibility, not statistical validity.<\/p>\n<p>Forecasting requires explicit handling of seasonality, non-linearity, and critical interpretation of outputs. These analytical responsibilities can\u2019t be abstracted away through prompting alone.<\/p>\n<p>LLMs can assist with workflows, accelerate analysis, and even help operationalize models. But they can\u2019t replace the role of an analyst in framing the problem, selecting the methodology, and validating the results.<\/p>\n<h2 id=\"how-to-do-an-seo-forecast-that-accounts-for-seasonal-effects\" class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"How_to_do_an_SEO_forecast_that_accounts_for_seasonal_effects\"><\/span>How to do an SEO forecast that accounts for seasonal effects<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Asking the right questions is often the hardest part of any analysis.<\/p>\n<p>SEO forecasts are often requested by enterprise stakeholders or pushed by agencies during new business pitches. This typically makes forecasting more straightforward because the research question is already defined upfront.<\/p>\n<p>Either way, the subject of the analysis is usually one of the following search indicators:<\/p>\n<ul class=\"wp-block-list\">\n<li>Clicks (search demand).<\/li>\n<li>Impressions (search visibility).<\/li>\n<li>Rankings (position distribution).<\/li>\n<li>CTR (SERP behavior).<\/li>\n<\/ul>\n<p>For this article, we\u2019ll use Python to forecast synthetic clicks for a fictitious website influenced by seasonal demand.<\/p>\n<h3 class=\"wp-block-heading\" id=\"h-retrieving-and-preprocessing-seasonal-fluctuations\"><span class=\"ez-toc-section\" id=\"Retrieving_and_preprocessing_seasonal_fluctuations\"><\/span>Retrieving and preprocessing seasonal fluctuations<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Based on the scope of analysis, gather historical data from Google Search Console through either the API or Google BigQuery.<\/p>\n<p>While a larger dataset with broader historical coverage is technically better, it may not justify the query costs in BigQuery for an SEO forecast.<\/p>\n<p>Carefully assess the tradeoff between cost, resources, time, and data sampling. You might find that using an API to retrieve as much historical data as possible (e.g., via Search Analytics for Sheets) does the job.<\/p>\n<p>Set up a Google Colab notebook, install the required dependencies, load your dataset with date and clicks as columns, and convert the date column into a datetime index.<\/p>\n<p>Enforce daily frequency to ensure consistency across dates, and quickly fill any missing data gaps using interpolation.<\/p>\n<figure class=\"wp-block-table\">\n<table class=\"has-fixed-layout\">\n<tbody>\n<tr>\n<td><code>#data viz<br \/>!pip install plotly<br \/>import plotly.graph_objects as go<br \/>import plotly.express as px<br \/>import matplotlib.pyplot as plt<br \/>import matplotlib.pyplot as pyplot<br \/>import seaborn as sns<br \/>from scipy.stats import boxcox<\/p>\n<p>#anomaly detection<br \/>from statsmodels.tsa.stattools import adfuller<br \/>from statsmodels.tsa.seasonal import STL<\/p>\n<p>#time<a href=\"https:\/\/buradabiliyorum.com\/en\/category\/watch-movies-tv-seriess\/\" data-internallinksmanager029f6b8e52c=\"8\" title=\"Watch Movies &amp; TV Series\" target=\"_blank\" rel=\"noopener\">series<\/a> decomposition<br \/>from statsmodels.tsa.seasonal import seasonal_decompose<br \/>from statsmodels.graphics.tsaplots import plot_acf, plot_pacf<\/p>\n<p>#data manipulation<br \/>import pandas as pd<br \/>import numpy as np<\/p>\n<p>#time series plotting<br \/>from prophet import Prophet<br \/>from statsmodels.tsa.statespace.sarimax import SARIMAX<br \/>from sklearn.metrics import mean_absolute_error, mean_squared_error<\/p>\n<p>df = pd.read_excel('\/content\/input.xlsx')<br \/>df.columns = map(str.lower, df.columns)<\/p>\n<p>df['date'] = pd.to_datetime(df['date'])<br \/>df = df.sort_values('date')<\/p>\n<p># Set index<br \/>df.set_index('date', inplace=True)<\/p>\n<p># Ensure daily frequency (important for decomposition)<br \/>df = df.asfreq('D')<\/p>\n<p># Handle missing values<br \/>df['clicks'] = df['clicks'].interpolate()<br \/>df.head()<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img fetchpriority=\"high\" decoding=\"async\" width=\"1369\" height=\"417\" http: alt=\"Raw clicks line for all available date\u00a0\" class=\"wp-image-477580\" srcset=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Raw-clicks-line-for-all-available-date.png.webp 1369w,https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Raw-clicks-line-for-all-available-date-768x234.png.webp 768w\" data-lazy-sizes=\"(max-width: 1369px) 100vw, 1369px\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Raw-clicks-line-for-all-available-date.png.webp\"><img fetchpriority=\"high\" decoding=\"async\" width=\"1369\" height=\"417\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Raw-clicks-line-for-all-available-date.png.webp\" alt=\"Raw clicks line for all available date\u00a0\" class=\"wp-image-477580\" srcset=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Raw-clicks-line-for-all-available-date.png.webp 1369w,https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Raw-clicks-line-for-all-available-date-768x234.png.webp 768w\" sizes=\"(max-width: 1369px) 100vw, 1369px\"><figcaption class=\"wp-element-caption\"><em>Raw clicks line for all available date\u00a0<\/em><\/figcaption><\/figure>\n<\/div>\n<p>Does it look like a linear distribution, or can you already spot anomalies?<\/p>\n<p>Data preprocessing involves standardizing and cleaning your dataset to reduce the impact of outliers on your next forecast. This step is often overlooked, yet it\u2019s critical for improving model reliability.<\/p>\n<p>To prove this, we need to assess stationarity, i.e., whether the relevant measures of central tendency, namely the mean and variance, remain stable over time.<\/p>\n<figure class=\"wp-block-table\">\n<table class=\"has-fixed-layout\">\n<tbody>\n<tr>\n<td><code>result = adfuller(df['clicks'].dropna())<br \/>print(f\"ADF Statistic: {result[0]}\")<br \/>print(f\"p-value: {result[1]}\")<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>For context, the smaller the p-value (&lt;0.05), the more confident you can be that patterns in the time series aren\u2019t random.<\/p>\n<figure class=\"wp-block-table\">\n<table class=\"has-fixed-layout\">\n<tbody>\n<tr>\n<td><code>ADF Statistic: -3.014113904399305<\/code><br \/><code>p-value: 0.06246422059834887<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>The p-value isn\u2019t convincing here, meaning the series isn\u2019t stationary (linear), and seasonality likely plays a role.<\/p>\n<p>As discussed, assuming SEO data is stationary (i.e., follows a linear distribution) is a flawed heuristic.<\/p>\n<p>SEO data often follows non-linear trends, so relying on simple methods that assume stable data can lead to poor forecasts. Instead, you should decompose the time series and model seasonality.<\/p>\n<p>Seasonality decomposition helps separate true performance trends from recurring patterns such as weekly or monthly cycles.<\/p>\n<p>To do this, we need to zoom in on granular weekly search patterns.\u00a0<\/p>\n<figure class=\"wp-block-table\">\n<table class=\"has-fixed-layout\">\n<tbody>\n<tr>\n<td><code>#If data recorded daily, and you want to analyse weekly seasonality (period=7)<br \/>result_weekly = seasonal_decompose(df['clicks'], model=\"additive\", period=7)<\/p>\n<p>#If data recorded monthly, and you want to analyse yearly seasonality (period=12)<br \/>#result_monthly = seasonal_decompose(df['clicks'], model=\"additive\", period=12)<\/p>\n<p># Plot the decomposition for monthly data<br \/>result_weekly.plot()<br \/>plt.title('Weekly Seasonal Decomposition')<br \/>plt.show()<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"578\" height=\"427\" http: alt=\"STL decomposition framework\u00a0\" class=\"wp-image-477581\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/STL-decomposition-framework.png.webp\"><img loading=\"lazy\" decoding=\"async\" width=\"578\" height=\"427\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/STL-decomposition-framework.png.webp\" alt=\"STL decomposition framework\u00a0\" class=\"wp-image-477581\"><figcaption class=\"wp-element-caption\"><em>STL decomposition framework\u00a0<\/em><\/figcaption><\/figure>\n<\/div>\n<p>The trend plot itself is already suggestive:<\/p>\n<ul class=\"wp-block-list\">\n<li>Search interest (clicks) is trending downward.<\/li>\n<li>Search interest is likely affected by weekly sales cycles \u2013 look at the numerous small peaks.<\/li>\n<li>Search interest likely follows seasonal demand \u2013 it ebbs and flows at certain times of year.<\/li>\n<\/ul>\n<p>However, the residuals plot contains clusters of large spikes, both positive and negative, reaching up to 500,000. These represent anomalies, or outliers, that appear connected to the trend\u2019s inflection points.<\/p>\n<p>This means the model made a \u201cmistake\u201d when decomposing the trend line because it didn\u2019t fully capture sudden spikes.<\/p>\n<p><!-- START INLINE FORM --><\/p>\n<p><!-- END INLINE FORM --><\/p>\n<hr class=\"wp-block-separator has-text-color has-cyan-bluish-gray-color has-css-opacity has-cyan-bluish-gray-background-color has-background\">\n<h3 class=\"wp-block-heading\" id=\"h-handling-seasonality-with-seo-forecast-nbsp\"><span class=\"ez-toc-section\" id=\"Handling_seasonality_with_SEO_forecast\"><\/span>Handling seasonality with SEO forecast\u00a0<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>To decompose and isolate seasonality, you can use several models depending on the level of complexity and flexibility you need:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<tbody>\n<tr>\n<td><strong>Model<\/strong><\/td>\n<td><strong>Description<\/strong><\/td>\n<\/tr>\n<tr>\n<td>STL decomposition<\/td>\n<td>A robust technique for separating a time series into trend, seasonality, and residuals. Ideal for revealing the underlying structure in data where patterns vary over time, making it useful for anomaly detection.<\/td>\n<\/tr>\n<tr>\n<td>SARIMAX<\/td>\n<td>ARIMA extended to seasonal data. A statistical model that handles non-stationary data, seasonal patterns, and external independent variables such as algorithm updates.<\/td>\n<\/tr>\n<tr>\n<td>Prophet<\/td>\n<td>Built by Meta for real-world data, it handles multiple seasonalities, missing data, and abrupt shifts. Leveraging additive models, it\u2019s particularly suited for time series with strong seasonal patterns.<\/td>\n<\/tr>\n<tr>\n<td>BSTS<\/td>\n<td>A Bayesian model that captures trend and seasonality while incorporating uncertainty. BSTS is commonly used for counterfactual estimation in causal impact analysis (\u201cwhat would have happened if X never occurred?\u201d), making it suitable for testing applications such as pre- versus post-analysis. Useful if you want to learn R.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>For this article, we\u2019re going to use STL decomposition for anomaly detection in a \u201cwobbling\u201d (non-stationary) time series.<\/p>\n<figure class=\"wp-block-table\">\n<table class=\"has-fixed-layout\">\n<tbody>\n<tr>\n<td><code># Fit STL decomposition (period=7 for weekly cycle)<br \/>stl = STL(df['clicks'], period=7, robust=True)<br \/>result = stl.fit()<\/code><br \/><code><br \/># Extract residuals and flag anomalies via IQR<br \/>resid = result.resid<br \/>Q1, Q3 = resid.quantile(0.25), resid.quantile(0.75)<br \/>IQR = Q3 - Q1<br \/>anomalies = df[(resid &lt; Q1 - 1.5 * IQR) | (resid &gt; Q3 + 1.5 * IQR)]<\/code><br \/><code><br \/># Plot<br \/>fig, ax = plt.subplots(figsize=(14, 5))<br \/>ax.plot(df.index, df['clicks'], label=\"Clicks\", color=\"steelblue\")<br \/>ax.scatter(anomalies.index, anomalies['clicks'], color=\"red\", label=\"Anomalies\", zorder=5)<br \/>ax.set_title('Click Anomalies (STL + IQR)')<br \/>ax.legend()<br \/>plt.tight_layout()<br \/>plt.show()<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1278\" height=\"444\" http: alt=\"Weekly anomaly detection using STL decomposition\" class=\"wp-image-477582\" srcset=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Weekly-anomaly-detection-using-STL-decomposition.png.webp 1278w,https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Weekly-anomaly-detection-using-STL-decomposition-768x267.png.webp 768w\" data-lazy-sizes=\"(max-width: 1278px) 100vw, 1278px\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Weekly-anomaly-detection-using-STL-decomposition.png.webp\"><img loading=\"lazy\" decoding=\"async\" width=\"1278\" height=\"444\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Weekly-anomaly-detection-using-STL-decomposition.png.webp\" alt=\"Weekly anomaly detection using STL decomposition\" class=\"wp-image-477582\" srcset=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Weekly-anomaly-detection-using-STL-decomposition.png.webp 1278w,https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Weekly-anomaly-detection-using-STL-decomposition-768x267.png.webp 768w\" sizes=\"auto, (max-width: 1278px) 100vw, 1278px\"><figcaption class=\"wp-element-caption\"><em>Weekly anomaly detection using STL decomposition<\/em><\/figcaption><\/figure>\n<\/div>\n<p>The red points are extreme values that aren\u2019t explained by either trend or seasonality. However, detecting anomalies isn\u2019t the same as removing them.<\/p>\n<p>In non-stationary time series, variability changes over time (e.g., seasonality, trends, algorithm updates). Removing outliers outright breaks the time index and introduces artificial gaps that bias the actual seasonal impact.<\/p>\n<p>A more robust approach is to replace anomalies with expected values.<\/p>\n<figure class=\"wp-block-table\">\n<table class=\"has-fixed-layout\">\n<tbody>\n<tr>\n<td><code>df['trend'] = result.trend<br \/>df['seasonal'] = result.seasonal<br \/>df['resid'] = result.resid<br \/># --- Define anomaly flag (based on residuals) ---<br \/>Q1, Q3 = df['resid'].quantile(0.25), df['resid'].quantile(0.75)<br \/>IQR = Q3 - Q1<\/p>\n<p>df['anomaly'] = (<br \/>\u00a0\u00a0\u00a0\u00a0(df['resid'] &lt; Q1 - 1.5 * IQR) |<br \/>\u00a0\u00a0\u00a0\u00a0(df['resid'] &gt; Q3 + 1.5 * IQR)<br \/>)<br \/># --- Replace anomalies with expected value (trend + seasonal) ---<br \/>df['clean_clicks'] = df['clicks'].copy()<br \/>df.loc[df['anomaly'], 'clean_clicks'] = (<br \/>\u00a0\u00a0\u00a0\u00a0df['trend'] + df['seasonal']<br \/>)<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Because this approach preserves the time series rows, the forecasting baseline is now protected from bias and artificial gaps. You can validate this by applying STL decomposition to the cleaned time series.<\/p>\n<figure class=\"wp-block-table\">\n<table class=\"has-fixed-layout\">\n<tbody>\n<tr>\n<td><code>result_clean = seasonal_decompose(df['clean_clicks'], model=\"additive\", period=7)<br \/>result_clean.plot()<br \/>plt.title('Weekly Seasonal Decomposition (Cleaned Data)')<br \/>plt.show()<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"577\" height=\"434\" http: alt=\"STL decomposition framework without anomalies\" class=\"wp-image-477583\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/STL-decomposition-framework-without-anomalies.png.webp\"><img loading=\"lazy\" decoding=\"async\" width=\"577\" height=\"434\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/STL-decomposition-framework-without-anomalies.png.webp\" alt=\"STL decomposition framework without anomalies\" class=\"wp-image-477583\"><figcaption class=\"wp-element-caption\"><em>STL decomposition framework without anomalies<\/em><\/figcaption><\/figure>\n<\/div>\n<p>What finally stands out is that once a week (every seven observations), there\u2019s a spike. This suggests peak search demand on Saturday or Sunday, indicating stable and consistent interest patterns.<\/p>\n<p>A few scattered residuals, or anomalies, remain, but they\u2019re rare and random, showing no clustering or drift. This confirms that outlier handling has been effective and the model fit is robust.<\/p>\n<p>At this stage, the time series decomposition is clean enough and ready for forecasting.<\/p>\n<h3 class=\"wp-block-heading\" id=\"h-plotting-a-non-stationary-seo-forecast\"><span class=\"ez-toc-section\" id=\"Plotting_a_non-stationary_SEO_forecast\"><\/span>Plotting a non-stationary SEO forecast<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>While you could experiment with SARIMAX or BSTS, this synthetic SEO forecast uses Prophet because it\u2019s well-suited for handling time series with strong seasonality.<\/p>\n<p>Using our anomaly-free dataset with a preserved time index, Prophet can forecast click performance over the next 90 days. To add more context, you can introduce a regressor to flag external factors such as Google core updates or measurement issues.<\/p>\n<p>In this example, you can apply a flag to account for the Google Search Console logging issue that artificially inflated impressions between May 2025 and April 2026.<\/p>\n<p>The code below generates a 90-day forecast and outputs a line chart, with the option to export the forecast as an .xlsx table.<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"656\" height=\"524\" http: alt=\"Tabular output of Prophet\u2019s 90-day click forecast from anomaly-free non-stationary timeseries\" class=\"wp-image-477584\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Tabular-output-of-Prophets-90-day-click-forecast-from-anomaly-free-non-stationary-timeseries.png\"><img loading=\"lazy\" decoding=\"async\" width=\"656\" height=\"524\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Tabular-output-of-Prophets-90-day-click-forecast-from-anomaly-free-non-stationary-timeseries.png\" alt=\"Tabular output of Prophet\u2019s 90-day click forecast from anomaly-free non-stationary timeseries\" class=\"wp-image-477584\"><figcaption class=\"wp-element-caption\"><em>Tabular output of Prophet\u2019s 90-day click forecast from anomaly-free non-stationary timeseries.<\/em><\/figcaption><\/figure>\n<\/div>\n<p>Note that the lower and upper bounds represent the confidence interval, indicating the range within which clicks are expected to fall over the forecast horizon.<\/p>\n<figure class=\"wp-block-table\">\n<table class=\"has-fixed-layout\">\n<tbody>\n<tr>\n<td><code>prophet_df = df[['clean_clicks']].reset_index()<br \/>prophet_df.columns = ['date', 'clicks']<br \/>prophet_df['date'] = pd.to_datetime(prophet_df['date'])<br \/>prophet_df = prophet_df.rename(columns={'date': 'ds', 'clicks': 'y'})<\/p>\n<p># \u2500\u2500 GSC INFLATION FLAG \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>start = pd.to_datetime('2025-05-13')<br \/>end \u00a0 = pd.to_datetime('2026-04-13')<br \/>prophet_df['gsc_inflation_flag'] = 0<br \/>prophet_df.loc[<br \/>\u00a0\u00a0\u00a0\u00a0(prophet_df['ds'] &gt;= start) &amp; (prophet_df['ds'] &lt;= end),<br \/>\u00a0\u00a0\u00a0\u00a0'gsc_inflation_flag'<br \/>] = 1<\/p>\n<p>model = Prophet(<br \/>\u00a0\u00a0\u00a0\u00a0yearly_seasonality=True,<br \/>\u00a0\u00a0\u00a0\u00a0weekly_seasonality=True,<br \/>\u00a0\u00a0\u00a0\u00a0daily_seasonality=False<br \/>)<br \/>model.add_regressor('gsc_inflation_flag')<br \/>model.fit(prophet_df)<\/p>\n<p># \u2500\u2500 FORECAST\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>future = model.make_future_dataframe(periods=90)<\/p>\n<p>future['gsc_inflation_flag'] = 0<br \/>future.loc[<br \/>\u00a0\u00a0\u00a0\u00a0(future['ds'] &gt;= start) &amp; (future['ds'] &lt;= end),<br \/>\u00a0\u00a0\u00a0\u00a0'gsc_inflation_flag'<br \/>] = 1<br \/>forecast = model.predict(future)<\/p>\n<p>forecast_clean = forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].copy()<br \/>forecast_clean.columns = [<br \/>\u00a0\u00a0\u00a0\u00a0'date',<br \/>\u00a0\u00a0\u00a0\u00a0'clicks_forecast',<br \/>\u00a0\u00a0\u00a0\u00a0'lower bound',<br \/>\u00a0\u00a0\u00a0\u00a0'upper bound'<br \/>]<\/p>\n<p># Extract next 90 days only<br \/>forecast_90 = forecast_clean.tail(90)<\/p>\n<p># \u2500\u2500 EXPORT OPTION \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>EXPORT = True<br \/>if EXPORT:<br \/>\u00a0\u00a0\u00a0\u00a0forecast_90.to_excel('seo_forecast_90_days.xlsx', index=False)<br \/># \u2500\u2500 PLOTLY VISUALISATION \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>fig = go.Figure()<br \/># Actuals<br \/>fig.add_trace(go.Scatter(<br \/>\u00a0\u00a0\u00a0\u00a0x=prophet_df['ds'],<br \/>\u00a0\u00a0\u00a0\u00a0y=prophet_df['y'],<br \/>\u00a0\u00a0\u00a0\u00a0mode=\"lines\",<br \/>\u00a0\u00a0\u00a0\u00a0name=\"Actual (Cleaned)\",<br \/>\u00a0\u00a0\u00a0\u00a0opacity=0.6<br \/>))<br \/># Forecast<br \/>fig.add_trace(go.Scatter(<br \/>\u00a0\u00a0\u00a0\u00a0x=forecast_clean['date'],<br \/>\u00a0\u00a0\u00a0\u00a0y=forecast_clean['clicks_forecast'],<br \/>\u00a0\u00a0\u00a0\u00a0mode=\"lines\",<br \/>\u00a0\u00a0\u00a0\u00a0name=\"Forecast\",<br \/>\u00a0\u00a0\u00a0\u00a0line=dict(dash=\"dash\")<br \/>))<br \/># Confidence band<br \/>fig.add_trace(go.Scatter(<br \/>\u00a0\u00a0\u00a0\u00a0x=forecast_clean['date'],<br \/>\u00a0\u00a0\u00a0\u00a0y=forecast_clean['upper bound'],<br \/>\u00a0\u00a0\u00a0\u00a0mode=\"lines\",<br \/>\u00a0\u00a0\u00a0\u00a0line=dict(width=0),<br \/>\u00a0\u00a0\u00a0\u00a0showlegend=False<br \/>))<br \/>fig.add_trace(go.Scatter(<br \/>\u00a0\u00a0\u00a0\u00a0x=forecast_clean['date'],<br \/>\u00a0\u00a0\u00a0\u00a0y=forecast_clean['lower bound'],<br \/>\u00a0\u00a0\u00a0\u00a0mode=\"lines\",<br \/>\u00a0\u00a0\u00a0\u00a0fill=\"tonexty\",<br \/>\u00a0\u00a0\u00a0\u00a0name=\"Confidence Interval\",<br \/>\u00a0\u00a0\u00a0\u00a0line=dict(width=0)<br \/>))<br \/># Highlight inflation period<br \/>fig.add_vrect(<br \/>\u00a0\u00a0\u00a0\u00a0x0=start, x1=end,<br \/>\u00a0\u00a0\u00a0\u00a0annotation_text=\"GSC Inflation Period\",<br \/>\u00a0\u00a0\u00a0\u00a0annotation_position=\"top left\",<br \/>\u00a0\u00a0\u00a0\u00a0opacity=0.2<br \/>)<br \/>fig.update_layout(<br \/>\u00a0\u00a0\u00a0\u00a0title=\"SEO Forecast Adjusted for GSC Impression Inflation Bias\",<br \/>\u00a0\u00a0\u00a0\u00a0xaxis_title=\"Date\",<br \/>\u00a0\u00a0\u00a0\u00a0yaxis_title=\"Clicks\"<br \/>)<br \/>fig.show()<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1202\" height=\"539\" http: alt=\"Prophet\u2019s 90-day clicks forecast from anomaly-free non-stationary timeseries\u00a0\" class=\"wp-image-477585\" srcset=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Prophets-90-day-clicks-forecast-from-anomaly-free-non-stationary-timeseries.png.webp 1202w,https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Prophets-90-day-clicks-forecast-from-anomaly-free-non-stationary-timeseries-768x344.png.webp 768w,https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Prophets-90-day-clicks-forecast-from-anomaly-free-non-stationary-timeseries-1200x539.png.webp 1200w\" data-lazy-sizes=\"(max-width: 1202px) 100vw, 1202px\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Prophets-90-day-clicks-forecast-from-anomaly-free-non-stationary-timeseries.png.webp\"><img loading=\"lazy\" decoding=\"async\" width=\"1202\" height=\"539\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Prophets-90-day-clicks-forecast-from-anomaly-free-non-stationary-timeseries.png.webp\" alt=\"Prophet\u2019s 90-day clicks forecast from anomaly-free non-stationary timeseries\u00a0\" class=\"wp-image-477585\" srcset=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Prophets-90-day-clicks-forecast-from-anomaly-free-non-stationary-timeseries.png.webp 1202w,https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Prophets-90-day-clicks-forecast-from-anomaly-free-non-stationary-timeseries-768x344.png.webp 768w,https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/Prophets-90-day-clicks-forecast-from-anomaly-free-non-stationary-timeseries-1200x539.png.webp 1200w\" sizes=\"auto, (max-width: 1202px) 100vw, 1202px\"><figcaption class=\"wp-element-caption\"><em><em>Prophet\u2019s 90-day clicks forecast from anomaly-free non-stationary timeseries\u00a0<\/em><\/em><\/figcaption><\/figure>\n<\/div>\n<div style=\"background: radial-gradient(circle at 30% 40%, rgba(184, 111, 255, 0.15), rgba(0, 169, 255, 0.15) 40%, #CDE8FD 70%); padding: 30px; width: 100%; max-width: 802px; color: #000000 !important; font-family: Arial, sans-serif; margin: 25px 0 30px 0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); position: relative; box-sizing: border-box;\">\n<div style=\"width: 100%; max-width: 100%; margin-bottom: 20px; text-align: left; padding-right: 20px; box-sizing: border-box;\">\n<div id=\"semrush-one-headline-bottom\" class=\"headline-responsive\" style=\"font-family: Oswald, sans-serif; font-size: 30px; font-weight: normal; margin: 0; color: #000000 !important; line-height: 1.2;\">\n        See the <span style=\"background: linear-gradient(90deg, #D56EFE 0%, #068EF8 51%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;\">complete picture<\/span> of your search visibility.\n      <\/div>\n<p id=\"semrush-one-subhead-bottom\" style=\"font-family: Roboto, sans-serif; font-size: 18px; font-weight: 300; line-height: 25px; margin: 12px 0 0 0; color: #000000 !important;\">\n        Track, optimize, and win in Google and AI search from one platform.\n      <\/p>\n<\/p><\/div>\n<div style=\"margin-bottom: 15px;\">\n      <span id=\"semrush-one-cta-bottom\" style=\"display: inline-block; background-color: #FF642D; color: white; height: 44px; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; padding: 0 24px; font-weight: bold; white-space: nowrap; box-sizing: border-box; text-decoration: none; line-height: 44px;\">Start Free Trial<\/span>\n    <\/div>\n<div style=\"font-size: 12px;\">\n<div style=\"font-family: Roboto, sans-serif; font-weight: 300; color: #000000; margin-bottom: 4px;\">Get started with<\/div>\n<p>      <img loading=\"lazy\" width=\"400\" height=\"52\" decoding=\"async\" http: alt=\"Semrush One Logo\" style=\"height: 16px; width: auto; display: block;\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2025\/11\/semrush-one.webp\"><img loading=\"lazy\" width=\"400\" height=\"52\" decoding=\"async\" src=\"https:\/\/searchengineland.com\/wp-content\/seloads\/2025\/11\/semrush-one.webp\" alt=\"Semrush One Logo\" style=\"height: 16px; width: auto; display: block;\">\n    <\/div>\n<\/p><\/div>\n<style>\n  @media (max-width: 768px) {\n    .headline-responsive {\n      font-size: 30px !important;\n      line-height: 1.3 !important;\n    }\n  }\n<\/style>\n<\/p>\n<h2 id=\"seo-forecasting-isnt-usually-linear\" class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"SEO_forecasting_isnt_usually_linear\"><\/span>SEO forecasting isn\u2019t usually linear<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>SEO forecasting isn\u2019t about projecting neat, linear trends \u2013 it\u2019s about understanding messy, non-stationary data shaped by seasonality, anomalies, and external shocks.<\/p>\n<p>By cleaning data properly, modeling seasonality, and accounting for real-world distortions such as SERP changes and tracking issues, forecasts become less about false certainty and more about informed direction.<\/p>\n<p>While the goal isn\u2019t perfect accuracy, a robust approach to forecasting non-stationary time series is essential for framing stakeholder expectations within a realistic range and making better decisions.<\/p>\n<div class=\"ttd-topics-display\">\n<div class=\"ttd-topics-content\">\n<h5><span class=\"ez-toc-section\" id=\"Topics_on_this_page\"><\/span>Topics on this page<span class=\"ez-toc-section-end\"><\/span><\/h5>\n<div class=\"ttd-topics-links\">Search engine optimizationProphetGooglePythonForecastingGoogle AI ModeGoogle Search ConsoleApplication programming interfaceAI OverviewsArtificial intelligenceSearch analyticsLarge language modelBigQuerySearch engine results pageGoogle SheetsGoogle ColabModel Context ProtocolData analysisSeasonality<\/div>\n<\/div>\n<div class=\"ttd-topics-show-extra-button\">+14 more<\/div>\n<\/div>\n<\/div>\n<blockquote><p><strong><span style=\"color: #ff6600;\">If you liked the article, do not forget to share it with your friends. Follow us on\u00a0<span style=\"color: #ff0000;\"><a style=\"color: #ff0000;\" href=\"https:\/\/news.google.com\/publications\/CAAqBwgKMN63nwsw68G3Aw\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Google News<\/a><\/span>\u00a0too, click on the star and choose us from your favorites.<\/span><\/strong><\/p><\/blockquote>\n<blockquote>\n<p style=\"text-align: center;\"><strong>If you want to read more like this article, you can visit our <span style=\"color: #ff9900;\"><a style=\"color: #ff9900;\" href=\"https:\/\/buradabiliyorum.com\/en\/category\/technology\/\" target=\"_blank\" >Technology<\/a><\/span> category.<\/strong><\/p>\n<\/blockquote>\n<p><span style=\"color: black;\"><a style=\"color: #ff9900;\" href=\"https:\/\/searchengineland.com\/non-linear-seo-seasonality-prophet-477570\" target=\"_blank\" >Source<\/a><\/span><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Traditional SEO forecasts often miss seasonality and volatility. Learn how to model non-stationary search behavior with Prophet and Python. Forecasting SEO performance means estimating future outcomes from historical data. But search behavior rarely follows stable or linear patterns. Seasonal demand, anomalies, SERP changes, and measurement issues can all distort your data and lead to unreliable&#8230;<\/p>\n","protected":false},"author":1,"featured_media":727965,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"https:\/\/searchengineland.com\/wp-content\/seloads\/2026\/05\/How-to-model-non-linear-SEO-seasonality-with-Prophet.png","fifu_image_alt":"","footnotes":""},"categories":[18],"tags":[],"class_list":["post-727964","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-technology"],"_links":{"self":[{"href":"https:\/\/buradabiliyorum.com\/en\/wp-json\/wp\/v2\/posts\/727964","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/buradabiliyorum.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/buradabiliyorum.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/buradabiliyorum.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/buradabiliyorum.com\/en\/wp-json\/wp\/v2\/comments?post=727964"}],"version-history":[{"count":0,"href":"https:\/\/buradabiliyorum.com\/en\/wp-json\/wp\/v2\/posts\/727964\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/buradabiliyorum.com\/en\/wp-json\/wp\/v2\/media\/727965"}],"wp:attachment":[{"href":"https:\/\/buradabiliyorum.com\/en\/wp-json\/wp\/v2\/media?parent=727964"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/buradabiliyorum.com\/en\/wp-json\/wp\/v2\/categories?post=727964"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/buradabiliyorum.com\/en\/wp-json\/wp\/v2\/tags?post=727964"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}