library(tidyverse) # manipulação e visualização de dados
library(fixest) # regressão com efeitos fixos (event study via feols)
library(did) # estimador de Callaway & Sant'Anna (2021)
library(modelsummary) # tabelas de comparação de modelos
library(gt) # tabelas com qualidade de publicação
library(gtExtras) # temas adicionais para gt
library(broom) # extrair coeficientes em formato tidyAula Prática - Diferença-em-Diferenças
1 Introdução
Nesta aula prática, replicaremos os principais resultados do artigo de Baker et al (2026), que trata de modelos de Diferença-em-Diferenças (DD) com múltiplos grupos de tratamento e múltiplos períodos. A aplicação empírica central explora a seguinte pergunta: a expansão do Medicaid via ACA (Affordable Care Act) reduziu a mortalidade adulta nos EUA?
O Medicaid é um programa público de saúde americano voltado para famílias de baixa renda. A ACA, aprovada em 2010, deu aos estados a opção de expandir a elegibilidade do Medicaid para adultos com renda de até 138% da linha de pobreza federal. Como a adesão foi opcional e ocorreu em momentos diferentes entre os estados, a expansão do Medicaid constitui um exemplo clássico de adoção escalonada (staggered adoption). Portanto, este caso constitui um bom exempo pedagógico para estudar as diferentes especificações DD.
A base de dados, disponível no repositório do artigo, contém informações anuais de 2009 a 2019 sobre condados americanos. A variável de interesse é a taxa bruta de mortalidade por 100.000 habitantes entre adultos de 20 a 64 anos.1
1 Os dados e o código original de replicação estão disponíveis em https://github.com/pedrohcgs/JEL-DiD. Aqui usamos uma versão adaptada para fins didáticos.
2 Pacotes e Dados
Os pacotes utilizados nesta aula são:2
2 O pacote principal a ser utilizado para DD é o pacote did. Mais informações podem ser encontrados na documentação do pacote. Observação: Caso durante a instalação do pacote did você obtenha uma mensagem de erro da instalação do pacote de dependência DRDID, instale a dependência utilizando o seguinte comando remotes::install_github("pedrohcgs/DRDID").
Os dados podem ser carregados diretamente do repositório público do artigo no GitHub. Se preferir, você também pode fazer download do arquivo csv e importá-lo no R como feito em aulas anteriores.3
3 Esses estados expandiram o Medicaid antes do ACA, portanto não servem de controle válido para o grupo de 2014.
url_dados <- "https://raw.githubusercontent.com/pedrohcgs/JEL-DiD/main/data/county_mortality_data.csv"
dados_raw <- read_csv(url_dados, show_col_types = FALSE) |>
# sigla do estado = 2 últimos caracteres do nome do condado
mutate(state = str_sub(county, nchar(county) - 1, nchar(county))) |>
filter(state != "DC") # excluir Distrito de Columbia
glimpse(dados_raw)Rows: 31,832
Columns: 22
$ state <chr> "AL", "AL", "AL", "AL", "AL", "AL", "AL", "A…
$ stfips <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
$ county <chr> "Autauga County, AL", "Autauga County, AL", …
$ county_code <dbl> 1001, 1001, 1001, 1001, 1001, 1001, 1001, 10…
$ year <dbl> 2009, 2010, 2011, 2012, 2013, 2014, 2015, 20…
$ year_code <dbl> 2009, 2010, 2011, 2012, 2013, 2014, 2015, 20…
$ deaths <dbl> 129, 135, 157, 131, 170, 156, 155, 150, 144,…
$ population_20_64 <dbl> 31271, 31875, 32538, 32324, 32315, 32448, 32…
$ crude_rate_20_64 <dbl> 412.5228, 423.5294, 482.5128, 405.2716, 526.…
$ population_total <dbl> 54135, 54571, 55267, 55514, 55246, 55395, 55…
$ population_20_64_hispanic <dbl> 650, 714, 752, 763, 806, 816, 841, 832, 897,…
$ population_20_64_female <dbl> 16099, 16378, 16642, 16558, 16616, 16632, 16…
$ population_20_64_white <dbl> 25151, 25642, 25890, 25598, 25462, 25487, 25…
$ unemployed <dbl> 2196, 2268, 2157, 1830, 1628, 1489, 1335, 13…
$ labor_force <dbl> 24660, 25749, 25845, 25762, 25783, 25639, 25…
$ unemp_rate <dbl> 0.08905109, 0.08808109, 0.08345908, 0.071034…
$ poverty_rate <dbl> 11.2, 11.9, 14.9, 12.7, 13.5, 13.1, 12.7, 13…
$ median_income <dbl> 53081, 53049, 48863, 51441, 51868, 54366, 56…
$ `Expansion Status` <chr> "Not Adopted", "Not Adopted", "Not Adopted",…
$ Description <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ yaca <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ maca <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
2.1 Dicionário de variáveis
| Variável | Descrição |
|---|---|
county_code |
Código FIPS do condado |
year |
Ano (2009–2019) |
yaca |
Ano em que o estado expandiu o Medicaid (NA = nunca expandiu) |
crude_rate_20_64 |
Taxa bruta de mortalidade por 100.000 hab. (20–64 anos) |
population_20_64 |
População de 20 a 64 anos |
unemp_rate |
Taxa de desemprego |
poverty_rate |
Taxa de pobreza |
median_income |
Renda mediana |
2.2 Preparação dos dados
Para o caso DD 2×2, restringimos a amostra a:
- Condados de estados que expandiram o Medicaid em 2014 (grupo tratado)
- Condados de estados que nunca expandiram até 2019 (grupo de controle)
- Excluímos estados que já haviam expandido antes de 2014 (DE, MA, NY, VT)4
4 Esses estados expandiram o Medicaid antes do ACA, portanto não servem de controle válido para o grupo de 2014.
dados_2x2 <- dados_raw |>
# excluir adotantes precoces (antes de 2014)
filter(!(state %in% c("DE", "MA", "NY", "VT"))) |>
# manter só: expansão 2014 ou nunca (e após 2019)
filter(yaca == 2014 | is.na(yaca) | yaca > 2019) |>
# variáveis de tratamento e período
mutate(
Treat = if_else(yaca == 2014 & !is.na(yaca), 1, 0),
Post = if_else(year >= 2014, 1, 0)
) |>
filter(!is.na(crude_rate_20_64)) |>
# manter apenas condados com painel completo (2009–2019)
group_by(county_code) |>
filter(n() == 11) |>
# peso = população do condado em 2013 (fixo ao longo do tempo)
mutate(peso = population_20_64[year == 2013]) |>
ungroup()
# tamanho dos grupos
dados_2x2 |>
filter(year == 2013) |>
count(Treat) |>
mutate(Grupo = if_else(Treat == 1, "Expansão 2014", "Não-expansão"))# A tibble: 2 × 3
Treat n Grupo
<dbl> <int> <chr>
1 0 1223 Não-expansão
2 1 978 Expansão 2014
3 Análise Exploratória
3.1 Grupos de adoção
A Figura 1 mostra a linha do tempo de adoção do Medicaid por grupo de estados. Cada estado escolheu se e quando expandir. Essa heterogeneidade é o que torna este exemplo interessante do ponto de vista pedagógico pois permite utilizar a literatura mais recente de DD.
cores_grupo <- c(
"2014 (978 condados)" = "#1245FF",
"2015 (171 condados)" = "#4470FF",
"2016 (93 condados)" = "#F5AC00",
"2019+ (140 condados)" = "#FF6B35",
"Nunca (1.222 cond.)" = "gray70"
)
tibble(
grupo = names(cores_grupo),
inicio = 2009,
trat = c(2014, 2015, 2016, 2019, 2020),
fim = 2019
) |>
ggplot() +
geom_segment(aes(x = inicio, xend = fim, y = grupo, yend = grupo),
color = "gray85", linewidth = 5) +
geom_segment(aes(x = trat, xend = fim, y = grupo, yend = grupo, color = grupo),
linewidth = 5, show.legend = FALSE) +
geom_vline(xintercept = 2014, linetype = "dashed", color = "#002555") +
scale_color_manual(values = cores_grupo) +
scale_x_continuous(breaks = 2009:2019) +
labs(x = NULL, y = NULL,
title = "Linha do Tempo de Adoção do Medicaid",
subtitle = "A cor indica o período de tratamento ativo de cada grupo") +
theme_minimal(base_size = 12) +
theme(panel.grid.minor = element_blank(), panel.grid.major.y = element_blank())
Cada grupo de tratamento define seus próprios parâmetros de interesse: o efeito médio do tratamento para o grupo \(g\) no período \(t\), denotado \(ATT(g,t)\). No caso mais simples (2×2), há apenas um grupo e dois períodos. Com adoção escalonada, precisamos estimar e combinar múltiplos \(ATT(g,t)\).
3.2 Tendências de mortalidade: grupo tratado vs. controle
A Figura 2 mostra a evolução da taxa de mortalidade para os dois grupos do DD 2×2.
dados_2x2 |>
mutate(Grupo = if_else(Treat == 1, "Expansão 2014", "Não-expansão")) |>
group_by(Grupo, year) |>
summarize(mort = weighted.mean(crude_rate_20_64, peso), .groups = "drop") |>
ggplot(aes(x = year, y = mort, color = Grupo)) +
geom_line(linewidth = 1) +
geom_point(size = 2) +
geom_vline(xintercept = 2013.5, linetype = "dashed", color = "gray50") +
annotate("text", x = 2013.3, y = 440, label = "Expansão →", hjust = 1,
size = 3.5, color = "gray40") +
scale_color_manual(values = c("Expansão 2014" = "#1565C0", "Não-expansão" = "#C62828")) +
scale_x_continuous(breaks = 2009:2019) +
labs(x = NULL, y = "Taxa de mortalidade (por 100.000)", color = NULL,
title = "Tendências de Mortalidade por Grupo",
subtitle = "Média ponderada pela população — 2009 a 2019") +
theme_minimal(base_size = 13) +
theme(legend.position = "top")
As duas séries apresentam trajetórias visualmente paralelas antes de 2014, o que apoia (mas não prova!) a hipótese de tendências paralelas. Após 2014, a mortalidade no grupo de expansão parece cair mais rapidamente.
4 DD 2×2: o caso mais simples
4.1 As quatro médias do estimador DD
Restringindo a análise a 2013 (antes) e 2014 (depois), a Tabela 1 apresenta as quatro médias necessárias para o estimador DD e o cálculo do \(\widehat{ATT}\).
dados_curto <- dados_2x2 |> filter(year %in% c(2013, 2014))
medias <- dados_curto |>
mutate(Grupo = if_else(Treat == 1, "Expansão 2014", "Não-expansão")) |>
group_by(Grupo, year) |>
summarize(media = mean(crude_rate_20_64), .groups = "drop") |>
pivot_wider(names_from = year, values_from = media, names_prefix = "ano_") |>
mutate(Tendência = ano_2014 - ano_2013)
# linha DiD
did_row <- tibble(
Grupo = "DiD (Tendência: Expansão − Controle)",
ano_2013 = diff(medias$ano_2013),
ano_2014 = diff(medias$ano_2014),
Tendência = diff(medias$Tendência)
)
bind_rows(medias, did_row) |>
rename(`2013 (antes)` = ano_2013, `2014 (depois)` = ano_2014) |>
gt() |>
tab_header(
title = md("**Estimador DD 2×2 — Medicaid e Mortalidade**"),
subtitle = "Taxa de mortalidade bruta por 100.000 hab. (20–64 anos)"
) |>
fmt_number(columns = 2:4, decimals = 1) |>
tab_style(style = cell_text(weight = "bold"), locations = cells_column_labels()) |>
tab_style(
style = cell_fill(color = "#EBF5FB"),
locations = cells_body(rows = str_starts(Grupo, "DiD"))
) |>
tab_style(
style = cell_text(style = "italic"),
locations = cells_body(rows = str_starts(Grupo, "DiD"))
) |>
tab_source_note("Médias simples (não ponderadas). A coluna 'Tendência' é a diferença 2014 − 2013.") |>
opt_stylize(style = 6, color = "gray")| Estimador DD 2×2 — Medicaid e Mortalidade | |||
|---|---|---|---|
| Taxa de mortalidade bruta por 100.000 hab. (20–64 anos) | |||
| Grupo | 2013 (antes) | 2014 (depois) | Tendência |
| Expansão 2014 | 419.2 | 428.5 | 9.3 |
| Não-expansão | 474.4 | 483.4 | 9.0 |
| DiD (Tendência: Expansão − Controle) | 55.2 | 54.9 | −0.3 |
| Médias simples (não ponderadas). A coluna 'Tendência' é a diferença 2014 − 2013. | |||
Leitura: o \(\widehat{ATT}\) é a diferença das tendências. Se negativo, indica que a mortalidade nos condados em que houve expansão caiu mais (ou subiu menos) do que nos condados de controle entre 2013 e 2014.
4.2 Recuperando o DD via regressão
O estimador DD pode ser obtido por três especificações de regressão equivalentes.
# (1) Sem efeitos fixos: Treat × Post com constante
mod1 <- feols(crude_rate_20_64 ~ Treat * Post,
data = dados_curto, cluster = ~county_code)
# (2) TWFE: Treat:Post com efeitos fixos de condado e ano
mod2 <- feols(crude_rate_20_64 ~ Treat:Post | county_code + year,
data = dados_curto, cluster = ~county_code)
# (3) Em primeiras diferenças: Δmort ~ Treat
dados_fd <- dados_curto |>
group_by(county_code, Treat) |>
summarize(
delta = crude_rate_20_64[year == 2014] - crude_rate_20_64[year == 2013],
.groups = "drop"
)
mod3 <- feols(delta ~ Treat, data = dados_fd, cluster = ~county_code)modelsummary(
list("(1) Sem EF" = mod1, "(2) TWFE" = mod2, "(3) 1ª Diferença" = mod3),
coef_map = c(
"Treat:Post" = "Expansão × Pós (DiD)",
"Treat" = "Expansão 2014",
"Post" = "Pós (2014)",
"(Intercept)" = "Constante"
),
fmt = 1,
stars = c("*" = 0.05, "**" = 0.01),
gof_map = list(list(raw = "nobs", clean = "Observações", fmt = 0)),
notes = "Erros-padrão clusterizados no nível do condado. * p < 0,05; ** p < 0,01.",
output = "gt"
) |>
tab_header(
title = md("**DD 2×2 via Regressão**"),
subtitle = "Variável dependente: taxa de mortalidade (por 100.000 hab.)"
) |>
tab_style(style = cell_text(weight = "bold"), locations = cells_column_labels()) |>
opt_stylize(style = 6, color = "gray")| DD 2×2 via Regressão | |||
|---|---|---|---|
| Variável dependente: taxa de mortalidade (por 100.000 hab.) | |||
| (1) Sem EF | (2) TWFE | (3) 1ª Diferença | |
| Expansão × Pós (DiD) | 0.3 | 0.3 | |
| (3.7) | (3.7) | ||
| Expansão 2014 | -55.2** | 0.3 | |
| (6.3) | (3.7) | ||
| Pós (2014) | 9.0** | ||
| (2.6) | |||
| Constante | 474.4** | 9.0** | |
| (4.3) | (2.6) | ||
| Observações | 4402 | 4402 | 2201 |
| * p < 0.05, ** p < 0.01 | |||
| Erros-padrão clusterizados no nível do condado. * p < 0,05; ** p < 0,01. | |||
As três especificações produzem o mesmo estimador DD. Esse resultado ilustra que o TWFE com dois grupos e dois períodos é simplesmente um jeito diferente de calcular a diferença das diferenças.
5 Event Study: DD com Múltiplos Períodos
5.1 Motivação
O event study extende o DD básico para múltiplos períodos, estimando o efeito do tratamento em cada período ao redor do evento de tratamento. Isso nos permite:
Verificar pré-tendências: se os grupos eram realmente paralelos antes do tratamento, os coeficientes estimados para \(k < -1\) devem ser próximos de zero.
Analisar a dinâmica do efeito: o efeito cresce, decai ou se estabiliza com o tempo?
A regressão de event study é uma TWFE com interações entre a dummy de tratamento e dummies de tempo relativo:
\[Y_{i,t} = \theta_t + \eta_i + \sum_{k \neq -1} \beta_k \cdot \mathbf{1}\{D_i = 1\} \cdot \mathbf{1}\{t - g = k\} + \varepsilon_{i,t}\]
O período \(k = -1\) (o ano imediatamente antes do tratamento) é a referência. Os \(\beta_k\) com \(k < -1\) testam pré-tendências; os \(\beta_k\) com \(k \geq 0\) estimam o efeito causal.
5.2 Estimação e gráfico
Usamos feols() com a função auxiliar i(), que cria automaticamente as interações e omite o período de referência.
dados_es <- dados_2x2 |>
mutate(
tempo_rel = if_else(Treat == 1, year - 2014L, 0L),
county_code = as.numeric(county_code)
)
es_twfe <- feols(
crude_rate_20_64 ~ i(tempo_rel, Treat, ref = -1) | county_code + year,
data = dados_es,
cluster = ~county_code,
weights = ~peso
)# extrair coeficientes e ICs em formato tidy
tidy(es_twfe, conf.int = TRUE) |>
filter(str_detect(term, "tempo_rel")) |>
mutate(k = as.integer(str_extract(term, "-?\\d+"))) |>
# adicionar período de referência (k = -1, efeito = 0 por construção)
bind_rows(tibble(k = -1L, estimate = 0, conf.low = 0, conf.high = 0)) |>
arrange(k) |>
ggplot(aes(x = k, y = estimate)) +
geom_ribbon(aes(ymin = conf.low, ymax = conf.high), alpha = 0.15, fill = "#1565C0") +
geom_line(color = "#1565C0", linewidth = 0.8) +
geom_point(color = "#1565C0", size = 2) +
geom_vline(xintercept = -0.5, linetype = "dashed", color = "gray50") +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
scale_x_continuous(breaks = -5:5) +
labs(
x = "Anos em relação à expansão (k = 0: ano da expansão)",
y = "Estimativa (mortalidade por 100.000)",
title = "Event Study — Expansão do Medicaid em 2014",
subtitle = "Referência: k = −1. Banda: IC 95%. TWFE com EF de condado e ano."
) +
theme_minimal(base_size = 13)
Leitura: coeficientes próximos de zero para \(k < -1\) apoiam a hipótese de tendências paralelas. Coeficientes negativos e significativos para \(k \geq 0\) indicam que a expansão reduziu a mortalidade.
6 Adoção Escalonada: o Problema do TWFE
6.1 Grupos de tratamento com diferentes momentos de adoção
Para usar toda a informação disponível, incluímos agora todos os grupos de tratamento (2014, 2015, 2016, 2019). A Figura 4 mostra as tendências de mortalidade por grupo.
dados_stag <- dados_raw |>
filter(!(state %in% c("DE", "MA", "NY", "VT"))) |>
mutate(
grupo_exp = case_when(
yaca == 2014 & !is.na(yaca) ~ "Expansão 2014",
yaca == 2015 & !is.na(yaca) ~ "Expansão 2015",
yaca == 2016 & !is.na(yaca) ~ "Expansão 2016",
!is.na(yaca) & yaca >= 2019 ~ "Expansão 2019+",
TRUE ~ "Nunca expandiu"
)
) |>
filter(!is.na(crude_rate_20_64)) |>
group_by(county_code) |>
filter(n() == 11) |>
mutate(peso = population_20_64[year == 2013]) |>
ungroup()
cores_stag <- c(
"Expansão 2014" = "#7C7189",
"Expansão 2015" = "#D04E59",
"Expansão 2016" = "#BC8E7D",
"Expansão 2019+" = "#2F3D70",
"Nunca expandiu" = "#CABEE9"
)
dados_stag |>
group_by(grupo_exp, year) |>
summarize(mort = weighted.mean(crude_rate_20_64, peso), .groups = "drop") |>
ggplot(aes(x = year, y = mort, color = grupo_exp)) +
geom_line(linewidth = 1) +
geom_point(size = 1.5) +
scale_color_manual(values = cores_stag) +
scale_x_continuous(breaks = 2009:2019) +
labs(
x = NULL,
y = "Taxa de mortalidade (por 100.000)",
color = NULL,
title = "Tendências de Mortalidade por Grupo de Expansão",
subtitle = "Média ponderada pela população — 2009 a 2019"
) +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom")
6.2 Por que o TWFE simples falha
A regressão TWFE convencional estima um único coeficiente para todo o efeito do tratamento:
\[Y_{i,t} = \theta_t + \eta_i + \beta^{TWFE} D_{i,t} + \varepsilon_{i,t}\]
O problema fundamental com adoção escalonada: grupos já tratados são usados como controle para outros grupos tratados. Por exemplo, ao estimar o efeito do grupo que expandiu em 2015, o TWFE compara esses condados com os que expandiram em 2014, mas esses já foram afetados pelo Medicaid!
O \(\beta^{TWFE}\) é uma média ponderada de comparações DiD 2×2, sendo que algumas dessas comparações usam grupos tratados como controle, com pesos que podem ser negativos.
dados_twfe <- dados_stag |>
mutate(
D_it = if_else(!is.na(yaca) & yaca <= 2019 & year >= yaca, 1, 0),
county_code = as.numeric(county_code)
)
twfe_simples <- feols(
crude_rate_20_64 ~ D_it | county_code + year,
data = dados_twfe,
cluster = ~county_code,
weights = ~peso
)
coeftest_twfe <- tidy(twfe_simples, conf.int = TRUE)
coeftest_twfe |>
select(term, estimate, std.error, conf.low, conf.high) |>
gt() |>
tab_header(
title = md("**TWFE Simples — Adoção Escalonada**"),
subtitle = "Variável dependente: taxa de mortalidade (por 100.000 hab.)"
) |>
fmt_number(columns = 2:5, decimals = 2) |>
cols_label(
term = "Variável",
estimate = "Estimativa",
std.error = "Erro-padrão",
conf.low = "IC 95% — inf.",
conf.high = "IC 95% — sup."
) |>
tab_style(style = cell_text(weight = "bold"), locations = cells_column_labels()) |>
tab_source_note("Erros-padrão clusterizados no nível do condado.") |>
opt_stylize(style = 6, color = "gray")| TWFE Simples — Adoção Escalonada | ||||
|---|---|---|---|---|
| Variável dependente: taxa de mortalidade (por 100.000 hab.) | ||||
| Variável | Estimativa | Erro-padrão | IC 95% — inf. | IC 95% — sup. |
| D_it | −0.94 | 2.18 | −5.20 | 3.33 |
| Erros-padrão clusterizados no nível do condado. | ||||
O coeficiente TWFE pode ser difícil de interpretar e em casos extremos pode ter o sinal errado, mesmo que todos os efeitos individuais \(ATT(g,t)\) sejam negativos.
7 Solução: estimar vários DD 2x2 e agregar
7.1 O estimador Callaway & Sant’Anna
A solução é estimar o \(ATT(g,t)\) separadamente para cada combinação grupo × período, usando apenas condados ainda-não-tratados como controle. Cada bloco é um DD 2×2 limpo.
Em seguida, os \(ATT(g,t)\) são agregados com pesos proporcionais ao tamanho de cada grupo tratado para obter uma estimativa resumida.
No R, o pacote did implementa esse procedimento em dois passos:
att_gt(): estima todos os \(ATT(g,t)\)aggte(): agrega em um parâmetro de interesse (efeito médio, curva de event study, etc.)
dados_cs <- dados_stag |>
mutate(
treat_year = if_else(!is.na(yaca) & yaca <= 2019, as.integer(yaca), 0L),
county_code = as.numeric(county_code)
)
# Passo 1: estimar ATT(g,t) para cada grupo × período
att_res <- att_gt(
yname = "crude_rate_20_64",
tname = "year",
idname = "county_code",
gname = "treat_year",
xformla = NULL, # sem covariáveis (por simplicidade)
data = dados_cs,
panel = TRUE,
control_group = "notyettreated", # controle = ainda-não-tratados
weightsname = "peso",
base_period = "universal",
bstrap = TRUE,
biters = 1000 # aumente para ≥ 5.000 em trabalhos finais
)
summary(att_res)
Call:
att_gt(yname = "crude_rate_20_64", tname = "year", idname = "county_code",
gname = "treat_year", xformla = NULL, data = dados_cs, panel = TRUE,
control_group = "notyettreated", weightsname = "peso", bstrap = TRUE,
biters = 1000, base_period = "universal")
Reference: Callaway, Brantly and Pedro H.C. Sant'Anna. "Difference-in-Differences with Multiple Time Periods." Journal of Econometrics, Vol. 225, No. 2, pp. 200-230, 2021. <https://doi.org/10.1016/j.jeconom.2020.12.001>, <https://arxiv.org/abs/1803.09015>
Group-Time Average Treatment Effects:
Group Time ATT(g,t) Std. Error [95% Simult. Conf. Band]
2014 2009 6.8315 3.9261 -4.7398 18.4028
2014 2010 5.2072 3.0115 -3.6687 14.0830
2014 2011 1.8417 3.7960 -9.3461 13.0295
2014 2012 4.1062 2.5118 -3.2969 11.5092
2014 2013 0.0000 NA NA NA
2014 2014 -2.5956 1.9185 -8.2499 3.0587
2014 2015 -5.3160 3.4719 -15.5485 4.9166
2014 2016 -6.7844 4.9406 -21.3458 7.7771
2014 2017 -12.6162 5.0821 -27.5947 2.3624
2014 2018 -10.9162 5.5168 -27.1758 5.3435
2015 2009 -3.2412 7.0695 -24.0770 17.5947
2015 2010 0.4562 5.3584 -15.3365 16.2489
2015 2011 11.3396 6.4804 -7.7601 30.4393
2015 2012 3.3768 4.8916 -11.0400 17.7936
2015 2013 0.7882 3.8743 -10.6306 12.2069
2015 2014 0.0000 NA NA NA
2015 2015 2.4508 4.6018 -11.1119 16.0136
2015 2016 9.0182 4.4034 -3.9598 21.9962
2015 2017 10.2104 5.5865 -6.2547 26.6755
2015 2018 1.4771 4.9506 -13.1137 16.0680
2016 2009 -4.5634 10.1014 -34.3351 25.2083
2016 2010 -9.4189 8.3863 -34.1358 15.2980
2016 2011 -16.4768 9.9734 -45.8714 12.9177
2016 2012 -7.4918 8.0710 -31.2793 16.2957
2016 2013 5.3448 6.7825 -14.6451 25.3348
2016 2014 2.3486 8.7004 -23.2939 27.9911
2016 2015 0.0000 NA NA NA
2016 2016 -8.3063 8.5632 -33.5444 16.9319
2016 2017 -9.0667 7.3754 -30.8040 12.6706
2016 2018 -13.5186 7.4419 -35.4520 8.4147
---
Signif. codes: `*' confidence band does not cover 0
P-value for pre-test of parallel trends assumption: 0.00137
Control Group: Not Yet Treated, Anticipation Periods: 0
Estimation Method: Doubly Robust
7.2 Event study com adoção escalonada
Agregando os \(ATT(g,t)\) em relação ao momento da adoção do tratamento obtemos a curva de event study robusta à adoção escalonada (Figura 5).
# Passo 2a: agregar em tempo relativo ("dynamic") para o event study
agg_dinamico <- aggte(att_res, type = "dynamic", na.rm = TRUE, biters = 1000)
# extrair resultados e plotar com ggplot2
tidy(agg_dinamico, conf.int = TRUE) |>
filter(between(event.time, -5, 5)) |>
ggplot(aes(x = event.time, y = estimate)) +
geom_ribbon(aes(ymin = conf.low, ymax = conf.high), alpha = 0.15, fill = "#1565C0") +
geom_line(color = "#1565C0", linewidth = 0.8) +
geom_point(color = "#1565C0", size = 2) +
geom_vline(xintercept = -0.5, linetype = "dashed", color = "gray50") +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
scale_x_continuous(breaks = -5:5) +
labs(
x = "Anos em relação à expansão",
y = "Estimativa (mortalidade por 100.000)",
title = "Event Study — Callaway & Sant'Anna (2021)",
subtitle = "Adoção escalonada do Medicaid. Controle: ainda-não-tratados. Banda: IC 95%."
) +
theme_minimal(base_size = 13)
7.3 ATT médio (estimativa agregada)
Por fim, agregamos todos os \(ATT(g,t)\) pós-tratamento em um único número — o ATT médio — para resumir o efeito da expansão do Medicaid sobre a mortalidade.
# Passo 2b: agregar em um único número ("simple" = média ponderada)
agg_simples <- aggte(att_res, type = "simple", na.rm = TRUE, biters = 1000)
tibble(
Parâmetro = "ATT médio (todos os grupos e períodos pós-tratamento)",
Estimativa = agg_simples$overall.att,
`Erro-padrão` = agg_simples$overall.se,
`IC 95% — inf.` = agg_simples$overall.att - 1.96 * agg_simples$overall.se,
`IC 95% — sup.` = agg_simples$overall.att + 1.96 * agg_simples$overall.se
) |>
gt() |>
tab_header(
title = md("**ATT Médio — Medicaid e Mortalidade Adulta**"),
subtitle = "Sem covariáveis. Controle: condados ainda-não-tratados."
) |>
fmt_number(columns = 2:5, decimals = 2) |>
tab_style(style = cell_text(weight = "bold"), locations = cells_column_labels()) |>
tab_source_note(
md("O ATT médio é a média ponderada de todos os $ATT(g,t)$ pós-tratamento, com pesos proporcionais ao tamanho de cada grupo.")
) |>
opt_stylize(style = 6, color = "gray")| ATT Médio — Medicaid e Mortalidade Adulta | ||||
|---|---|---|---|---|
| Sem covariáveis. Controle: condados ainda-não-tratados. | ||||
| Parâmetro | Estimativa | Erro-padrão | IC 95% — inf. | IC 95% — sup. |
| ATT médio (todos os grupos e períodos pós-tratamento) | −6.37 | 3.44 | −13.12 | 0.38 |
| O ATT médio é a média ponderada de todos os \(ATT(g,t)\) pós-tratamento, com pesos proporcionais ao tamanho de cada grupo. | ||||
Interpretação: o ATT médio indica a variação estimada na taxa de mortalidade (por 100.000 hab., adultos 20–64 anos) associada à expansão do Medicaid, em média para todos os grupos e anos pós-tratamento.
8 Atividade (não há necessidade de entregar)
Leia a aula teórica (lec-15) e responda às perguntas abaixo:
Hipótese de tendências paralelas: No event study da Figura 3 (apenas grupos de 2014 vs. nunca tratadas), os coeficientes pré-tratamento são próximos de zero? O balanceamento de covariáveis (discutido em sala) e as tendências da Figura 2 são consistentes com essa leitura? Argumente.
Problema do TWFE: Explique com suas palavras por que o estimador TWFE pode ser enganoso em designs com adoção escalonada. O que o TWFE usa como “controle” ao estimar o efeito do grupo que expandiu em 2015, e por que isso é problemático?
Comparação TWFE vs. CS: Compare a estimativa do TWFE simples com o ATT médio de Callaway & Sant’Anna. As duas diferem em magnitude e/ou sinal? O que pode explicar a diferença?
Interpretação econômica: Com base nos resultados, a expansão do Medicaid reduziu a mortalidade adulta? A estimativa é grande em termos econômicos? Para contextualizar, compare com a taxa média de mortalidade na amostra em 2013 (você pode calculá-la diretamente dos dados).