### The problem that keeps engineers up at night It is 11 PM on a Friday. Zerodha's trading platform has been growing steadily for months. CPU on the order processing cluster is at 78 percent. Nothing is on fire — yet. But at the current rate of growth, it will cross 90 percent sometime next week. Nobody knows exactly when. The on-call engineer has no way to tell if it happens at 2 AM on Saturday or during peak trading hours on Monday morning. This is the gap that time-series forecasting fills. You already have months of CPU metrics sitting in Prometheus. With the right model, you can look at that history and say: at the current trend, CPU will cross 90 percent in approximately 4 days. That gives the team time to scale before the incident — not after it. Forecasting in ops is not about predicting the future perfectly. It is about converting historical metric data into an early warning signal so your team acts proactively instead of reactively. ### What this module covers This module covers two approaches every AIOps engineer uses: **ARIMA** for precise short-range forecasting of stationary metrics, and **Prophet** for flexible long-range forecasting of metrics with trend and seasonality. You will also learn how to use Prophet's confidence bands to automatically flag anomalies — the same pattern used in production monitoring systems. ### Where this fits in the AIOps stack The anomaly detection module earlier in this roadmap covered statistical thresholds and rule-based detection. Forecasting takes that further. Instead of asking "is this metric abnormal right now?", forecasting asks "where will this metric be in 6 hours, 24 hours, or 7 days?" That forward-looking answer is what feeds capacity planning, auto-scaling decisions, and proactive alerting. Historical metrics (Prometheus / InfluxDB) | v Time-series model (ARIMA or Prophet) | v Forecast + confidence band | _____|_____ | | v v Anomaly Capacity detection planning (is today (when will abnormal?) we hit 90%?) ---
### What makes time-series data different A time series is a sequence of measurements recorded at regular intervals over time. CPU usage sampled every minute, HTTP request count per second, disk bytes written per hour — these are all time series. What makes them special is that **the order matters**. The value at 2:05 PM is connected to the value at 2:04 PM in a way that a row in a database table is not connected to the row above it. This dependency on past values is what makes time-series analysis both powerful and tricky. Standard machine learning models that treat each row independently do not work well here. You need models that understand the sequence. ### The four components inside every time series Every real-world time series is made up of a combination of four things: **Trend** is the long-term direction of the data. Disk usage on a growing service trends upward over months. Response time on an optimised service trends downward after a performance fix. The trend is the overall slope when you zoom out. **Seasonality** is a repeating pattern at a fixed interval. HTTP traffic on an e-commerce site peaks every evening and drops at night — that is daily seasonality. It also spikes every Saturday — that is weekly seasonality. Seasonality repeats predictably, which makes it very useful for forecasting. **Residual** is everything left over after you remove the trend and seasonality. It is the noise — random spikes, unexplained dips, one-off events. A large residual on a particular day means something unusual happened that the model did not expect. **Cyclical variation** is longer-term fluctuation that does not repeat at a fixed interval — like how traffic on a B2B SaaS platform dips every quarter during holidays. This is harder to model and usually handled as part of trend. Here is how these components combine to form what you observe: What you see in Prometheus: Observed value = Trend + Seasonality + Residual Example — CPU at 2 PM on a Tuesday: Observed CPU = base growth trend (55%) + afternoon traffic peak (+12%) + random noise (+3%) = 70% ### Additive vs multiplicative — which one to use An **additive model** means the seasonal effect stays the same size regardless of the trend. If your evening traffic spike is always about 500 extra requests per minute whether the base rate is 1000 or 5000, the model is additive. A **multiplicative model** means the seasonal effect grows with the trend. If your evening spike is always roughly 30 percent above the base rate — so 300 extra when base is 1000, and 1500 extra when base is 5000 — the model is multiplicative. For most ops metrics, the additive model works well. Use multiplicative when you notice that the height of your seasonal peaks visually grows larger as the overall metric grows. ### Stationarity — why ARIMA needs it A **stationary** time series has a constant mean and constant variance over time. It does not drift up or down — it fluctuates around a stable average. Most raw ops metrics are NOT stationary. CPU usage trends up as traffic grows. Disk usage trends up as data accumulates. Memory usage might trend up and then suddenly drop after a restart. ARIMA requires stationary data to work correctly — which is why the first step of ARIMA modelling is always checking for stationarity and then applying **differencing** to remove the trend if needed. **Differencing** means replacing each value with the difference between it and the previous value. Instead of modeling the actual CPU percentages, you model how much CPU changed from one minute to the next. That change series is often stationary even when the original is not. > 📌 **Remember:** Prophet handles non-stationary data automatically — it models the trend explicitly as part of its formula. ARIMA requires you to make the data stationary first. This is the biggest practical difference between the two. ---
### What ARIMA stands for and why each letter matters **ARIMA** stands for AutoRegressive Integrated Moving Average. Each part captures a different pattern in the data. **AR — AutoRegressive (p):** The model predicts the current value using its own past values. If CPU at 2:05 PM is partly explained by CPU at 2:04, 2:03, and 2:02 PM, that is autoregression. The parameter `p` is how many past values to look back at. **I — Integrated (d):** This is the differencing step that makes the data stationary. The parameter `d` is how many times you difference the series. For most ops metrics, `d=1` (one round of differencing) is enough. **MA — Moving Average (q):** The model also uses past forecast errors to improve its prediction. If the model consistently underpredicted by 5 percent three minutes ago, it adjusts for that. The parameter `q` is how many past errors to factor in. An ARIMA model is written as **ARIMA(p, d, q)**. So ARIMA(2, 1, 2) means: use 2 past values, difference once, and use 2 past errors. > 📌 **Remember:** The `p` and `q` in ARIMA are not the model weights themselves — they are the number of lag terms to include. The actual weights are estimated from the data during fitting. ### Using auto_arima to find the right parameters Choosing p, d, and q by hand requires reading ACF and PACF plots — a skill that takes practice. The `pmdarima` library's `auto_arima` function does this automatically by trying combinations and picking the one with the best AIC score. **AIC (Akaike Information Criterion)** is a score that balances how well the model fits the data against how complex it is. Lower AIC is better. auto_arima tries dozens of parameter combinations and returns the one with the lowest AIC. ```bash ## Install required libraries pip install pmdarima statsmodels numpy pandas matplotlib ``` ### Building an ARIMA model for CPU forecasting ```python ## arima_cpu_forecast.py ## Forecast CPU usage for the next 30 minutes using ARIMA import numpy as np import pandas as pd import matplotlib.pyplot as plt from pmdarima import auto_arima from statsmodels.tsa.stattools import adfuller ## Step 1 - Generate synthetic CPU data ## Simulates 8 hours of per-minute CPU readings on a Zerodha backend server ## Base load of 45% with a gradual upward trend and realistic noise np.random.seed(42) n_minutes = 480 ## 8 hours of data ## Build a realistic CPU time series: ## steady base + slow growth + small random fluctuations time_index = pd.date_range(start="2026-06-09 09:00", periods=n_minutes, freq="min") trend = np.linspace(45, 65, n_minutes) ## gradual rise from 45% to 65% noise = np.random.normal(0, 2.5, n_minutes) ## realistic sensor noise cpu_series = pd.Series(trend + noise, index=time_index, name="cpu_percent") print("Generated CPU time series:") print(cpu_series.describe()) print(f"\nFirst value : {cpu_series.iloc[0]:.1f}%") print(f"Last value : {cpu_series.iloc[-1]:.1f}%") ``` > **Note:** `np.linspace(45, 65, 480)` creates 480 evenly spaced numbers from 45 to 65. This simulates a gradual CPU rise over 8 hours — the kind of trend you see on a server handling growing daytime traffic. ```python ## Step 2 - Check stationarity with the ADF test ## The Augmented Dickey-Fuller test checks whether a series has a unit root ## (meaning it has a trend and is non-stationary) ## Null hypothesis: the series is non-stationary ## If p-value < 0.05, we reject the null and the series IS stationary result = adfuller(cpu_series) print(f"\nADF Statistic : {result[0]:.4f}") print(f"p-value : {result[1]:.4f}") if result[1] > 0.05: print("Series is NON-STATIONARY — differencing needed (d=1 or higher)") else: print("Series is STATIONARY — can model directly") ``` > **Note:** ADF stands for Augmented Dickey-Fuller. It is the standard stationarity test for time series. A p-value above 0.05 means the series has a trend and needs differencing before ARIMA can model it correctly. ```python ## Step 3 - Fit auto_arima to find best (p, d, q) parameters automatically ## auto_arima tests combinations and picks the one with the lowest AIC print("\nFitting auto_arima — searching for best parameters...") model = auto_arima( cpu_series, start_p=1, max_p=4, ## test AR orders 1 through 4 start_q=1, max_q=4, ## test MA orders 1 through 4 d=None, ## auto-detect differencing order seasonal=False, ## no seasonal component for now information_criterion="aic", trace=True, ## print each model being tested error_action="ignore", suppress_warnings=True, stepwise=True ## use smart stepwise search (faster) ) print(f"\nBest model found: ARIMA{model.order}") print(model.summary()) ``` ```python ## Step 4 - Forecast the next 30 minutes n_forecast = 30 forecast_values, conf_int = model.predict(n_periods=n_forecast, return_conf_int=True) ## Build a clean dataframe with forecast and confidence bands forecast_index = pd.date_range( start=cpu_series.index[-1] + pd.Timedelta(minutes=1), periods=n_forecast, freq="min" ) forecast_df = pd.DataFrame({ "forecast" : forecast_values, "lower_95" : conf_int[:, 0], ## lower bound of 95% prediction interval "upper_95" : conf_int[:, 1], ## upper bound of 95% prediction interval }, index=forecast_index) print("\nNext 30 minutes forecast:") print(forecast_df.head(10)) ## Check if CPU is forecast to cross the 85% warning threshold will_breach = (forecast_df["forecast"] > 85).any() first_breach = forecast_df[forecast_df["forecast"] > 85].index if will_breach: print(f"\n⚠️ CPU forecast to cross 85% at {first_breach[0]}") else: print("\n✅ CPU stays below 85% for the next 30 minutes") ``` ```python ## Step 5 - Visualise the forecast fig, ax = plt.subplots(figsize=(14, 5)) ## Plot historical data (last 2 hours only for clarity) cpu_series[-120:].plot(ax=ax, label="Historical CPU", color="#203147", linewidth=1.5) ## Plot forecast forecast_df["forecast"].plot(ax=ax, label="Forecast", color="#01ef63", linewidth=2, linestyle="--") ## Shade the 95% confidence band ax.fill_between( forecast_df.index, forecast_df["lower_95"], forecast_df["upper_95"], alpha=0.2, color="#01ef63", label="95% confidence band" ) ## Draw the alert threshold ax.axhline(y=85, color="red", linestyle=":", linewidth=1.5, label="85% alert threshold") ax.set_title("CPU Forecast — Zerodha Backend Server (ARIMA)") ax.set_ylabel("CPU %") ax.set_xlabel("Time") ax.legend() plt.tight_layout() plt.savefig("arima_cpu_forecast.png", dpi=120) plt.show() print("Plot saved as arima_cpu_forecast.png") ``` > **Note:** The shaded band around the forecast is the 95% prediction interval. It means: given the model's understanding of the data, we are 95% confident the actual CPU reading will fall within this band. The band widens as you forecast further into the future because uncertainty accumulates over time. ### Reading auto_arima output When `trace=True` is set, auto_arima prints something like: ```text Fit ARIMA(1,1,1) with AIC=1423.5 Fit ARIMA(2,1,1) with AIC=1419.2 Fit ARIMA(2,1,2) with AIC=1421.8 Best model: ARIMA(2,1,1) with AIC=1419.2 ``` The model with the **lowest AIC** wins. `ARIMA(2,1,1)` means the best fit used 2 past values, differenced once to achieve stationarity, and 1 past error term. > 🔴 **Common Mistake:** Running auto_arima on data with many missing values or gaps. ARIMA requires a complete, regularly-spaced time series. Fill gaps with interpolation before fitting — `series.interpolate(method='time')` handles this cleanly for time-indexed data. ---
### Why Prophet exists and what problem it solves ARIMA is precise for short-range forecasting but has two limitations that make it frustrating for ops work: First, it requires the data to be stationary — any trend must be removed manually before fitting. Second, it handles only one type of seasonality at a time, which is a problem for metrics that have both daily and weekly patterns (like HTTP request rates that are high on weekdays and peak in the evening). Meta's open-source **Prophet** library solves both problems. It models trend and seasonality explicitly rather than removing them. It handles multiple overlapping seasonal patterns automatically. And it requires minimal parameter tuning — you can get a working forecast in about 10 lines of code. ### How Prophet thinks about time series Prophet decomposes the series into three additive components: y(t) = g(t) + s(t) + h(t) + error Where: * `g(t)` = trend — the long-term direction * `s(t)` = seasonality — repeating patterns (daily, weekly, yearly) * `h(t)` = holiday effects — spikes from special events * `error` = the random noise left over Prophet learns each component separately from your data and then adds them back together for the forecast. This decomposition is also what makes Prophet easy to debug — you can inspect each component individually with `plot_components()`. ### The ds and y column requirement Prophet requires your data in a **DataFrame with exactly two columns**: * `ds` — the timestamp (datetime format) * `y` — the numeric value to forecast This is a strict requirement. Naming the columns anything else causes an error. The convention is simple to follow. ```bash ## Install Prophet pip install prophet ``` ### Building a Prophet forecast for HTTP request rate ```python ## prophet_request_rate.py ## Forecast hourly HTTP request rate for a Swiggy order API ## 30 days of history -> forecast the next 7 days import numpy as np import pandas as pd from prophet import Prophet import matplotlib.pyplot as plt ## Step 1 - Generate 30 days of realistic hourly request rate data ## Swiggy traffic pattern: peaks at lunch (1 PM) and dinner (8 PM) ## Higher on weekends, lower on Monday mornings np.random.seed(7) hours = pd.date_range(start="2026-05-01", periods=30*24, freq="h") n = len(hours) ## Build components: ## 1. Gradual growth trend (platform growing ~15% over 30 days) trend = np.linspace(4000, 4600, n) ## 2. Daily seasonality — two peaks per day (lunch and dinner) hour_of_day = hours.hour daily = ( 800 * np.sin(2 * np.pi * (hour_of_day - 7) / 24) ## morning rise + 600 * np.sin(2 * np.pi * (hour_of_day - 13) / 12) ## afternoon lunch peak ) ## 3. Weekly seasonality — weekends are ~20% busier day_of_week = hours.dayofweek ## Monday=0, Sunday=6 weekly = np.where(day_of_week >= 5, 500, 0) ## add 500 req/min on weekends ## 4. Realistic noise noise = np.random.normal(0, 120, n) ## Combine all components requests = trend + daily + weekly + noise requests = np.clip(requests, 500, None) ## floor at 500 req/hr ## Prophet format: exactly two columns named ds and y df = pd.DataFrame({"ds": hours, "y": requests}) print("Data prepared for Prophet:") print(df.head()) print(f"\nShape: {df.shape}") print(f"Mean requests/hr: {df['y'].mean():.0f}") ``` ```python ## Step 2 - Fit the Prophet model m = Prophet( yearly_seasonality=False, ## only 30 days of data — not enough for yearly weekly_seasonality=True, ## we have 4+ weeks — weekly pattern detectable daily_seasonality=True, ## hourly data — daily pattern clearly visible changepoint_prior_scale=0.05 ## conservative trend flexibility (default) ) m.fit(df) print("Prophet model fitted successfully.") ``` > **Note:** `changepoint_prior_scale` controls how flexible the trend line is. A lower value (0.05) means the trend changes slowly and smoothly — good for ops metrics that grow gradually. A higher value (0.5) allows sharp sudden changes in trend direction — use this only if your metric has genuine structural breaks like a major deployment. ```python ## Step 3 - Build the future dataframe and generate forecast ## make_future_dataframe extends the historical dates by the requested periods ## freq="h" means hourly — must match the frequency of your training data future = m.make_future_dataframe(periods=7*24, freq="h") ## 7 days ahead forecast = m.predict(future) ## The forecast dataframe contains: ## yhat = point forecast (the model's best guess) ## yhat_lower = lower bound of the 80% prediction interval ## yhat_upper = upper bound of the 80% prediction interval ## trend = isolated trend component ## weekly = isolated weekly seasonal component ## daily = isolated daily seasonal component print("\nForecast columns available:") print([c for c in forecast.columns if not c.startswith("additive")]) print("\nNext 5 forecast points:") print(forecast[["ds", "yhat", "yhat_lower", "yhat_upper"]].tail(5)) ``` ```python ## Step 4 - Plot the full forecast fig1 = m.plot(forecast, figsize=(14, 5)) plt.title("Swiggy Order API — Request Rate Forecast (Prophet)") plt.ylabel("Requests per hour") plt.xlabel("Date") plt.tight_layout() plt.savefig("prophet_forecast.png", dpi=120) plt.show() ``` ```python ## Step 5 - Plot component decomposition ## This is Prophet's most useful diagnostic — see each component separately fig2 = m.plot_components(forecast, figsize=(12, 8)) plt.suptitle("Prophet Components — Trend, Weekly, Daily", y=1.02) plt.tight_layout() plt.savefig("prophet_components.png", dpi=120) plt.show() print("\nComponents plot saved — inspect to verify:") print(" Trend : should show gradual upward slope") print(" Weekly : should show weekend uplift") print(" Daily : should show two peaks (lunch + dinner)") ``` > 💡 **Tip:** Always plot the components before trusting the forecast. If the weekly seasonality component shows a pattern you do not expect for your metric, it means either your data has an issue or the model has picked up a spurious pattern. The components plot is your model debugger. ### Interpreting the forecast output The three most important columns in the forecast DataFrame are: | Column | Meaning | |:-------|:--------| | `yhat` | The model's best single-number prediction | | `yhat_lower` | Lower edge of the 80% prediction interval | | `yhat_upper` | Upper edge of the 80% prediction interval | The gap between `yhat_lower` and `yhat_upper` tells you the model's confidence. A narrow band means the metric is predictable. A wide band means high uncertainty — you should be more cautious about acting on that forecast. ---
### How forecasting becomes anomaly detection This is the pattern used in production AIOps systems and it is elegant in its simplicity. Prophet generates a forecast with upper and lower confidence bounds. If the **actual observed value falls outside those bounds**, that data point is an anomaly — something happened that the model did not expect based on historical patterns. Prophet confidence band: ___________ yhat_upper (expected max) * <- normal reading (inside band) ___________ yhat (point forecast) * <- normal reading ___________ yhat_lower (expected min) Anomaly: ___________ yhat_upper * <- ANOMALY (above band -- unexpected spike) ___________ yhat_upper The threshold is not a fixed number — it adapts to the trend and seasonality of each metric. This is why it outperforms static threshold alerts: the band rises during expected peak hours and drops during off-peak hours automatically. ### Building a Prophet anomaly detector for error rate ```python ## prophet_anomaly_detection.py ## Detect anomalies in a Hotstar API error rate using Prophet confidence bands import numpy as np import pandas as pd from prophet import Prophet import matplotlib.pyplot as plt ## Step 1 - Generate 14 days of hourly error rate data ## Normal error rate oscillates between 0.5% and 2.5% with daily pattern ## On day 10, inject a real incident (error spike lasting 6 hours) np.random.seed(99) hours = pd.date_range(start="2026-05-15 00:00", periods=14*24, freq="h") n = len(hours) ## Normal error rate with daily pattern (higher during peak traffic hours) hour_of_day = hours.hour base_rate = 1.2 ## baseline 1.2% errors daily_effect = 0.5 * np.sin(2 * np.pi * (hour_of_day - 8) / 24) noise = np.random.normal(0, 0.15, n) error_rate = base_rate + daily_effect + noise error_rate = np.clip(error_rate, 0.1, None) ## floor at 0.1% ## Inject a real incident on day 10 (hours 216 to 222) ## Simulates a bad deployment causing elevated error rate incident_start = 10 * 24 ## hour 240 = day 10 start incident_end = incident_start + 6 error_rate[incident_start:incident_end] += np.array([2.5, 4.1, 5.8, 4.9, 3.2, 2.1]) df_all = pd.DataFrame({"ds": hours, "y": error_rate}) ## Split: train on first 13 days, detect on day 14 df_train = df_all[df_all["ds"] < "2026-05-25 00:00"] df_eval = df_all[df_all["ds"] >= "2026-05-25 00:00"] print(f"Training points : {len(df_train)}") print(f"Evaluation points: {len(df_eval)}") ``` ```python ## Step 2 - Train Prophet on historical (clean) data m = Prophet( yearly_seasonality=False, weekly_seasonality=True, daily_seasonality=True, interval_width=0.95, ## use 95% prediction interval for anomaly detection changepoint_prior_scale=0.05 ) m.fit(df_train) print("Model trained on historical error rate data.") ``` > **Note:** `interval_width=0.95` means the confidence band captures 95% of normal variation. Points outside this band occur by chance only 5% of the time under normal conditions — so any reading outside the band is likely a genuine anomaly, not just random noise. ```python ## Step 3 - Forecast over the full evaluation period future = m.make_future_dataframe(periods=len(df_eval), freq="h") forecast = m.predict(future) ## Merge forecast with actual observed values results = forecast[["ds", "yhat", "yhat_lower", "yhat_upper"]].copy() results = results.merge(df_all.rename(columns={"y": "actual"}), on="ds", how="inner") ## Flag anomalies: actual value outside the 95% prediction interval results["is_anomaly"] = ( (results["actual"] < results["yhat_lower"]) | (results["actual"] > results["yhat_upper"]) ) anomalies = results[results["is_anomaly"]] print(f"\nAnomalies detected: {len(anomalies)}") print(anomalies[["ds", "actual", "yhat", "yhat_lower", "yhat_upper"]].head(10)) ``` ```python ## Step 4 - Visualise: normal readings vs flagged anomalies fig, ax = plt.subplots(figsize=(15, 5)) ax.plot(results["ds"], results["actual"], color="#203147", linewidth=1, label="Actual error rate", alpha=0.8) ax.plot(results["ds"], results["yhat"], color="#01ef63", linewidth=1.5, linestyle="--", label="Expected (forecast)") ax.fill_between( results["ds"], results["yhat_lower"], results["yhat_upper"], alpha=0.15, color="#01ef63", label="95% confidence band" ) if not anomalies.empty: ax.scatter( anomalies["ds"], anomalies["actual"], color="red", zorder=5, s=40, label=f"Anomaly ({len(anomalies)} points)" ) ax.set_title("Hotstar API Error Rate — Prophet Anomaly Detection") ax.set_ylabel("Error rate (%)") ax.set_xlabel("Time") ax.legend() plt.tight_layout() plt.savefig("prophet_anomaly_detection.png", dpi=120) plt.show() print("\nAnomaly detection complete.") print(f"Total points evaluated : {len(results)}") print(f"Anomalies flagged : {len(anomalies)}") print(f"Anomaly rate : {len(anomalies)/len(results)*100:.1f}%") ``` > 💡 **Tip:** In production, retrain the model regularly on fresh data — daily or weekly. As your traffic pattern evolves, the model's expected band should evolve too. A model trained on January data will produce stale confidence bands by March. ---
### When each model fits best Both models forecast time series but they make different assumptions and have different strengths. Picking the wrong one does not cause a crash — it just gives you worse forecasts. | Situation | Use ARIMA | Use Prophet | |:----------|:----------|:------------| | Short-range forecast (minutes to hours) | ✅ Better | Works | | Long-range forecast (days to weeks) | Degrades | ✅ Better | | Multiple overlapping seasonal patterns | Difficult | ✅ Handles natively | | Data has missing values | Problematic | ✅ Handles automatically | | You need confidence bands for anomaly detection | Works | ✅ Built in | | You want to explain which component drove the forecast | Hard | ✅ plot_components() | | Stationary metric with no clear trend | ✅ Natural fit | Works | | Metric with rapid recent trend changes | Works | ✅ Changepoints handle this | ### Practical decision rule for ops engineers Use **ARIMA** when you are forecasting a slow-moving metric (database query time, cache hit rate, error rate in normal operation) 15 to 60 minutes ahead and the metric does not have obvious daily or weekly patterns. Use **Prophet** when you are forecasting metrics that have visible daily or weekly cycles (HTTP traffic, CPU on a web server, disk I/O on a data pipeline) and you need forecasts more than a few hours out. When in doubt, try both. Compute RMSE on a holdout period and pick whichever performs better for your specific metric. ---
The problem that keeps engineers up at night It is 11 PM on a Friday. Zerodha's trading platform has been growing steadi...
What makes time-series data different A time series is a sequence of measurements recorded at regular intervals over tim...
What ARIMA stands for and why each letter matters ARIMA stands for AutoRegressive Integrated Moving Average. Each part c...
Why Prophet exists and what problem it solves ARIMA is precise for short-range forecasting but has two limitations that ...
How forecasting becomes anomaly detection This is the pattern used in production AIOps systems and it is elegant in its ...
When each model fits best Both models forecast time series but they make different assumptions and have different streng...
Predicting when you will run out The most direct operational use of forecasting is capacity planning: given the current ...
What you are building A complete mini-system that takes synthetic ops metrics, forecasts them using Prophet, flags anoma...
ARIMA vs Prophet at a glance Feature ARIMA Prophet Setup complexity Medium — needs stationarity check Low — works out of...
Aligns directly with DevOps, Site Reliability (SRE), and Platform Engineering job descriptions.