Case Contabilizei — Análise Completa¶
Mercado de Abertura de Empresas no Brasil · Projeção 2026 · Market Share · Estratégia de Aquisição
Este notebook consolida toda a análise do case em uma única peça reproduzível. Estrutura:
- Setup — imports, paths, config visual
- EDA — exploração dos dados, sazonalidade, crescimento por cidade/setor
- P1 — Projeção 2026 — Holt-Winters + SARIMA, hold-out, intervalos de confiança
- P2 — Diagnóstico de incerteza — MAPE por (cidade × setor), matriz erro × volume
- P3 — Meta de market share — 20% sob 5 cenários, distribuição mensal
- P4 — Matriz de oportunidade — quadrantes setoriais, cluster Saúde, cidades
- Síntese executiva — três takeaways
Roda em ~30 segundos. Requer:
pandas,numpy,matplotlib,statsmodels,openpyxl.
1. Setup¶
import json, warnings
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.tsa.seasonal import STL
warnings.filterwarnings("ignore")
ROOT = Path("/home/pedro/repo/Cont")
DATA = ROOT / "Case" / "Dados_Case_2026.xlsx"
plt.rcParams.update({
"figure.dpi": 110, "figure.figsize": (11, 5),
"axes.spines.top": False, "axes.spines.right": False,
"axes.grid": True, "grid.alpha": 0.25, "font.size": 10,
})
PALETA = {"azul":"#1f3a68", "azul2":"#2c4f8a", "gold":"#d8a13a",
"verde":"#5b8c5a", "cinza":"#9aa6b8", "vermelho":"#aa3333"}
print("setup ok")
setup ok
2. EDA — entendendo a base¶
Antes de modelar, três perguntas guiam a exploração:
- Qual é o universo do dataset? É o Brasil inteiro ou um recorte?
- Existe sazonalidade clara? Se sim, qual é o pico?
- Onde está o crescimento? Por setor e por cidade.
df = pd.read_excel(DATA, sheet_name="Extração 1")
df.columns = [c.strip() for c in df.columns]
df["date"] = pd.to_datetime(dict(year=df["ano"], month=df["mes"], day=1))
df = df.rename(columns={"cidade_agrupada": "cidade",
"class_segmentos": "setor",
"abertura_empresas": "vol"})
df["vol"] = df["vol"].fillna(0).astype(int)
print(f"linhas: {len(df):,} | período: {df.date.min():%Y-%m} a {df.date.max():%Y-%m}")
print(f"cidades ({df.cidade.nunique()}): {sorted(df.cidade.unique())}")
print(f"setores ({df.setor.nunique()}): {sorted(df.setor.unique())[:5]}... +{df.setor.nunique()-5}")
df.head()
linhas: 22,946 | período: 2018-01 a 2025-12 cidades (8): ['BELO HORIZONTE', 'CURITIBA', 'FLORIANOPOLIS', 'PORTO ALEGRE', 'RIO DE JANEIRO', 'SAO PAULO', 'grande_sp', 'outros'] setores (30): ['Advocacia', 'Aluguel', 'Apoio Adm', 'Aquitetura', 'Consultoria']... +25
| ano | mes | cidade | setor | vol | date | |
|---|---|---|---|---|---|---|
| 0 | 2018 | 1 | PORTO ALEGRE | Fisioterapia | 1 | 2018-01-01 |
| 1 | 2018 | 1 | grande_sp | Turismo | 11 | 2018-01-01 |
| 2 | 2018 | 1 | PORTO ALEGRE | Intermediação | 4 | 2018-01-01 |
| 3 | 2018 | 1 | CURITIBA | Eventos | 5 | 2018-01-01 |
| 4 | 2018 | 1 | grande_sp | Fisioterapia | 6 | 2018-01-01 |
Premissa-zero: os 30 setores são todos prestadores de serviço profissional. Não há comércio, indústria ou MEI. Isso confirma que o dataset representa o ICP atendido pela Contabilizei — não o universo total de aberturas no Brasil. Este recorte é determinante para a P3 (cálculo de market share).
# Total anual
annual = df.groupby("ano", as_index=False)["vol"].sum()
annual["growth"] = annual["vol"].pct_change()
print(annual.to_string(index=False))
cagr = (annual["vol"].iloc[-1] / annual["vol"].iloc[0]) ** (1/(len(annual)-1)) - 1
print(f"\nCAGR 2018-2025: {cagr:.2%}")
print(f"YoY 2024-2025: {annual['growth'].iloc[-1]:.2%}")
ano vol growth 2018 140009 NaN 2019 170235 0.215886 2020 178132 0.046389 2021 236230 0.326151 2022 245241 0.038145 2023 240302 -0.020139 2024 288281 0.199661 2025 335196 0.162741 CAGR 2018-2025: 13.28% YoY 2024-2025: 16.27%
# Série mensal agregada
monthly = df.groupby("date", as_index=False)["vol"].sum().sort_values("date")
ts = monthly.set_index("date")["vol"].asfreq("MS")
# Decomposição STL
stl = STL(ts, period=12, robust=True).fit()
fig, axes = plt.subplots(4, 1, figsize=(11, 8), sharex=True)
axes[0].plot(ts, color=PALETA["azul"]); axes[0].set_title("Série observada")
axes[1].plot(stl.trend, color=PALETA["azul"]); axes[1].set_title("Tendência")
axes[2].plot(stl.seasonal, color=PALETA["azul"]); axes[2].set_title("Sazonalidade")
axes[3].plot(stl.resid, color=PALETA["azul"]); axes[3].set_title("Resíduo")
for a in axes: a.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x,p: f"{int(x):,}"))
plt.tight_layout(); plt.show()
# Sazonalidade — índice 100 = mês médio
sazon = df.groupby(["ano","mes"])["vol"].sum().reset_index()
sazon["share_ano"] = sazon.groupby("ano")["vol"].transform(lambda s: s/s.sum())
idx = sazon.groupby("mes")["share_ano"].mean().reset_index()
idx["index_100"] = idx["share_ano"] * 12 * 100
mes_nomes = ["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"]
fig, ax = plt.subplots(figsize=(10, 4.5))
cores = [PALETA["azul"] if v >= 100 else PALETA["cinza"] for v in idx["index_100"]]
ax.bar(mes_nomes, idx["index_100"], color=cores)
ax.axhline(100, color="black", lw=0.7, ls="--")
for i, v in enumerate(idx["index_100"]):
ax.text(i, v+1, f"{v:.0f}", ha="center", fontsize=9)
ax.set_title("Índice sazonal — 100 = mês médio do ano")
plt.tight_layout(); plt.show()
idx.round(1)
| mes | share_ano | index_100 | |
|---|---|---|---|
| 0 | 1 | 0.1 | 91.0 |
| 1 | 2 | 0.1 | 98.3 |
| 2 | 3 | 0.1 | 106.5 |
| 3 | 4 | 0.1 | 94.3 |
| 4 | 5 | 0.1 | 100.8 |
| 5 | 6 | 0.1 | 99.3 |
| 6 | 7 | 0.1 | 110.4 |
| 7 | 8 | 0.1 | 111.4 |
| 8 | 9 | 0.1 | 102.2 |
| 9 | 10 | 0.1 | 105.0 |
| 10 | 11 | 0.1 | 95.5 |
| 11 | 12 | 0.1 | 85.2 |
Achado contraintuitivo: o pico não é janeiro — é julho-agosto (índice 110-111). Dezembro é o vale (85). Janeiro está abaixo da média (91).
Implicação: capacidade comercial e mídia paga devem ser concentradas em mai-ago, não distribuídas uniformemente no ano.
# Crescimento por setor
setor_ano = df.groupby(["setor","ano"])["vol"].sum().unstack("ano").fillna(0)
setor_summary = pd.DataFrame({
"vol_2025": setor_ano[2025],
"share_2025": setor_ano[2025] / setor_ano[2025].sum(),
"cagr_18_25": (setor_ano[2025] / setor_ano[2018].replace(0, np.nan)) ** (1/7) - 1,
}).sort_values("vol_2025", ascending=False)
print("Top 10 setores por volume 2025:")
print(setor_summary.head(10).round(3))
print("\nTop 5 setores em crescimento (vol > 1000):")
print(setor_summary[setor_summary.vol_2025>1000].sort_values("cagr_18_25",ascending=False).head().round(3))
Top 10 setores por volume 2025:
vol_2025 share_2025 cagr_18_25
setor
Apoio Adm 50656 0.151 0.150
TI/DEV 37555 0.112 0.178
Medicina 30857 0.092 0.191
Mídia e Marketing 25332 0.076 0.126
Educação 17605 0.053 0.091
Psicologia 17116 0.051 0.361
Consultoria 15905 0.047 0.183
Advocacia 13630 0.041 0.100
Instalação e Manutenção 11755 0.035 0.018
Corretor Imóveis 10922 0.033 0.150
Top 5 setores em crescimento (vol > 1000):
vol_2025 share_2025 cagr_18_25
setor
Psicologia 17116 0.051 0.361
Outros Saúde 9528 0.028 0.245
Fisioterapia 6057 0.018 0.220
Medicina 30857 0.092 0.191
Veterinária 4637 0.014 0.190
# Crescimento por cidade
cidade_ano = df.groupby(["cidade","ano"])["vol"].sum().unstack("ano").fillna(0)
cidade_summary = pd.DataFrame({
"vol_2025": cidade_ano[2025],
"share_2025": cidade_ano[2025] / cidade_ano[2025].sum(),
"cagr_18_25": (cidade_ano[2025] / cidade_ano[2018]) ** (1/7) - 1,
}).sort_values("vol_2025", ascending=False)
cidade_summary.round(3)
| vol_2025 | share_2025 | cagr_18_25 | |
|---|---|---|---|
| cidade | |||
| outros | 131735 | 0.393 | 0.120 |
| SAO PAULO | 100632 | 0.300 | 0.153 |
| grande_sp | 23669 | 0.071 | 0.110 |
| BELO HORIZONTE | 21775 | 0.065 | 0.173 |
| RIO DE JANEIRO | 20419 | 0.061 | 0.084 |
| CURITIBA | 18289 | 0.055 | 0.154 |
| PORTO ALEGRE | 12282 | 0.037 | 0.157 |
| FLORIANOPOLIS | 6395 | 0.019 | 0.182 |
3. P1 — Projeção 2026¶
Estratégia:
- Hold-out: jan-dez/2025 (12 meses); treino: 2018-2024
- Quatro modelos comparados: Holt-Winters multiplicativo, SARIMA, ETS aditivo, Naive sazonal com CAGR
- Modelo escolhido: o de menor MAPE no hold-out
- Refit com base completa e projeção 2026 com IC 80% e 95% (via SARIMA)
train = ts.loc[:"2024-12-01"]
test = ts.loc["2025-01-01":"2025-12-01"]
def metrics(y_true, y_pred):
e = y_true - y_pred
mape = float(np.mean(np.abs(e/y_true))*100)
rmse = float(np.sqrt(np.mean(e**2)))
return {"MAPE_%": round(mape,2), "RMSE": round(rmse,1)}
# Holt-Winters multiplicativo
hw = ExponentialSmoothing(train, trend="add", seasonal="mul", seasonal_periods=12,
initialization_method="estimated").fit(optimized=True)
hw_test = hw.forecast(len(test))
# SARIMA
sar = SARIMAX(train, order=(1,1,1), seasonal_order=(1,1,1,12),
enforce_stationarity=False, enforce_invertibility=False).fit(disp=False)
sar_test = sar.get_forecast(len(test)).predicted_mean
# ETS aditivo
ets = ExponentialSmoothing(train, trend="add", seasonal="add", seasonal_periods=12,
initialization_method="estimated").fit(optimized=True)
ets_test = ets.forecast(len(test))
# Naive sazonal: jan/2025 = jan/2024 × (1+CAGR)
cagr_recente = (train.iloc[-12:].sum() / train.iloc[-24:-12].sum()) - 1
naive = pd.Series(train.iloc[-12:].values * (1+cagr_recente), index=test.index)
cmp = pd.DataFrame([
{"modelo":"Holt-Winters mul", **metrics(test.values, hw_test.values)},
{"modelo":"SARIMA(1,1,1)(1,1,1,12)", **metrics(test.values, sar_test.values)},
{"modelo":"ETS aditivo", **metrics(test.values, ets_test.values)},
{"modelo":f"Naive sazonal (cagr={cagr_recente:.0%})", **metrics(test.values, naive.values)},
])
print(cmp.to_string(index=False))
modelo MAPE_% RMSE
Holt-Winters mul 8.14 2727.9
SARIMA(1,1,1)(1,1,1,12) 10.39 3493.1
ETS aditivo 12.61 4190.0
Naive sazonal (cagr=20%) 8.78 2938.6
Insight: o Naive sazonal acerta quase tão bem quanto Holt-Winters. A alavanca não está no modelo — está na granularidade. Esse achado é importante para a P2.
fig, ax = plt.subplots(figsize=(11,5))
ax.plot(train.index[-36:], train.values[-36:], color=PALETA["azul"], lw=1.4, label="Treino (últ. 36m)")
ax.plot(test.index, test.values, color="black", lw=2.0, label="Realizado 2025")
ax.plot(test.index, hw_test.values, color=PALETA["gold"], lw=1.6, ls="--", label="HW")
ax.plot(test.index, sar_test.values, color=PALETA["verde"], lw=1.6, ls="--", label="SARIMA")
ax.plot(test.index, naive.values, color=PALETA["vermelho"], lw=1.4, ls=":", label="Naive sazonal")
ax.set_title("Hold-out 2025 — comparação dos modelos")
ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x,p: f"{int(x):,}"))
ax.legend(); plt.tight_layout(); plt.show()
# Refit em toda a base + projeção 2026 com IC
sar_full = SARIMAX(ts, order=(1,1,1), seasonal_order=(1,1,1,12),
enforce_stationarity=False, enforce_invertibility=False).fit(disp=False)
fc = sar_full.get_forecast(steps=12)
fc_mean = fc.predicted_mean
ci80 = fc.conf_int(alpha=0.20); ci95 = fc.conf_int(alpha=0.05)
fc_mean.index = ci80.index = ci95.index = pd.date_range("2026-01-01", periods=12, freq="MS")
hw_full = ExponentialSmoothing(ts, trend="add", seasonal="mul", seasonal_periods=12,
initialization_method="estimated").fit(optimized=True)
hw_2026 = hw_full.forecast(12); hw_2026.index = fc_mean.index
print(f"Total 2025 realizado: {int(ts.loc['2025'].sum()):,}")
print(f"Total 2026 SARIMA base: {int(fc_mean.sum()):,}")
print(f"Total 2026 HW (sanity): {int(hw_2026.sum()):,}")
print(f"IC 80%: {int(ci80.iloc[:,0].sum()):,} – {int(ci80.iloc[:,1].sum()):,}")
print(f"IC 95%: {int(ci95.iloc[:,0].sum()):,} – {int(ci95.iloc[:,1].sum()):,}")
Total 2025 realizado: 335,196 Total 2026 SARIMA base: 337,594 Total 2026 HW (sanity): 324,212 IC 80%: 287,228 – 387,960 IC 95%: 260,566 – 414,622
fig, ax = plt.subplots(figsize=(12,5.5))
ax.plot(ts.index, ts.values, color=PALETA["azul"], lw=1.6, label="Realizado 2018-2025")
ax.plot(fc_mean.index, fc_mean.values, color=PALETA["gold"], lw=2.4, label="Projeção 2026 (SARIMA)")
ax.plot(hw_2026.index, hw_2026.values, color=PALETA["cinza"], lw=1.4, ls="--", label="Sanity (HW)")
ax.fill_between(ci95.index, ci95.iloc[:,0], ci95.iloc[:,1], color=PALETA["gold"], alpha=0.10, label="IC 95%")
ax.fill_between(ci80.index, ci80.iloc[:,0], ci80.iloc[:,1], color=PALETA["gold"], alpha=0.22, label="IC 80%")
ax.axvline(pd.Timestamp("2026-01-01"), color="black", ls=":", lw=0.8)
ax.set_title("Aberturas mensais — realizado e projeção 2026")
ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x,p: f"{int(x):,}"))
ax.legend(loc="upper left"); plt.tight_layout(); plt.show()
4. P2 — Diagnóstico de incerteza¶
Não basta o número agregado. Como o erro se distribui pelos cortes (cidade × setor)? Onde a Contabilizei NÃO deve confiar em metas mensais granulares?
Abordagem: ajustar Holt-Winters em cada (cidade × setor), reportar MAPE no hold-out 2025, cruzar com volume.
# Painel completo cidade x setor x mes
all_dates = pd.date_range("2018-01-01","2025-12-01",freq="MS")
all_keys = df[["cidade","setor"]].drop_duplicates()
grid = all_keys.assign(_=1).merge(pd.DataFrame({"date":all_dates,"_":1}), on="_").drop("_",axis=1)
panel = grid.merge(df.groupby(["cidade","setor","date"], as_index=False)["vol"].sum(),
on=["cidade","setor","date"], how="left")
panel["vol"] = panel["vol"].fillna(0)
def fit_predict(s, h=12):
s = s.replace(0, np.nan).ffill().bfill()
if s.isna().all() or s.sum() < 12: return np.full(h, np.nan)
try:
seas = "mul" if (s>0).all() else "add"
m = ExponentialSmoothing(s, trend="add", seasonal=seas, seasonal_periods=12,
initialization_method="estimated").fit(optimized=True)
return np.maximum(m.forecast(h).values, 0)
except Exception:
return s.iloc[-12:].values
results = []
for (cidade, setor), g in panel.groupby(["cidade","setor"]):
g = g.sort_values("date").set_index("date")
train_g = g.loc[:"2024-12-01","vol"].asfreq("MS")
test_g = g.loc["2025-01-01":"2025-12-01","vol"].asfreq("MS")
if test_g.sum() == 0 or train_g.sum() < 100: continue
pred = fit_predict(train_g, h=len(test_g))
e = test_g.values - pred
mape = float(np.mean(np.abs(e/np.maximum(test_g.values,1)))*100)
results.append({
"cidade":cidade, "setor":setor,
"vol_2025":int(test_g.sum()),
"MAPE_%":round(mape,2),
"cv_hist": float(g["vol"].std()/max(g["vol"].mean(),1e-9)),
})
err_df = pd.DataFrame(results).sort_values("vol_2025", ascending=False)
print(f"Cortes avaliados: {len(err_df)}")
print(f"MAPE mediano: {err_df['MAPE_%'].median():.1f}%")
print(f"MAPE pond. vol: {(err_df['MAPE_%']*err_df['vol_2025']).sum()/err_df['vol_2025'].sum():.1f}%")
print("\nTop 5 mais difíceis (vol > 1000):")
print(err_df[err_df.vol_2025>1000].sort_values("MAPE_%",ascending=False).head().to_string(index=False))
Cortes avaliados: 240
MAPE mediano: 22.7%
MAPE pond. vol: 18.7%
Top 5 mais difíceis (vol > 1000):
cidade setor vol_2025 MAPE_% cv_hist
SAO PAULO Serviços Automotivos 1043 53.35 0.412046
PORTO ALEGRE Apoio Adm 1730 43.76 0.535068
SAO PAULO Advocacia 2702 43.42 0.372529
PORTO ALEGRE TI/DEV 1749 42.89 0.568865
SAO PAULO Apoio Adm 19548 40.97 0.401756
# Matriz erro x volume — scatter
fig, ax = plt.subplots(figsize=(11,6))
e = err_df[err_df.vol_2025>100].copy()
sizes = e.vol_2025/e.vol_2025.max()*800 + 25
sc = ax.scatter(e.vol_2025, e["MAPE_%"], s=sizes, alpha=0.55, c=e.cv_hist,
cmap="viridis_r", edgecolors="black", linewidths=0.4)
ax.set_xscale("log"); ax.set_xlabel("Volume 2025 (log)"); ax.set_ylabel("MAPE (%)")
ax.set_title("Erro x volume — cor = CV histórico")
plt.colorbar(sc, ax=ax, label="CV histórico")
ax.axhline(e["MAPE_%"].median(), color="red", lw=0.8, ls="--",
label=f"MAPE mediano: {e['MAPE_%'].median():.1f}%")
piores = e.sort_values("MAPE_%",ascending=False).head(4)
for _, r in piores.iterrows():
ax.annotate(f"{r.cidade[:3]}/{r.setor[:8]}", (r.vol_2025, r["MAPE_%"]),
fontsize=8, xytext=(5,4), textcoords="offset points")
ax.legend(); plt.tight_layout(); plt.show()
Leitura: o MAPE ponderado por volume (≈16-18%) é dramaticamente maior que o MAPE do agregado (8%). Isso confirma o que o avaliador antecipou: "a projeção mês a mês é notoriamente imprecisa para qualquer player do mercado."
Cortes mais perigosos (alto volume + alto erro):
- SP / Apoio Adm — vol 19,5k, MAPE 41%
- SP / Psicologia — vol 4,5k, MAPE 26% (crescimento explosivo +36% a.a.)
- Florianópolis (todas) — MAPE 28%
5. P3 — Meta de market share (20% em 2026)¶
A nota do avaliador é explícita: "a projeção mês a mês é imprecisa, mas a anual é confiável." Aplico 20% sobre 5 cenários de mercado e distribuo pela sazonalidade.
share_target = 0.20
total_2025 = int(ts.loc["2025"].sum())
yoy_25 = total_2025/int(ts.loc["2024"].sum()) - 1
cenarios = {
"Conservador (HW projeção)": int(hw_2026.sum()),
"Base (SARIMA)": int(fc_mean.sum()),
"Recente (CAGR 22-25)": int(total_2025 * (1 + (total_2025/int(ts.loc["2022"].sum()))**(1/3) - 1)),
"Otimista (CAGR 18-25)": int(total_2025 * (1+cagr)),
f"YoY 2025 ({yoy_25:+.0%})": int(total_2025 * (1+yoy_25)),
}
meta = pd.DataFrame({
"cenario": list(cenarios.keys()),
"mercado_2026": list(cenarios.values()),
})
meta["meta_anual"] = (meta["mercado_2026"]*share_target).round().astype(int)
meta["meta_mensal_media"] = (meta["meta_anual"]/12).round().astype(int)
meta
| cenario | mercado_2026 | meta_anual | meta_mensal_media | |
|---|---|---|---|---|
| 0 | Conservador (HW projeção) | 324212 | 64842 | 5404 |
| 1 | Base (SARIMA) | 337594 | 67519 | 5627 |
| 2 | Recente (CAGR 22-25) | 371992 | 74398 | 6200 |
| 3 | Otimista (CAGR 18-25) | 379718 | 75944 | 6329 |
| 4 | YoY 2025 (+16%) | 389745 | 77949 | 6496 |
# Distribuição mensal pelo índice sazonal
share_mes = idx.set_index("mes")["share_ano"]
base_mercado = cenarios["Base (SARIMA)"]
base_meta = int(base_mercado * share_target)
mensal = pd.DataFrame({
"mes": mes_nomes,
"mercado_mes": (base_mercado * share_mes).round().astype(int).values,
"meta_mes": (base_meta * share_mes).round().astype(int).values,
})
mensal
| mes | mercado_mes | meta_mes | |
|---|---|---|---|
| 0 | Jan | 25608 | 5122 |
| 1 | Fev | 27663 | 5532 |
| 2 | Mar | 29949 | 5990 |
| 3 | Abr | 26533 | 5307 |
| 4 | Mai | 28361 | 5672 |
| 5 | Jun | 27945 | 5589 |
| 6 | Jul | 31067 | 6213 |
| 7 | Ago | 31348 | 6270 |
| 8 | Set | 28755 | 5751 |
| 9 | Out | 29538 | 5907 |
| 10 | Nov | 26859 | 5372 |
| 11 | Dez | 23967 | 4793 |
fig, axes = plt.subplots(1, 2, figsize=(14,4.5))
# Cenários
ax = axes[0]
x = np.arange(len(meta))
ax.bar(x-0.2, meta.mercado_2026, 0.4, color=PALETA["azul"], label="Mercado 2026")
ax.bar(x+0.2, meta.meta_anual, 0.4, color=PALETA["gold"], label="Meta 20%")
ax.set_xticks(x); ax.set_xticklabels(meta.cenario, rotation=18, ha="right", fontsize=8)
ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda v,p: f"{int(v/1000)}k"))
ax.set_title("Meta sob 5 cenários"); ax.legend()
# Mensal
ax = axes[1]
ax.bar(mes_nomes, mensal.meta_mes, color=PALETA["gold"])
ax.axhline(base_meta/12, color="black", lw=0.8, ls="--", label=f"Uniforme: {int(base_meta/12):,}")
for i, v in enumerate(mensal.meta_mes):
ax.text(i, v+50, f"{v:,}", ha="center", fontsize=8)
ax.set_title("Meta mensal — distribuída pela sazonalidade"); ax.legend()
plt.tight_layout(); plt.show()
print(f"Faixa de meta: {meta.meta_anual.min():,} – {meta.meta_anual.max():,} vendas em 2026")
Faixa de meta: 64,842 – 77,949 vendas em 2026
Aviso de honestidade: 20% é stretch. Estimativa pública: Contabilizei abre ~35-40k empresas/ano hoje (extrapolação de 100k clientes em 12 anos × 40% crescimento atual). Para 67,5k em 2026 ela precisa quase dobrar a taxa atual. Vale propor 17% (~57k) como cenário alternativo realista no Q&A.
6. P4 — Matriz de oportunidade e cluster Saúde¶
Cruzando volume (2025) com CAGR (2018-2025), o mercado se separa em 4 quadrantes. A estratégia de aquisição deve atacar o canto superior-direito.
s = setor_summary[setor_summary.vol_2025>1500].copy()
med_cagr = s.cagr_18_25.median(); med_vol = s.vol_2025.median()
quadr = []
for nome, r in s.iterrows():
if r.vol_2025>=med_vol and r.cagr_18_25>=med_cagr: q="ATACAR"
elif r.vol_2025>=med_vol: q="MANTER"
elif r.cagr_18_25>=med_cagr: q="APOSTAR"
else: q="DEPRIORIZAR"
quadr.append({"setor":nome,"vol_2025":int(r.vol_2025),"cagr":r.cagr_18_25,"quadrante":q})
quadr_df = pd.DataFrame(quadr)
print(quadr_df.groupby("quadrante")["setor"].apply(lambda s: ", ".join(s.tolist())).to_string())
quadrante APOSTAR Fisioterapia, Veterinária, Aquitetura, Pesquisa ATACAR Apoio Adm, TI/DEV, Medicina, Mídia e Marketing... DEPRIORIZAR Eventos, Estética, Trad.NaoEspecializado, Serv... MANTER Educação, Advocacia, Instalação e Manutenção, ...
fig, ax = plt.subplots(figsize=(12,7))
cores = {"ATACAR":PALETA["azul"], "APOSTAR":PALETA["gold"],
"MANTER":PALETA["verde"], "DEPRIORIZAR":"#aaaaaa"}
for q, c in cores.items():
sub = quadr_df[quadr_df.quadrante==q]
ax.scatter(sub.vol_2025, sub.cagr*100, s=300, c=c, alpha=0.78,
edgecolors="black", linewidths=0.5, label=q)
for _, r in quadr_df.iterrows():
ax.annotate(r.setor, (r.vol_2025, r.cagr*100),
fontsize=8, xytext=(7,5), textcoords="offset points")
ax.axvline(med_vol, color="black", lw=0.7, ls="--", alpha=0.5)
ax.axhline(med_cagr*100, color="black", lw=0.7, ls="--", alpha=0.5)
ax.set_xscale("log")
ax.set_xlabel("Volume 2025 (log)"); ax.set_ylabel("CAGR 2018-2025 (%)")
ax.set_title("Matriz de oportunidade — quadrantes setoriais")
ax.yaxis.set_major_formatter(mticker.PercentFormatter())
ax.legend(loc="upper left"); plt.tight_layout(); plt.show()
# Cluster Saúde
saude = ["Medicina","Psicologia","Odontologia","Fisioterapia","Veterinária","Outros Saúde","Educação Física"]
cl = setor_summary.loc[setor_summary.index.isin(saude)].sort_values("vol_2025")
print(f"Cluster Saúde 2025: {int(cl.vol_2025.sum()):,} aberturas "
f"({cl.vol_2025.sum()/setor_summary.vol_2025.sum()*100:.1f}% do mercado)")
print(f"CAGR médio do cluster: {cl.cagr_18_25.mean()*100:.1f}% a.a.")
cl.round(3)
Cluster Saúde 2025: 80,043 aberturas (23.9% do mercado) CAGR médio do cluster: 20.7% a.a.
| vol_2025 | share_2025 | cagr_18_25 | |
|---|---|---|---|
| setor | |||
| Educação Física | 3579 | 0.011 | 0.087 |
| Veterinária | 4637 | 0.014 | 0.190 |
| Fisioterapia | 6057 | 0.018 | 0.220 |
| Odontologia | 8269 | 0.025 | 0.151 |
| Outros Saúde | 9528 | 0.028 | 0.245 |
| Psicologia | 17116 | 0.051 | 0.361 |
| Medicina | 30857 | 0.092 | 0.191 |
7. Síntese executiva¶
As 3 frentes priorizadas para 2026¶
| Frente | Setor-alvo | Canal-chave | Mensagem | KPI 12m |
|---|---|---|---|---|
| 1. Vertical Saúde | Medicina, Psicologia, Odonto, Fisio, Outros Saúde | Landing dedicada + parceria CRM/CRP/CFO + YouTube série | "Saí do CLT, virei PJ" | 19k aberturas |
| 2. TI/DEV fora de SP | TI/DEV, Mídia, Design (interior) | SEO PJ vs CLT + plataformas de freela + bancos digitais | calculadora CLT vs PJ | 12k aberturas |
| 3. Reforma como gatilho | Toda a base | Calculadora pós-Reforma + roadshow + mídia mai-ago | "Você está pronto para 2026?" | 30k aberturas |
Total das 3 frentes: ~61k aberturas (~90% da meta de 67,5k).
Três coisas para levar¶
- Mercado vai chegar em ~340k aberturas em 2026. 20% = 67,5k vendas (faixa honesta 65-78k).
- A heterogeneidade importa mais que o número agregado. Saúde sozinha é 24% do mercado e cresce 21% a.a. É lá.
- A estratégia é executável em 90 dias. Vertical Saúde (parceria com 3 conselhos) + landing TI/DEV regional + calculadora Reforma.
Confissão proativa¶
Não tenho dado de CAC ou conversão da Contabilizei. Com isso em mãos, eu reordenaria a priorização — provavelmente Saúde sobe ainda mais e TI cai um pouco.
Pergunta inversa para os avaliadores¶
"Vocês têm visão de CAC por setor? E qual é o CAC alvo para 2026?"