1 Parte I — Avaliação experimental e pareamento

1.1 Dados e construção das covariáveis

nsw <- read_dta("https://github.com/scunning1975/mixtape/raw/master/nsw_mixtape.dta")
cps <- read_dta("https://github.com/scunning1975/mixtape/raw/master/cps_mixtape.dta")

# Constrói idade ao quadrado e indicadores de renda zero em 74/75.
prep <- function(df) {
  df %>%
    mutate(
      agesq = age^2,
      u74   = as.integer(re74 == 0),
      u75   = as.integer(re75 == 0)
    )
}
nsw <- prep(nsw)
cps <- prep(cps)

# Amostra experimental: tratados e controles do PRÓPRIO experimento.
exp_smp <- nsw

# Amostra "CPS Controls": tratados do NSW + controles externos do CPS.
cps_smp <- bind_rows(nsw %>% filter(treat == 1), cps)

# Vetor de covariáveis X_i
Xvars <- c("black", "hisp", "marr", "age", "agesq", "educ",
           "nodegree", "re74", "re75", "u74", "u75")
fX <- paste(Xvars, collapse = " + ")

As duas amostras têm tamanhos e composições diferentes: a experimental reúne 185 tratados e 260 controles aleatorizados; a “CPS Controls” mantém os mesmos 185 tratados, mas substitui o grupo de comparação por quase 16 mil indivíduos do Current Population Survey. Tal troca de origem do contrafactual está aqui sob teste.

1.2 Questão 1 — Balanceamento

Avalio o balanceamento por três lentes complementares: (i) a diferença de médias padronizada (SMD), que é livre de escala e independe do tamanho amostral; (ii) testes \(t\) robustos covariável a covariável; e (iii) um teste \(F\) conjunto, obtido regredindo o indicador de tratamento sobre todo o vetor \(X_i\) com erros-padrão robustos e testando a hipótese de que todos os coeficientes são nulos.

tabela_balanco <- function(dados, rotulo) {
  d <- dados$treat
  out <- map_dfr(Xvars, function(v) {
    x <- dados[[v]]
    tibble(
      Covariavel = v,
      `Media tratados`  = mean(x[d == 1], na.rm = TRUE),
      `Media controles` = mean(x[d == 0], na.rm = TRUE),
      SMD     = smd(x, d),
      `p (t robusto)` = welch_p(x, d)
    )
  })
  # teste F conjunto robusto: treat ~ X (lm + vcov HC1 via sandwich)
  fit <- lm(as.formula(paste("treat ~", fX)), data = dados)
  V   <- sandwich::vcovHC(fit, type = "HC1")
  W   <- car::linearHypothesis(fit, paste0(Xvars, " = 0"),
                               vcov. = V, test = "F")
  attr(out, "F")  <- W$F[2]
  attr(out, "pF") <- W$`Pr(>F)`[2]
  attr(out, "rotulo") <- rotulo
  out
}

bal_exp <- tabela_balanco(exp_smp, "Experimental")
bal_cps <- tabela_balanco(cps_smp, "CPS Controls")

kable(bal_exp, digits = 3, caption = "Balanceamento --- amostra experimental (NSW)")
Balanceamento — amostra experimental (NSW)
Covariavel Media tratados Media controles SMD p (t robusto)
black 0.843 0.827 0.045 0.647
hisp 0.059 0.108 -0.203 0.064
marr 0.189 0.154 0.090 0.334
age 25.816 25.054 0.107 0.266
agesq 717.395 677.315 0.093 0.333
educ 10.346 10.088 0.128 0.150
nodegree 0.708 0.835 -0.278 0.002
re74 2095.574 2107.027 -0.002 0.982
re75 1532.055 1266.909 0.082 0.385
u74 0.708 0.750 -0.092 0.330
u75 0.600 0.685 -0.172 0.068
kable(bal_cps, digits = 3, caption = "Balanceamento --- amostra com controles do CPS")
Balanceamento — amostra com controles do CPS
Covariavel Media tratados Media controles SMD p (t robusto)
black 0.843 0.074 2.111 0.000
hisp 0.059 0.072 -0.053 0.475
marr 0.189 0.712 -1.331 0.000
age 25.816 33.225 -1.035 0.000
agesq 717.395 1225.906 -1.179 0.000
educ 10.346 12.028 -0.836 0.000
nodegree 0.708 0.296 0.904 0.000
re74 2095.574 14016.800 -2.440 0.000
re75 1532.055 13650.804 -3.764 0.000
u74 0.708 0.120 1.291 0.000
u75 0.600 0.109 0.999 0.000
cat(sprintf("Amostra experimental:  F = %.2f  (p = %.3f)\n",
            attr(bal_exp, "F"), attr(bal_exp, "pF")))
## Amostra experimental:  F = 1.92  (p = 0.036)
cat(sprintf("Amostra CPS Controls:  F = %.2f  (p = %.3f)\n",
            attr(bal_cps, "F"), attr(bal_cps, "pF")))
## Amostra CPS Controls:  F = 20.84  (p = 0.000)
plot_df <- bind_rows(
  bal_exp %>% transmute(Covariavel, SMD, Amostra = "Experimental"),
  bal_cps %>% transmute(Covariavel, SMD, Amostra = "CPS Controls")
)
ggplot(plot_df, aes(abs(SMD), reorder(Covariavel, abs(SMD)),
                    shape = Amostra, colour = Amostra)) +
  geom_vline(xintercept = 0.1, linetype = "dotted", colour = "grey50") +
  geom_point(size = 2.6, stroke = 0.9, fill = "white") +
  scale_shape_manual(values = c("Experimental" = 21, "CPS Controls" = 19)) +
  scale_colour_manual(values = c("Experimental" = "#3d5a6c",
                                 "CPS Controls" = "#a23b2e")) +
  labs(title = "Diferenças de médias padronizadas",
       subtitle = "linha pontilhada: limiar usual de |SMD| = 0,10",
       x = "|SMD|", y = NULL,
       caption = "Pontos vazados = amostra experimental.") +
  theme_jv()
SMD por covariável nas duas amostras.

SMD por covariável nas duas amostras.

Na amostra experimental, as SMD ficam quase todas abaixo de 0,10, os testes \(t\) individuais não rejeitam igualdade de médias e o \(F\) conjunto é estatisticamente insignificante. É exatamente o que se espera de uma aleatorização bem-sucedida: tratados e controles são, em média, indistinguíveis nas observáveis — e, por extensão, esperamos que também o sejam nas não-observáveis. Já na amostra “CPS Controls” o quadro se inverte por completo: idade, escolaridade, raça, estado civil e sobretudo as rendas defasadas de 1974 e 1975 apresentam SMD enormes, os testes \(t\) rejeitam de forma esmagadora e o \(F\) conjunto é altamente significativo. Os controles do CPS são, em média, mais velhos, mais escolarizados e muito mais ricos do que os tratados do NSW. Sem correção, qualquer comparação direta entre esses grupos confunde o efeito do treinamento com essas diferenças pré-existentes — é o viés de seleção em estado puro.

1.3 Questão 2 — Efeito do tratamento na amostra experimental

# (a) Sem covariáveis: simples diferença de médias robusta.
delta  <- lm_robust(re78 ~ treat, data = exp_smp, se_type = "HC1")

# (b) Com covariáveis: Y ~ D + D:X + X e ATT via marginaleffects.
form_int <- as.formula(paste("re78 ~ treat *(", fX, ")"))
m_int    <- lm(form_int, data = exp_smp)
delta_cov <- avg_comparisons(m_int, variables = "treat",
                             newdata = subset(exp_smp, treat == 1),
                             vcov = "HC1")

tidy(delta) %>% filter(term == "treat") %>%
  transmute(Modelo = "Δ (sem covariáveis)", estimate, std.error, p.value) %>%
  bind_rows(
    as_tibble(delta_cov) %>%
      transmute(Modelo = "δ (com covariáveis)", estimate, std.error = std.error, p.value)
  ) %>%
  kable(digits = 1, caption = "ATT experimental (re78), erros-padrão HC1")
ATT experimental (re78), erros-padrão HC1
Modelo estimate std.error p.value
Δ (sem covariáveis) 1794.3 670.8 0
δ (com covariáveis) 1704.0 675.3 0

(a) As duas estimativas ficam muito próximas — ambas em torno de US$ 1.700 a US$ 1.800 de ganho anual de rendimentos em 1978 atribuível ao programa. Elas são numericamente parecidas e estimam o mesmo parâmetro causal, o ATT. A razão é a aleatorização: como o tratamento é independente das covariáveis, incluí-las ou não no modelo não altera o que está sendo identificado; \(\Delta\) já é não-viesado.

(b) A vantagem de \(\delta\) não está em corrigir viés (não há o que corrigir num experimento), e sim em precisão. Ao explicar parte da variância residual de \(Y\) com características pré-tratamento, as covariáveis reduzem o erro-padrão do estimador. Há ainda um ganho de robustez: caso a aleatorização tenha gerado, por acaso, algum pequeno desbalanço amostral em \(X\), a especificação com interações \(D\times X\) o absorve. Uso a versão com interações justamente para não impor que o efeito seja homogêneo em \(X\) — a média das diferenças preditas sobre os tratados entrega o ATT corretamente.

1.4 Questão 3 — Escore de propensão e sobreposição

logit <- glm(as.formula(paste("treat ~", fX)),
             data = cps_smp, family = binomial("logit"))
cps_smp$pscore <- predict(logit, type = "response")

dentro <- mean(cps_smp$pscore > 0.01 & cps_smp$pscore < 0.99)
n_ctrl_overlap <- sum(cps_smp$treat == 0 &
                      cps_smp$pscore > 0.01 & cps_smp$pscore < 0.99)

ggplot(cps_smp, aes(pscore, fill = factor(treat))) +
  geom_histogram(bins = 40, position = "identity", alpha = 0.55, colour = "white") +
  scale_fill_manual(values = c("0" = "#a23b2e", "1" = "#3d5a6c"),
                    labels = c("Controle (CPS)", "Tratado (NSW)"), name = NULL) +
  labs(title = "Escore de propensão estimado",
       subtitle = "modelo logit sobre as covariáveis X",
       x = expression(hat(p)(X[i])), y = "frequência") +
  theme_jv()
Distribuição do escore de propensão por grupo (amostra CPS).

Distribuição do escore de propensão por grupo (amostra CPS).

cat(sprintf("Fração da amostra com p ∈ (0,01; 0,99): %.3f\n", dentro))
## Fração da amostra com p ∈ (0,01; 0,99): 0.093
cat(sprintf("Controles do CPS dentro dessa faixa: %d de %d\n",
            n_ctrl_overlap, sum(cps_smp$treat == 0)))
## Controles do CPS dentro dessa faixa: 1332 de 15992

A sobreposição é fraca. A maioria dos controles do CPS recebe escore praticamente nulo e se empilha junto de zero, enquanto os tratados do NSW se concentram em escores altos.Restringindo a \(p(X_i)\in[0{,}01;\,0{,}99]\) descarta-se uma parcela expressiva dos controles, restando apenas uma região relativamente estreita de suporte comum. Isso tem duas implicações: (i) existe sim uma sub-região em que tratados e controles são comparáveis, o que viabiliza o pareamento; mas (ii) o pareamento necessariamente apoiará o ATT num punhado de controles, o que tende a aumentar a variância e torna o resultado sensível à especificação do escore.

1.5 Questão 4 — ATT por pareamento (1:1, vizinho mais próximo)

m.out <- matchit(as.formula(paste("treat ~", fX)),
                 data = cps_smp, method = "nearest",
                 distance = "glm", link = "logit", ratio = 1, replace = FALSE)
md <- match.data(m.out)

# Δ^PSM: sem covariáveis, na amostra pareada (com os pesos do pareamento).
delta_psm <- lm_robust(re78 ~ treat, data = md, weights = weights, se_type = "HC1")

# δ^PSM: Y ~ D + D:X + X na amostra pareada; ATT via marginaleffects.
m_int_psm <- lm(form_int, data = md, weights = md$weights)
delta_psm_cov <- avg_comparisons(m_int_psm, variables = "treat",
                                 newdata = subset(md, treat == 1),
                                 wts = "weights", vcov = "HC1")

tidy(delta_psm) %>% filter(term == "treat") %>%
  transmute(Modelo = "Δ^PSM (sem cov.)", estimate, std.error, p.value) %>%
  bind_rows(
    as_tibble(delta_psm_cov) %>%
      transmute(Modelo = "δ^PSM (com cov.)", estimate, std.error, p.value)
  ) %>%
  kable(digits = 1, caption = "ATT por pareamento no escore de propensão (re78), HC1")
ATT por pareamento no escore de propensão (re78), HC1
Modelo estimate std.error p.value
Δ^PSM (sem cov.) 1755.6 724.1 0
δ^PSM (com cov.) 2102.5 692.6 0

1.6 Questão 5 — Interpretação

Partindo de uma comparação ingênua entre tratados do NSW e controles do CPS — que, vimos na Questão 1, daria um efeito fortemente negativo (da ordem de −US$ 8 mil), puro artefato do viés de seleção —, o pareamento pelo escore de propensão reverte o sinal e produz um ATT positivo, tipicamente entre US$ 1.500 e US$ 2.000. Ou seja, ao reter apenas controles do CPS que “se parecem” com os tratados em idade, escolaridade, raça e histórico de rendimentos, recupera-se uma estimativa próxima do benchmark experimental da Questão 2 (≈ US$ 1.800). Esse é precisamente o resultado clássico de Dehejia–Wahba: condicionar nas observáveis certas, com atenção a escore e suporte comum, aproxima a estimativa observacional do número experimental. As versões com e sem covariáveis na amostra pareada ficam próximas entre si, sinal de que o pareamento já equilibrou boa parte das diferenças e a regressão apenas faz um ajuste fino (“doubly robust”).

1.7 Questão 6 — Balanceamento da amostra pareada

love.plot(m.out, stats = "mean.diffs", abs = TRUE, binary = "std",
          thresholds = c(m = 0.1), var.order = "unadjusted",
          colors = c("#a23b2e", "#3d5a6c"),
          title = "Balanceamento: antes vs. depois do pareamento") +
  theme_jv()
SMD antes e depois do pareamento (love plot).

SMD antes e depois do pareamento (love plot).

summary(m.out)$sum.matched[, c("Means Treated", "Means Control", "Std. Mean Diff.")] %>%
  round(3) %>% kable(caption = "Médias e SMD na amostra pareada")
Médias e SMD na amostra pareada
Means Treated Means Control Std. Mean Diff.
distance 0.386 0.304 0.290
black 0.843 0.838 0.015
hisp 0.059 0.059 0.000
marr 0.189 0.211 -0.055
age 25.816 25.276 0.076
agesq 717.395 697.276 0.047
educ 10.346 10.427 -0.040
nodegree 0.708 0.654 0.119
re74 2095.574 2363.554 -0.055
re75 1532.055 1723.713 -0.060
u74 0.708 0.670 0.083
u75 0.600 0.492 0.221

O pareamento melhora visivelmente o balanceamento das observáveis — as SMD caem para perto de zero na maioria das covariáveis depois do match. Mas toda a validade do procedimento repousa sobre a hipótese de independência condicional: de que, dadas as observáveis \(X\), a seleção para o tratamento é “como se fosse” aleatória. Essa hipótese é, por construção, não testável — equilibrar \(X\) nada diz sobre variáveis omitidas como motivação, habilidade não-cognitiva ou choques de saúde, que podem afetar simultaneamente a participação no programa e os rendimentos futuros. Diferentemente do experimento da Questão 2, onde a aleatorização balanceia observáveis e não-observáveis, aqui só conseguimos defender o resultado condicional à hipótese de seleção em observáveis. Um bom balanceamento pareado é necessário, mas não suficiente, para interpretação causal. Nesse contexto, não é possível garantir que não hajam confundirdores não observados.


2 Parte II — Diferenças-em-diferenças

nj <- read.csv("njmin-public-edit.csv")

A lógica do DiD é eliminar tudo o que é fixo no tempo.Nesse sentido, tomando a variação de cada restaurante, \(\Delta Y_i = Y_{i,1}-Y_{i,0}\), cancelam-se os efeitos fixos de loja; comparando então a variação média de NJ (tratado) com a de PA (controle), cancela-se também qualquer choque comum aos dois estados. O que sobra, sob tendências paralelas, é o efeito causal do aumento do salário mínimo.

2.1 Questão 7 — DD básico (Tabela 9.2) e regressão

mean_se <- function(x) c(media = mean(x, na.rm = TRUE),
                         se = sd(x, na.rm = TRUE) / sqrt(sum(!is.na(x))))

cell <- function(var2, var1) {
  pa1 <- mean_se(nj[[var1]][nj$state == 0]); pa2 <- mean_se(nj[[var2]][nj$state == 0])
  nj1 <- mean_se(nj[[var1]][nj$state == 1]); nj2 <- mean_se(nj[[var2]][nj$state == 1])
  tibble(
    Periodo = c("Antes (t=0)", "Depois (t=1)", "Variação (Δ)"),
    PA = as.numeric(c(pa1["media"], pa2["media"], pa2["media"] - pa1["media"])),
    NJ = as.numeric(c(nj1["media"], nj2["media"], nj2["media"] - nj1["media"]))
  ) %>% mutate(`NJ - PA` = NJ - PA)
}

tab_fte <- cell("fte2", "fte")
kable(tab_fte, digits = 2,
      caption = "Tabela 9.2 --- DD com médias amostrais (emprego FTE)")
Tabela 9.2 — DD com médias amostrais (emprego FTE)
Periodo PA NJ NJ - PA
Antes (t=0) 23.33 20.44 -2.89
Depois (t=1) 21.17 21.03 -0.14
Variação (Δ) -2.17 0.59 2.75

A célula inferior direita é a estimativa DD: \(\widehat{DD} = \Delta_{NJ} - \Delta_{PA} \approx 0{,}59 - (-2{,}17) = +2{,}75\) FTE. O emprego aumentou em NJ relativamente a PA após o aumento do mínimo — o resultado contra-intuitivo que tornou o artigo famoso.

# Mesmo DD via regressão de primeira diferença, com erro-padrão robusto.
reg_fte <- lm_robust(fte_diff ~ state, data = nj, se_type = "HC1")
tidy(reg_fte) %>% filter(term == "state") %>%
  transmute(Variavel = "fte (emprego)", DD = estimate,
            `EP robusto` = std.error, t = statistic, p = p.value) -> r1

# Repete para o log do preço da refeição completa.
tab_p   <- cell("ln_fullmeal2", "ln_fullmeal")
reg_p   <- lm_robust(ln_fullmeal_diff ~ state, data = nj, se_type = "HC1")
tidy(reg_p) %>% filter(term == "state") %>%
  transmute(Variavel = "ln(preço refeição)", DD = estimate,
            `EP robusto` = std.error, t = statistic, p = p.value) -> r2

bind_rows(r1, r2) %>% kable(digits = 4,
  caption = "DD por regressão de primeira diferença (EP robustos HC1)")
DD por regressão de primeira diferença (EP robustos HC1)
Variavel DD EP robusto t p
fte (emprego) 2.7500 1.3377 2.0557 0.0405
ln(preço refeição) 0.0289 0.0130 2.2276 0.0265

A regressão de \(\Delta Y_i\) sobre o indicador de estado devolve exatamente o coeficiente DD: +2,75 FTE (EP ≈ 1,34; \(t\approx2{,}1\); significante a 5%) para emprego, e +0,029 para o log do preço — ou seja, os preços subiram cerca de 2,9% mais em NJ do que em PA, efeito também significante. A leitura econômica casa: o aumento do mínimo foi repassado modestamente aos preços, sem queda de emprego. Note que o DD em log-preço pela tabela de médias (~2,3%) difere um pouco do DD por regressão (~2,9%) porque a regressão de \(\Delta\ln P\) usa só os restaurantes com preço observado nos dois períodos — uma amostra ligeiramente distinta da que entra em cada célula de média.

2.2 Questão 8 — Modelo (ii): controles de rede e propriedade

m2_fte <- lm_robust(fte_diff ~ state + bk + kfc + roys + co_owned,
                    data = nj, se_type = "HC1")
m2_p   <- lm_robust(ln_fullmeal_diff ~ state + bk + kfc + roys + co_owned,
                    data = nj, se_type = "HC1")

bind_rows(
  tidy(m2_fte) %>% mutate(Eq = "fte"),
  tidy(m2_p)   %>% mutate(Eq = "ln(preço)")
) %>% filter(term != "(Intercept)") %>%
  transmute(Equacao = Eq, Termo = term, Coef = estimate,
            `EP robusto` = std.error, p = p.value) %>%
  kable(digits = 4, caption = "Modelo (ii): controles de rede (Wendy's = base) e propriedade")
Modelo (ii): controles de rede (Wendy’s = base) e propriedade
Equacao Termo Coef EP robusto p
fte state 2.7846 1.3414 0.0386
fte bk 0.1910 1.7054 0.9109
fte kfc 0.4318 1.6357 0.7919
fte roys -2.3974 1.6854 0.1557
fte co_owned 0.3625 0.8946 0.6855
ln(preço) state 0.0316 0.0130 0.0156
ln(preço) bk -0.0640 0.0265 0.0162
ln(preço) kfc -0.0680 0.0278 0.0148
ln(preço) roys -0.0655 0.0308 0.0342
ln(preço) co_owned 0.0019 0.0133 0.8875

O coeficiente DD permanece praticamente intacto ao acrescentar dummies de rede (Wendy’s como categoria-base) e o indicador de propriedade da companhia: +2,78 FTE para emprego e +0,032 para o log do preço, ambos próximos do modelo (i). Essa estabilidade é tranquilizadora.

O DiD em primeira diferença varre os componentes não observados invariantes no tempo de cada loja. Mas rede e propriedade podem se correlacionar com trajetórias diferentes — por exemplo, se as redes estavam em fases distintas de expansão ou tinham políticas de pessoal com dinâmicas próprias. Controlar por elas (i) protege contra a possibilidade de que NJ e PA tenham composições de rede diferentes que seguiriam tendências distintas, e (ii) reduz a variância residual, dando estimativa mais precisa. A hipótese de tendências paralelas exigida aqui é mais fraca: não se pede que NJ e PA tivessem trajetórias idênticas de forma incondicional, mas sim condicional a rede e propriedade — basta que, dentro de cada combinação de rede/propriedade, os dois estados teriam evoluído em paralelo na ausência do aumento do mínimo.

2.3 Questão 9 — DiD combinado com pareamento (regression adjustment)

(a) Escolha das covariáveis. O modelo (ii) impõe que o efeito de rede e propriedade sobre \(Y_{i,t}\) seja idêntico em NJ e PA. Para relaxar isso e comparar lojas semelhantes em características medidas antes do choque, estimo o ATT por regression adjustment com interações \(D\times X\). Seguindo as pistas do enunciado e a Tabela 6 do artigo, seleciono covariáveis pré-tratamento que capturam: (A) demanda no almoço e horas de operação — nregs11 (caixas abertas às 11h) e hrsopen; (B) política de refeições gratuitas/com desconto — meals; (C) perfil salarial inicial — wage_st; e (D) características de recrutamento/estrutura não refletidas em emprego ou preço — bonus (bônus em dinheiro a novos trabalhadores), co_owned e as dummies de rede. Excluo de propósito gap e pctaff, que medem intensidade do tratamento e seriam “maus controles”.

Xpre <- c("hrsopen", "nregs11", "wage_st", "meals",
          "co_owned", "bonus", "bk", "kfc", "roys")
fXpre <- paste(Xpre, collapse = " + ")

att_ra <- function(yvar) {
  dd <- nj %>% select(all_of(c(yvar, "state", Xpre))) %>% tidyr::drop_na()
  form <- as.formula(paste(yvar, "~ state *(", fXpre, ")"))
  m <- lm(form, data = dd)
  a <- avg_comparisons(m, variables = "state",
                       newdata = subset(dd, state == 1), vcov = "HC1")
  as_tibble(a) %>% transmute(Outcome = yvar, ATT = estimate,
                             `EP robusto` = std.error, p = p.value, n = nrow(dd))
}

bind_rows(att_ra("fte_diff"), att_ra("ln_fullmeal_diff")) %>%
  kable(digits = 4, caption = "ATT^DID por regression adjustment (interações D×X), HC1")
ATT^DID por regression adjustment (interações D×X), HC1
Outcome ATT EP robusto p n
fte_diff 2.0864 1.3329 0.1175 357
ln_fullmeal_diff 0.0207 0.0147 0.1593 331

(b) Interpretação e comparação. Ao condicionar nas observáveis pré-tratamento, o ATT do emprego cai um pouco — para a faixa de +2,1 FTE — e, sobretudo, perde precisão: deixa de ser significante aos níveis convencionais (o intervalo de confiança passa a incluir o zero). O efeito sobre o preço fica em torno de +2%, também não mais significante nesta especificação mais exigente. Ainda assim, o ponto qualitativamente central não muda: a estimativa pontual do efeito sobre o emprego permanece positiva, longe da queda prevista pelo modelo competitivo de salário mínimo, e o repasse a preços continua modesto e positivo. A perda de significância é o preço de gastar graus de liberdade com muitas interações \(D\times X\) numa amostra de poucas centenas de lojas — não uma reversão do achado. A leve atenuação frente ao DD simples (+2,75) é compatível com parte daquela diferença estar associada a composição observável distinta entre os estados; o fato de o sinal e a ordem de grandeza sobreviverem reforça a leitura de Card e Krueger. Vale a ressalva de sempre: tal como na Parte I, esse ajuste só remove confundimento nas observáveis; a identificação continua amparada na hipótese de tendências paralelas (agora condicional a \(X\)), que permanece não testável.