Aula 10 — Módulo 2: Inferência Estatística
Como transformar a variabilidade entre amostras em uma margem de erro?
Exemplo da aula: tempo de entrega de pedidos do e-commerce brasileiro Olist.
Quanto tempo um pedido leva para chegar — e quão precisa é nossa estimativa?
Não basta reportar uma média. Precisamos comunicar sua incerteza.
| Coluna | Significado |
|---|---|
order_purchase_timestamp |
instante da compra |
order_delivered_customer_date |
entrega efetiva |
order_estimated_delivery_date |
entrega prometida |
order_status |
situação do pedido |
A unidade de observação é um pedido. A partir das datas, calculamos tempo de entrega e indicador de atraso.
Após remover pedidos sem datas completas:
| Medida | Valor |
|---|---|
| Pedidos | 96.476 |
| Tempo médio | 12,56 dias |
| Desvio padrão | 9,55 dias |
| Entregas atrasadas | 8,11% |
Trataremos esses pedidos como uma população conhecida para validar os métodos.
Há uma cauda longa: poucos pedidos demoram muito mais que o típico.
plt.figure(figsize=(9, 5.2))
sns.histplot(delivery, bins=np.arange(0, 61, 2), color=BLUE)
plt.axvline(mu, color=ORANGE, lw=3, label=f"média = {mu:.1f} dias")
plt.xlim(0, 60)
plt.xlabel("Tempo entre compra e entrega (dias)")
plt.ylabel("Pedidos")
plt.title("O tempo de entrega é assimétrico à direita")
plt.legend()
finish("populacao-entrega.png")Duas amostras da mesma população produzem histogramas, médias e estimativas da densidade diferentes. Com apenas 30 observações, a variabilidade amostral fica fácil de perceber.
sample_a = rng.choice(delivery, size=30, replace=False)
sample_b = rng.choice(delivery, size=30, replace=False)
bins = np.arange(0, 61, 5)
fig, axes = plt.subplots(1, 2, figsize=(11, 4.8), sharex=True, sharey=True)
for ax, sample, label, color in [
(axes[0], sample_a, "Amostra A", BLUE),
(axes[1], sample_b, "Amostra B", ORANGE),
]:
sns.histplot(sample, bins=bins, stat="density", color=color, alpha=.48, ax=ax)
sns.kdeplot(
sample, color=color, lw=3, cut=0, clip=(0, 60),
label=r"KDE: $\hat f_h(x)$", ax=ax,
)
ax.axvline(mu, color=INK, lw=2, ls="--", label=f"μ = {mu:.1f}")
ax.axvline(sample.mean(), color=color, lw=3, label=f"x̄ = {sample.mean():.1f}")
ax.set_xlim(0, 60)
ax.set_xlabel("Tempo de entrega (dias)")
ax.set_title(f"{label}: n = 30")
ax.legend()
axes[0].set_ylabel("Densidade")
axes[1].set_ylabel("")
finish("amostra-entrega.png")| Objeto | Exemplo |
|---|---|
| Parâmetro | média populacional \(\mu=12{,}56\) |
| Estimador | \(\bar{X}=n^{-1}\sum X_i\) |
| Estimativa | valor de \(\bar{x}\) obtido na amostra |
O parâmetro é fixo; o estimador varia com a amostra.
Imagine retirar muitas amostras de mesmo tamanho:
\[ \bar{x}_1,\ \bar{x}_2,\ \ldots,\ \bar{x}_R \]
A distribuição desses valores é a distribuição amostral da média.
Ela responde perguntas sobre o estimador, não sobre pedidos individuais:
Se \(X_1,\ldots,X_n\) são independentes, com \(E[X_i]=\mu\) e \(\operatorname{Var}(X_i)=\sigma^2<\infty\):
\[ \operatorname{Var}(\bar{X})=\frac{\sigma^2}{n} \]
\[ \operatorname{SE}(\bar{X})=\frac{\sigma}{\sqrt{n}} \]
\(\bar X=n^{-1}\sum_iX_i\) é a média amostral; \(n\) é o tamanho da amostra; \(\sigma\) é o desvio padrão populacional. O erro padrão é o desvio padrão da distribuição amostral de \(\bar X\).
O centro permanece em \(\mu\); a dispersão diminui.
means_10 = sample_means(delivery, 10)
means_50 = sample_means(delivery, 50)
means_200 = sample_means(delivery, 200)
plt.figure(figsize=(9, 5.2))
for vals, label, color in [(means_10, "n = 10", ORANGE), (means_50, "n = 50", BLUE), (means_200, "n = 200", PURPLE)]:
sns.kdeplot(vals, lw=3, color=color, label=label)
plt.axvline(mu, color=INK, lw=2, ls="--", label="μ")
plt.xlabel("Média amostral do tempo de entrega (dias)")
plt.ylabel("Densidade")
plt.title("Aumentar n concentra a distribuição amostral")
plt.legend()
finish("tcl-tamanhos.png")Lei dos Grandes Números
\[{\bar X}_n \xrightarrow{P} \mu\]
Pergunta: para onde a média amostral vai?
Resposta: \(\bar X_n\) se aproxima de \(\mu\) quando \(n\) cresce.
Não informa: o formato aproximado da distribuição de \(\bar X_n\).
Teorema Central do Limite
\[\frac{\bar X_n-\mu}{\sigma/\sqrt n}\xrightarrow{d}N(0,1)\]
Pergunta: como \(\bar X_n\) oscila ao redor de \(\mu\)?
Resposta: descreve o formato aproximadamente Normal e a escala \(\sigma/\sqrt n\).
Não afirma: que os dados originais sejam Normais.
LGN fala de convergência ao alvo; TCL, da distribuição do erro.
Qual afirmação descreve corretamente o Teorema Central do Limite?
Sob condições usuais, quando \(n\) é suficientemente grande:
\[ \frac{\bar{X}-\mu}{\sigma/\sqrt{n}} \xrightarrow{d}N(0,1) \]
Equivalentemente:
\[ \bar{X}\approx N\left(\mu,\frac{\sigma^2}{n}\right) \]
\(\xrightarrow{d}\) significa convergência em distribuição; \(N(0,1)\) é a Normal padrão. A aproximação afirma que \(\bar X\) tem centro \(\mu\) e variância aproximada \(\sigma^2/n\) — não que cada \(X_i\) seja Normal.
O TCL clássico pressupõe observações independentes ou fracamente dependentes.
Pode falhar quando:
O TCL descreve variabilidade aleatória dentro do mecanismo assumido.
Ele não corrige:
Quando amostramos grande parte de uma população finita:
\[ \operatorname{SE}(\bar{X}) =\frac{\sigma}{\sqrt{n}} \sqrt{\frac{N-n}{N-1}} \]
Se \(n/N<10\%\), a correção costuma ser pequena.
Para a média amostral na versão clássica:
Amostragem adequada
O mecanismo de coleta deve sustentar a inferência para a população de interesse.
Independência
As observações devem ser independentes ou ter dependência suficientemente fraca.
Variância finita
Precisamos de \(E[X_i]=\mu\) e \(0<\operatorname{Var}(X_i)=\sigma^2<\infty\).
Tamanho suficiente
Assimetria, caudas pesadas e outliers exigem amostras maiores. Não existe um \(n=30\) universal.
Sem reposição, \(n/N<10\%\) permite tratar a dependência como pequena. Para frações maiores, usamos a correção para população finita.
O TCL atua sobre as médias, não transforma os dados originais.
fig, axes = plt.subplots(2, 3, figsize=(12, 7.2))
populations = [
(rng.uniform(0, 1, 40000), "Uniforme"),
(rng.exponential(1, 40000), "Assimétrica"),
(np.r_[rng.normal(-2, .6, 20000), rng.normal(2, .6, 20000)], "Bimodal"),
]
for col, (pop, title) in enumerate(populations):
sns.histplot(pop, bins=35, color=ORANGE, ax=axes[0, col])
axes[0, col].set_title(title)
axes[0, col].set_ylabel("População" if col == 0 else "")
means = sample_means(pop, 40, 4000)
sns.histplot(means, bins=30, color=BLUE, ax=axes[1, col])
axes[1, col].set_ylabel("Médias, n = 40" if col == 0 else "")
fig.suptitle("Populações diferentes, médias aproximadamente normais", fontweight="bold")
finish("tcl-tres-populacoes.png")Não existe um número mágico universal como \(n=30\).
O tamanho adequado depende de:
A aparência de uma única amostra não garante que a população seja simétrica. Com \(n=20\), valores raros podem não aparecer; com \(n=100\), a amostra tem mais oportunidade de revelar a cauda.
sigma_log = 1.15
x = np.linspace(0.03, 25, 1200)
population_pdf = (
np.exp(-(np.log(x) ** 2) / (2 * sigma_log**2))
/ (x * sigma_log * np.sqrt(2 * np.pi))
)
rng_shape = np.random.default_rng(20260228)
small_sample = rng_shape.lognormal(mean=0, sigma=sigma_log, size=20)
larger_sample = rng_shape.lognormal(mean=0, sigma=sigma_log, size=100)
fig, axes = plt.subplots(1, 3, figsize=(14.2, 4.8))
axes[0].plot(x, population_pdf, color=BLUE, lw=3)
axes[0].fill_between(x, population_pdf, color=BLUE, alpha=.22)
axes[0].set(xlim=(0, 25), title="População com cauda pesada")
sns.histplot(small_sample, bins=np.linspace(0, 3, 7), stat="density",
color=ORANGE, alpha=.62, ax=axes[1])
sns.kdeplot(small_sample, color=ORANGE, lw=3, cut=0, clip=(0, 3), ax=axes[1])
axes[1].set(xlim=(0, 3), title="Uma amostra de n = 20")
sns.histplot(larger_sample, bins=np.linspace(0, 15, 11), stat="density",
color=PURPLE, alpha=.58, ax=axes[2])
sns.kdeplot(larger_sample, color=PURPLE, lw=3, cut=0, clip=(0, 15), ax=axes[2])
axes[2].set(xlim=(0, 15), title="Outra amostra: n = 100")\[ Z=\frac{\bar{X}-\mu}{\sigma/\sqrt{n}} \]
\(Z\) mede quantos erros padrão a média amostral está distante de \(\mu\).
Valores próximos de zero são comuns; valores extremos são raros.
Área sob a curva representa a proporção esperada de observações.
x = np.linspace(-3.7, 3.7, 1200)
densidade = np.exp(-x**2 / 2) / np.sqrt(2 * np.pi)
fig, ax = plt.subplots(figsize=(10.5, 5.4))
ax.plot(x, densidade, color="#17324d", lw=3)
faixas = [
(-3, -2, "#6a4c93", "2,1%"),
(-2, -1, "#d95f02", "13,6%"),
(-1, 0, "#0f6b78", "34,1%"),
( 0, 1, "#0f6b78", "34,1%"),
( 1, 2, "#d95f02", "13,6%"),
( 2, 3, "#6a4c93", "2,1%"),
]
for inicio, fim, cor, rotulo in faixas:
trecho = (x >= inicio) & (x <= fim)
ax.fill_between(x[trecho], 0, densidade[trecho], color=cor, alpha=.82)A unidade “dias” desaparece após a padronização.
z_means = (means_50 - mu) / (sigma / np.sqrt(50))
x = np.linspace(-4, 4, 400)
normal_pdf = np.exp(-x**2 / 2) / np.sqrt(2 * np.pi)
plt.figure(figsize=(9, 5.2))
sns.histplot(z_means, bins=35, stat="density", color=BLUE, alpha=.6, label="simulação")
plt.plot(x, normal_pdf, color=ORANGE, lw=3, label="Normal padrão")
plt.xlabel("z = (x̄ − μ) / (σ/√n)")
plt.ylabel("Densidade")
plt.title("Padronizar revela a Normal padrão")
plt.legend()
finish("padronizacao-tcl.png")O padrão simulado coincide com \(\sigma/\sqrt{n}\).
ns = np.array([10, 20, 50, 100, 200, 500, 1000])
se_emp = np.array([sample_means(delivery, int(n), 1800).std(ddof=1) for n in ns])
se_theory = sigma / np.sqrt(ns)
plt.figure(figsize=(9, 5.2))
plt.plot(ns, se_emp, "o-", color=ORANGE, lw=3, label="simulado")
plt.plot(ns, se_theory, "--", color=BLUE, lw=3, label="σ/√n")
plt.xscale("log")
plt.xlabel("Tamanho da amostra (escala log)")
plt.ylabel("Erro padrão (dias)")
plt.title("O erro padrão cai com a raiz de n")
plt.legend()
finish("erro-padrao-entrega.png")Em repetições, 95% das regiões construídas pelo procedimento contêm a localização verdadeira.
Se aproximadamente 95% das médias satisfazem:
\[ -1{,}96<\frac{\bar{X}-\mu}{\sigma/\sqrt{n}}<1{,}96 \]
como \(\sigma/\sqrt{n}>0\), multiplicamos os três termos pelo erro padrão:
\[ -1{,}96\frac{\sigma}{\sqrt n}<\bar X-\mu <1{,}96\frac{\sigma}{\sqrt n}. \]
Subtraímos \(\bar X\), multiplicamos por \(-1\) e invertemos as desigualdades:
\[ \bar X+1{,}96\frac{\sigma}{\sqrt n}>\mu> \bar X-1{,}96\frac{\sigma}{\sqrt n}. \]
Em ordem crescente:
\[ \boxed{\bar X-1{,}96\frac{\sigma}{\sqrt n}<\mu< \bar X+1{,}96\frac{\sigma}{\sqrt n}} \]
Uma média amostral de 12,6 dias pode vir de:
O mesmo número pode carregar incertezas muito diferentes.
Como \(\sigma\) normalmente é desconhecido, usamos o desvio padrão amostral \(s\):
\[ \widehat{\operatorname{SE}}(\bar{X})=\frac{s}{\sqrt{n}} \]
Para amostras pequenas, a distribuição t substitui a Normal.
Para um intervalo bilateral:
| Confiança | Valor crítico Normal |
|---|---|
| 80% | 1,282 |
| 90% | 1,645 |
| 95% | 1,960 |
| 99% | 2,576 |
Mais confiança exige alcançar uma área maior da distribuição.
Uma aproximação de 95% é:
\[ \bar{x}\ \pm\ 1{,}96\frac{s}{\sqrt{n}} \]
ou:
\[ [\bar{x}-1{,}96\operatorname{SE},\ \bar{x}+1{,}96\operatorname{SE}] \]
\(\bar x\) é a média observada, \(s\) estima \(\sigma\), \(s/\sqrt n\) estima o erro padrão e \(1{,}96\) é o quantil \(0{,}975\) da Normal padrão. A forma geral é estimativa \(\pm\) valor crítico \(\times\) erro padrão.
Esta amostra produziu um intervalo de aproximadamente 10,8 a 14,4 dias.
n_ci = 100
sample_ci = rng.choice(delivery, n_ci, replace=False)
mean_ci = sample_ci.mean()
se_ci = sample_ci.std(ddof=1) / np.sqrt(n_ci)
lo, hi = mean_ci - 1.96 * se_ci, mean_ci + 1.96 * se_ci
plt.figure(figsize=(9, 3.8))
plt.errorbar(mean_ci, 0, xerr=1.96 * se_ci, fmt="o", color=BLUE, capsize=10, lw=4, ms=10)
plt.axvline(mu, color=ORANGE, lw=3, ls="--", label=f"μ = {mu:.1f}")
plt.yticks([])
plt.xlabel("Tempo médio de entrega (dias)")
plt.title(f"IC 95%: {lo:.1f} a {hi:.1f} dias")
plt.legend()
finish("intervalo-exemplo.png")Qual interpretação frequentista está correta?
Depois de calcular o intervalo, \(\mu\) está ou não está dentro dele.
Evite dizer:
Há 95% de probabilidade de \(\mu\) estar neste intervalo.
Na interpretação frequentista, a aleatoriedade está no procedimento.
Se repetíssemos a amostragem muitas vezes, cerca de 95% dos intervalos construídos pelo mesmo método cobririam \(\mu\).
Não esperamos que todos cubram o parâmetro.
Os intervalos laranja são erros esperados do procedimento.
intervals = []
for _ in range(100):
s = rng.choice(delivery, n_ci, replace=True)
m = s.mean()
se = s.std(ddof=1) / np.sqrt(n_ci)
intervals.append((m - 1.96 * se, m + 1.96 * se, m))
plt.figure(figsize=(9, 7))
for i, (lo_i, hi_i, m_i) in enumerate(intervals):
covered = lo_i <= mu <= hi_i
color = BLUE if covered else ORANGE
plt.plot([lo_i, hi_i], [i, i], color=color, lw=1.7)
plt.scatter(m_i, i, color=color, s=10)
plt.axvline(mu, color=INK, lw=2.5)
plt.xlabel("Tempo médio de entrega (dias)")
plt.ylabel("Amostras repetidas")
plt.title("95% é uma propriedade do procedimento")
finish("cobertura-intervalos.png")Não ganhamos cobertura gratuitamente.
levels = np.array([0.80, 0.90, 0.95, 0.99])
z_values = np.array([1.282, 1.645, 1.960, 2.576])
widths = 2 * z_values * sigma / np.sqrt(100)
plt.figure(figsize=(8.5, 5.2))
plt.bar(["80%", "90%", "95%", "99%"], widths, color=[LIGHT, BLUE, PURPLE, ORANGE])
plt.ylabel("Largura esperada do intervalo (dias)")
plt.xlabel("Nível de confiança")
plt.title("Mais confiança exige um intervalo mais largo")
finish("confianca-largura.png")Para 95% de confiança:
\[ ME=1{,}96\frac{s}{\sqrt{n}} \]
Ela diminui quando:
Dobrar a precisão exige aproximadamente quadruplicar \(n\).
need_half_day = (1.96 * sigma / .5) ** 2
ns_margin = np.linspace(20, need_half_day, 300)
margin = 1.96 * sigma / np.sqrt(ns_margin)
plt.figure(figsize=(9, 5.2))
plt.plot(ns_margin, margin, color=BLUE, lw=3)
for target in [1.0, .5]:
need = (1.96 * sigma / target) ** 2
plt.hlines(target, xmin=0, xmax=need, color=ORANGE, lw=1.5, ls="--")
plt.scatter(need, target, color=ORANGE, s=70)
plt.annotate(r"$n\approx 1400$", xy=(need_half_day, .5), xytext=(1120, .78),
arrowprops={"arrowstyle": "->", "color": INK})
plt.xlim(0, 1450)
plt.xlabel("Tamanho da amostra")
plt.ylabel("Margem de erro 95% (dias)")
plt.title("Planejar n começa pela precisão desejada")
finish("margem-tamanho-ate-1400.png")Se desejamos margem máxima \(m\):
\[ n\approx\left(\frac{z^*\sigma}{m}\right)^2 \]
Na prática, usamos:
Antes de reportar um intervalo, verifique:
Quando a fórmula analítica é difícil:
O bootstrap não corrige uma amostra enviesada.
Para uma proporção amostral \(\widehat{p}\):
\[ \widehat{\operatorname{SE}}(\widehat{p}) =\sqrt{\frac{\widehat{p}(1-\widehat{p})}{n}} \]
Aproximação Normal:
\[ \widehat{p}\pm1{,}96\widehat{\operatorname{SE}}(\widehat{p}) \]
Cada unidade fornece duas medidas relacionadas. Preservamos o par calculando uma única diferença:
\[ D_i=Y_i-X_i, \qquad \bar D=\frac{1}{n}\sum_{i=1}^nD_i. \]
O intervalo para a diferença média \(\mu_D\) é
\[ \bar D\ \pm\ t^*_{n-1}\frac{s_D}{\sqrt n}. \]
Por que usamos \(t^*\)?
Como \(\sigma_D\) é desconhecido, usamos \(s_D\). Sob diferenças Normais, \(T=(\bar D-\mu_D)/(s_D/\sqrt n)\) segue \(t_{n-1}\), que tem caudas mais pesadas.
Quando \(t\) se aproxima da Normal?
À medida que \(n\) cresce, \(t_{n-1}\to N(0,1)\). Para \(n>30\), \(t^*\) já costuma ser próximo de \(z^*\); em 95%, \(t^*_{29}=2{,}045\) e \(z^*=1{,}96\).
As duas medidas do par podem ser dependentes; as diferenças \(D_1,\ldots,D_n\) devem ser independentes entre pares.
Cinco pedidos têm prazo estimado \(X_i\) e tempo efetivo \(Y_i\), em dias. Definimos \(D_i=Y_i-X_i\).
| Pedido | \(X_i\) | \(Y_i\) | \(D_i\) |
|---|---|---|---|
| 1 | 12 | 10 | \(-2\) |
| 2 | 10 | 11 | \(1\) |
| 3 | 15 | 12 | \(-3\) |
| 4 | 9 | 8 | \(-1\) |
| 5 | 14 | 13 | \(-1\) |
\[ \bar D=-1{,}20,\qquad s_D=1{,}48, \qquad \operatorname{SE}(\bar D)=\frac{1{,}48}{\sqrt5}=0{,}66. \]
Como \(n=5\), usamos \(t^*_{4}=2{,}776\) para 95% de confiança:
\[ -1{,}20\pm2{,}776(0{,}66) =[-3{,}04;\ 0{,}64]\text{ dias}. \]
Interpretação
Em média, os pedidos chegaram 1,2 dia antes do previsto. Como o intervalo contém zero, os dados também são compatíveis com diferença média nula.
Uma amostra de 100 pedidos tem média de 13,1 dias e desvio padrão de 9 dias.
Um intervalo mostra valores plausíveis. Como avaliar uma afirmação específica?
Na Aula 11, construiremos testes de hipóteses, p-valores e poder estatístico.
olistbr/brazilian-ecommerce.ICD: Aula 10 — Teorema Central do Limitevoltar para a Aula