Load Libraries

library(sf)
library(maps)
library(ggplot2)
library(ggmap)
library(dplyr)
library(mapdata)
library(tidyr)
library(osmdata)
library(tidyverse)
library(terra)
library(sp)
library(fixest)
library(raster)
library(scpi)
library(MatchIt)
library(knitr)
library(kableExtra)
library(sjPlot)

Data Preperation

Load Cities

#Load the 'world.cities' dataset
data(world.cities)
head(world.cities)
##                 name country.etc   pop   lat  long capital
## 1 'Abasan al-Jadidah   Palestine  5629 31.31 34.34       0
## 2 'Abasan al-Kabirah   Palestine 18999 31.32 34.35       0
## 3       'Abdul Hakim    Pakistan 47788 30.55 72.11       0
## 4 'Abdullah-as-Salam      Kuwait 21817 29.36 47.98       0
## 5              'Abud   Palestine  2456 32.03 35.07       0
## 6            'Abwein   Palestine  3434 32.03 35.20       0
filtered_cities <- subset(world.cities, country.etc %in% c("France", "Germany", "South Africa"))
#Define the host cities for each country
host_cities_list <- list(
  France = c("Paris", "Marseille", "Lyon", "Lens", "Nantes", "Toulouse", "Saint-Etienne", "Bordeaux", "Montpellier"),
  `South Africa` = c("Johannesburg", "Cape Town", "Durban", "Pretoria", "Port Elizabeth", "Rustenburg", 
                     "Pietersburg", "Nelspruit", "Bloemfontein"),
  Germany = c("Berlin", "Dortmund", "Frankfurt", "Gelsenkirchen", "Hamburg", "Hanover", 
              "Kaiserslautern", "Cologne", "Leipzig", "Munich", "Nuremberg", "Stuttgart")
)

#Normalize city names and countries to handle potential mismatches
filtered_cities$name <- tolower(filtered_cities$name)
filtered_cities$country.etc <- tolower(filtered_cities$country.etc)

host_cities_list <- lapply(host_cities_list, tolower)

#Add a column 'host_city' and initialize it with 0
filtered_cities$host_city <- 0

#Update the 'host_city' column to 1 for host cities
for (country in names(host_cities_list)) {
  cities <- host_cities_list[[country]]
  country_index <- filtered_cities$country.etc == tolower(country)
  city_index <- filtered_cities$name %in% cities
  filtered_cities$host_city[country_index & city_index] <- 1
}

Filter Out Problematic cities or Rename them

filtered_cities<- subset(filtered_cities, !(name == "frankfurt" & pop == 64389))

filtered_cities <- filtered_cities %>%
  mutate(name = ifelse(name == "emnambithi", "ladysmith", name))

filtered_cities <- filtered_cities %>%
  mutate(name = ifelse(name == "spires", "speyer", name))

Filter out unnecassary cities

filtered_cities_fr <- subset(filtered_cities, country.etc == "france" & as.numeric(pop) > 20000)
filtered_cities_ge <- subset(filtered_cities, country.etc == "germany" & as.numeric(pop) > 20000)
filtered_cities_sa <- subset(filtered_cities, country.etc == "south africa" & as.numeric(pop) > 20000)

Match the cities to polygons

library(osmdata)
library(sf)
library(dplyr)

#Function to convert bounding box to polygon 
bbox_to_polygon <- function(location, country) {
  #Attempt to get bounding box from osmdata
  bbox <- tryCatch(
    {
      full_location <- paste(location, country, sep = ", ")
      getbb(full_location)
    },
    error = function(e) {
      message(paste("Error retrieving bounding box for:", full_location, ":", e$message))
      return(NULL)
    }
  )
  
  
  if (is.null(bbox)) {
    return(NULL)
  }
  
  min_x <- bbox["x", "min"]
  max_x <- bbox["x", "max"]
  min_y <- bbox["y", "min"]
  max_y <- bbox["y", "max"]
  
  
  coords <- matrix(c(
    min_x, min_y,
    max_x, min_y,
    max_x, max_y,
    min_x, max_y,
    min_x, min_y
  ), ncol = 2, byrow = TRUE)
  
  polygon <- st_polygon(list(coords))
  
  sf_polygon <- st_sfc(polygon, crs = 4326)
  
  return(sf_polygon)
}

#Function to create a polygon from a row
polygon_from_row <- function(row) {
  loc <- row$name
  country <- row$country.etc
  bbox_to_polygon(loc, country)
}

Apply the polygon function to all the Countries

#Apply the function to each row
filtered_cities_fr <- filtered_cities_fr %>%
  rowwise() %>%
  mutate(polygon = list(polygon_from_row(cur_data()))) %>%
  ungroup()
## Error retrieving bounding box for: bruay-la-brussiere, france : `place_name` 'bruay-la-brussiere, france' can't be found
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `polygon = list(polygon_from_row(cur_data()))`.
## ℹ In row 1.
## Caused by warning:
## ! `cur_data()` was deprecated in dplyr 1.1.0.
## ℹ Please use `pick()` instead.
#Apply the function to each row
filtered_cities_ge <- filtered_cities_ge %>%
  rowwise() %>%
  mutate(polygon = list(polygon_from_row(cur_data()))) %>%
  ungroup()
#Apply the function to each row
filtered_cities_sa <- filtered_cities_sa %>%
  rowwise() %>%
  mutate(polygon = list(polygon_from_row(cur_data()))) %>%
  ungroup()
## Error retrieving bounding box for: bulfontein, south africa : `place_name` 'bulfontein, south africa' can't be found
## Error retrieving bounding box for: epumalanga, south africa : `place_name` 'epumalanga, south africa' can't be found
## Error retrieving bounding box for: garankuwa, south africa : `place_name` 'garankuwa, south africa' can't be found
## Error retrieving bounding box for: kwangema, south africa : `place_name` 'kwangema, south africa' can't be found
## Error retrieving bounding box for: nkowakowa, south africa : `place_name` 'nkowakowa, south africa' can't be found
filtered_cities_fr_sf <- filtered_cities_fr %>%
  unnest_wider(polygon, names_sep = '_1') %>%
  st_as_sf()
filtered_cities_ge_sf <- filtered_cities_ge %>%
  unnest_wider(polygon, names_sep = '_1') %>%
  st_as_sf()
filtered_cities_sa_sf <- filtered_cities_sa %>%
  unnest_wider(polygon, names_sep = '_1') %>%
  st_as_sf()

Remove bugs in Polygons

filtered_cities_ge_sf$polygon_area <- st_area(filtered_cities_ge_sf$polygon_11)
filtered_cities_fr_sf$polygon_area <- st_area(filtered_cities_fr_sf$polygon_11)
filtered_cities_sa_sf$polygon_area <- st_area(filtered_cities_sa_sf$polygon_11)

#Filter filtered_cities_ge_sf dataset
filtered_cities_ge_sf <- filtered_cities_ge_sf %>%
  filter(!(host_city == 0 & as.numeric(polygon_area) > 10000000000))

#Filter filtered_cities_fr_sf dataset
filtered_cities_fr_sf <- filtered_cities_fr_sf %>%
  filter(!(host_city == 0 & as.numeric(polygon_area) > 10000000000))

#Filter filtered_cities_sa_sf dataset
filtered_cities_sa_sf <- filtered_cities_sa_sf %>%
  filter(!(host_city == 0 & as.numeric(polygon_area) > 10000000000))

saveRDS(filtered_cities_fr_sf, "filtered_cities_fr_sf.rds")
saveRDS(filtered_cities_ge_sf, "filtered_cities_ge_sf.rds")
saveRDS(filtered_cities_sa_sf, "filtered_cities_sa_sf.rds")
filtered_cities_ge_sf <- readRDS("filtered_cities_ge_sf.rds")
filtered_cities_fr_sf <- readRDS("filtered_cities_fr_sf.rds")
filtered_cities_sa_sf <- readRDS("filtered_cities_sa_sf.rds")

Bounding boxes to only contain Mainland

#Define bounding boxes for mainland regions
bounding_boxes <- list(
  Germany = c(xmin = 5.87, xmax = 15.04, ymin = 47.27, ymax = 55.12),
  South_Africa = c(xmin = 16.28, xmax = 32.89, ymin = -34.83, ymax = -22.09),
  France = c(xmin = -5.25, xmax = 9.6, ymin = 41.25, ymax = 51.1)
)

#Define the CRS
crs <- st_crs("+proj=longlat +datum=WGS84")

#Convert bounding boxes to sf objects
bounding_boxes_sf <- lapply(bounding_boxes, function(bb) {
  st_bbox(bb, crs = crs) %>%
    st_as_sfc()
})

#Filter each dataset for mainland regions
filtered_cities_fr_sf <- st_intersection(filtered_cities_fr_sf, bounding_boxes_sf[["France"]])
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
filtered_cities_ge_sf <- st_intersection(filtered_cities_ge_sf, bounding_boxes_sf[["Germany"]])
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
filtered_cities_sa_sf <- st_intersection(filtered_cities_sa_sf, bounding_boxes_sf[["South_Africa"]])
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries

Plot city polygons and a comparison in nightlights for South Africa

ggplot(data = filtered_cities_fr_sf) +
  geom_sf(color = "black", alpha = 0.5) +  
  theme_minimal() +
  labs(title = "City Polygons in France")

ggplot(data = filtered_cities_ge_sf) +
  geom_sf(color = "black", alpha = 0.5) +  
  theme_minimal() +
  labs(title = "City Polygons in Germany")

ggplot(data = filtered_cities_sa_sf) +
  geom_sf(color = "black", alpha = 0.5) +  
  theme_minimal() +
  labs(title = "City Polygons in South Africa")

shapefile_sa <- st_read("sa/gadm41_ZAF_0.shp")
## Reading layer `gadm41_ZAF_0' from data source 
##   `/Users/stevendenotter/Documents/Scriptie/SA/gadm41_ZAF_0.shp' 
##   using driver `ESRI Shapefile'
## Simple feature collection with 1 feature and 2 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: 16.45189 ymin: -34.83514 xmax: 32.89125 ymax: -22.12503
## Geodetic CRS:  WGS 84
nightlights_sa_94 <- raster("nightlights/F121994.v4b_web.stable_lights.avg_vis.tif")
nightlights_sa_13 <- raster("nightlights/F182013.v4c_web.stable_lights.avg_vis.tif")

st_as_sf(shapefile_sa)
## Simple feature collection with 1 feature and 2 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: 16.45189 ymin: -34.83514 xmax: 32.89125 ymax: -22.12503
## Geodetic CRS:  WGS 84
##   GID_0      COUNTRY                       geometry
## 1   ZAF South Africa MULTIPOLYGON (((19.66291 -3...
nl_sa_94 <- crop(nightlights_sa_94, shapefile_sa)
nl_sa_13 <- crop(nightlights_sa_13, shapefile_sa)

nl_sa_94 <- raster::mask(nl_sa_94 ,mask=shapefile_sa)
nl_sa_13 <- raster::mask(nl_sa_13 ,mask=shapefile_sa)

par(mfrow = c(1, 2))

#Plot 1: Nightlights in South Africa 1994
plot(nl_sa_94, col = gray.colors(100), main = "Nightlights In South Africa in 1994")

#Plot 2: Nightlights in South Africa 2013
plot(nl_sa_13, col = gray.colors(100), main = "Nightlights In South Africa in 2013")

par(mfrow = c(1, 1))

ggplot() +
  geom_sf(data = shapefile_sa, fill = "lightgray", color = "black") +  
  geom_sf(data = filtered_cities_sa_sf, color = "black", alpha = 0.5) +  
  theme_minimal() +
  labs(title = "City Polygons in South Africa", x = "Longitude", y = "Latitude")

Function to extract nightlights per polygon per year per country

library(stringr)
library(purrr)

process_nightlights <- function(year_range, city_dataset) {
  years <- paste0(year_range, collapse='|')
  files_l <- list.files('nightlights/', pattern = 'stable_lights', full.names = TRUE)
  files <- files_l[str_detect(files_l, years)]
  
  nili_list <- map(files, ~ {
    nili <- rast(.x)
    
    intermediate <- city_dataset |>
      filter(!st_is_empty(polygon_11)) |>
      vect()
    

    nili_per_polygon <- terra::extract(nili, intermediate, fun='mean')
    
   
    neem <- str_extract(.x, "\\d{6}")
    new_name <- paste0('nili_per_polygon', neem, collapse='_')
    names(nili_per_polygon)[2] <- new_name
    return(nili_per_polygon)
  })
  
  return(nili_list)
}


nili_fr_results <- process_nightlights(1994:2002, filtered_cities_fr_sf)
nili_ge_results <- process_nightlights(2002:2010, filtered_cities_ge_sf)
nili_sa_results <- process_nightlights(2006:2013, filtered_cities_sa_sf)
saveRDS(nili_fr_results, "nili_fr_results.rds")
saveRDS(nili_ge_results, "nili_ge_results.rds")
saveRDS(nili_sa_results, "nili_sa_results.rds")
nili_fr_results <- readRDS("nili_fr_results.rds")
nili_ge_results <- readRDS("nili_ge_results.rds")
nili_sa_results <- readRDS("nili_sa_results.rds")
nili_fr_all <- reduce(nili_fr_results, full_join, by = "ID")
nili_ge_all <- reduce(nili_ge_results, full_join, by = "ID")
nili_sa_all <- reduce(nili_sa_results, full_join, by = "ID")
fr_data <- filtered_cities_fr_sf |>
    filter(!st_is_empty(polygon_11))
ge_data <- filtered_cities_ge_sf |>
    filter(!st_is_empty(polygon_11))
sa_data <- filtered_cities_sa_sf |>
    filter(!st_is_empty(polygon_11))
fr_nili_data <- cbind(fr_data, nili_fr_all)
ge_nili_data <- cbind(ge_data, nili_ge_all)
sa_nili_data <- cbind(sa_data, nili_sa_all)

Rename and make the dataframe tidy for analysis

# For France
tidy_fr <- fr_nili_data %>%
  pivot_longer(cols = starts_with("nili_per_polygon"), 
               names_to = "Year", 
               values_to = "Value") %>%
  mutate(Year = str_sub(Year, -4, nchar(Year)))

# For Germany
tidy_ge <- ge_nili_data %>%
  pivot_longer(cols = starts_with("nili_per_polygon"), 
               names_to = "Year", 
               values_to = "Value") %>%
  mutate(Year = str_sub(Year, -4, nchar(Year)))

# For South Africa
tidy_sa <- sa_nili_data %>%
  pivot_longer(cols = starts_with("nili_per_polygon"), 
               names_to = "Year", 
               values_to = "Value") %>%
  mutate(Year = str_sub(Year, -4, nchar(Year)))
saveRDS(tidy_fr, "tidy_fr.rds")
saveRDS(tidy_ge, "tidy_ge.rds")
saveRDS(tidy_sa, "tidy_sa.rds")

Compute Nightlights trend before Treatment

tidy_fr <- tidy_fr %>%
  mutate(Year = as.numeric(Year))



#Compute Nightlights Trend Before Treatment
nightlights_trend_before_treatment <- tapply(tidy_fr$Value[tidy_fr$Year < 1998], tidy_fr$name[tidy_fr$Year < 1998], function(x) {
  if (all(x == unique(63))) {
    #Constant trend, assign a specific value (e.g., 0)
    return(0)
  } else {
    #Non-constant trend, fit the linear model
    return(lm(x ~ tidy_fr$Year[match(x, tidy_fr$Value)])$coefficients[2])
  }
})


nightlights_trend_before_treatment[is.na(nightlights_trend_before_treatment)] <- 0


trend_df_fr <- data.frame(row_number = 1:length(nightlights_trend_before_treatment),
          nightlights_trend = nightlights_trend_before_treatment)


tidy_ge <- tidy_ge %>%
  mutate(Year = as.numeric(Year))


nightlights_trend_before_treatment_ge <- tapply(tidy_ge$Value[tidy_ge$Year < 2006], tidy_ge$name[tidy_ge$Year < 2006], function(x) {
  if (all(x == unique(63))) {
    return(0)
  } else {
    return(lm(x ~ tidy_ge$Year[match(x, tidy_ge$Value)])$coefficients[2])
  }
})

nightlights_trend_before_treatment_ge[is.na(nightlights_trend_before_treatment_ge)] <- 0


trend_df_ge <- data.frame(row_number = 1:length(nightlights_trend_before_treatment_ge),
          nightlights_trend = nightlights_trend_before_treatment_ge)


tidy_sa <- tidy_sa %>%
  mutate(Year = as.numeric(Year))


nightlights_trend_before_treatment_sa <- tapply(tidy_sa$Value[tidy_sa$Year < 2010], tidy_sa$name[tidy_sa$Year < 2010], function(x) {
  if (all(x == unique(63))) {

    return(0)
  } else {

    return(lm(x ~ tidy_sa$Year[match(x, tidy_sa$Value)])$coefficients[2])
  }
})

nightlights_trend_before_treatment_sa[is.na(nightlights_trend_before_treatment_sa)] <- 0


trend_df_sa <- data.frame(row_number = 1:length(nightlights_trend_before_treatment_sa),
          nightlights_trend = nightlights_trend_before_treatment_sa)

Add trend to Data

#France

filtered_cities_fr_sf <- filtered_cities_fr_sf %>%
  mutate(ID = row_number())

fr_trend <- merge(filtered_cities_fr_sf, tibble(ID = 1:nrow(trend_df_fr), nightlights_trend = nightlights_trend_before_treatment), by = "ID", all.x = TRUE)

cities_with_treatment <- fr_trend[fr_trend$host_city == 1, "name"]

fr_trend <- fr_trend %>%
  filter(!is.na(nightlights_trend))

#Germany
filtered_cities_ge_sf <- filtered_cities_ge_sf %>%
  mutate(ID = row_number())


ge_trend <- merge(filtered_cities_ge_sf, tibble(ID = 1:nrow(trend_df_ge), nightlights_trend = nightlights_trend_before_treatment_ge), by = "ID", all.x = TRUE)


cities_with_treatment <- ge_trend[ge_trend$host_city == 1, "name"]

ge_trend <- ge_trend %>%
  filter(!is.na(nightlights_trend))

#South Africe
filtered_cities_sa_sf <- filtered_cities_sa_sf %>%
  mutate(ID = row_number())


sa_trend <- merge(filtered_cities_sa_sf, tibble(ID = 1:nrow(trend_df_sa), nightlights_trend = nightlights_trend_before_treatment_sa), by = "ID", all.x = TRUE)


cities_with_treatment <- sa_trend[sa_trend$host_city == 1, "name"]

sa_trend <- sa_trend %>%
  filter(!is.na(nightlights_trend))

Propensity Score Matching

Matching for France

#Calculate propensity scores using logistic regression
model_fr <- glm(host_city ~ pop + nightlights_trend, data = fr_trend, family = "binomial")
## Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
pscore <- predict(model_fr, type = "response")

#Match treatment and control groups using nearest neighbor matching
matched_data_fr <- matchit(host_city ~ pop + nightlights_trend, data = fr_trend, method = "nearest")

#Create control group
matched_data_with_distance_fr <- match.data(matched_data_fr)
control_group_fr <- subset(matched_data_with_distance_fr, host_city == 0)
treatment_group_fr <- subset(matched_data_with_distance_fr, host_city == 1)

Matching for Germany

#Calculate propensity scores using logistic regression
model_ge <- glm(host_city ~ pop + nightlights_trend , data = ge_trend, family = "binomial")
## Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
pscore_ge <- predict(model_ge, type = "response")

#Match treatment and control groups using nearest neighbor matching
matched_data_ge <- matchit(host_city ~ pop + nightlights_trend, data = ge_trend, method = "nearest")

#Create control group
matched_data_with_distance_ge <- match.data(matched_data_ge)
control_group_ge <- subset(matched_data_with_distance_ge, matched_data_with_distance_ge$host_city == 0)

control_group_ge
## Simple feature collection with 12 features and 13 fields
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: 6.625631 ymin: 49.41036 xmax: 13.96606 ymax: 54.43294
## Geodetic CRS:  WGS 84
## First 10 features:
##      ID       name country.etc    pop   lat  long capital host_city
## 66   66  bielefeld     germany 335158 52.03  8.53       0         0
## 73   73     bochum     germany 384208 51.48  7.20       0         0
## 74   74       bonn     germany 315138 50.73  7.10       0         0
## 79   79     bremen     germany 547915 53.08  8.81       0         0
## 132 132    dresden     germany 489883 51.05 13.74       0         0
## 134 134   duisburg     germany 502251 51.43  6.75       0         0
## 137 137 dusseldorf     germany 573521 51.24  6.79       0         0
## 167 167      essen     germany 596204 51.47  7.00       0         0
## 321 321       kiel     germany 232422 54.32 10.12       0         0
## 385 385   mannheim     germany 307579 49.50  8.47       0         0
##         polygon_area nightlights_trend                       geometry
## 66   434358443 [m^2]        -1.3742647 POLYGON ((8.377817 51.91487...
## 73   230170261 [m^2]        -1.0246305 POLYGON ((7.102082 51.4105,...
## 74   208815341 [m^2]        -1.1969309 POLYGON ((7.022535 50.63269...
## 79  2209553130 [m^2]        -0.1388525 POLYGON ((8.481593 53.01104...
## 132  609222498 [m^2]        -1.7446087 POLYGON ((13.57932 50.97494...
## 134  357437472 [m^2]        -0.7709877 POLYGON ((6.625631 51.33338...
## 137  443348258 [m^2]        -1.7230952 POLYGON ((6.688815 51.12437...
## 167  350006396 [m^2]        -0.5092954 POLYGON ((6.894344 51.34757...
## 321  243902958 [m^2]        -1.9664706 POLYGON ((10.03293 54.25071...
## 385  254295143 [m^2]        -1.2576923 POLYGON ((8.41416 49.41036,...
##       distance weights subclass
## 66  0.07317060       1       10
## 73  0.11990694       1       11
## 74  0.05461506       1        6
## 79  0.47666062       1        9
## 132 0.40004051       1        1
## 134 0.37388595       1        3
## 137 0.66508079       1       12
## 167 0.65561918       1        5
## 321 0.02363519       1        4
## 385 0.05052467       1        8
#Extract matched data
matched_data_ge_plot <- match.data(matched_data_ge)


#Set up the plotting layout
par(mfrow = c(1, 2))

#Plot 1: Before matching
boxplot(pop ~ host_city, data = ge_trend, 
        main = "Before Matching",
        xlab = "Group", ylab = "Population")

#Plot 2: After matching
boxplot(pop ~ host_city, data = matched_data_ge_plot, 
        main = "After Matching",
        xlab = "Group", ylab = "Population")

#Reset the plotting layout to default
par(mfrow = c(1, 1))

Matching for South Africa

# Calculate propensity scores using logistic regression
model_sa <- glm(host_city ~ pop + nightlights_trend, data = sa_trend, family = "binomial")
pscore_sa <- predict(model_sa, type = "response")

#Match treatment and control groups using nearest neighbor matching
library(MatchIt)
matched_data_sa <- matchit(host_city ~ pop + nightlights_trend, data = sa_trend, method = "nearest")




#Create control group
matched_data_with_distance_sa <- match.data(matched_data_sa)
control_group_sa <- subset(matched_data_with_distance_sa, matched_data_with_distance_sa$host_city == 0)
control_names_fr <- control_group_fr$name

#Grab the rows that match the specified names
france_control <- tidy_fr[tidy_fr$name %in% control_names_fr, ]

tidy_fr_treated <- tidy_fr %>%
  filter(host_city == "1")

tidy_fr_all <- rbind(tidy_fr_treated, france_control)
control_names_ge <- control_group_ge$name

ge_control <- tidy_ge[tidy_ge$name %in% control_names_ge, ]

tidy_ge_treated <- tidy_ge %>%
  filter(host_city == "1")

tidy_ge_all <- rbind(tidy_ge_treated, ge_control)
control_names_sa <- control_group_sa$name

sa_control <- tidy_sa[tidy_sa$name %in% control_names_sa, ]

tidy_sa_treated <- tidy_sa %>%
  filter(host_city == "1")

tidy_sa_all <- rbind(tidy_sa_treated, sa_control)

Adding a dummy for the Treatment

#France
tidy_fr_all <- tidy_fr_all %>%
  mutate(
    treatment = if_else(host_city == "1" & Year > 1998, 1, 0),
    year_diff = Year - 1998  # Change the reference year as needed
  )

#Germany
tidy_ge_all <- tidy_ge_all %>%
  mutate(
    treatment = if_else(host_city == "1" & Year > 2006, 1, 0),
    year_diff = Year - 2006  # Germany reference year
  )


#South Africa
tidy_sa_all <- tidy_sa_all %>%
  mutate(
    treatment = if_else(host_city == "1" & Year > 2010, 1, 0),
    year_diff = Year - 2010  # Change the reference year as needed
  )
#Combine them all
tidy_all <- bind_rows(tidy_fr_all, tidy_ge_all, tidy_sa_all)
tidy_control_all <- bind_rows(france_control, ge_control, sa_control)
#Exclude years -4 and 4
tidy_all<- tidy_all %>%
  filter(!(year_diff %in% c(-4, 4)))

Difference-in-Difference

did_all <- tidy_all %>%
  mutate(treated = ifelse(host_city == 1, 1, 0),
         post_period = ifelse(year_diff >= 0, 1, 0),
         treated_post = treated * post_period)

did_model <- lm(Value ~ treated + post_period + treated_post, data = did_all)
table_output <- tab_model(did_model, show.ci = FALSE, show.se = TRUE, file = "model_table.html")

Synthetic Control Method

donors_est <- tidy_all %>%
  filter(host_city == "0") %>%
  distinct(name) %>%
  pull(name)
#Leipzig caused problems in the data due not matching with a control
tidy_all <- tidy_all %>%
  filter(!(name %in% c("leipzig")))



#Perform synthetic control analysis across time
results_all <- scdataMulti(tidy_all, 
                            id.var = "name", 
                            outcome.var = "Value",
                            treatment.var = "treatment", 
                            time.var = "year_diff",
                            donors.est = list(donors_est),
                            features = list(c("Value")),
                            cov.adj = list(c("constant", "trend")),
                            effect = "time")


#Estimate across-time treatment effects with positive weights from the donor pool
res <- scest(results_all, w.constr = list("name" = "simplex"))
scplotMulti(res)
## $plot_out

#Estimate bootstrapped confidence intervals
respi <- scpi(results_all, 
              w.constr = list("name" = "simplex"), 
              cores = 4, 
              sims = 50, 
              e.method = "gaussian"
              )
## ---------------------------------------------------------------
## Estimating Weights...
## Quantifying Uncertainty
## 5/50 iterations completed (10%) 10/50 iterations completed (20%) 15/50 iterations completed (30%) 20/50 iterations completed (40%) 25/50 iterations completed (50%) 30/50 iterations completed (60%) 35/50 iterations completed (70%) 40/50 iterations completed (80%) 45/50 iterations completed (90%) 50/50 iterations completed (100%) 
scplotMulti(respi, type="series")
## $plot_out

Add City Fixed Effects

#Fit the fixed effects model
resid_model_city <- feols(Value ~ 1 | name, data = tidy_all)

#Extract the residuals from the model
residuals_city <- resid(resid_model_city)

#Add the residuals as a new column to the data frame
tidy_all$nl_city <- residuals_city
results_all_city <- scdataMulti(tidy_all, 
                            id.var = "name", 
                            outcome.var = "nl_city",
                            treatment.var = "treatment", 
                            time.var = "year_diff",
                            donors.est = list(donors_est),
                            features = list(c("nl_city")),
                            cov.adj = list(c("constant", "trend")),
                            effect = "time")


#Estimate across-time treatment effects with positive weights from the donor pool
res_city <- scest(results_all_city, w.constr = list("name" = "simplex"))
scplotMulti(res_city)
## $plot_out

#Estimate bootstrapped confidence intervals
respi_city <- scpi(results_all_city, 
              w.constr = list("name" = "simplex"), 
              cores = 4, 
              sims = 50, 
              e.method = "gaussian")
## ---------------------------------------------------------------
## Estimating Weights...
## Quantifying Uncertainty
## 5/50 iterations completed (10%) 10/50 iterations completed (20%) 15/50 iterations completed (30%) 20/50 iterations completed (40%) 25/50 iterations completed (50%) 30/50 iterations completed (60%) 35/50 iterations completed (70%) 40/50 iterations completed (80%) 45/50 iterations completed (90%) 50/50 iterations completed (100%) 
scplotMulti(respi_city, type="series")
## $plot_out

Add Time Fixed Effects

resid_model_time <- feols(Value ~ 1 | year_diff, data = tidy_all)

#Extract residuals
residuals_time <- resid(resid_model_time)

#Add residuals to tidy_all
tidy_all$nl_time <- residuals_time
results_all_year <- scdataMulti(tidy_all, 
                            id.var = "name", 
                            outcome.var = "nl_time",
                            treatment.var = "treatment", 
                            time.var = "year_diff",
                            donors.est = list(donors_est),
                            features = list(c("nl_time")),
                            cov.adj = list(c("constant", "trend")),
                            effect = "time")

#Estimate across-time treatment effects with positive weights from the donor pool
res_time <- scest(results_all_year, w.constr = list("name" = "simplex"))
scplotMulti(res_time)
## $plot_out

#Estimate bootstrapped confidence intervals
respi_time <- scpi(results_all_year, 
              w.constr = list("name" = "simplex"), 
              cores = 4, 
              sims = 50,
              e.method = "gaussian")
## ---------------------------------------------------------------
## Estimating Weights...
## Quantifying Uncertainty
## 5/50 iterations completed (10%) 10/50 iterations completed (20%) 15/50 iterations completed (30%) 20/50 iterations completed (40%) 25/50 iterations completed (50%) 30/50 iterations completed (60%) 35/50 iterations completed (70%) 40/50 iterations completed (80%) 45/50 iterations completed (90%) 50/50 iterations completed (100%) 
scplotMulti(respi_time, type="series")
## $plot_out

Add City and Time Fixed Effects

resid_model_fe <- feols(Value ~ 1 | name + year_diff, data = tidy_all)
  
residuals_fe <- resid(resid_model_fe)
  
tidy_all$nl_fe <- residuals_fe
results_all_fe <- scdataMulti(tidy_all, 
                            id.var = "name", 
                            outcome.var = "nl_fe",
                            treatment.var = "treatment", 
                            time.var = "year_diff",
                            donors.est = list(donors_est),
                            features = list(c("nl_fe")),
                            cov.adj = list(c("constant", "trend")),
                            effect = "time")



#Estimate across-time treatment effects with positive weights from the donor pool
res_fe <- scest(results_all_fe, w.constr = list("name" = "simplex"))
scplotMulti(res_fe)
## $plot_out

#Estimate bootstrapped confidence intervals
respi_fe <- scpi(results_all_fe, 
              w.constr = list("name" = "simplex"), 
              cores = 4, 
              sims = 50,
              e.method = "gaussian")
## ---------------------------------------------------------------
## Estimating Weights...
## Quantifying Uncertainty
## 5/50 iterations completed (10%) 10/50 iterations completed (20%) 15/50 iterations completed (30%) 20/50 iterations completed (40%) 25/50 iterations completed (50%) 30/50 iterations completed (60%) 35/50 iterations completed (70%) 40/50 iterations completed (80%) 45/50 iterations completed (90%) 50/50 iterations completed (100%) 
scplotMulti(respi_fe, type="series")
## $plot_out

Grab data to use in Table

filtered_tidy <- tidy_all[tidy_all$host_city == 1 & tidy_all$year_diff %in% c(1, 2, 3), 
                          c("year_diff", "nl_fe", "Value", "nl_city", "nl_time")]

mean_nl_fe <- aggregate(nl_fe ~ year_diff, data = filtered_tidy, FUN = mean)
mean_Value <- aggregate(Value ~ year_diff, data = filtered_tidy, FUN = mean)
mean_nl_city <- aggregate(nl_city ~ year_diff, data = filtered_tidy, FUN = mean)
mean_nl_time <- aggregate(nl_time ~ year_diff, data = filtered_tidy, FUN = mean)

results_value <- as.data.frame(res$est.results$Y.post.fit)
results_fe <- as.data.frame(res_fe$est.results$Y.post.fit)
results_city <- as.data.frame(res_city$est.results$Y.post.fit)
results_time <- as.data.frame(res_time$est.results$Y.post.fit)

table_value <- cbind(results_value, mean_Value, as.data.frame(respi$inference.results$bounds$subgaussian) %>%
  rename(lower = V1, upper = V2))

table_fe <- cbind(results_fe, mean_nl_fe,
as.data.frame(respi_fe$inference.results$bounds$subgaussian) %>%
  rename(lower = V1, upper = V2))

table_city <- cbind(results_city, mean_nl_city, as.data.frame(respi_city$inference.results$bounds$subgaussian) %>%
  rename(lower = V1, upper = V2))

table_time <- cbind(results_time, mean_nl_time, as.data.frame(respi_time$inference.results$bounds$subgaussian) %>%
  rename(lower = V1, upper = V2))

table_value$difference <- table_value$Value - table_value$V1
table_fe$difference <- table_fe$nl_fe - table_fe$V1
table_city$difference <- table_city$nl_city - table_city$V1
table_time$difference <- table_time$nl_time - table_time$V1

# For table_city
table_city <- table_city %>%
  rename(Untreated_NL = V1, Treated_nl = nl_city) %>%
  rename(Year = year_diff, 
         Lower_Bound = lower, 
         Upper_Bound = upper, 
         Difference = difference) %>%
  relocate(Year, Untreated_NL, Treated_nl, Difference)

# For table_time
table_time <- table_time %>%
  rename(Untreated_NL = V1, Treated_nl = nl_time) %>%
  rename(Year = year_diff, 
         Lower_Bound = lower, 
         Upper_Bound = upper, 
         Difference = difference) %>%
  relocate(Year, Untreated_NL, Treated_nl, Difference)

# For table_fe
table_fe <- table_fe %>%
  rename(Untreated_NL = V1, Treated_nl = nl_fe) %>%
  rename(Year = year_diff, 
         Lower_Bound = lower, 
         Upper_Bound = upper, 
         Difference = difference) %>%
  relocate(Year, Untreated_NL, Treated_nl, Difference)

# For table_value
table_value <- table_value %>%
  rename(Untreated_NL = V1, Treated_nl = Value) %>%
  rename(Year = year_diff, 
         Lower_Bound = lower, 
         Upper_Bound = upper, 
         Difference = difference) %>%
  relocate(Year, Untreated_NL, Treated_nl, Difference)


# Load necessary libraries
library(knitr)
library(kableExtra)

# Function to format tables with a title and two decimal places
format_table <- function(df, title) {
  df_formatted <- df %>%
    mutate(across(where(is.numeric), round, 2))  # Format all numeric values to 2 decimals
  kbl(df_formatted, caption = title, format = "html") %>%
    kable_classic(full_width = FALSE)
}

# Print tables with captions and headers
cat("Table 1: Comparison in City Data\n")
## Table 1: Comparison in City Data
format_table(table_city, "Results with City Fixed Effects")
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `across(where(is.numeric), round, 2)`.
## Caused by warning:
## ! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
## Supply arguments directly to `.fns` through an anonymous function instead.
## 
##   # Previously
##   across(a:b, mean, na.rm = TRUE)
## 
##   # Now
##   across(a:b, \(x) mean(x, na.rm = TRUE))
Results with City Fixed Effects
Year Untreated_NL Treated_nl Difference Lower_Bound Upper_Bound
1 -0.38 0.45 0.83 -1.94 1.77
2 2.45 1.67 -0.79 -2.17 2.14
3 1.67 1.16 -0.51 -3.04 2.68
cat("\nTable 2: Comparison over Time\n")
## 
## Table 2: Comparison over Time
format_table(table_time, "Results with Time Fixed Effects")
Results with Time Fixed Effects
Year Untreated_NL Treated_nl Difference Lower_Bound Upper_Bound
1 -1.78 -1.13 0.65 -1.89 1.61
2 -0.37 -1.55 -1.18 -1.99 1.89
3 -0.73 -1.59 -0.86 -2.85 2.31
cat("\nTable 3: Comparison in Fixed Effects\n")
## 
## Table 3: Comparison in Fixed Effects
format_table(table_fe, "Results with Time and City Fixed Effects")
Results with Time and City Fixed Effects
Year Untreated_NL Treated_nl Difference Lower_Bound Upper_Bound
1 -0.66 0.14 0.80 -2.08 1.83
2 0.55 -0.28 -0.83 -2.28 2.14
3 0.21 -0.33 -0.54 -3.23 2.69
cat("\nTable 4: Comparison in Value\n")
## 
## Table 4: Comparison in Value
format_table(table_value, "Results")
Results
Year Untreated_NL Treated_nl Difference Lower_Bound Upper_Bound
1 42.47 43.23 0.77 -2.39 1.82
2 45.33 44.45 -0.88 -2.47 2.33
3 44.50 43.94 -0.55 -3.53 2.97