Q1 Figures and Tables

Impact of FEMA designated risk zones on sale price

Setup stuff

Code
library(tidyverse)
library(fixest)
library(did)  
library(etwfe)
library(tidyverse)

library(modelsummary)
library(gt)

options(scipen = 999)

# bldg_sfha <- readRDS("data/processed/variants/df_prep__bldg_sfha.RDS")
# 
# parcels <- readRDS("data/processed/variants/df_prep__parcel_sfha.RDS")
# 
# bldg_1in500 <- readRDS("data/processed/variants/df_prep__bldg_1in500.RDS")

source("R/helper_pins_to_drop.R")
Code
lake100ft <- readxl::read_xlsx("data/processed/parcels_2025_lakeMI_within100ft_ExportTable_TableToExcel.xlsx")
lake500ft <- readxl::read_xlsx("data/processed/parcels_2025_lakeMI_within500ft_ExportTable_TableToExcel.xlsx")

bldg_sfha <- readRDS("data/processed/targets/sales/df_prep_bldg_v2026_03.RDS")

parcels <- readRDS("data/processed/targets/sales/df_prep_land_v2026_03.RDS")

bldg_1in500 <- readRDS("data/processed/targets/sales/df_prep_risk500_v2026_03.RDS")  |>
  filter(!pin10 %in% drop_parcels  |
               (pin10 == "0113301013" & sale_year > 2014))

# bldg_sfha <- readRDS("data/processed/variants/df_prep__bldg_sfhaarcgis_redo_2026sales.RDS")
# 
# parcels <- readRDS("data/processed/variants/df_prep__parcel_sfhaarcgis_redo_2026sales.RDS")
# 
# bldg_1in500 <- readRDS("data/processed/variants/df_prep__bldg_1in500arcgis_redo_2026sales.RDS") |> 
#   filter(!pin10 %in% drop_parcels  |          
#            (pin10 == "0113301013" & sale_year > 2014))


bldg_sfha <- bldg_sfha |> filter(!pin10 %in% drop_parcels  |
               (pin10 == "0113301013" & sale_year > 2014)
             )

parcels <- parcels |> filter(!pin10 %in% drop_parcels  |
               (pin10 == "0113301013" & sale_year > 2014)
             )

make_control_groups <- function(df, 
                                ever_added,
                                ever_removed,
                    pre_eff_change_type, 
                    type = c("pre", "eff")
                                ){
  
  # Treated Group A: Added to Risk Zone
  group_T_A <- df |>
    filter( {{ever_added}} == TRUE )
  
  # Treated Group B: Removed from Risk Zone
  group_T_B <- df |> 
      filter( {{ever_removed}}  == TRUE)

  # Control Group A:  Never in SFHA, FIRM panels never updated
    
   if(type == "pre"){
   group_C_A <- df |> 
      filter(
        {{pre_eff_change_type}} == "Never SFHA" &
             pre_date_chr %in% c("2005-01-01")
         )
  }else if(type == "eff"){  # for effective maps
  group_C_A <- df |> 
      filter(
        {{pre_eff_change_type}} == "Never SFHA" &
         eff_date_chr %in% c("2008-08-19") 
         ) 
  }  

    # Control Group B: Never in SFHA, FIRM Panels are updated
    group_C_B  <- df |> 
      filter( 
        {{pre_eff_change_type}} == "Never SFHA" &
         (eff_date_chr %in% c("2019-11-01", "2021-09-10") | 
             pre_date_chr %in% c("2015-02-12", "2019-07-01", "2021-09-22")
         )
         )

    # Control Group C: Always in SFHA, FIRM Panels are not updated 
    if(type == "pre"){
    group_C_C <- df |> 
      filter(
        {{pre_eff_change_type}} == "Always SFHA" &             
          pre_date_chr %in% c("2005-01-01")
      )}
    else if(type == "eff") {  # this probably is tainted by prelim map cohort in 2021 though....
    group_C_C <- df |> 
      filter(
        {{pre_eff_change_type}} == "Always SFHA" &
         (eff_date_chr %in% c("2008-08-19") )
      )
    
    group_C_C_alt <- df |> 
      filter(
        {{pre_eff_change_type}} == "Always SFHA" &
         (eff_date_chr %in% c("2008-08-19") | 
            pre_date_chr %in% c("2005-01-01")
          )
      )
    }
    
    # Always in SFHA but FIRM panels have been recently updated
   
    if(type == "eff") {
      group_C_D <- df |> 
        filter(
        {{pre_eff_change_type}} == "Always SFHA" &
         (eff_date_chr %in% c("2019-11-01", "2021-09-10"))
         )
      
       group_C_D_alt <- df |> 
        filter(
        {{pre_eff_change_type}} == "Always SFHA" &
         (eff_date_chr %in% c("2019-11-01", "2021-09-10") | 
             pre_date_chr %in% c("2015-02-12", "2019-07-01", "2021-09-22")
             )
         )
    } else if (type == "pre") {
      group_C_D <- df |> 
         filter(
        {{pre_eff_change_type}} == "Always SFHA" &
             pre_date_chr %in% c("2015-02-12", "2019-07-01", "2021-09-22")
         )
    }
      list(
    group_T_A = group_T_A,
    group_T_B = group_T_B,
    group_C_A = group_C_A,
    group_C_B = group_C_B,
    group_C_C = group_C_C,
    group_C_D = group_C_D

  )

}

groups <- make_control_groups(df = bldg_sfha, 
                    ever_added = ever_added_prelim, 
                    ever_removed = ever_removed_prelim, 
                    pre_eff_change_type = change_type_prelim, 
                    type = "pre")

groups_long <- imap_dfr(groups, ~ {
  .x |>
    mutate(group = .y)
})
Code
groups_long |>
  group_by(group) |>
  summarise(
    n = n(),
    n_properties = n_distinct(pin),
    mean_price = mean(sale_price, na.rm = TRUE),
    mean_log_price = mean(log_price, na.rm = TRUE)
  )
group n n_properties mean_price mean_log_price
group_C_A 490915 215004 303329.3 12.28778
group_C_B 162730 69781 429702.7 12.59933
group_C_C 14188 6206 268833.6 12.16733
group_C_D 595 257 505159.2 12.56855
group_T_A 13 6 274300.0 12.34933
group_T_B 155 67 124150.5 11.48264

Line Graphs

Code
group_trends <- groups_long |>
  group_by(group, sale_year) |>
  summarise(
    mean_log_price = mean(log_price, na.rm = TRUE),
    n = n(),
    .groups = "drop"
  )

ggplot(group_trends, aes(x = sale_year, y = mean_log_price, color = group)) +
  geom_line(linewidth = 1) +
  geom_point(size = 1.5) +
  labs(
    x = "Year",
    y = "Mean Log Price",
    color = "Group"
  ) +
  theme_minimal()

Code
groups_long <- groups_long |>
  mutate(group_type = case_when(
    group %in% c("group_T_A", "group_T_B") ~ "Treated",
    TRUE ~ "Control"
  ))

group_trends2 <- groups_long |>
  group_by(group_type, sale_year) |>
  summarise(
    mean_log_price = mean(log_price, na.rm = TRUE),
    .groups = "drop"
  )

ggplot(group_trends2, aes(x = sale_year, y = mean_log_price, color = group_type)) +
  geom_line(linewidth = 1.2)
ggplot(group_trends, aes(x = sale_year, y = mean_log_price)) +
  geom_line() +
  facet_wrap(~ group)

Question 1

Table 2: Sample Counts

Code
table(bldg_sfha$prelim_sfha_category)

    Added to prelim SFHA              Always SFHA               Never SFHA 
                      13                    14783                   653645 
Removed from prelim SFHA 
                     155 
Code
table(bldg_sfha$change_type_prelim)

  Never SFHA  Always SFHA Changes SFHA 
      653645        14783          168 
Code
df_es_added <- bldg_sfha |>
  filter(pin10 %in% groups$group_T_A$pin10 | pin10 %in% groups$group_C_B$pin10 )|>
 # filter(prelim_sfha_category %in% c("Added to prelim SFHA", "Never SFHA")) |>
  mutate(pin_num = as.numeric(pin)) |>
  dplyr::arrange(pin_num, sale_year, sale_date) |>
  dplyr::group_by(pin_num, sale_year, pre_date_chr) |>
  dplyr::slice_tail(n = 1) |>
  dplyr::ungroup() |>
  mutate(treated = prelim_sfha_category == "Added to prelim SFHA") |> 
  
   group_by(pin_num) |>
  mutate(
    g_add = if (any(addedto_prelim_sfha == 1, na.rm = TRUE)) {
      min(sale_year[addedto_prelim_sfha == 1], na.rm = TRUE)
    } else {
      0L
    }
  ) |>
  ungroup()


added_counts <- table(df_es_added$g_add, df_es_added$pre_date)



df_es_removed <- bldg_sfha |>
    filter(pin10 %in% groups$group_T_B$pin10 | pin10 %in% groups$group_C_D$pin10 )|>

  #filter(prelim_sfha_category %in% c("Removed from prelim SFHA", "Always SFHA")) |>
  mutate(pin_num = as.numeric(pin)) |>
  dplyr::arrange(pin_num, sale_year, sale_date) |>
  dplyr::group_by(pin_num, sale_year, pre_date_chr) |>
  dplyr::slice_tail(n = 1) |>
  dplyr::ungroup() |>
  mutate(treated = prelim_sfha_category == "Removed from prelim SFHA") |> 
  
   group_by(pin_num) |>
  mutate(
    g_remove = if (any(removedfrom_prelim_sfha == TRUE, na.rm = TRUE)) {
      min(sale_year[removedfrom_prelim_sfha == TRUE], na.rm = TRUE)
    } else {
      0L
    }
  ) |>
  ungroup()


removed_counts <- table(df_es_removed$g_remove, df_es_removed$pre_date)
Code
added_counts 
removed_counts 
      
       2015-02-12 2019-07-01 2021-09-22
  0         11934     126483      19443
  2018          2          0          0
  2021          0          0          2
  2022          0          0          4
  2023          0          0          3
  2025          0          0          2
      
       2015-02-12 2019-07-01 2021-09-22
  0           287        314         72
  2015         19          0          0
  2016         12          0          0
  2017         15          0          0
  2018         11          0          0
  2019         13          0          0
  2020          8          0          0
  2021         14          0          5
  2022         19          0         11
  2023          2          0          4
  2024         10          0          2
  2025          2          0          2
Table 1: Table 1: Treated Sales by Cohort and Treatment Direction

Table 3

Code
sfha_category_stats <- bldg_sfha |>
  group_by(pin) |> mutate(n_sales = n()) |> ungroup() |>
  group_by(change_type_prelim) |>
 summarize(
    n_properties = n_distinct(pin),
    avg_sales_per_property = mean(n_sales),
    n_sales      = n(),
    avg_price = mean(sale_price),
    median_price = median(sale_price)  ) |>
  arrange(desc(n_properties))

sfha_category_stats
sfha_category_stats <- bldg_sfha |>
    mutate(ever_firm_update =  ifelse(pre_date =="2005-01-01", FALSE, TRUE) ) |>
  group_by(change_type_prelim) |>
  filter(ever_firm_update==TRUE) |>
  summarize(
    n_properties = n_distinct(pin),
    avg_sales_per_property = mean(n_sales),
    n_sales      = n(),
    avg_price = mean(sale_price),
    median_price = median(sale_price)  ) |>
  arrange(desc(n_properties))

sfha_category_stats
sfha_category_stats <- bldg_sfha |>
  group_by(prelim_sfha_category) |>
  summarize(
    n_properties = n_distinct(pin),
    avg_sales_per_property = mean(n_sales),
    n_sales      = n(),
    avg_price = mean(sale_price),
    median_price = median(sale_price)  ) |>
  arrange(desc(n_properties))

sfha_category_stats
sfha_category_stats <- bldg_sfha |>
    mutate(ever_firm_update =  ifelse(pre_date =="2005-01-01", FALSE, TRUE) ) |>
  group_by(prelim_sfha_category) |>
  filter(ever_firm_update==TRUE) |>
  summarize(
    n_properties = n_distinct(pin),
    avg_sales_per_property = mean(n_sales),
    n_sales      = n(),
    avg_price = mean(sale_price),
    median_price = median(sale_price)  ) |>
  arrange(desc(n_properties))

sfha_category_stats
change_type_prelim n_properties avg_sales_per_property n_sales avg_price median_price
Never SFHA 284785 2.441165 653645 334790.9 250000
Always SFHA 6463 2.424474 14783 278345.5 197000
Changes SFHA 73 2.440476 168 135769.2 98250
change_type_prelim n_properties avg_sales_per_property n_sales avg_price median_price
Never SFHA 69781 2.489990 162730 429702.7 300000
Always SFHA 257 2.448739 595 505159.2 250000
Changes SFHA 73 2.440476 168 135769.2 98250
prelim_sfha_category n_properties avg_sales_per_property n_sales avg_price median_price
Never SFHA 284785 2.441815 653645 334790.9 250000
Always SFHA 6463 2.425015 14783 278345.5 197000
Removed from prelim SFHA 67 2.458064 155 124150.5 92000
Added to prelim SFHA 6 2.230769 13 274300.0 250000
prelim_sfha_category n_properties avg_sales_per_property n_sales avg_price median_price
Never SFHA 69781 2.489990 162730 429702.7 300000
Always SFHA 257 2.448739 595 505159.2 250000
Removed from prelim SFHA 67 2.458064 155 124150.5 92000
Added to prelim SFHA 6 2.230769 13 274300.0 250000
Table 2: Table3: Descriptive Statistics for Sample
Code
allsales_bldg_sfha <- readRDS("data/processed/targets/sales/allsales_bldg_v2026_03.RDS")


allsales_bldg_sfha <- allsales_bldg_sfha |> 
  filter(!pin10 %in% c("0122400028", "0516106013"))|> 
  filter(!(Triad != "City" & log_price > 13.5))
Code
allsales_bldg_sfha |>
  filter(!pin10 %in% c("0122400028", "0516106013")) |> #filter(log_price > 13.5)|>
  filter(prelim_sfha_category %in% c("Always SFHA", "Never SFHA")) |> 
    group_by(sale_year, prelim_sfha_category, pre_date_chr) |> 
  summarise(avg_logprice = mean(log_price, na.rm = TRUE) )|> 

  ggplot(aes(x = sale_year , y = avg_logprice, shape = prelim_sfha_category)) +
  theme_classic() + 
  theme(legend.position = "bottom")+
  geom_smooth(aes(lty = prelim_sfha_category), method = "lm", se = FALSE, color = "darkgray", alpha= 0.5, lwd = 1) + 
  geom_line(aes(lty = prelim_sfha_category)) +
  geom_point(size = 2) + 
  
  scale_x_continuous(breaks = c(2010, 2015, 2020, 2026)) +
  labs(#title = "Cohort Trends by SFHA Group: Always In vs. Always Out",
       #subtitle = "Includes Chicago",
       y = "Avg. Logged Sale Price",
       x = NULL,
              shape = "SFHA Group",
       lty = "SFHA Group",
              caption = "Graphed with all sales, not just repeat sales. Includes Chicago") +
  facet_wrap(~pre_date_chr)

# EXCLUDE CHCICGO
allsales_bldg_sfha |>   
  filter(!pin10 %in% c("0122400028", "0516106013")) |> filter(Triad != "City")|>
  filter(prelim_sfha_category %in% c("Always SFHA", "Never SFHA")) |> 
    group_by(sale_year, prelim_sfha_category, pre_date_chr) |> 
  summarise(avg_logprice = mean(log_price, na.rm = TRUE) )|> 

  ggplot(aes(x = sale_year , y = avg_logprice, shape = prelim_sfha_category)) +
  theme_classic() + 
  theme(legend.position = "bottom")+
  geom_smooth(aes(lty = prelim_sfha_category), method = "lm", se = FALSE, color = "darkgray", alpha= 0.5, lwd = 1) + 
  geom_line(aes(lty = prelim_sfha_category)) +
  geom_point(size = 2) + 
  
  scale_x_continuous(breaks = c(2010, 2015, 2020, 2026)) + 
  labs(#title = "Cohort Trends by SFHA Group: Always In vs. Always Out",
       #subtitle = "Excludes Chicago",
       y = "Avg. Logged Sale Price",
       x = NULL,
              shape = "SFHA Group",
       lty = "SFHA Group",
       caption = "Graphed with all sales, not just repeat sales. Excludes Chicago") +
  facet_wrap(~pre_date_chr)
Figure 1: Includes Chicago
Figure 2: Excludes Chicago
Code
allsales_bldg_sfha |>   
   filter(!pin10 %in% c("0122400028", "0516106013")) |> 
  #filter(!(Triad != "City" & log_price > 13.5))|>
  filter(prelim_sfha_category %in% c("Always SFHA", "Never SFHA")) |> 
    group_by(sale_year, prelim_sfha_category) |> 
  summarise(avg_logprice = mean(log_price, na.rm = TRUE) )|> 

  ggplot(aes(x = sale_year , y = avg_logprice, shape = prelim_sfha_category)) +
  theme_classic() + 
  theme(legend.position = "bottom")+
  geom_smooth(aes(lty = prelim_sfha_category), method = "lm", se = FALSE, color = "darkgray", alpha= 0.5, lwd = 1) + 
  geom_line(aes(lty = prelim_sfha_category)) +
  geom_point(size = 2.5) + 
  
  scale_x_continuous(breaks = c(2010, 2015, 2020, 2026)) + 
  labs(title = "County Trends by SFHA Group: Always In vs. Always Out",
       y = "Avg. Logged Sale Price",
       x = NULL,
       caption= "Uses All Sales",
              shape = "SFHA Group",
       lty = "SFHA Group")
Figure 3

Table 4: County Wide Effect

Code
bldg_sfha <- bldg_sfha |>
  mutate(nbhd_code = if_else(
    is.na(nbhd_code),
    "Missing",
    as.character(nbhd_code)
  ))

mods <- list (
 no_fe <- feols(
    log_price ~ in_prelim_sfha,
    vcov = ~pin10,
    data = bldg_sfha
  ),
  
 one_fe <- feols(
    log_price ~ in_prelim_sfha | sale_year,
    data = bldg_sfha,
     vcov = ~pin10
  ),
   pin_fe <- feols(
    log_price ~ in_prelim_sfha | pin ,
    data = bldg_sfha,
        vcov = ~pin10,

  ),
  
  two_fe <- feols(
    log_price ~ in_prelim_sfha | pin + sale_year,
    data = bldg_sfha,
     vcov = ~pin10
  ),
  
  three_fe <- feols(
    log_price ~ in_prelim_sfha | pin + sale_year + qtr,
    data = bldg_sfha,
     vcov = ~pin10
)
)
# mod_prelim_3fe <- feols(
#   log_price ~ in_prelim_sfha | pin + sale_year + qtr,
#   data = bldg_sfha
# )
# 
# mod_eff_3fe <- feols(
#   log_price ~ in_eff_sfha | pin + sale_year + qtr,
#   data = bldg_sfha
# )
# 
# mods_names <- list(
#   "Preliminary"      = mod_prelim,
#   "Effective"        = mod_eff
#   # , "Prelim w/ Qtr FE" = mod_prelim_3fe,
#   #"Eff w/ Qtr FE"    = mod_eff_3fe
# )
# 
# vcovs <- list(
#   "Parcel" = ~pin10,
#   "Municipality" = ~clean_name
#   # , "Neighborhood" = ~nbhd_code
# )
# 
# gof_labels <- c(
#   "nobs"      = "Observations",
#   "r.squared" = "R-squared"
# )

coef_labels = c(
  "in_prelim_sfhaTRUE" = "In SFHA (Preliminary Map)",
  "in_eff_sfhaTRUE"    = "In SFHA (Effective Map)"
)

# ms_models <- list()
# ms_vcov   <- list()
# 
# for (m in names(mods)) {
#   for (v in names(vcovs)) {
#     ms_models[[paste(m, v, sep = "\n")]] <- mods[[m]]
#     ms_vcov[[length(ms_vcov) + 1]] <- vcovs[[v]]
#   }
# }
# 
# modelsummary::modelsummary(
#   ms_models,
#   vcov = ms_vcov,
#   stars = TRUE,
# 
# )

modelsummary::modelsummary(
  mods,
  stars = TRUE,
 gof_omit = "IC|Log|Adj|Within"
#  ,
 # estimate = "{estimate}{stars}",
#  statistic = "\n({std.error})"#,
,  coef_map = coef_labels
)
(1) (2) (3) (4) (5)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
In SFHA (Preliminary Map) -0.187** -0.184** -0.445*** -0.028 -0.022
(0.067) (0.067) (0.064) (0.039) (0.041)
Num.Obs. 668596 668596 667288 667288 667288
R2 0.001 0.076 0.796 0.859 0.860
RMSE 0.86 0.83 0.39 0.32 0.32
Std.Errors by: pin10 by: pin10 by: pin10 by: pin10 by: pin10
FE: sale_year X X X
FE: pin X X X
FE: qtr X
Table 3: Table 4
Code
modelsummary::modelsummary(
  mods,
  stars = TRUE,
 gof_omit = "IC|Log|Adj|Within",
vcov = ~clean_name
,  coef_map = coef_labels
)
(1) (2) (3) (4) (5)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
In SFHA (Preliminary Map) -0.187 -0.184 -0.445*** -0.028 -0.022
(0.135) (0.136) (0.059) (0.023) (0.026)
Num.Obs. 668596 668596 667288 667288 667288
R2 0.001 0.076 0.796 0.859 0.860
RMSE 0.86 0.83 0.39 0.32 0.32
Std.Errors by: clean_name by: clean_name by: clean_name by: clean_name by: clean_name
FE: sale_year X X X
FE: pin X X X
FE: qtr X
Code
modelsummary::modelsummary(
  mods,
  stars = TRUE,
 gof_omit = "IC|Log|Adj|Within",
vcov = ~nbhd_code
,  coef_map = coef_labels
)
(1) (2) (3) (4) (5)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
In SFHA (Preliminary Map) -0.187 -0.184 -0.445*** -0.028 -0.022
(0.122) (0.123) (0.082) (0.046) (0.049)
Num.Obs. 668596 668596 667288 667288 667288
R2 0.001 0.076 0.796 0.859 0.860
RMSE 0.86 0.83 0.39 0.32 0.32
Std.Errors by: nbhd_code by: nbhd_code by: nbhd_code by: nbhd_code by: nbhd_code
FE: sale_year X X X
FE: pin X X X
FE: qtr X

Table 4 robustness check

Table 5: Simple Directional Effects - Standard TWFE

Code
prelim_twfe <- list(

  
 "I" =  feols(
    log(sale_price) ~ addedto_prelim_sfha  | pin + sale_year,
    cluster = ~pin10,
        data = bldg_sfha
  ),

  "II" =  feols(
    log(sale_price) ~ removedfrom_prelim_sfha | pin + sale_year,
    cluster = ~ pin10,
        data = bldg_sfha,

  ),
 
  # Asymmetric: added vs removed in one equation
  "0" = feols(
    log(sale_price) ~ addedto_prelim_sfha + removedfrom_prelim_sfha | pin + sale_year,
    cluster = ~ pin10,
        data = bldg_sfha,
  ))
 


all_models_formatted <- modelsummary::modelsummary(prelim_twfe,
               output = "flextable",
               fmt = function(x) round(x, 2),

             stars = TRUE)

all_models_formatted

I

II

0

addedto_prelim_sfhaTRUE

0.13

0.13

(0.1)

(0.1)

removedfrom_prelim_sfhaTRUE

0.04

0.04

(0.04)

(0.04)

Num.Obs.

667288

667288

667288

R2

0.859

0.859

0.859

R2 Adj.

0.751

0.751

0.751

R2 Within

0.000

0.000

0.000

R2 Within Adj.

-0.000

-0.000

-0.000

AIC

966832.3

966832.2

966833.7

BIC

4276346.6

4276346.5

4276359.4

RMSE

0.32

0.32

0.32

Std.Errors

by: pin10

by: pin10

by: pin10

FE: pin

X

X

X

FE: sale_year

X

X

X

+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Table 4: Table 5: Effect of Being Mapped Into or Out of a FEMA Flood Plain

Table 5 & 6: Directional Effects & Control Group Subsamples

Code
##| eval: false

prelim_twfe <- list(

  
 "I" =  feols(
    log(sale_price) ~ addedto_prelim_sfha  | pin + sale_year,
    cluster = ~pin10,
        data = bldg_sfha
  ),

    "I Subset" = feols(
    log(sale_price) ~ addedto_prelim_sfha  | pin + sale_year,
    cluster = ~ pin10,
        data = (bldg_sfha |> filter(change_type_prelim != "Always SFHA" & treat_year_remove_prelim == 10000)),

  ),

  "II" =  feols(
    log(sale_price) ~ removedfrom_prelim_sfha | pin + sale_year,
    cluster = ~ pin10,
        data = bldg_sfha,

  ),


  "II Subset" =  feols(
    log(sale_price) ~ removedfrom_prelim_sfha | pin + sale_year,
    cluster = ~ pin10,
        data = (bldg_sfha |> filter(change_type_prelim != "Never SFHA" & treat_year_add_prelim == 10000)),

  ),
 
  # Symmetric effect of location in risk zone
    "Symmetric" = feols(
    log(sale_price) ~ in_prelim_sfha | pin + sale_year,
    cluster = ~ pin10,
        data = bldg_sfha,
  ),
 
  # Asymmetric: added vs removed in one equation
  "0" = feols(
    log(sale_price) ~ addedto_prelim_sfha + removedfrom_prelim_sfha | pin + sale_year,
    cluster = ~ pin10,
        data = bldg_sfha,
  )
)

all_models_formatted <- modelsummary::modelsummary(prelim_twfe,
               output = "flextable",
               fmt = function(x) round(x, 2),

             stars = TRUE)

all_models_formatted

I

I Subset

II

II Subset

Symmetric

0

addedto_prelim_sfhaTRUE

0.13

0.13

0.13

(0.1)

(0.1)

(0.1)

removedfrom_prelim_sfhaTRUE

0.04

0.08

0.04

(0.04)

(0.05)

(0.04)

in_prelim_sfhaTRUE

-0.03

(0.04)

Num.Obs.

667288

652380

667288

14908

667288

667288

R2

0.859

0.858

0.859

0.894

0.859

0.859

R2 Adj.

0.751

0.749

0.751

0.812

0.751

0.751

R2 Within

0.000

0.000

0.000

0.000

0.000

0.000

R2 Within Adj.

-0.000

-0.000

-0.000

0.000

-0.000

-0.000

AIC

966832.3

949742.5

966832.2

16063.4

966832.5

966833.7

BIC

4276346.6

4178679.2

4276346.5

65647.9

4276346.8

4276359.4

RMSE

0.32

0.32

0.32

0.27

0.32

0.32

Std.Errors

by: pin10

by: pin10

by: pin10

by: pin10

by: pin10

by: pin10

FE: pin

X

X

X

X

X

X

FE: sale_year

X

X

X

X

X

X

+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Table 5: Table 5 & 6 Effect of Being Mapped Into or Out of a FEMA Flood Plain. A lot of models all by each other.

Table 7: Restrict Samples - Updated FIRM Panels only

Code
# function with default options:
fn_valid_cohorts <- function(df){
  
  df |> 
    mutate(pre_date_chr = as.character(pre_date_chr)) |>
    group_by(pre_date_chr) |>
    summarise(
      n = n(),
      n_treated = sum(removedfrom_prelim_sfha == TRUE, na.rm = TRUE),
      n_untreated = sum(removedfrom_prelim_sfha == FALSE, na.rm = TRUE),
          .groups = "drop"

      ) |>
    filter(n_treated > 0,  n_untreated > 0) |>
    pull(pre_date_chr)
}



#table(bldg_sfha$Triad, bldg_sfha$prelim_sfha_category)

df_sub <- bldg_sfha |> 
      filter(pin10 %in% groups$group_T_B$pin10 | pin10 %in% groups$group_C_D$pin10 )
  # filter(change_type_prelim != "Never SFHA" & 
  #          # and properties that are never treated for being added to the SFHA
  #          treat_year_add_prelim == 10000 & treat_year_add_eff == 10000)

valid_cohorts_bldg <- fn_valid_cohorts(df_sub)
bldgs_fsplit <- df_sub |> filter(pre_date_chr %in% valid_cohorts_bldg)


#table(parcels$Triad, parcels$prelim_sfha_category)
#table(parcels$removed_pre_thisyear, parcels$year)

# df_sub_parcels <- parcels |> 
#       filter(pin10 %in% groups$group_T_B$pin10 | pin10 %in% groups$group_C_D$pin10 )
#   # filter(change_type_prelim != "Never SFHA" & 
#   #          # and properties that are never treated for being added to the SFHA
#   #          treat_year_add_prelim == 10000 & treat_year_add_eff == 10000)
# 
# valid_cohorts_parcels <- fn_valid_cohorts(df = df_sub_parcels)
# parcels_fsplit <- df_sub_parcels |> filter(pre_date_chr %in% valid_cohorts_parcels)
# 
# table(bldg_1in500$removed_pre_thisyear, bldg_1in500$year)
# table(bldg_1in500$removedfrom_prelim_sfha, bldg_1in500$year)
# table(bldg_1in500$Triad, bldg_1in500$prelim_sfha_category)
# df_sub_1in500 <- bldg_1in500 |> 
#   filter(change_type_prelim != "Never SFHA" & 
#            # and properties that are never treated for being added to the SFHA
#            treat_year_add_prelim == 10000 & treat_year_add_eff == 10000)
# valid_cohorts_1in500 <- fn_valid_cohorts(df = df_sub_1in500)
# bldg_1in500_fsplit <- df_sub_1in500 |> filter(pre_date_chr %in% valid_cohorts_1in500)
# 
# 




models <- list(
 "Bldg SFHA" = feols(
       log(sale_price) ~ removedfrom_prelim_sfha | pin + sale_year,

   # log(sale_price) ~ event_remapped*ever_removed_prelim | pin + sale_year,
    data = bldgs_fsplit,
  fsplit = ~pre_date_chr,
    cluster = ~pin10
  )
  # 
  # "Bldg 1in500" = feols(
  #          log(sale_price) ~ removedfrom_prelim_sfha | pin + sale_year,
  # 
  # #  log(sale_price) ~ event_remapped*ever_removed_prelim | pin + sale_year,
  #   data = bldg_1in500_fsplit,
  # fsplit = ~pre_date_chr,
  #   cluster = ~pin10
  # 
  # ),
  # 
  # "Parcels" = feols(
  #          log(sale_price) ~ removedfrom_prelim_sfha | pin + sale_year,
  # 
  # #  log(sale_price) ~ event_remapped*ever_removed_prelim | pin + sale_year,
  #   data = parcels_fsplit,
  # fsplit = ~pre_date_chr,
  #   cluster = ~pin10
  # 
  # )
)

all_models_formatted <- modelsummary::modelsummary(models,
               output = "flextable",
               fmt = function(x) round(x, 2),

             stars = TRUE)

all_models_formatted

Bldg SFHA sample: Full sample

Bldg SFHA sample: 2015-02-12

Bldg SFHA sample: 2021-09-22

removedfrom_prelim_sfhaTRUE

-0.08

-0.06

0.09

(0.08)

(0.1)

(0.21)

Num.Obs.

533

431

100

R2

0.940

0.931

0.959

R2 Adj.

0.888

0.870

0.900

R2 Within

0.006

0.003

0.005

R2 Within Adj.

0.002

-0.001

-0.019

AIC

187.7

94.5

84.2

BIC

1244.5

915.8

237.9

RMSE

0.18

0.17

0.20

Std.Errors

by: pin10

by: pin10

by: pin10

FE: pin

X

X

X

FE: sale_year

X

X

X

+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Table 6: Table 7: Mapped out of Preliminary SFHA - Restricted Samples

Table 8

Code
#table(bldg_sfha$added_pre_thisyear, bldg_sfha$year)

# function with default options:
fn_valid_cohorts <- function(df){
  
  df |>  
    mutate(pre_date_chr = as.character(pre_date_chr)) |>
    group_by(pre_date_chr) |>
    summarise(
      n = n(),
      n_treated = sum(addedto_prelim_sfha == TRUE, na.rm = TRUE),
      n_untreated = sum(addedto_prelim_sfha == FALSE, na.rm = TRUE),
          .groups = "drop"

      ) |>
    filter(n_treated > 0,  n_untreated > 0) |>
    pull(pre_date_chr)
}




table(bldg_sfha$Triad, bldg_sfha$prelim_sfha_category)
       
        Added to prelim SFHA Always SFHA Never SFHA Removed from prelim SFHA
  South                    2        5244     157264                      129
  North                   11        5228     145900                       26
  City                     0        4311     350481                        0
Code
df_sub <- bldg_sfha |> 
    filter(pin10 %in% groups$group_T_A$pin10 | pin10 %in% groups$group_C_B$pin10 )
  # filter(change_type_prelim != "Always SFHA" & 
  #          # and properties that are never treated for being removed from the SFHA
  #          treat_year_remove_prelim == 10000 & treat_year_remove_eff == 10000)

valid_cohorts_bldg <- fn_valid_cohorts(df_sub)
bldgs_fsplit <- df_sub |> filter(pre_date_chr %in% valid_cohorts_bldg)


# table(parcels$Triad, parcels$prelim_sfha_category)
# table(parcels$removed_pre_thisyear, parcels$year)

df_sub_parcels <- parcels |> 
    filter(pin10 %in% groups$group_T_A$pin10 | pin10 %in% groups$group_C_B$pin10 )
  # filter(change_type_prelim != "Always SFHA" & 
  #          # and properties that are never treated for being removed from the SFHA
  #          treat_year_remove_prelim == 10000 & treat_year_remove_eff == 10000)
valid_cohorts_parcels <- fn_valid_cohorts(df = df_sub_parcels)
parcels_fsplit <- df_sub_parcels |> filter(pre_date_chr %in% valid_cohorts_parcels)


# 
# table(bldg_1in500$removed_pre_thisyear, bldg_1in500$year)
# table(bldg_1in500$removedfrom_prelim_sfha, bldg_1in500$year)
# table(bldg_1in500$Triad, bldg_1in500$prelim_sfha_category)

# df_sub_1in500 <- bldg_1in500 |> 
#     filter(pin10 %in% groups$group_T_A$pin10 | pin10 %in% groups$group_C_B$pin10 )
  # filter(change_type_prelim != "Always SFHA" & 
  #          # and properties that are never treated for being removed from the SFHA
  #          treat_year_remove_prelim == 10000 & treat_year_remove_eff == 10000)
#valid_cohorts_1in500 <- fn_valid_cohorts(df = df_sub_1in500)
#bldg_1in500_fsplit <- df_sub_1in500 |> filter(pre_date_chr %in% valid_cohorts_1in500)




models_pre_add <- list(
  # Heterogeneity by Triad: added/removed interacted with Triad
  # 
  # # Heterogeneity by Triad: added/removed interacted with Triad
  "Buildings in SFHA" =  feols(
    log(sale_price) ~ addedto_prelim_sfha | pin + sale_year,
    fsplit = ~pre_date_chr,
    cluster = ~pin10,
    data = bldgs_fsplit
  ),
    "Buildings in SFHA" =  feols(
    log(sale_price) ~ addedto_prelim_sfha*condo | pin + sale_year,
    fsplit = ~pre_date_chr,
    cluster = ~pin10,
    data = bldgs_fsplit
  ),

  "Buildings in SFHA" =  feols(
    log(sale_price) ~ addedto_prelim_sfha | pin + sale_year,
    cluster = ~pin10,
    data = bldg_sfha
  )
  # 
  # "Buildings - 1-in-500" =  feols(
  #   log(sale_price) ~ addedto_prelim_sfha | pin + year,
  #   fsplit = ~pre_date_chr,
  #   cluster = ~pin10,
  #   data = bldg_1in500_fsplit
  # ),
  # 
  # "Buildings - 1-in-500" =  feols(
  #   log(sale_price) ~ addedto_prelim_sfha | pin + sale_year,
  #   cluster = ~pin10,
  #   data = bldg_1in500
  # )
)

all_models_formatted <- modelsummary::modelsummary(models_pre_add,
                                                   output = "flextable",
                                                   fmt = function(x) round(x, 2),
                                                   
                                                   stars = TRUE)

all_models_formatted

Buildings in SFHA sample: Full sample

Buildings in SFHA sample: 2015-02-12

Buildings in SFHA sample: 2021-09-22

Buildings in SFHA sample: Full sample

Buildings in SFHA sample: 2015-02-12

Buildings in SFHA sample: 2021-09-22

Buildings in SFHA

addedto_prelim_sfhaTRUE

0.12

0.11***

0.13

0.12

0.11***

0.13

0.13

(0.11)

(0.02)

(0.13)

(0.11)

(0.02)

(0.13)

(0.1)

condoNot Condo

0.25***

0.29***

(0.01)

(0.01)

Num.Obs.

33067

12509

20558

33067

12509

20558

667288

R2

0.906

0.893

0.913

0.906

0.893

0.913

0.859

R2 Adj.

0.832

0.807

0.844

0.832

0.807

0.844

0.751

R2 Within

0.000

0.000

0.000

0.001

0.000

0.002

0.000

R2 Within Adj.

-0.000

-0.000

-0.000

0.001

-0.000

0.002

-0.000

AIC

14266.7

4679.3

9133.6

14236.9

4679.3

9095.2

966832.3

BIC

137309.6

46006.0

81258.2

137288.2

46006.0

81227.7

4276346.6

RMSE

0.19

0.19

0.19

0.19

0.19

0.19

0.32

Std.Errors

by: pin10

by: pin10

by: pin10

by: pin10

by: pin10

by: pin10

by: pin10

FE: pin

X

X

X

X

X

X

X

FE: sale_year

X

X

X

X

X

X

X

+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Table 7: Table 8: Mapped into Preliminary SFHA - Restricted Sample

Table 9: County-wide and Cohort Directional Models using Extended TWFE

Added To Risk Zone

Code
mod1c = etwfe(
  fml = log_price ~ 1, 
  tvar = sale_year, 
  ivar = pin, 
  cgroup = "never",
  gvar = treat_year_add_prelim, 
  vcov = ~pin10, 
  data = (bldg_sfha |>
            filter(
             (pin %in% groups$group_T_A$pin |
              pin %in% groups$group_C_A$pin |
              pin %in% groups$group_C_B$pin | 
              pin %in% groups$group_C_C$pin |
              pin %in% groups$group_C_D$pin )
                     )
          )
  )
# mod1c
emfx(mod1c)
Code
emfx(mod1c, type = "group")

 .Dtreat Estimate Std. Error     z Pr(>|z|)     S   2.5 %  97.5 %
    TRUE  -0.0173   0.000737 -23.5   <0.001 401.6 -0.0187 -0.0158

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE

 treat_year_add_prelim Estimate Std. Error     z Pr(>|z|)     S   2.5 % 97.5 %
                  2015   0.0000         NA    NA       NA    NA      NA     NA
                  2021  -0.0207   0.000884 -23.5   <0.001 401.6 -0.0225 -0.019

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Table 8: Control Groups A, B, C, & D (so all PINs that never changed SFHA status; both always in, and always out)

Mapped Out of SFHA, Column 3

Code
mod2 = etwfe(
  fml = log_price ~ 1, 
  tvar = sale_year, 
  ivar = pin, 
  cgroup = "never",
  gvar = treat_year_remove_prelim, 
  vcov = ~pin10, 
  data = (bldg_sfha|> 
                filter(pin10 %in% groups$group_T_B$pin10 | 
                         pin10 %in% groups$group_C_D$pin10 | 
                         pin10 %in% groups$group_C_C$pin10 |
                         pin10 %in% groups$group_C_A$pin10 | 
                         pin10 %in% groups$group_C_B$pin10 )

          )
  )
# mod2
emfx(mod2)

 .Dtreat Estimate Std. Error    z Pr(>|z|)   S  2.5 % 97.5 %
    TRUE    0.119     0.0549 2.17   0.0301 5.1 0.0115  0.227

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
emfx(mod2, type = "group")

 treat_year_remove_prelim Estimate Std. Error    z Pr(>|z|)    S   2.5 % 97.5 %
                     2015    0.109     0.0649 1.68   0.0931  3.4 -0.0182  0.236
                     2021    0.174     0.0255 6.82   <0.001 36.7  0.1242  0.224

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
plot(emfx(mod2, type = "group"))

Removed - Subset (Column 4)

Code
mod2 = etwfe(
  fml = log_price ~ 1, 
  tvar = sale_year, 
  ivar = pin, 
  cgroup = "never",
  gvar = treat_year_remove_prelim, 
  vcov = ~pin10, 
  data = (bldg_sfha|> 
                filter(pin10 %in% groups$group_T_B$pin10 | 
                         pin10 %in% groups$group_C_D$pin10 | 
                         pin10 %in% groups$group_C_C$pin10 )

          )
  )
# mod2
emfx(mod2)

 .Dtreat Estimate Std. Error    z Pr(>|z|)   S  2.5 % 97.5 %
    TRUE    0.138     0.0612 2.26   0.0239 5.4 0.0183  0.258

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
emfx(mod2, type = "group")

 treat_year_remove_prelim Estimate Std. Error    z Pr(>|z|)    S   2.5 % 97.5 %
                     2015    0.131     0.0715 1.83   0.0673  3.9 -0.0093  0.271
                     2021    0.178     0.0316 5.62   <0.001 25.6  0.1157  0.240

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
#plot(emfx(mod2, type = "group"))

Table 10: Regional Slopes

Mapped into SFHA

Code
library(etwfe)

mod1 = etwfe(
  fml = log_price ~ 1, 
  tvar = sale_year, 
  ivar = pin, 
  xvar = Triad, 
  cgroup = "never",
  gvar = treat_year_add_prelim,
  vcov = ~pin10, 
  data = (bldg_sfha |> 
            filter(treat_year_remove_prelim==10000
                   
                   & change_type_prelim != "Always SFHA")
          )
  
  )
#mod1
emfx(mod1)

 .Dtreat Triad Estimate Std. Error     z Pr(>|z|)     S   2.5 %  97.5 %
    TRUE South   0.0000         NA    NA       NA    NA      NA      NA
    TRUE North  -0.0388    0.00154 -25.2   <0.001 461.9 -0.0419 -0.0358

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
emfx(mod1, type = "group")

 treat_year_add_prelim Triad Estimate Std. Error     z Pr(>|z|)     S   2.5 %
                  2015 South   0.0000         NA    NA       NA    NA      NA
                  2021 North  -0.0388    0.00154 -25.2   <0.001 461.9 -0.0419
  97.5 %
      NA
 -0.0358

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
plot(emfx(mod1))

Added with regional slopes

Code
mod2 = etwfe(
  fml = log_price ~ 1, 
  tvar = sale_year, 
  ivar = pin, 
  cgroup = "never",
  gvar = treat_year_add_prelim, 
    xvar = Triad, 

  vcov = ~pin10, 
  data = (bldg_sfha|> 
                filter(pin10 %in% groups$group_T_A$pin10 | 
                         pin10 %in% groups$group_C_A$pin10 |
                         pin10 %in% groups$group_C_B$pin10)
          )
  )
# mod2
emfx(mod2)

 .Dtreat Triad Estimate Std. Error     z Pr(>|z|)     S   2.5 %  97.5 %
    TRUE South   0.0000         NA    NA       NA    NA      NA      NA
    TRUE North  -0.0388    0.00154 -25.2   <0.001 462.1 -0.0419 -0.0358

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
emfx(mod2, type = "group")

 treat_year_add_prelim Triad Estimate Std. Error     z Pr(>|z|)     S   2.5 %
                  2015 South   0.0000         NA    NA       NA    NA      NA
                  2021 North  -0.0388    0.00154 -25.2   <0.001 462.1 -0.0419
  97.5 %
      NA
 -0.0358

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
mod2 = etwfe(
  fml = log_price ~ 1, 
  tvar = sale_year, 
  ivar = pin, 
  cgroup = "never",
  gvar = treat_year_add_prelim, 
  xvar = group_name_prelim, 
  vcov = ~pin10, 
  data = (bldg_sfha|> 
                filter(pin10 %in% groups$group_T_A$pin10 | 
                         pin10 %in% groups$group_C_A$pin10 |
                         pin10 %in% groups$group_C_B$pin10 
                         )
          )
  )
# mod2
emfx(mod2)

 group_name_prelim .Dtreat Estimate Std. Error     z Pr(>|z|)     S   2.5 %
              2015    TRUE   0.0000         NA    NA       NA    NA      NA
              2021    TRUE  -0.0251    0.00106 -23.8   <0.001 411.9 -0.0272
 97.5 %
     NA
 -0.023

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
emfx(mod2, type = "group")

 group_name_prelim treat_year_add_prelim Estimate Std. Error     z Pr(>|z|)
              2015                  2015   0.0000         NA    NA       NA
              2021                  2021  -0.0251    0.00106 -23.8   <0.001
     S   2.5 % 97.5 %
    NA      NA     NA
 411.9 -0.0272 -0.023

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
mod2 = etwfe(
  fml = log_price ~ 1, 
  tvar = sale_year, 
  ivar = pin, 
  cgroup = "never",
  gvar = treat_year_remove_prelim, 
    xvar = Triad, 

  vcov = ~pin10, 
  data = (bldg_sfha|> 
                filter(pin10 %in% groups$group_T_B$pin10 | 
                         pin10 %in% groups$group_C_D$pin10 |
                         pin10 %in% groups$group_C_C$pin10 
                         )
          )
  )
# mod2
#emfx(mod2)
emfx(mod2, type = "group")

 treat_year_remove_prelim Triad Estimate Std. Error      z Pr(>|z|)   S   2.5 %
                     2015 South  -0.0436     0.0718 -0.607  0.54369 0.9 -0.1842
                     2021 North   0.1072     0.0366  2.926  0.00343 8.2  0.0354
 97.5 %
 0.0971
 0.1790

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
mod2 = etwfe(
  fml = log_price ~ 1, 
  tvar = sale_year, 
  ivar = pin, 
  cgroup = "never",
  gvar = treat_year_remove_prelim, 
  xvar = group_name_prelim, 

  vcov = ~pin10, 
  data = (bldg_sfha|> 
                filter(pin10 %in% groups$group_T_B$pin10 | 
                         pin10 %in% groups$group_C_D$pin10 |
                         pin10 %in% groups$group_C_C$pin10 
                         )
          )
  )
# mod2
emfx(mod2)

 group_name_prelim .Dtreat Estimate Std. Error    z Pr(>|z|)    S    2.5 %
              2015    TRUE    0.132     0.0721 1.83   0.0669  3.9 -0.00919
              2021    TRUE    0.181     0.0318 5.68   <0.001 26.2  0.11855
 97.5 %
  0.273
  0.243

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
emfx(mod2, type = "group")

 group_name_prelim treat_year_remove_prelim Estimate Std. Error    z Pr(>|z|)
              2015                     2015    0.132     0.0721 1.83   0.0669
              2021                     2021    0.181     0.0318 5.68   <0.001
    S    2.5 % 97.5 %
  3.9 -0.00919  0.273
 26.2  0.11855  0.243

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
#table(bldg_sfha$added_pre_thisyear, bldg_sfha$year)

# function with default options:
fn_valid_cohorts <- function(df){
  
  df |> 
    mutate(eff_date_chr = as.character(eff_date_chr)) |>
    group_by(eff_date_chr) |>
    summarise(
      n = n(),
      n_treated = sum(removedfrom_eff_sfha == TRUE, na.rm = TRUE),
      n_untreated = sum(removedfrom_eff_sfha == FALSE, na.rm = TRUE),
          .groups = "drop"

      ) |>
    filter(n_treated > 0,  n_untreated > 0) |>
    pull(eff_date_chr)
}





df_sub <- bldg_sfha |> 
      filter(pin10 %in% groups$group_T_B$pin10 | pin10 %in% groups$group_C_D$pin10 )
  # filter(change_type_prelim != "Never SFHA" & 
  #          # and properties that are never treated for being addedto from the SFHA
  #          treat_year_add_prelim == 10000 & treat_year_add_eff == 10000)

valid_cohorts_bldg <- fn_valid_cohorts(df_sub)
bldgs_fsplit <- df_sub |> filter(eff_date_chr %in% valid_cohorts_bldg)


# table(parcels$Triad, parcels$prelim_sfha_category)
# table(parcels$removed_pre_thisyear, parcels$year)

df_sub_parcels <- parcels |> 
      filter(pin10 %in% groups$group_T_B$pin10 | pin10 %in% groups$group_C_D$pin10 )
  # filter(change_type_prelim != "Never SFHA" & 
  #          # and properties that are never treated for being added to the SFHA
  #          treat_year_add_prelim == 10000 & treat_year_add_eff == 10000)
valid_cohorts_parcels <- fn_valid_cohorts(df = df_sub_parcels)
parcels_fsplit <- df_sub_parcels |> filter(eff_date_chr %in% valid_cohorts_parcels)


# 
# table(bldg_1in500$removed_pre_thisyear, bldg_1in500$year)
# table(bldg_1in500$removedfrom_prelim_sfha, bldg_1in500$year)
# table(bldg_1in500$Triad, bldg_1in500$prelim_sfha_category)

df_sub_1in500 <- bldg_1in500 |> 
      filter(pin10 %in% groups$group_T_B$pin10 | pin10 %in% groups$group_C_D$pin10 ) 
  # filter(change_type_prelim != "Never SFHA" & 
  #          # and properties that are never treated for being addedto the SFHA
  #          treat_year_add_prelim == 10000 & treat_year_add_eff == 10000)
valid_cohorts_1in500 <- fn_valid_cohorts(df = df_sub_1in500)
bldg_1in500_fsplit <- df_sub_1in500 |> filter(eff_date_chr %in% valid_cohorts_1in500)




models <- list(
  
  # "Buildings in SFHA" =  feols(
  #   log(sale_price) ~ addedto_eff_sfha | pin + sale_year,
  #   fsplit = ~eff_date_chr,
  #   cluster = ~pin10,
  #   data = bldgs_fsplit
  # ),

  "Buildings in SFHA" =  feols(
    log(sale_price) ~ removedfrom_eff_sfha | pin + sale_year,
    fsplit = ~eff_date_chr,

    cluster = ~pin10,
    data = parcels_fsplit
  ),

  "Buildings - 1-in-500" =  feols(
    log(sale_price) ~ removedfrom_eff_sfha | pin + year,
    fsplit = ~eff_date_chr,
    cluster = ~pin10,
    data = bldg_1in500_fsplit
  )
)

all_models_formatted <- modelsummary::modelsummary(models,
                                                   output = "flextable",
                                                   fmt = function(x) round(x, 2),
                                                   
                                                   stars = TRUE)

all_models_formatted

Buildings in SFHA sample: Full sample

Buildings in SFHA sample: 2019-11-01

Buildings - 1-in-500 sample: Full sample

Buildings - 1-in-500 sample: 2019-11-01

removedfrom_eff_sfhaTRUE

0.07

0.07

-0.13

-0.13

(0.07)

(0.07)

(0.09)

(0.09)

Num.Obs.

431

431

431

431

R2

0.931

0.931

0.931

0.931

R2 Adj.

0.870

0.870

0.871

0.871

R2 Within

0.006

0.006

0.009

0.009

R2 Within Adj.

0.001

0.001

0.004

0.004

AIC

93.4

93.4

92.1

92.1

BIC

914.8

914.8

913.5

913.5

RMSE

0.17

0.17

0.17

0.17

Std.Errors

by: pin10

by: pin10

by: pin10

by: pin10

FE: pin

X

X

X

X

FE: year

X

X

FE: sale_year

X

X

+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Table 9: Table 11: Mapped out of EFFECTIVE SFHA - Restricted Sample
Code
#table(bldg_sfha$added_pre_thisyear, bldg_sfha$year)

# function with default options:
fn_valid_cohorts <- function(df){
  
  df |> 
    mutate(eff_date_chr = as.character(eff_date_chr)) |>
    group_by(eff_date_chr) |>
    summarise(
      n = n(),
      n_treated = sum(addedto_eff_sfha == TRUE, na.rm = TRUE),
      n_untreated = sum(addedto_eff_sfha == FALSE, na.rm = TRUE),
          .groups = "drop"

      ) |>
    filter(n_treated > 0,  n_untreated > 0) |>
    pull(eff_date_chr)
}




table(bldg_sfha$Triad, bldg_sfha$prelim_sfha_category)
       
        Added to prelim SFHA Always SFHA Never SFHA Removed from prelim SFHA
  South                    2        5244     157264                      129
  North                   11        5228     145900                       26
  City                     0        4311     350481                        0
Code
df_sub <- bldg_sfha |> 
  filter(change_type_prelim != "Always SFHA" & 
           # and properties that are never treated for being removed from the SFHA
           treat_year_remove_prelim == 10000 & treat_year_remove_eff == 10000)

valid_cohorts_bldg <- fn_valid_cohorts(df_sub)
bldgs_fsplit <- df_sub |> filter(eff_date_chr %in% valid_cohorts_bldg)


# table(parcels$Triad, parcels$prelim_sfha_category)
# table(parcels$removed_pre_thisyear, parcels$year)

df_sub_parcels <- parcels |> 
  filter(change_type_prelim != "Always SFHA" & 
           # and properties that are never treated for being removed from the SFHA
           treat_year_remove_prelim == 10000 & treat_year_remove_eff == 10000)
valid_cohorts_parcels <- fn_valid_cohorts(df = df_sub_parcels)
parcels_fsplit <- df_sub_parcels |> filter(eff_date_chr %in% valid_cohorts_parcels)


# 
# table(bldg_1in500$removed_pre_thisyear, bldg_1in500$year)
# table(bldg_1in500$removedfrom_prelim_sfha, bldg_1in500$year)
# table(bldg_1in500$Triad, bldg_1in500$prelim_sfha_category)

df_sub_1in500 <- bldg_1in500 |> 
  filter(change_type != "Always SFHA" & 
           # and properties that are never treated for being removed from the SFHA
           treat_year_remove_prelim == 10000 & treat_year_remove_eff == 10000)
valid_cohorts_1in500 <- fn_valid_cohorts(df = df_sub_1in500)
bldg_1in500_fsplit <- df_sub_1in500 |> filter(eff_date_chr %in% valid_cohorts_1in500)




models <- list(

  # "Buildings in SFHA" =  feols(
  #   log(sale_price) ~ addedto_eff_sfha | pin + sale_year,
  #   fsplit = ~eff_date_chr,
  #   cluster = ~pin10,
  #   data = bldgs_fsplit
  # ),

  "Buildings in SFHA" =  feols(
    log(sale_price) ~ addedto_eff_sfha | pin + sale_year,
    cluster = ~pin10,
    data = bldg_sfha
  ),

  "Buildings - 1-in-500" =  feols(
    log(sale_price) ~ addedto_eff_sfha | pin + year,
    fsplit = ~eff_date_chr,
    cluster = ~pin10,
    data = bldg_1in500_fsplit
  ),

  "Buildings - 1-in-500" =  feols(
    log(sale_price) ~ addedto_eff_sfha | pin + sale_year,
    cluster = ~pin10,
    data = bldg_1in500
  )
)

all_models_formatted <- modelsummary::modelsummary(models,
                                                   output = "flextable",
                                                   fmt = function(x) round(x, 2),
                                                   
                                                   stars = TRUE)

all_models_formatted

Buildings in SFHA

Buildings - 1-in-500 sample: Full sample

Buildings - 1-in-500 sample: 2019-11-01

Buildings - 1-in-500

addedto_eff_sfhaTRUE

0.07

0.05**

0.05**

0.05**

(0.09)

(0.02)

(0.02)

(0.02)

Num.Obs.

667288

11614

11614

667288

R2

0.859

0.884

0.884

0.859

R2 Adj.

0.751

0.792

0.792

0.751

R2 Within

0.000

0.000

0.000

0.000

R2 Within Adj.

-0.000

0.000

0.000

-0.000

AIC

966832.7

4509.7

4509.7

966831.6

BIC

4276347.0

42546.0

42546.0

4276345.8

RMSE

0.32

0.19

0.19

0.32

Std.Errors

by: pin10

by: pin10

by: pin10

by: pin10

FE: pin

X

X

X

X

FE: sale_year

X

X

FE: year

X

X

+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Table 11: Mapped into EFFECTIVE SFHA - Restricted Sample

Table 11: Countywide Effective Map Dates

Code
eff_twfe = list(
 "I-Eff" =  feols(
    log(sale_price) ~ addedto_eff_sfha  | pin + sale_year,
    cluster = ~ pin10,
        data = bldg_sfha
  ),

    "I-Eff Subset" = feols(
    log(sale_price) ~ addedto_eff_sfha  | pin + sale_year,
    cluster = ~ pin10,
        data = (bldg_sfha |> filter(change_type != "Always SFHA" & treat_year_remove_eff == 10000)),

  ),

  "II-Eff" =  feols(
    log(sale_price) ~ removedfrom_eff_sfha | pin + sale_year,
    cluster = ~ pin10,
        data = bldg_sfha,

  ),


  "II-Eff Subset" =  feols(
    log(sale_price) ~ removedfrom_eff_sfha | pin + sale_year,
    cluster = ~ pin10,
        data = bldg_sfha |> filter(change_type != "Never SFHA" & treat_year_add_prelim == 10000),

  ),
 
  # Symmetric effect of location in risk zone
    "III-Eff" = feols(
    log(sale_price) ~ in_eff_sfha | pin + sale_year,
    cluster = ~ pin10,
        data = bldg_sfha,

  ),
  # Asymmetric: added vs removed in one equation
  "0-Eff" = feols(
    log(sale_price) ~ addedto_eff_sfha + removedfrom_eff_sfha | pin + sale_year,
    cluster = ~ pin10,
        data = bldg_sfha,

  )
 )
all_models_formatted <- modelsummary::modelsummary(eff_twfe,
               output = "flextable",
               fmt = function(x) round(x, 2),

             stars = TRUE)

all_models_formatted

I-Eff

I-Eff Subset

II-Eff

II-Eff Subset

III-Eff

0-Eff

addedto_eff_sfhaTRUE

0.07

0.07

0.07

(0.09)

(0.09)

(0.09)

removedfrom_eff_sfhaTRUE

0.18***

0.22***

0.18***

(0.03)

(0.05)

(0.03)

in_eff_sfhaTRUE

-0.17***

(0.03)

Num.Obs.

667288

652241

667288

15053

667288

667288

R2

0.859

0.858

0.859

0.895

0.859

0.859

R2 Adj.

0.751

0.749

0.751

0.813

0.751

0.751

R2 Within

0.000

0.000

0.000

0.002

0.000

0.000

R2 Within Adj.

-0.000

-0.000

0.000

0.002

0.000

0.000

AIC

966832.7

949625.6

966819.2

16137.7

966820.0

966821.1

BIC

4276347.0

4177830.0

4276333.4

66257.7

4276334.2

4276346.8

RMSE

0.32

0.32

0.32

0.27

0.32

0.32

Std.Errors

by: pin10

by: pin10

by: pin10

by: pin10

by: pin10

by: pin10

FE: pin

X

X

X

X

X

X

FE: sale_year

X

X

X

X

X

X

+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Code
mods <- list (
 no_fe <- feols(
    log_price ~ in_eff_sfha,
    vcov = ~pin10,
    data = bldg_sfha
  ),
  
 one_fe <- feols(
    log_price ~ in_eff_sfha | sale_year,
    data = bldg_sfha,
     vcov = ~pin10
  ),
pin_fe <- feols(
    log_price ~ in_eff_sfha | pin ,
    data = bldg_sfha,
        vcov = ~pin10,
  ),
  
  two_fe <- feols(
    log_price ~ in_eff_sfha | pin + sale_year,
    data = bldg_sfha,
     vcov = ~pin10
  ),
  
  three_fe <- feols(
    log_price ~ in_eff_sfha | pin + sale_year + qtr,
    data = bldg_sfha,
     vcov = ~pin10
)
)


coef_labels = c(
  "in_prelim_sfhaTRUE" = "In SFHA (Preliminary Map)",
  "in_eff_sfhaTRUE"    = "In SFHA (Effective Map)"
)

modelsummary::modelsummary(
  mods,
  stars = TRUE,
 gof_omit = "IC|Log|Adj|Within"
,  coef_map = coef_labels
)
(1) (2) (3) (4) (5)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
In SFHA (Effective Map) -0.194** -0.190** -0.615*** -0.169*** -0.164***
(0.067) (0.067) (0.040) (0.034) (0.033)
Num.Obs. 668596 668596 667288 667288 667288
R2 0.001 0.076 0.796 0.859 0.860
RMSE 0.86 0.83 0.39 0.32 0.32
Std.Errors by: pin10 by: pin10 by: pin10 by: pin10 by: pin10
FE: sale_year X X X
FE: pin X X X
FE: qtr X
Table 10: Table 11

Table 12 & 13

Code
tbl <- readRDS("outputs/tables/grid_tbl_twfe.rds")
tbl
Model Term
Bldg - SFHA
Parcel - SFHA
Bldg - 1-in-500
Parcel - 1-in-500
Preliminary Effective Preliminary Effective Preliminary Effective Preliminary Effective
County - Symmetric In SFHA -0.03
(0.04)
-0.17***
(0.03)
0.04
(0.04)
-0.14***
(0.03)
-0.01
(0.02)
0.02
(0.02)
0.05
(0.04)
0.02
(0.04)
County - R Mapped Out 0.04
(0.04)
0.18***
(0.03)
-0.05
(0.04)
0.15***
(0.03)
0.05
(0.08)
0.09
(0.06)
-0.10+
(0.06)
0.02
(0.05)
County - Add Mapped In 0.13
(0.10)
0.07
(0.09)
0.03
(0.09)
0.00
(0.04)
0.00
(0.02)
0.05**
(0.02)
0.03
(0.05)
0.06
(0.05)
Triads - Symmetric In SFHA -0.01
(0.03)
-0.17***
(0.03)
0.03
(0.04)
-0.14***
(0.03)
0.00
(0.02)
0.02
(0.02)
0.07
(0.05)
0.02
(0.04)
Triads - Symmetric In SFHA × North -0.07
(0.12)
NA 0.06
(0.09)
NA -0.05
(0.09)
NA -0.05
(0.07)
NA
Triads - R Mapped Out 0.01
(0.03)
0.18***
(0.03)
-0.03
(0.04)
0.15***
(0.03)
-0.05
(0.06)
0.09
(0.06)
-0.07
(0.06)
0.02
(0.05)
Triads - R Mapped Out × North 0.17
(0.13)
NA -0.12
(0.12)
NA 0.23+
(0.14)
NA -0.12
(0.17)
NA
Triads - A Mapped In 0.03***
(0.00)
0.07
(0.09)
-0.13**
(0.04)
0.00
(0.04)
-0.01
(0.02)
0.05**
(0.02)
0.07
(0.08)
0.06
(0.05)
Triads - A Mapped In × North 0.11
(0.12)
NA 0.17
(0.11)
NA 0.07
(0.06)
NA -0.07
(0.10)
NA
* p < 0.1, ** p < 0.05, *** p < 0.01

Table 14

Code
tbl <- readRDS("outputs/tables/grid_tbl_etwfe.rds")
tbl
Model Term
Buildings - SFHA
Parcels - SFHA
Buildings - 1 in 500
Parcels - 1 in 500
Preliminary Effective Preliminary Effective Preliminary Effective Preliminary Effective
County - Add Overall ATT -0.017***
(0.001)
0.111***
(0.033)
0.265*
(0.140)
0.043***
(0.001)
0.214***
(0.039)
0.104***
(0.014)
0.110
(0.073)
0.057
(0.054)
County - R Overall ATT 0.138**
(0.061)
0.216***
(0.066)
0.102
(1333.565)
0.131**
(0.060)
-0.044
(0.070)
0.036
(0.137)
-0.076
(1706.536)
0.014
(0.161)
Triads - Add North -0.039***
(0.002)
NA 0.214
(0.159)
NA -0.016
(0.088)
NA 0.068
(0.098)
NA
Triads - Add South 0.000
( NA)
0.030
(0.033)
-0.177***
(0.007)
0.048***
(0.003)
0.112***
(0.043)
-0.014
(0.014)
-0.074
(0.110)
-0.077
(0.054)
Triads - R North 0.107***
(0.037)
NA 0.136
(10565.428)
NA 0.061*
(0.032)
NA -0.817
(6959.621)
NA
Triads - R South -0.043
(0.072)
0.017
(0.068)
-0.028
(0.062)
0.022
(0.061)
-0.369***
(0.116)
-0.143
(0.137)
-0.094
(0.094)
-0.113
(0.162)
Cohorts - Add 2015 0.000
( NA)
NA -0.143***
(0.005)
NA 0.193***
(0.043)
NA 0.003
(0.110)
NA
Cohorts - Add 2019 NA 0.088***
(0.033)
NA 0.046***
(0.002)
NA 0.070***
(0.014)
NA 0.018
(0.054)
Cohorts - Add 2021 -0.025***
(0.001)
NA 0.294*
(0.158)
NA 0.057
(0.088)
NA 0.146
(0.098)
NA
Cohorts - R 2015 0.132*
(0.072)
NA 0.084
(0.062)
NA -0.168
(0.117)
NA 0.058
(0.095)
NA
Cohorts - R 2019 NA 0.215***
(0.066)
NA 0.130**
(0.060)
NA 0.040
(0.137)
NA 0.014
(0.161)
Cohorts - R 2021 0.181***
(0.032)
NA 0.309
(11627.637)
NA 0.137***
(0.030)
NA -0.465
(7696.784)
NA
p < 0.1, ** p < 0.05, *** p < 0.01
  • p < 0.1, ** p < 0.05, *** p < 0.01

Appendix A

Table 16 & 17, and Figure 11

Code
out_dir <- "outputs/q2_marginaleffects"
prep_path <- file.path(out_dir, "bldg_sfha_q2_dissertation_prepped.rds")
if (!file.exists(prep_path)) {
  source("scripts/q2_dissertation_00_prep_data.R")
}

bldg_sfha <- readRDS(prep_path)

df_prep <- bldg_sfha |>
  mutate(
    pre_year = if ("pre_year" %in% names(df)) pre_year else as.integer(format(pre_date, "%Y")),
    eff_year = if ("eff_year" %in% names(df)) eff_year else as.integer(format(eff_date, "%Y")),
    event_time = sale_year - pre_year,
    event_time = ifelse(event_time == 0 & sale_date < pre_date, -1, event_time)
  )

# ----  bin leads/lags ----
df_prep <- df_prep |>
  mutate(
    event_time_binned = case_when(
      event_time <= -5 ~ -5L,
      event_time >=  5 ~  5L,
      TRUE ~ as.integer(event_time)
    )
  )

# ---- define sample + treatment (Added vs Never) ----
# Added to SFHA comparison!
df_es_add <- df_prep |>
  filter(prelim_sfha_category %in% c("Added to prelim SFHA", "Never SFHA")) |>
  mutate(treated = prelim_sfha_category == "Added to prelim SFHA")

# ---- (A) event-study regression ----
# base model, no flexible fixed effects
mod_es_add_none <- feols(
  log(sale_price) ~ i(event_time_binned, treated, ref = -1) | pin + sale_year,
  cluster = ~ pin10,
  data = df_es_add
)


# ----  plot ----
iplot(mod_es_add_none, xlab = "Years relative to map year", main = "Event Study: Added")

Code
# ---- pre-trend check (leads only) ----
pretrends <- broom::tidy(mod_es_add_none) |>
  filter(str_detect(term, "^event_time_binned::")) |>
  mutate(rel_time = as.integer(str_extract(term, "-?\\d+"))) |>
  filter(rel_time < 0) |>
  arrange(rel_time)

pretrends
term estimate std.error statistic p.value rel_time
event_time_binned::-5:treated 0.1104162 0.0395717 2.790280 0.0052668 -5
event_time_binned::-4:treated -0.5796238 0.0063581 -91.162598 0.0000000 -4
event_time_binned::-3:treated -0.2672640 0.0038471 -69.470805 0.0000000 -3
event_time_binned::-2:treated -0.0340017 0.0048248 -7.047211 0.0000000 -2
Code
# ----  joint test of pre-trends ----
wald(mod_es_add_none, keep = "event_time_binned::-(5|4|3|2)")
Wald test, H0: joint nullity of event_time_binned::-5:treated, event_time_binned::-4:treated, event_time_binned::-3:treated and event_time_binned::-2:treated
 stat = 2,874.2, p-value < 2.2e-16, on 4 and 368,846 DoF, VCOV: Clustered (pin10).

Removed from SFHA - Event Study

Code
# --- define sample + treatment (Removed vs Always) ----
df_es_removed <- df_prep |>
  filter(prelim_sfha_category %in% c("Removed from prelim SFHA", "Always SFHA")) |>
  mutate(treated = prelim_sfha_category == "Removed from prelim SFHA")

range(df_es_removed$sale_year, na.rm = TRUE)
[1] 2010 2025
Code
# ---- event-study regression ----
mod_es_removed_none <- feols(
  log(sale_price) ~ i(event_time_binned, treated, ref = -1) | pin + sale_year,
  cluster = ~ pin10,
  data = df_es_removed
)

# ----  plot ----
iplot(mod_es_removed_none, xlab = "Years relative to map year", main = "Event Study: Removed")

This spec is implicitly assuming North Cook, South Cook, and Chicago share a single price trend. That assumption is false. Regional growth differences get misattributed to the flood-risk information shock.

Code
# ---- pre-trend check (leads only) ----
pretrends <- broom::tidy(mod_es_removed_none) |>
  filter(str_detect(term, "^event_time_binned::")) |>
  mutate(rel_time = as.integer(str_extract(term, "-?\\d+"))) |>
  filter(rel_time < 0) |>
  arrange(rel_time)

pretrends
term estimate std.error statistic p.value rel_time
event_time_binned::-5:treated -0.0272036 0.1429671 -0.1902788 0.8491055 -5
event_time_binned::-4:treated 0.1548045 0.0941084 1.6449593 0.1000987 -4
event_time_binned::-3:treated 0.0670327 0.0770443 0.8700546 0.3843506 -3
event_time_binned::-2:treated -0.0432119 0.1293224 -0.3341406 0.7383003 -2
Code
# ----  joint test of pre-trends ----
wald(mod_es_removed_none, keep = "event_time_binned::-(5|4|3|2)")
Wald test, H0: joint nullity of event_time_binned::-5:treated, event_time_binned::-4:treated, event_time_binned::-3:treated and event_time_binned::-2:treated
 stat = 1.16631, p-value = 0.323484, on 4 and 8,383 DoF, VCOV: Clustered (pin10).

Passed pre-trend test: Coefficients were not statistically significant from each other.

Figure 12: Local Average Treatment Effect

Code
mod2 = etwfe(
  fml = log_price ~ 1, 
  tvar = sale_year, 
  ivar = pin, 
  xvar = group_name_prelim, 
    cgroup = "never",

  gvar = treat_year_remove_prelim, 
  vcov = ~pin10, 
  data = (df_prep|> 
            # do not want treated properties that move in opposite direction. 
            filter(treat_year_add_prelim==10000 &
                     # and only want to compare to properties that were Always in the SFHA (also keeps properties that changed SFHA status), 
                     change_type_prelim != "Never SFHA") 
          )
  )
#mod2
emfx(mod2, type = "group")

 group_name_prelim treat_year_remove_prelim Estimate Std. Error    z Pr(>|z|)
              2015                     2015    0.132     0.0721 1.83   0.0669
              2021                     2021    0.181     0.0318 5.68   <0.001
    S   2.5 % 97.5 %
  3.9 -0.0092  0.273
 26.2  0.1186  0.243

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
plot(emfx(mod2), pch = c(16, 15, 18, 19))

Code
plot(emfx(mod2, type = "event"), pch = c(16, 15, 18, 19)) 

Code
mod2 = etwfe(
  fml = log_price ~ 1, 
  tvar = sale_year, 
  ivar = pin, 
  xvar = group_name_prelim, 
    cgroup = "never",

  gvar = treat_year_add_prelim, 
  vcov = ~pin10, 
  data = (df_prep|> 
            # do not want treated properties that move in opposite direction. 
            filter(treat_year_remove_prelim==10000 &
                     # and only want to compare to properties that were Never in the SFHA (also keeps properties that changed SFHA status), 
                     change_type_prelim != "Always SFHA") 
          )
  )
#mod2
emfx(mod2, type = "group")

 group_name_prelim treat_year_add_prelim Estimate Std. Error     z Pr(>|z|)
              2015                  2015   0.0000         NA    NA       NA
              2021                  2021  -0.0251    0.00106 -23.8   <0.001
     S   2.5 % 97.5 %
    NA      NA     NA
 411.9 -0.0272 -0.023

Term: .Dtreat
Type: response
Comparison: TRUE - FALSE
Code
plot(emfx(mod2), pch = c(16, 15, 18, 19))

Code
plot(emfx(mod2, type = "event"), pch = c(16, 15, 18, 19)) 

Code
# mod2 = etwfe(
#   fml = log_price ~ 1, 
#   tvar = sale_year, 
#   ivar = pin, 
#   xvar = group_name_prelim, 
#     cgroup = "notyet",
# 
#   gvar = treat_year_add_prelim, 
#   vcov = ~pin10, 
#   data = (df_prep|> 
#             # do not want treated properties that move in opposite direction. 
#             filter(treat_year_remove_prelim==10000 &
#                      # and only want to compare to properties that were Never in the SFHA (also keeps properties that changed SFHA status), 
#                      change_type_prelim != "Always SFHA") 
#           )
#   )
# #mod2
# emfx(mod2, type = "group")
# plot(emfx(mod2), pch = c(16, 15, 18, 19))
# plot(emfx(mod2, type = "event"), pch = c(16, 15, 18, 19))

Table 17: Q1 Placebo Test

Purpose: Estimate treatment effects in periods before any treatment occurs.

Why: If “effects” show up before treatment, you’ve got either model misspecification or anticipation dynamics. Either way, your core assumptions are shaky.

Code
# df_prep must have: pin, pin10, sale_date, sale_year, pre_date, prelim_sfha_category, log_price

run_placebo_did <- function(df,
                            fake_shift_years = 2,
                            treated_label = "Added to prelim SFHA",
                            unit_fe = "pin",
                            time_fe = "sale_year",
                            cluster_var = "pin10") {
  if(treated_label == "Added to prelim SFHA"){
    df <- df |> filter(prelim_sfha_category %in% c("Added to prelim SFHA", "Never SFHA"))
  }
  if(treated_label == "Removed from prelim SFHA"){
    df <- df |> filter(prelim_sfha_category %in% c("Removed from prelim SFHA", "Always SFHA"))
  }
  df_placebo <- df |>
    mutate(
      treated = prelim_sfha_category == treated_label,
      placebo_date = pre_date %m-% years(fake_shift_years),
      placebo_post = sale_date >= placebo_date
    ) |>
    # keep only observations strictly before the *real* event (so placebo is truly placebo)
    filter(sale_date < pre_date) |>
    # optional: keep only observations with a defined placebo_date
    filter(!is.na(placebo_date))

  fml <- as.formula(paste0(
    "log_price ~ treated * placebo_post | ",
    unit_fe, " + ", time_fe
  ))

  feols(
    fml,
    cluster = as.formula(paste0("~", cluster_var)),
    data = df_placebo
  )
}


m_placebo <- run_placebo_did(bldg_sfha, treated_label = "Removed from prelim SFHA",
                             fake_shift_years = 2)
etable(m_placebo)
m_placebo
Dependent Var.: log_price
placebo_postTRUE -0.1085 (0.1330)
treatedTRUE x placebo_postTRUE -0.1975 (0.1934)
Fixed-Effects: —————-
pin Yes
sale_year Yes
______________________________ ________________
S.E.: Clustered by: pin10
Observations 224
R2 0.96363
Within R2 0.01679
Code
m_placebo <- run_placebo_did(bldg_sfha, treated_label = "Added to prelim SFHA",
                             fake_shift_years = 2)
etable(m_placebo)
m_placebo
Dependent Var.: log_price
placebo_postTRUE 0.0126 (0.0105)
treatedTRUE x placebo_postTRUE 0.3056*** (0.0127)
Fixed-Effects: ——————
pin Yes
sale_year Yes
______________________________ __________________
S.E.: Clustered by: pin10
Observations 64,308
R2 0.89959
Within R2 6.12e-5
Code
run_normal_did <- function(df,
                            treated_label = "Added to prelim SFHA",
                            unit_fe = "pin",
                            time_fe = "sale_year",
                            cluster_var = "pin10") {
  if(treated_label == "Added to prelim SFHA"){
    df <- df |> filter(prelim_sfha_category %in% c("Added to prelim SFHA", "Never SFHA"))
  }
  if(treated_label == "Removed from prelim SFHA"){
    df <- df |> filter(prelim_sfha_category %in% c("Removed from prelim SFHA", "Always SFHA"))
  }

  df_did <- df |>
    mutate(
      treated = prelim_sfha_category == treated_label, #== treated_label,
      treated_date = pre_date,
      event_post = (sale_date >= treated_date  & pre_date_chr != "2005-01-01")
    ) 

  fml <- as.formula(paste0(
    "log_price ~ treated * event_post | ",
    unit_fe, " + ", time_fe
  ))

  feols(
    fml,
    cluster = as.formula(paste0("~", cluster_var)),
    data = df_did
  )
}


normal_did_add <- run_normal_did(bldg_sfha, treated_label = "Added to prelim SFHA")
etable(normal_did_add)
normal_did_remove <- run_normal_did(bldg_sfha, treated_label = "Removed from prelim SFHA")
etable(normal_did_remove)
normal_did_add
Dependent Var.: log_price
event_postTRUE -0.1968*** (0.0074)
treatedTRUE x event_postTRUE 0.2844** (0.1003)
Fixed-Effects: ——————-
pin Yes
sale_year Yes
____________________________ ___________________
S.E.: Clustered by: pin10
Observations 652,380
R2 0.85960
Within R2 0.01033
normal_did_rem..
Dependent Var.: log_price
event_postTRUE -0.0080 (0.0483)
treatedTRUE x event_postTRUE 0.0897 (0.0568)
Fixed-Effects: —————-
pin Yes
sale_year Yes
____________________________ ________________
S.E.: Clustered by: pin10
Observations 14,908
R2 0.89396
Within R2 0.00023
Table 11: Same set up as placebo test, but without the placebo. Includes all years of data.