feat(chart): agentic AI market size forecasts
This commit is contained in:
177
src/charts/market_forecasts.py
Normal file
177
src/charts/market_forecasts.py
Normal file
@@ -0,0 +1,177 @@
|
||||
"""Agent Market Forecasts Chart"""
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.lines as mlines
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from src.data.agent_adoption import agent_market_forecasts
|
||||
from src.utils.styling import get_theme, EXPORT_DPI, AGENT_GROWTH, AI_SPEND, GRAY_DARK
|
||||
|
||||
|
||||
def _interpolate_forecast(forecast: dict) -> dict:
|
||||
"""Interpolate yearly values from start/end using the stated CAGR."""
|
||||
start_val = forecast["year_2025_billions"]
|
||||
cagr = forecast["cagr_percent"]
|
||||
|
||||
# Determine end year key
|
||||
if "year_2033_billions" in forecast:
|
||||
end_key = "year_2033_billions"
|
||||
end_year = 2033
|
||||
else:
|
||||
end_key = "year_2030_billions"
|
||||
end_year = 2030
|
||||
|
||||
end_val = forecast[end_key]
|
||||
|
||||
# Interpolate yearly values using CAGR
|
||||
values = {}
|
||||
for year in range(2025, 2035):
|
||||
if year <= end_year:
|
||||
values[year] = start_val * ((1 + cagr / 100) ** (year - 2025))
|
||||
else:
|
||||
# No forecast beyond end year — leave as None
|
||||
values[year] = None
|
||||
|
||||
return {
|
||||
"source": forecast["source"],
|
||||
"category": forecast.get("category", ""),
|
||||
"cagr": cagr,
|
||||
"start": start_val,
|
||||
"end_val": end_val,
|
||||
"end_year": end_year,
|
||||
"values": values,
|
||||
}
|
||||
|
||||
|
||||
def plot_market_forecasts() -> str:
|
||||
plt.rcParams.update(get_theme())
|
||||
fig, ax = plt.subplots(figsize=(14, 8))
|
||||
|
||||
# Define colors per source
|
||||
colors = {
|
||||
"Omdia": "#e74c3c",
|
||||
"BCC Research": "#2980b9",
|
||||
"MarketsandMarkets": "#27ae60",
|
||||
"Grand View Research": "#8e44ad",
|
||||
}
|
||||
|
||||
# Process forecasts
|
||||
processed = [_interpolate_forecast(f) for f in agent_market_forecasts]
|
||||
|
||||
all_years = list(range(2025, 2035))
|
||||
|
||||
# Collect per-year min/max for shaded band (only where forecasts exist)
|
||||
min_vals = []
|
||||
max_vals = []
|
||||
for year in all_years:
|
||||
vals = [p["values"][year] for p in processed if p["values"][year] is not None]
|
||||
if vals:
|
||||
min_vals.append(min(vals))
|
||||
max_vals.append(max(vals))
|
||||
else:
|
||||
min_vals.append(None)
|
||||
max_vals.append(None)
|
||||
|
||||
# Plot each forecast line
|
||||
handles = []
|
||||
labels = []
|
||||
for p in processed:
|
||||
pts_x = []
|
||||
pts_y = []
|
||||
for year in all_years:
|
||||
v = p["values"][year]
|
||||
if v is not None:
|
||||
pts_x.append(year)
|
||||
pts_y.append(v)
|
||||
|
||||
if pts_x:
|
||||
label = f'{p["source"]} ({p["cagr"]}% CAGR)'
|
||||
color = colors.get(p["source"], AI_SPEND)
|
||||
line, = ax.plot(
|
||||
pts_x, pts_y,
|
||||
color=color,
|
||||
linewidth=2.5,
|
||||
label=label,
|
||||
marker="o",
|
||||
markersize=5,
|
||||
)
|
||||
handles.append(line)
|
||||
labels.append(label)
|
||||
|
||||
# Annotate endpoint
|
||||
ax.annotate(
|
||||
f"${p['end_val']:.1f}B",
|
||||
xy=(pts_x[-1], pts_y[-1]),
|
||||
xytext=(5, 8),
|
||||
textcoords="offset points",
|
||||
fontsize=9,
|
||||
fontweight="bold",
|
||||
color=color,
|
||||
)
|
||||
|
||||
# Shaded confidence band between min and max
|
||||
band_x = []
|
||||
band_min = []
|
||||
band_max = []
|
||||
for i, year in enumerate(all_years):
|
||||
if min_vals[i] is not None:
|
||||
band_x.append(year)
|
||||
band_min.append(min_vals[i])
|
||||
band_max.append(max_vals[i])
|
||||
|
||||
if band_x:
|
||||
ax.fill_between(
|
||||
band_x, band_min, band_max,
|
||||
alpha=0.12,
|
||||
color=AGENT_GROWTH,
|
||||
label="Forecast Range",
|
||||
)
|
||||
handles.append(
|
||||
mlines.Line2D([], [], color=AGENT_GROWTH, alpha=0.3, linewidth=2)
|
||||
)
|
||||
labels.append("Forecast Range")
|
||||
|
||||
# Axes configuration
|
||||
ax.set_yscale("log")
|
||||
ax.set_ylim(0.8, 250)
|
||||
ax.set_xlim(2024.5, 2034.5)
|
||||
ax.set_xticks(all_years)
|
||||
ax.set_xlabel("Year", fontsize=12)
|
||||
ax.set_ylabel("Market Size ($ Billions, log scale)", fontsize=12)
|
||||
ax.set_title(
|
||||
"Agentic AI Market Size Forecasts",
|
||||
fontsize=16,
|
||||
fontweight="bold",
|
||||
)
|
||||
|
||||
# Subtitle
|
||||
fig.text(
|
||||
0.5,
|
||||
0.93,
|
||||
"Multiple analyst projections 2025\u20132034",
|
||||
fontsize=11,
|
||||
ha="center",
|
||||
style="italic",
|
||||
color=GRAY_DARK,
|
||||
)
|
||||
|
||||
ax.legend(handles=handles, labels=labels, loc="upper left", fontsize=9)
|
||||
ax.grid(True, alpha=0.3)
|
||||
|
||||
fig.savefig(
|
||||
"output/charts/11_agent_market_forecasts.png",
|
||||
dpi=EXPORT_DPI,
|
||||
facecolor=fig.get_facecolor(),
|
||||
edgecolor="none",
|
||||
)
|
||||
plt.close(fig)
|
||||
return "output/charts/11_agent_market_forecasts.png"
|
||||
|
||||
|
||||
def main():
|
||||
path = plot_market_forecasts()
|
||||
print(f"Chart saved: {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user