Aula 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").

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 tidy

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())
Figura 1: Linha do tempo de adoção do Medicaid por grupo de estados

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")
Figura 2: Tendências de mortalidade: expansão de 2014 vs. não-expansores

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")
Tabela 1: Estimador DD 2×2 — taxa de mortalidade por 100.000 hab.
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")
Tabela 2: DD 2×2 via regressão — três especificações equivalentes
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:

  1. 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.

  2. 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)
Figura 3: Event study: efeito da expansão do Medicaid (2014) sobre a mortalidade adulta

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")
Figura 4: Tendências de mortalidade por grupo de adoção do Medicaid

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:

  1. att_gt(): estima todos os \(ATT(g,t)\)
  2. 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)
Figura 5: Event study com adoção escalonada — estimador de Callaway & Sant’Anna (2021)

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")
Tabela 3: ATT médio — estimador de Callaway & Sant’Anna (2021)
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:

  1. 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.

  2. 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?

  3. 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?

  4. 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).