Introduction
This analysis explores the borders of the Roman Empire and their
relationship with various socioeconomic factors and geographical
features.
Libraries
# Load necessary libraries
library(cawd)
library(ggplot2)
library(rnaturalearthdata)
library(tidyverse)
library(sf)
library(dplyr)
library(terra)
library(raster)
library(elevatr)
library(rdrobust)
library(geodata)
library(lwgeom)
library(stars)
library(starsExtra)
library(modelsummary)
library(webshot2)
library(kableExtra)
Download and Process Maps
# Download map of Europe
europe <- rnaturalearthdata::sovereignty50 |>
st_as_sf() |>
filter(continent == "Europe")
# Download map of Roman Empire
roman_empire <- cawd::awmc.roman.empire.117.sp |> st_as_sf()
# Extract Border of the Roman Empire
boundary_re <- st_boundary(roman_empire)
# Extract Northern Border of the Roman Empire
north_countries <- europe |>
filter(is.element(sovereignt, c("Netherlands", "Germany", "Croatia",
"Austria", "Hungary", "Slovakia",
"Ukraine", "Romania", "Bulgaria",
"Republic of Serbia")))
# Filter the LINESTRING(s) with the maximum Y-coordinate
northernmost_lines <- st_intersection(boundary_re, north_countries)
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
# Filter out south border of Croatia
coords <- matrix(c(15, 46, 20, 42, 10, 42, 10, 46, 15, 46), ncol = 2, byrow = TRUE)
polygon <- st_polygon(list(coords)) |> st_sfc(crs = 4326)
northernmost_border <- northernmost_lines |> st_difference(polygon)
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
# Build a 200km Buffer Around Northern Border and Intersection With Europe
sf_use_s2(TRUE)
buffer_northern_border <- st_buffer(northernmost_border, 200000)
sf_use_s2(FALSE)
## Spherical geometry (s2) switched off
border_area <- europe |> st_intersection(buffer_northern_border)
## although coordinates are longitude/latitude, st_intersection assumes that they
## are planar
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
# Visualize the area
border_area |>
ggplot() + geom_sf()

Build Grid and Save Raster
# Build a grid on this basis
grid <- st_make_grid(border_area, n = 100)
filter <- st_within(grid, st_union(border_area)) |> lengths() > 0
## although coordinates are longitude/latitude, st_union assumes that they are
## planar
## although coordinates are longitude/latitude, st_within assumes that they are
## planar
final_grid <- grid[filter]
# Turn grid into an empty raster and save
raster_grid <- final_grid |> stars::st_as_stars()
## although coordinates are longitude/latitude, st_intersects assumes that they
## are planar
## although coordinates are longitude/latitude, st_intersects assumes that they
## are planar
stars::write_stars(raster_grid, 'raster_roman_empire.tiff', overwrite=TRUE)
Load and Provess Road Density Data
# Load the data
road_density_raster <- rast("C:/Users/jookk/OneDrive/Työpöytä/R_Thesis/grip4_tp1_dens_m_km2.asc")
raster_re <- rast("C:/Users/jookk/OneDrive/Työpöytä/R_Thesis/raster_re.tiff")
# Check if rasters are loaded by examining their properties
print(raster_re)
## class : SpatRaster
## dimensions : 164, 398, 1 (nrow, ncol, nlyr)
## resolution : 0.07593909, 0.07593909 (x, y)
## extent : 0.8761778, 31.09993, 41.10402, 53.55803 (xmin, xmax, ymin, ymax)
## coord. ref. : lon/lat WGS 84 (EPSG:4326)
## source : raster_re.tiff
## name : raster_re
print(road_density_raster)
## class : SpatRaster
## dimensions : 2160, 4320, 1 (nrow, ncol, nlyr)
## resolution : 0.08333333, 0.08333333 (x, y)
## extent : -180, 180, -90, 90 (xmin, xmax, ymin, ymax)
## coord. ref. : lon/lat WGS 84 (CRS84) (OGC:CRS84)
## source : grip4_tp1_dens_m_km2.asc
## name : grip4_tp1_dens_m_km2
if (crs(raster_re) != crs(road_density_raster)) {
road_density_raster <- project(road_density_raster, crs(raster_re))
}
# Crop the global road density raster to the extent of the raster_re to reduce computation load
road_density_cropped <- crop(road_density_raster, ext(raster_re))
# Resample the cropped road density raster to match the grid resolution of raster_re
road_density_resampled <- resample(road_density_cropped, raster_re, method = "bilinear")
# Perform the overlay operation by multiplying corresponding cells
overlay_result <- raster_re * road_density_resampled
# Plot the result to verify the overlay
plot(overlay_result, main = "Overlay of Road Density with Grid")

# Save the overlay result to a new file
writeRaster(overlay_result, "overlay_density_grid.tiff", overwrite=TRUE)
Ensure CRS, Create Intersection & The RE Dummy
# Ensure both have the same CRS
roman_empire <- st_transform(roman_empire, st_crs(raster_re))
# Create an intersection
intersection <- st_intersection(roman_empire, st_as_sfc(st_bbox(raster_re)))
## although coordinates are longitude/latitude, st_intersection assumes that they
## are planar
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
# Rasterize the intersection using the template
roman_empire_raster <- rasterize(intersection, raster_re, field = 1, fun = max, background = 0)
roman_empire_clipped <- mask(roman_empire_raster, raster_re)
# Plot to verify the clipped raster
plot(roman_empire_clipped, main = "Clipped Roman Empire Dummy Variable")

Load Socioeconomic Data
# Load socioeconomic data
gdp_data <- rast("C:/Users/jookk/OneDrive/Työpöytä/R_Thesis/GDP_PPP_1990_2015_5arcmin_v2.nc")
hdi_data <- rast("C:/Users/jookk/OneDrive/Työpöytä/R_Thesis/HDI_1990_2015_v2.nc")
# Resample and align GDP and HDI data with the road density raster
gdp_data_aligned <- resample(gdp_data, road_density_resampled, method = "bilinear")
hdi_data_aligned <- resample(hdi_data, road_density_resampled, method = "bilinear")
# Create dummy variables for areas within the Roman Empire and mask the GDP and HDI data
gdp_masked <- mask(gdp_data_aligned, roman_empire_clipped)
hdi_masked <- mask(hdi_data_aligned, roman_empire_clipped)
# Extract data for a specific year, I use 2015
gdp_2015 <- gdp_data_aligned[["GDP_PPP_26"]]
hdi_2015 <- hdi_data_aligned[["HDI_26"]]
# Align, mask, and prepare specific layers for analysis
gdp_2015_aligned <- resample(gdp_2015, road_density_resampled, method = "bilinear")
hdi_2015_aligned <- resample(hdi_2015, road_density_resampled, method = "bilinear")
gdp_masked_2015 <- mask(gdp_2015_aligned, roman_empire_clipped)
hdi_masked_2015 <- mask(hdi_2015_aligned, roman_empire_clipped)
Load and Process Elevation Data
# Get the extent of raster_re to define the area for elevation data extraction
raster_extent <- ext(raster_re)
print(raster_extent)
## SpatExtent : 0.876177769909369, 31.0999339127773, 41.1040216726612, 53.5580317415816 (xmin, xmax, ymin, ymax)
# Define a grid over the extent with suitable resolution
points <- expand.grid(x = seq(0.876177769909369, 31.0999339127773, length.out = 100),
y = seq(41.1040216726612, 53.5580317415816, length.out = 100))
# Get elevation data for these points
elevation_data <- get_elev_point(points, prj = "+proj=longlat +datum=WGS84", src = "aws")
## Mosaicing & Projecting
## Note: Elevation units are in meters
# Convert the 'sf' object to a regular dataframe with x, y, and elevation
coordinates <- st_coordinates(elevation_data)
elevation_df <- data.frame(
x = coordinates[, "X"],
y = coordinates[, "Y"],
elevation = elevation_data$elevation
)
# Convert the elevation data frame into a raster using the extent and resolution of raster_re
elevation_raster <- rasterFromXYZ(elevation_df, crs = crs(raster_re))
elevation_raster_terra <- terra::rast(elevation_raster)
elevation_aligned <- terra::resample(elevation_raster_terra, road_density_resampled, method = "bilinear")
# Crop elevation_aligned to match the extent of raster_re
elevation_cropped <- crop(elevation_aligned, ext(raster_re))
# Align it if there is any slight misalignment
elevation_cropped <- resample(elevation_cropped, raster_re, method = "bilinear")
Load and Process Population Data
# Load the TIF file
pop_density_raster <- rast("C:/Users/jookk/OneDrive/Työpöytä/R_Thesis/gpw_v4_population_density_rev11_2015_30_sec.tif")
# Reproject the population density raster
if (crs(pop_density_raster) != crs(raster_re)) {
pop_density_raster <- projectRaster(pop_density_raster, crs = crs(raster_re))
}
# Crop the population density raster to the extent of the study area
pop_density_cropped <- crop(pop_density_raster, ext(raster_re))
# Resample the population density raster to match the resolution of raster_re
pop_density_resampled <- resample(pop_density_cropped, raster_re, method = "bilinear")
# Mask the resampled population density raster
pop_density_masked <- mask(pop_density_resampled, raster_re)
# Plot the masked population density raster
plot(pop_density_masked, main = "Masked Population Density 2015")

Filter Data for Analysis
# Extract the values from relevant rasters
road_density_values <- values(road_density_resampled)
gdp_values <- values(gdp_masked_2015)
hdi_values <- values(hdi_masked_2015)
roman_empire_values <- values(roman_empire_clipped)
pop_density_values <- values(pop_density_masked)
elevation_values <- values(elevation_cropped)
# Ensure all values are of the same length
if (!all(length(road_density_values) == length(gdp_values),
length(gdp_values) == length(hdi_values),
length(hdi_values) == length(roman_empire_values),
length(roman_empire_values) == length(pop_density_values),
length(pop_density_values) == length(elevation_values))) {
stop("Length of values extracted from rasters do not match.")
}
# Create a logical mask to filter out NA values across all variables
valid_mask <- !is.na(road_density_values) & !is.na(gdp_values) &
!is.na(hdi_values) & !is.na(roman_empire_values) &
!is.na(pop_density_values) & !is.na(elevation_values)
# Apply the mask to filter out NA values
filtered_road_density_values <- road_density_values[valid_mask]
filtered_gdp_values <- gdp_values[valid_mask]
filtered_hdi_values <- hdi_values[valid_mask]
filtered_roman_empire_values <- roman_empire_values[valid_mask]
filtered_pop_density_values <- pop_density_values[valid_mask]
filtered_elevation_values <- elevation_values[valid_mask]
# Create a new dataframe with filtered data
analysis_data <- data.frame(
RoadDensity = filtered_road_density_values,
GDP = filtered_gdp_values,
HDI = filtered_hdi_values,
RomanEmpire = filtered_roman_empire_values,
PopDensity2015 = filtered_pop_density_values,
Elevation = filtered_elevation_values
)
# Remove any rows with missing values
analysis_data <- na.omit(analysis_data)
Create the Running Variable
# Calculate the distance to the nearest Roman Empire border
nearest_feat <- starsExtra::dist_to_nearest(raster_grid, northernmost_border)
## lines or polygons
## | | | 0% | | | 1% | |= | 1% | |= | 2% | |== | 2% | |== | 3% | |== | 4% | |=== | 4% | |=== | 5% | |==== | 5% | |==== | 6% | |===== | 6% | |===== | 7% | |===== | 8% | |====== | 8% | |====== | 9% | |======= | 9% | |======= | 10% | |======= | 11% | |======== | 11% | |======== | 12% | |========= | 12% | |========= | 13% | |========= | 14% | |========== | 14% | |========== | 15% | |=========== | 15% | |=========== | 16% | |============ | 16% | |============ | 17% | |============ | 18% | |============= | 18% | |============= | 19% | |============== | 19% | |============== | 20% | |============== | 21% | |=============== | 21% | |=============== | 22% | |================ | 22% | |================ | 23% | |================ | 24% | |================= | 24% | |================= | 25% | |================== | 25% | |================== | 26% | |=================== | 26% | |=================== | 27% | |=================== | 28% | |==================== | 28% | |==================== | 29% | |===================== | 29% | |===================== | 30% | |===================== | 31% | |====================== | 31% | |====================== | 32% | |======================= | 32% | |======================= | 33% | |======================= | 34% | |======================== | 34% | |======================== | 35% | |========================= | 35% | |========================= | 36% | |========================== | 36% | |========================== | 37% | |========================== | 38% | |=========================== | 38% | |=========================== | 39% | |============================ | 39% | |============================ | 40% | |============================ | 41% | |============================= | 41% | |============================= | 42% | |============================== | 42% | |============================== | 43% | |============================== | 44% | |=============================== | 44% | |=============================== | 45% | |================================ | 45% | |================================ | 46% | |================================= | 46% | |================================= | 47% | |================================= | 48% | |================================== | 48% | |================================== | 49% | |=================================== | 49% | |=================================== | 50% | |=================================== | 51% | |==================================== | 51% | |==================================== | 52% | |===================================== | 52% | |===================================== | 53% | |===================================== | 54% | |====================================== | 54% | |====================================== | 55% | |======================================= | 55% | |======================================= | 56% | |======================================== | 56% | |======================================== | 57% | |======================================== | 58% | |========================================= | 58% | |========================================= | 59% | |========================================== | 59% | |========================================== | 60% | |========================================== | 61% | |=========================================== | 61% | |=========================================== | 62% | |============================================ | 62% | |============================================ | 63% | |============================================ | 64% | |============================================= | 64% | |============================================= | 65% | |============================================== | 65% | |============================================== | 66% | |=============================================== | 66% | |=============================================== | 67% | |=============================================== | 68% | |================================================ | 68% | |================================================ | 69% | |================================================= | 69% | |================================================= | 70% | |================================================= | 71% | |================================================== | 71% | |================================================== | 72% | |=================================================== | 72% | |=================================================== | 73% | |=================================================== | 74% | |==================================================== | 74% | |==================================================== | 75% | |===================================================== | 75% | |===================================================== | 76% | |====================================================== | 76% | |====================================================== | 77% | |====================================================== | 78% | |======================================================= | 78% | |======================================================= | 79% | |======================================================== | 79% | |======================================================== | 80% | |======================================================== | 81% | |========================================================= | 81% | |========================================================= | 82% | |========================================================== | 82% | |========================================================== | 83% | |========================================================== | 84% | |=========================================================== | 84% | |=========================================================== | 85% | |============================================================ | 85% | |============================================================ | 86% | |============================================================= | 86% | |============================================================= | 87% | |============================================================= | 88% | |============================================================== | 88% | |============================================================== | 89% | |=============================================================== | 89% | |=============================================================== | 90% | |=============================================================== | 91% | |================================================================ | 91% | |================================================================ | 92% | |================================================================= | 92% | |================================================================= | 93% | |================================================================= | 94% | |================================================================== | 94% | |================================================================== | 95% | |=================================================================== | 95% | |=================================================================== | 96% | |==================================================================== | 96% | |==================================================================== | 97% | |==================================================================== | 98% | |===================================================================== | 98% | |===================================================================== | 99% | |======================================================================| 99% | |======================================================================| 100%
final_raster_grid <- raster_grid[raster_grid['values'] ==1]
final_raster_grid$distance <- nearest_feat$d
# Create indicator for being inside the Roman Empire
grid_sf <- st_as_sfc(final_raster_grid, as_points = TRUE)
logical <- as.logical(st_within(grid_sf, roman_empire))
## although coordinates are longitude/latitude, st_within assumes that they are
## planar
final_raster_grid <- final_raster_grid |>
mutate(inside_re = case_when(
!is.na(logical) & values == 1 ~ 1,
is.na(logical) & values == 1 ~ 0,
TRUE ~ NA
))
# Create the running variable
final_raster_grid <- final_raster_grid |>
mutate(running = if_else(inside_re == 1, distance, -distance))
# Plot the running variable
ggplot() +
geom_stars(data = final_raster_grid['running']) +
scale_fill_viridis_c()

Get the Coordinates
# Convert final_raster_grid to a data frame with coordinates
final_raster_grid_df <- as.data.frame(final_raster_grid, xy = TRUE)
# Add the distance to boundary variable to the data frame
final_raster_grid_df$DistanceToBoundary <- final_raster_grid_df$running
# Ensure analysis_data has the coordinates from the rasters
coordinates_df <- as.data.frame(road_density_resampled, xy = TRUE)
# Add values from other rasters to the coordinates_df
road_density_df <- as.data.frame(road_density_resampled, xy = TRUE)
gdp_df <- as.data.frame(gdp_masked_2015, xy = TRUE)
hdi_df <- as.data.frame(hdi_masked_2015, xy = TRUE)
roman_empire_df <- as.data.frame(roman_empire_clipped, xy = TRUE)
pop_density_df <- as.data.frame(pop_density_masked, xy = TRUE)
elevation_df <- as.data.frame(elevation_cropped, xy = TRUE)
Merge the Data Frames
# Rename columns for clarity
names(road_density_df)[3] <- "RoadDensity"
names(gdp_df)[3] <- "GDP"
names(hdi_df)[3] <- "HDI"
names(roman_empire_df)[3] <- "RomanEmpire"
names(pop_density_df)[3] <- "PopDensity"
names(elevation_df)[3] <- "Elevation"
# Merge all data frames by coordinates
analysis_data <- coordinates_df %>%
left_join(road_density_df, by = c("x", "y")) %>%
left_join(gdp_df, by = c("x", "y")) %>%
left_join(hdi_df, by = c("x", "y")) %>%
left_join(roman_empire_df, by = c("x", "y")) %>%
left_join(pop_density_df, by = c("x", "y")) %>%
left_join(elevation_df, by = c("x", "y"))
# Merge analysis_data with final_raster_grid_df using coordinates
analysis_data <- analysis_data %>%
left_join(final_raster_grid_df, by = c("x", "y"))
# Remove rows with NA values
analysis_data <- na.omit(analysis_data)
Clean the dataset and Create Logs
# Ensure no NA/NaN/Inf values in covariates and outcome variable
analysis_data <- analysis_data %>%
filter(!is.na(RoadDensity) & !is.na(DistanceToBoundary) & !is.na(GDP) & !is.na(HDI) & !is.na(Elevation) & !is.na(PopDensity) &
is.finite(RoadDensity) & is.finite(DistanceToBoundary) & is.finite(GDP) & is.finite(HDI) & is.finite(Elevation) & is.finite(PopDensity))
# Ensure positive and non-zero values for log transformation
analysis_data <- analysis_data %>%
filter(GDP > 0, HDI > 0, PopDensity > 0)
# Log transform the covariates and check for any problematic values
analysis_data <- analysis_data %>%
mutate(LogGDP = log(GDP),
LogHDI = log(HDI),
LogPopDensity = log(PopDensity))
Analysis
Perform Robustness checks with different bandwidths using
bwselect
# Compute the bandwidth using the msetwo method from the rdrobust package
rdd_result_msetwo <- rdrobust(y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = 0,
covs = cbind(analysis_data$LogGDP, analysis_data$LogHDI, analysis_data$Elevation, analysis_data$LogPopDensity),
bwselect = "msetwo")
# Summarize the results
summary(rdd_result_msetwo)
## Covariate-adjusted Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type msetwo
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 3512 2205
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 77067.969 45517.391
## BW bias (b) 109520.868 82616.052
## rho (h/b) 0.704 0.551
## Unique Obs. 7846 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 21.419 6.438 3.327 0.001 [8.800 , 34.037]
## Robust - - 3.140 0.002 [8.968 , 38.756]
## =============================================================================
# Compute the bandwidth using the msesum method from the rdrobust package
rdd_result_msesum <- rdrobust(y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = 0,
covs = cbind(analysis_data$LogGDP, analysis_data$LogHDI, analysis_data$Elevation, analysis_data$LogPopDensity),
bwselect = "msesum")
# Summarize the results
summary(rdd_result_msesum)
## Covariate-adjusted Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type msesum
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 2261 2382
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 49469.948 49469.948
## BW bias (b) 87457.597 87457.597
## rho (h/b) 0.566 0.566
## Unique Obs. 7846 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 20.595 7.103 2.899 0.004 [6.672 , 34.517]
## Robust - - 2.815 0.005 [6.953 , 38.821]
## =============================================================================
# Compute the bandwidth using the cerrd method from the rdrobust package
rdd_result_cerrd <- rdrobust(y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = 0,
covs = cbind(analysis_data$LogGDP, analysis_data$LogHDI, analysis_data$Elevation, analysis_data$LogPopDensity),
bwselect = "cerrd")
# Summarize the results
summary(rdd_result_cerrd)
## Covariate-adjusted Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type cerrd
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 1814 1939
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 39611.392 39611.392
## BW bias (b) 105645.627 105645.627
## rho (h/b) 0.375 0.375
## Unique Obs. 7846 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 16.305 7.999 2.038 0.042 [0.628 , 31.982]
## Robust - - 2.058 0.040 [0.830 , 33.877]
## =============================================================================
Perform Robustness checks with different bandwidths using specific
bandwidths
# Perform RDD analysis for specific bandwidths without covariates first
bandwidths <- c(20000, 40000, 60000, 80000, 100000)
for (bw in bandwidths) {
rdd_result <- rdrobust(y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = 0, # threshold for treatment assignment
h = bw, # specific bandwidth
p = 1, # order of polynomial
kernel = "triangular") # kernel type
cat("Results for bandwidth", bw, "m (without covariates):\n")
print(summary(rdd_result))
}
## Results for bandwidth 20000 m (without covariates):
## Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type Manual
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 901 991
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 20000.000 20000.000
## BW bias (b) 20000.000 20000.000
## rho (h/b) 1.000 1.000
## Unique Obs. 7847 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 15.544 14.660 1.060 0.289 [-13.189 , 44.277]
## Robust - - 0.773 0.439 [-24.969 , 57.516]
## =============================================================================
## NULL
## Results for bandwidth 40000 m (without covariates):
## Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type Manual
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 1831 1955
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 40000.000 40000.000
## BW bias (b) 40000.000 40000.000
## rho (h/b) 1.000 1.000
## Unique Obs. 7847 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 25.369 10.392 2.441 0.015 [5.002 , 45.736]
## Robust - - 1.006 0.314 [-14.347 , 44.637]
## =============================================================================
## NULL
## Results for bandwidth 60000 m (without covariates):
## Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type Manual
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 2760 2812
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 60000.000 60000.000
## BW bias (b) 60000.000 60000.000
## rho (h/b) 1.000 1.000
## Unique Obs. 7847 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 30.309 8.332 3.638 0.000 [13.979 , 46.638]
## Robust - - 1.866 0.062 [-1.166 , 47.264]
## =============================================================================
## NULL
## Results for bandwidth 80000 m (without covariates):
## Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type Manual
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 3641 3634
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 80000.000 80000.000
## BW bias (b) 80000.000 80000.000
## rho (h/b) 1.000 1.000
## Unique Obs. 7847 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 26.515 7.048 3.762 0.000 [12.700 , 40.329]
## Robust - - 2.967 0.003 [10.768 , 52.683]
## =============================================================================
## NULL
## Results for bandwidth 1e+05 m (without covariates):
## Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type Manual
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 4446 4406
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 100000.000 100000.000
## BW bias (b) 100000.000 100000.000
## rho (h/b) 1.000 1.000
## Unique Obs. 7847 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 23.096 6.164 3.747 0.000 [11.015 , 35.177]
## Robust - - 3.379 0.001 [13.464 , 50.652]
## =============================================================================
## NULL
# Perform RDD analysis for specific bandwidths with covariates
for (bw in bandwidths) {
# Add a small constant to avoid exact zeros in covariates
adjusted_data <- analysis_data %>%
mutate(LogGDP = log(GDP + 1e-6),
LogHDI = log(HDI + 1e-6),
LogPopDensity = log(PopDensity + 1e-6))
rdd_result <- rdrobust(y = adjusted_data$RoadDensity,
x = adjusted_data$DistanceToBoundary,
c = 0, # threshold for treatment assignment
h = bw, # specific bandwidth
covs = cbind(adjusted_data$LogGDP, adjusted_data$LogHDI, adjusted_data$Elevation, adjusted_data$LogPopDensity),
p = 1, # order of polynomial
kernel = "triangular") # kernel type
cat("Results for bandwidth", bw, "m (with covariates):\n")
print(summary(rdd_result))
}
## Results for bandwidth 20000 m (with covariates):
## Covariate-adjusted Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type Manual
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 901 991
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 20000.000 20000.000
## BW bias (b) 20000.000 20000.000
## rho (h/b) 1.000 1.000
## Unique Obs. 7847 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 8.914 11.339 0.786 0.432 [-13.311 , 31.139]
## Robust - - 0.327 0.744 [-26.868 , 37.634]
## =============================================================================
## NULL
## Results for bandwidth 40000 m (with covariates):
## Covariate-adjusted Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type Manual
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 1831 1955
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 40000.000 40000.000
## BW bias (b) 40000.000 40000.000
## rho (h/b) 1.000 1.000
## Unique Obs. 7847 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 16.409 7.958 2.062 0.039 [0.812 , 32.005]
## Robust - - 0.650 0.516 [-15.266 , 30.420]
## =============================================================================
## NULL
## Results for bandwidth 60000 m (with covariates):
## Covariate-adjusted Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type Manual
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 2760 2812
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 60000.000 60000.000
## BW bias (b) 60000.000 60000.000
## rho (h/b) 1.000 1.000
## Unique Obs. 7847 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 21.812 6.372 3.423 0.001 [9.324 , 34.301]
## Robust - - 1.524 0.128 [-4.157 , 33.190]
## =============================================================================
## NULL
## Results for bandwidth 80000 m (with covariates):
## Covariate-adjusted Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type Manual
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 3641 3634
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 80000.000 80000.000
## BW bias (b) 80000.000 80000.000
## rho (h/b) 1.000 1.000
## Unique Obs. 7847 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 17.847 5.393 3.309 0.001 [7.276 , 28.418]
## Robust - - 2.846 0.004 [7.282 , 39.505]
## =============================================================================
## NULL
## Results for bandwidth 1e+05 m (with covariates):
## Covariate-adjusted Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type Manual
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 4446 4406
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 100000.000 100000.000
## BW bias (b) 100000.000 100000.000
## rho (h/b) 1.000 1.000
## Unique Obs. 7847 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 15.249 4.732 3.223 0.001 [5.975 , 24.522]
## Robust - - 3.117 0.002 [8.428 , 36.972]
## =============================================================================
## NULL
# Perform RDD without any controls using the mserd bandwidth selection method
rdd_result_no_controls <- rdrobust(y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = 0, # threshold for treatment assignment
bwselect = "mserd", # bandwidth selection method
p = 1, # order of polynomial
kernel = "triangular") # kernel type
cat("Results for RDD without any controls using mserd bandwidth selection method:\n")
## Results for RDD without any controls using mserd bandwidth selection method:
print(summary(rdd_result_no_controls))
## Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type mserd
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 1950 2109
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 42730.967 42730.967
## BW bias (b) 63719.525 63719.525
## rho (h/b) 0.671 0.671
## Unique Obs. 7846 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 26.840 10.040 2.673 0.008 [7.162 , 46.518]
## Robust - - 2.051 0.040 [1.100 , 48.255]
## =============================================================================
## NULL
Perform Balance Tests for HDI, GDP & Population Density
# Perform a Balance test for HDI
rdd_hdi <- rdrobust(y = analysis_data$LogHDI,
x = analysis_data$DistanceToBoundary,
c = 0,
p = 1,
kernel = "triangular")
# Summarize the results for HDI
summary(rdd_hdi)
## Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type mserd
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 2657 2720
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 57753.102 57753.102
## BW bias (b) 90001.697 90001.697
## rho (h/b) 0.642 0.642
## Unique Obs. 7846 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 0.007 0.004 1.464 0.143 [-0.002 , 0.015]
## Robust - - 1.092 0.275 [-0.005 , 0.016]
## =============================================================================
# Perform a Balance test for GDP
rdd_gdp <- rdrobust(y = analysis_data$LogGDP,
x = analysis_data$DistanceToBoundary,
c = 0,
p = 1,
kernel = "triangular")
# Summarize the results for log(GDP)
summary(rdd_gdp)
## Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type mserd
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 1388 1510
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 30623.426 30623.426
## BW bias (b) 54982.059 54982.059
## rho (h/b) 0.557 0.557
## Unique Obs. 7846 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 0.252 0.165 1.521 0.128 [-0.073 , 0.576]
## Robust - - 0.993 0.321 [-0.182 , 0.556]
## =============================================================================
# Perform a Balance test for Population Density
rdd_popdensity <- rdrobust(y = analysis_data$LogPopDensity,
x = analysis_data$DistanceToBoundary,
c = 0,
p = 1,
kernel = "triangular")
# Summarize the results for Population Density
summary(rdd_popdensity)
## Sharp RD estimates using local polynomial regression.
##
## Number of Obs. 14942
## BW type mserd
## Kernel Triangular
## VCE method NN
##
## Number of Obs. 7847 7095
## Eff. Number of Obs. 1794 1915
## Order est. (p) 1 1
## Order bias (q) 2 2
## BW est. (h) 39007.354 39007.354
## BW bias (b) 56839.240 56839.240
## rho (h/b) 0.686 0.686
## Unique Obs. 7846 7095
##
## =============================================================================
## Method Coef. Std. Err. z P>|z| [ 95% C.I. ]
## =============================================================================
## Conventional 0.126 0.099 1.272 0.203 [-0.068 , 0.321]
## Robust - - 1.124 0.261 [-0.100 , 0.369]
## =============================================================================
Create tables
Linear regression model Results
# Extract summary statistics
summary_stats <- summary(linear_reg)
# Create a dataframe for the results
results <- data.frame(
Metric = c("Coefficient", "Std. Error"),
Intercept = c(
paste0(round(summary_stats$coefficients[1, 1], 3),
ifelse(summary_stats$coefficients[1, 4] < 0.001, "***",
ifelse(summary_stats$coefficients[1, 4] < 0.01, "**",
ifelse(summary_stats$coefficients[1, 4] < 0.05, "*", "")))),
paste0("(", round(summary_stats$coefficients[1, 2], 3), ")")
),
RomanEmpire = c(
paste0(round(summary_stats$coefficients[2, 1], 3),
ifelse(summary_stats$coefficients[2, 4] < 0.001, "***",
ifelse(summary_stats$coefficients[2, 4] < 0.01, "**",
ifelse(summary_stats$coefficients[2, 4] < 0.05, "*", "")))),
paste0("(", round(summary_stats$coefficients[2, 2], 3), ")")
),
GDP = c(
paste0(round(summary_stats$coefficients[3, 1], 3),
ifelse(summary_stats$coefficients[3, 4] < 0.001, "***",
ifelse(summary_stats$coefficients[3, 4] < 0.01, "**",
ifelse(summary_stats$coefficients[3, 4] < 0.05, "*", "")))),
paste0("(", round(summary_stats$coefficients[3, 2], 3), ")")
),
HDI = c(
paste0(round(summary_stats$coefficients[4, 1], 3),
ifelse(summary_stats$coefficients[4, 4] < 0.001, "***",
ifelse(summary_stats$coefficients[4, 4] < 0.01, "**",
ifelse(summary_stats$coefficients[4, 4] < 0.05, "*", "")))),
paste0("(", round(summary_stats$coefficients[4, 2], 3), ")")
),
Elevation = c(
paste0(round(summary_stats$coefficients[5, 1], 3),
ifelse(summary_stats$coefficients[5, 4] < 0.001, "***",
ifelse(summary_stats$coefficients[5, 4] < 0.01, "**",
ifelse(summary_stats$coefficients[5, 4] < 0.05, "*", "")))),
paste0("(", round(summary_stats$coefficients[5, 2], 3), ")")
),
PopDensity = c(
paste0(round(summary_stats$coefficients[6, 1], 3),
ifelse(summary_stats$coefficients[6, 4] < 0.001, "***",
ifelse(summary_stats$coefficients[6, 4] < 0.01, "**",
ifelse(summary_stats$coefficients[6, 4] < 0.05, "*", "")))),
paste0("(", round(summary_stats$coefficients[6, 2], 3), ")")
)
)
# Transpose the table
results_transposed <- as.data.frame(t(results))
# Add column names
colnames(results_transposed) <- results$Metric
results_transposed <- results_transposed[-1, ] # Remove the first row with metric names
# Add coefficient names as a column
results_transposed <- cbind(Variable = rownames(results_transposed), results_transposed)
rownames(results_transposed) <- NULL
# Create a table using modelsummary
datasummary_df(results_transposed, title = "Table - Linear Regression Results", output = "kableExtra")
Table - Linear Regression Results
|
Variable
|
Coefficient
|
Std. Error
|
|
Intercept
|
-171.706***
|
(7.807)
|
|
RomanEmpire
|
12.784***
|
(1.198)
|
|
GDP
|
0***
|
|
|
HDI
|
233.056***
|
(9.283)
|
|
Elevation
|
-0.031***
|
(0.002)
|
|
PopDensity
|
0.151***
|
(0.004)
|
RDD Results
# Perform RDD analyses and store the results
rdd_results <- list()
# Pre-specified bandwidths
bandwidths <- c(20000, 40000, 60000, 80000, 100000)
for (bw in bandwidths) {
rdd_results[[paste0(bw / 1000, "km without covariates")]] <- rdrobust(
y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = 0,
h = bw,
p = 1,
kernel = "triangular"
)
rdd_results[[paste0(bw / 1000, "km with covariates")]] <- rdrobust(
y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = 0,
h = bw,
covs = cbind(analysis_data$LogGDP, analysis_data$LogHDI, analysis_data$Elevation, analysis_data$LogPopDensity),
p = 1,
kernel = "triangular"
)
}
# Bandwidth selection methods
bwselect_methods <- c("mserd", "msesum", "msetwo", "cerrd")
for (method in bwselect_methods) {
rdd_results[[paste0(method, " without covariates")]] <- rdrobust(
y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = 0,
bwselect = method,
p = 1,
kernel = "triangular"
)
rdd_results[[paste0(method, " with covariates")]] <- rdrobust(
y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = 0,
bwselect = method,
covs = cbind(analysis_data$LogGDP, analysis_data$LogHDI, analysis_data$Elevation, analysis_data$LogPopDensity),
p = 1,
kernel = "triangular"
)
}
# Function to extract RDD results
make_table_row <- function(estimates) {
est <- round(estimates$Estimate[1], 3)
se <- round(estimates$Estimate[3], 3)
robust_p_value <- round(estimates$pv[3], 3)
stars <- case_when(
robust_p_value < 0.01 ~ "***",
robust_p_value < 0.05 ~ "**",
robust_p_value < 0.10 ~ "*",
TRUE ~ ""
)
n_control <- estimates$N[1]
n_treat <- estimates$N[2]
bw_left <- round(estimates$bws[1, 1], 3)
bw_right <- round(estimates$bws[1, 2], 3)
data.frame(
Method = "",
Coefficient = paste0(est, stars),
`SE (BC)` = paste0("(", se, ")"),
`N (Treated)` = as.character(n_treat),
`N (Control)` = as.character(n_control),
Bandwidth = paste0(bw_left, " / ", bw_right)
)
}
# Create a combined summary table
summary_table <- map_df(rdd_results, make_table_row, .id = "Method")
# Reformat the table to have methods as rows and metrics as columns
summary_table_transposed <- summary_table %>%
pivot_longer(cols = -Method, names_to = "Metric", values_to = "Value") %>%
pivot_wider(names_from = Metric, values_from = Value)
# Create a table using modelsummary
datasummary_df(summary_table_transposed, title = "Table 3 - RDD Results", output = "kableExtra")
Table 3 - RDD Results
|
Method
|
Coefficient
|
SE..BC.
|
N..Treated.
|
N..Control.
|
Bandwidth
|
|
20km without covariates
|
15.544
|
(14.66)
|
7095
|
7847
|
20000 / 20000
|
|
20km with covariates
|
8.914
|
(11.339)
|
7095
|
7847
|
20000 / 20000
|
|
40km without covariates
|
25.369
|
(10.392)
|
7095
|
7847
|
40000 / 40000
|
|
40km with covariates
|
16.488
|
(7.959)
|
7095
|
7847
|
40000 / 40000
|
|
60km without covariates
|
30.309*
|
(8.332)
|
7095
|
7847
|
60000 / 60000
|
|
60km with covariates
|
21.919
|
(6.373)
|
7095
|
7847
|
60000 / 60000
|
|
80km without covariates
|
26.515***
|
(7.048)
|
7095
|
7847
|
80000 / 80000
|
|
80km with covariates
|
17.948***
|
(5.394)
|
7095
|
7847
|
80000 / 80000
|
|
100km without covariates
|
23.096***
|
(6.164)
|
7095
|
7847
|
1e+05 / 1e+05
|
|
100km with covariates
|
15.334***
|
(4.732)
|
7095
|
7847
|
1e+05 / 1e+05
|
|
mserd without covariates
|
26.84**
|
(10.04)
|
7095
|
7847
|
42730.967 / 42730.967
|
|
mserd with covariates
|
21.656***
|
(6.138)
|
7095
|
7847
|
64053.158 / 64053.158
|
|
msesum without covariates
|
21.354
|
(11.931)
|
7095
|
7847
|
30384.082 / 30384.082
|
|
msesum with covariates
|
20.595***
|
(7.103)
|
7095
|
7847
|
49469.948 / 49469.948
|
|
msetwo without covariates
|
21.539
|
(11.503)
|
7095
|
7847
|
30258.83 / 36229.785
|
|
msetwo with covariates
|
21.419***
|
(6.438)
|
7095
|
7847
|
77067.969 / 45517.391
|
|
cerrd without covariates
|
19.729
|
(12.788)
|
7095
|
7847
|
26425.443 / 26425.443
|
|
cerrd with covariates
|
16.305**
|
(7.999)
|
7095
|
7847
|
39611.392 / 39611.392
|
Variables Summary
# Create a summary table for the variables
datasummary(
RoadDensity + RomanEmpire + GDP + HDI + Elevation + PopDensity ~ N + Mean + SD + Min + Max,
data = analysis_data,
title = "Table 1 - Summary of the Variables"
)
tinytable_3dnqt0jc5u1gt3iivyfl
Table 1 - Summary of the Variables
| |
N |
Mean |
SD |
Min |
Max |
| RoadDensity |
14942 |
49.73 |
97.97 |
0.00 |
1304.57 |
| RomanEmpire |
14942 |
0.47 |
0.50 |
0.00 |
1.00 |
| GDP |
14942 |
344856703.99 |
954220457.11 |
0.00 |
30173835264.00 |
| HDI |
14942 |
0.85 |
0.07 |
0.70 |
0.95 |
| Elevation |
14942 |
362.48 |
319.91 |
-14.85 |
2348.56 |
| PopDensity |
14942 |
171.29 |
329.68 |
1.51 |
8104.50 |
Balance Test Results
# Define the balance test results
balance_tests <- list(
`Log HDI` = rdrobust(y = analysis_data$LogHDI, x = analysis_data$DistanceToBoundary, c = 0, p = 1, kernel = "triangular"),
`Log GDP` = rdrobust(y = analysis_data$LogGDP, x = analysis_data$DistanceToBoundary, c = 0, p = 1, kernel = "triangular"),
`Log Population Density` = rdrobust(y = analysis_data$LogPopDensity, x = analysis_data$DistanceToBoundary, c = 0, p = 1, kernel = "triangular")
)
# Function to make table columns from balance test results
make_balance_table_column <- function(estimates) {
est <- estimates$coef[1, 1] |> round(3)
se <- estimates$se[1, 1] |> round(3)
robust_p_value <- estimates$pv[1, 1] |> round(3)
stars <- case_when(
robust_p_value < 0.01 ~ "***",
robust_p_value < 0.05 ~ "**",
robust_p_value < 0.10 ~ "*",
TRUE ~ ""
)
n_control <- estimates$N[2]
n_treat <- estimates$N[1]
bw <- estimates$bws[1, 1] |> round(3)
data.frame(out = c(
paste0(est, stars),
paste0("(", se, ")"),
n_treat,
n_control,
bw
))
}
# Function to summarize balance test models in a neat table
modelsummary_balance <- function(list_of_balance_tests, row_names = NULL, row_names_add = NULL, ...) {
if (is.null(row_names)) {
row_names <- c("Coefficient", "SE (BC)", "N (Treated)", "N (Control)", "Bandwidth")
if (!is.null(row_names_add)) {
row_names <- c(row_names, row_names_add)
}
}
names <- names(list_of_balance_tests)
df <- list_of_balance_tests |>
map(~make_balance_table_column(.x)) |>
discard(is.null) |>
reduce(bind_cols)
if (nrow(df) == 0) {
return(NULL)
}
out <- bind_cols(" " = row_names, df)
names(out) <- c(" ", names)
datasummary_df(out, ...)
}
# Create the summary table for balance tests
modelsummary_balance(balance_tests, title = "Table 2 - Balance Tests Results")
## New names:
## New names:
## New names:
## • `out` -> `out...1`
## • `out` -> `out...2`
tinytable_rjpqhm2t15nr4lba9ov4
Table 2 - Balance Tests Results
| |
Log HDI |
Log GDP |
Log Population Density |
| Coefficient |
0.007 |
0.252 |
0.126 |
| SE (BC) |
(0.004) |
(0.165) |
(0.099) |
| N (Treated) |
7847 |
7847 |
7847 |
| N (Control) |
7095 |
7095 |
7095 |
| Bandwidth |
57753.102 |
30623.426 |
39007.354 |
Placebo Test Results
# Perform Placebo RDD analyses and store the results
placebo_rdd_results <- list()
# Loop over placebo cutoffs and run rdrobust
for (cutoff in placebo_cutoffs) {
placebo_rdd_results[[paste0("Placebo cutoff at ", cutoff, " meters")]] <- rdrobust(
y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = cutoff,
covs = cbind(analysis_data$LogGDP, analysis_data$LogHDI, analysis_data$Elevation, analysis_data$LogPopDensity),
kernel = "triangular"
)
}
# Function to make table columns from RDD estimates
make_placebo_table_column <- function(estimates, margin_dv = 0.05, extra_rows = NULL) {
est <- estimates$Estimate[1] |> round(3)
se <- estimates$Estimate[3] |> round(3)
robust_p_value <- estimates$pv[3] |> round(3)
stars <- case_when(
robust_p_value < 0.01 ~ "***",
robust_p_value < 0.05 ~ "**",
robust_p_value < 0.10 ~ "*",
TRUE ~ ""
)
n_control <- estimates$N[1]
n_treat <- estimates$N[2]
bw <- estimates$bws[1, 1] |> round(3)
data.frame(out = c(
paste0(est, stars),
paste0("(", se, ")"),
n_treat,
n_control,
bw,
extra_rows
))
}
# Function to summarize RDD models in a neat table
modelsummary_placebo_rdrobust <- function(list_of_rdrobust_objects, row_names = NULL, row_names_add = NULL, ...) {
if (is.null(row_names)) {
row_names <- c("Coefficient", "SE (BC)", "N (Treated)", "N (Control)", "Bandwidth")
if (!is.null(row_names_add)) {
row_names <- c(row_names, row_names_add)
}
}
names <- names(list_of_rdrobust_objects)
df <- list_of_rdrobust_objects |>
map(make_placebo_table_column) |>
reduce(bind_cols)
names(df) <- names
out <- bind_cols(" " = row_names, df)
datasummary_df(out, ...)
}
# Create a table for placebo tests
modelsummary_placebo_rdrobust(placebo_rdd_results, title = "Table 5 - Placebo Test Results")
## New names:
## New names:
## • `out` -> `out...1`
## • `out` -> `out...2`
tinytable_co8ef3c8a9r2c05bskn9
Table 5 - Placebo Test Results
| |
Placebo cutoff at -50000 meters |
Placebo cutoff at 50000 meters |
Placebo cutoff at 1e+05 meters |
| Coefficient |
1.522 |
7.451 |
11.06 |
| SE (BC) |
(5.185) |
(7.742) |
(8.644) |
| N (Treated) |
9378 |
4696 |
2689 |
| N (Control) |
5564 |
10246 |
12253 |
| Bandwidth |
44970.121 |
23227.371 |
28214.108 |
# Perform sensitivity analyses with different polynomial orders and store the results
sensitivity_results <- list()
# Polynomial orders to test
polynomial_orders <- c(1, 2) # Example polynomial orders
# Loop over polynomial orders and run rdrobust
for (p_order in polynomial_orders) {
sensitivity_results[[paste0("Polynomial order ", p_order)]] <- rdrobust(
y = analysis_data$RoadDensity,
x = analysis_data$DistanceToBoundary,
c = 0,
p = p_order,
covs = cbind(analysis_data$LogGDP, analysis_data$LogHDI, analysis_data$Elevation, analysis_data$LogPopDensity),
kernel = "triangular"
)
}
# Function to make table columns from RDD estimates
make_sensitivity_table_column <- function(estimates, margin_dv = 0.05, extra_rows = NULL) {
est <- estimates$Estimate[1] |> round(3)
se <- estimates$Estimate[3] |> round(3)
robust_p_value <- estimates$pv[3] |> round(3)
stars <- case_when(
robust_p_value < 0.01 ~ "***",
robust_p_value < 0.05 ~ "**",
robust_p_value < 0.10 ~ "*",
TRUE ~ ""
)
n_control <- estimates$N[1]
n_treat <- estimates$N[2]
bw <- estimates$bws[1, 1] |> round(3)
data.frame(out = c(
paste0(est, stars),
paste0("(", se, ")"),
n_treat,
n_control,
bw,
extra_rows
))
}
# Function to summarize RDD models in a neat table
modelsummary_sensitivity_rdrobust <- function(list_of_rdrobust_objects, row_names = NULL, row_names_add = NULL, ...) {
if (is.null(row_names)) {
row_names <- c("Coefficient", "SE (BC)", "N (Treated)", "N (Control)", "Bandwidth")
if (!is.null(row_names_add)) {
row_names <- c(row_names, row_names_add)
}
}
names <- names(list_of_rdrobust_objects)
df <- list_of_rdrobust_objects |>
map(make_sensitivity_table_column) |>
reduce(bind_cols)
names(df) <- names
out <- bind_cols(" " = row_names, df)
datasummary_df(out, ...)
}
# Create the summary table for sensitivity analysis
modelsummary_sensitivity_rdrobust(sensitivity_results, title = "Table 6 - Sensitivity Analysis Results")
## New names:
## • `out` -> `out...1`
## • `out` -> `out...2`
tinytable_qz6c7pwykfzjiflpkf7c
Table 6 - Sensitivity Analysis Results
| |
Polynomial order 1 |
Polynomial order 2 |
| Coefficient |
21.656*** |
18.932* |
| SE (BC) |
(6.138) |
(8.996) |
| N (Treated) |
7095 |
7095 |
| N (Control) |
7847 |
7847 |
| Bandwidth |
64053.158 |
67221.096 |