Reimagining_SF_Shiny_App / Rscripts /prep /profile_startup.R
avephill
Deploy origin/main to HF Space (PNGs via LFS)
a4b8c45
Raw
History Blame Contribute Delete
15.8 kB
# ============================================================================
# Startup Performance Profiling
# ============================================================================
# This script benchmarks each data loading step in setup.R to identify
# bottlenecks and guide optimization decisions (precomputation, caching, etc.)
library(tidyverse)
library(profvis)
# Create benchmark dataframe to track timings
benchmarks <- tibble(
step = character(),
description = character(),
time_sec = numeric(),
time_pct = numeric()
)
start_overall <- Sys.time()
# ============================================================================
# 1. Library Loading
# ============================================================================
t1 <- Sys.time()
library(shiny)
library(shinydashboard)
library(leaflet)
library(mapboxapi)
library(tidyverse)
library(tidycensus)
library(sf)
library(DT)
library(RColorBrewer)
library(terra)
library(data.table)
library(mapview)
library(sjPlot)
library(sjlabelled)
library(bslib)
library(shinycssloaders)
library(DBI)
library(duckdb)
library(dbplyr)
library(gtfsrouter)
library(tidytransit)
library(fmsb)
library(scales)
time_libraries <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "00_libraries",
description = "Load all 23 packages",
time_sec = time_libraries,
time_pct = NA
))
# ============================================================================
# 2. Greenspace (OSM)
# ============================================================================
t1 <- Sys.time()
osm_greenspace <- st_read("/vsicurl/https://huggingface.co/datasets/boettiger-lab/sf_biodiv_access/resolve/main/greenspaces_osm_nad83.shp", quiet = TRUE) |>
st_transform(4326)
if (!"name" %in% names(osm_greenspace)) {
osm_greenspace$name <- "Unnamed Greenspace"
}
time_greenspace <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "01_greenspace",
description = "Load OSM greenspace polygons",
time_sec = time_greenspace,
time_pct = NA
))
# ============================================================================
# 3. Greenspace Distance Rasters
# ============================================================================
t1 <- Sys.time()
greenspace_dist_raster <- terra::rast("/vsicurl/https://huggingface.co/datasets/boettiger-lab/sf_biodiv_access/resolve/main/nearest_greenspace_dist.tif")
greenspace_osmid_raster <- terra::rast("/vsicurl/https://huggingface.co/datasets/boettiger-lab/sf_biodiv_access/resolve/main/nearest_greenspace_osmid.tif")
time_gs_rasters <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "02_gs_rasters",
description = "Load greenspace distance rasters",
time_sec = time_gs_rasters,
time_pct = NA
))
# ============================================================================
# 4. NDVI Raster
# ============================================================================
t1 <- Sys.time()
ndvi <- terra::rast("/vsicurl/https://huggingface.co/datasets/boettiger-lab/sf_biodiv_access/resolve/main/SF_EastBay_NDVI_Sentinel_10.tif")
time_ndvi <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "03_ndvi",
description = "Load NDVI raster",
time_sec = time_ndvi,
time_pct = NA
))
# ============================================================================
# 5. CBG Vector Data
# ============================================================================
t1 <- Sys.time()
download.file(
'https://huggingface.co/datasets/boettiger-lab/sf_biodiv_access/resolve/main/cbg_vect_sf.Rdata',
'/tmp/cbg_vect_sf.Rdata',
mode = 'wb',
quiet = TRUE
)
load('/tmp/cbg_vect_sf.Rdata')
if (!"unique_species" %in% names(cbg_vect_sf)) {
cbg_vect_sf$unique_species <- cbg_vect_sf$n_species
}
if (!"n_observations" %in% names(cbg_vect_sf)) {
cbg_vect_sf$n_observations <- cbg_vect_sf$n
}
if (!"median_inc" %in% names(cbg_vect_sf)) {
cbg_vect_sf$median_inc <- cbg_vect_sf$medincE
}
if (!"ndvi_mean" %in% names(cbg_vect_sf)) {
cbg_vect_sf$ndvi_mean <- cbg_vect_sf$ndvi_sentinel
}
time_cbg <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "04_cbg",
description = "Download + load census block groups",
time_sec = time_cbg,
time_pct = NA
))
# ============================================================================
# 6. Hotspots/Coldspots
# ============================================================================
t1 <- Sys.time()
biodiv_hotspots <- st_read("/vsicurl/https://huggingface.co/datasets/boettiger-lab/sf_biodiv_access/resolve/main/hotspots.shp", quiet = TRUE) |>
st_transform(4326)
biodiv_coldspots <- st_read("/vsicurl/https://huggingface.co/datasets/boettiger-lab/sf_biodiv_access/resolve/main/coldspots.shp", quiet = TRUE) |>
st_transform(4326)
time_hotcold <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "05_hotcold",
description = "Load hotspots and coldspots",
time_sec = time_hotcold,
time_pct = NA
))
# ============================================================================
# 7. RSF Projects
# ============================================================================
t1 <- Sys.time()
rsf_projects <- st_read("data/source/RSF_Program_Projects_polygons.gpkg", quiet = TRUE) |>
st_transform(4326)
time_rsf <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "06_rsf",
description = "Load RSF program projects",
time_sec = time_rsf,
time_pct = NA
))
# ============================================================================
# 8. GBIF Parquet Reference (just get path, don't load yet)
# ============================================================================
time_gbif_setup <- 0.001 # negligible
benchmarks <- bind_rows(benchmarks, tibble(
step = "07_gbif_setup",
description = "GBIF parquet path (lazy load in server)",
time_sec = time_gbif_setup,
time_pct = NA
))
# ============================================================================
# 9. GTFS Data
# ============================================================================
t1 <- Sys.time()
gtfs_path <- '/Users/diegoellis/Desktop/RSF_next_steps/GPFS_OSM_Transit/sf_muni_gtfs-current/'
# Stops
gtfs_stops_sf <- tryCatch({
read.csv(file.path(gtfs_path, 'stops.txt')) |>
st_as_sf(coords = c("stop_lon", "stop_lat"), crs = 4326)
}, error = function(e) {
warning("GTFS stops failed: ", e$message)
NULL
})
# Route shapes
gtfs_shapes_raw <- read.csv(file.path(gtfs_path, 'shapes.txt'))
gtfs_trips_raw <- read.csv(file.path(gtfs_path, 'trips.txt'))
gtfs_routes_raw <- read.csv(file.path(gtfs_path, 'routes.txt'))
shape_route_map <- gtfs_trips_raw |>
distinct(shape_id, route_id)
route_meta <- gtfs_routes_raw |>
select(route_id, route_short_name, route_long_name, route_color) |>
mutate(route_color_hex = paste0("#", trimws(route_color)))
shapes_split <- gtfs_shapes_raw |>
arrange(shape_id, shape_pt_sequence) |>
group_by(shape_id) |>
group_split()
shape_geoms <- lapply(shapes_split, function(s) {
st_linestring(cbind(s$shape_pt_lon, s$shape_pt_lat))
})
gtfs_routes_sf <- st_sf(
shape_id = sapply(shapes_split, function(s) s$shape_id[1]),
geometry = st_sfc(shape_geoms, crs = 4326)
) |>
left_join(shape_route_map, by = "shape_id") |>
left_join(route_meta, by = "route_id")
time_gtfs_basic <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "08_gtfs_basic",
description = "Load GTFS stops and route shapes",
time_sec = time_gtfs_basic,
time_pct = NA
))
# ============================================================================
# 10. gtfsrouter Initialization
# ============================================================================
t1 <- Sys.time()
gtfs_router <- tryCatch({
gtfs_zip_path <- tempfile(fileext = ".zip")
old_wd <- getwd()
setwd(gtfs_path)
utils::zip(gtfs_zip_path, files = list.files('.', pattern = "\\.txt$"))
setwd(old_wd)
gr <- gtfsrouter::extract_gtfs(gtfs_zip_path)
gtfsrouter::gtfs_timetable(gr, day = "Monday")
}, error = function(e) {
warning("gtfsrouter failed: ", e$message)
NULL
})
time_gtfs_router <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "09_gtfs_router",
description = "Initialize gtfsrouter + timetable",
time_sec = time_gtfs_router,
time_pct = NA
))
# ============================================================================
# 11. tidytransit Headways
# ============================================================================
t1 <- Sys.time()
gtfs_stop_headways <- tryCatch({
gt <- tidytransit::read_gtfs(gtfs_path)
tidytransit::get_stop_frequency(gt, start_time = 7 * 3600, end_time = 9 * 3600) |>
group_by(stop_id) |>
summarise(
mean_headway_min = mean(mean_headway, na.rm = TRUE) / 60,
n_departures_peak = sum(n_departures, na.rm = TRUE),
.groups = "drop"
) |>
mutate(stop_id = as.character(stop_id))
}, error = function(e) {
warning("tidytransit failed: ", e$message)
NULL
})
if (!is.null(gtfs_stop_headways) && !is.null(gtfs_stops_sf)) {
gtfs_stops_sf <- gtfs_stops_sf |>
mutate(stop_id = as.character(stop_id)) |>
left_join(gtfs_stop_headways, by = "stop_id")
}
time_gtfs_headways <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "10_gtfs_headways",
description = "Compute transit stop headways (AM peak)",
time_sec = time_gtfs_headways,
time_pct = NA
))
# ============================================================================
# 12. CalEnviroScreen
# ============================================================================
t1 <- Sys.time()
calenviro_path <- '/Users/diegoellis/Downloads/calenviroscreen40gdb_F_2021.gdb'
if (!file.exists(calenviro_path)) {
calenviro_path <- '/Users/diegoellis/Desktop/Projects/Presentations/Data_Schell_Lab_Tutorial/calenviroscreen40gdb_F_2021.gdb'
}
cenv_sf <- tryCatch({
sf::st_read(calenviro_path, quiet = TRUE) |>
dplyr::filter(grepl("san francisco", County, ignore.case = TRUE), !is.na(CIscore)) |>
dplyr::select(
Tract, CIscore, CIscoreP,
PM2_5, PM2_5_Pctl, Traffic, Traffic_Pctl,
Poverty, Poverty_Pctl, HousBurd, HousBurd_Pctl,
County
) |>
sf::st_transform(4326) |>
sf::st_make_valid()
}, error = function(e) {
warning("CalEnviroScreen failed: ", e$message)
NULL
})
time_calenviro <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "11_calenviro",
description = "Load CalEnviroScreen layer",
time_sec = time_calenviro,
time_pct = NA
))
# ============================================================================
# 13. SF EJ Communities
# ============================================================================
t1 <- Sys.time()
sf_ej_path <- '/Users/diegoellis/Downloads/San Francisco Environmental Justice Communities Map_20251217/geo_export_a21b0a0a-7306-46fd-8381-06581cdbe6e9.shp'
sf_ej_sf <- tryCatch({
sf::st_read(sf_ej_path, quiet = TRUE) |>
dplyr::mutate(
symbol_hex = stringr::str_split(symbol_rgb, ",\\s*") |>
lapply(function(x) sprintf("#%02X%02X%02X",
as.integer(x[1]), as.integer(x[2]), as.integer(x[3]))) |>
unlist(),
ej_label = dplyr::case_when(
is.na(score) ~ "Not EJ",
score >= 21 ~ "High EJ burden (21-30)",
score >= 11 ~ "Moderate EJ burden (11-20)",
score >= 1 ~ "Low EJ burden (1-10)",
score == 0 ~ "Score 0",
TRUE ~ "Unknown"
)
) |>
sf::st_transform(4326) |>
sf::st_make_valid()
}, error = function(e) {
warning("SF EJ layer failed: ", e$message)
NULL
})
time_ej <- as.numeric(difftime(Sys.time(), t1, units = "secs"))
benchmarks <- bind_rows(benchmarks, tibble(
step = "12_ej",
description = "Load SF EJ communities layer",
time_sec = time_ej,
time_pct = NA
))
# ============================================================================
# SUMMARY
# ============================================================================
time_overall <- as.numeric(difftime(Sys.time(), start_overall, units = "secs"))
benchmarks <- benchmarks |>
mutate(time_pct = round(100 * time_sec / time_overall, 1))
# Print results
cat("\n")
cat("================================================================================\n")
cat("STARTUP PERFORMANCE BENCHMARK\n")
cat("================================================================================\n\n")
print(benchmarks |> select(step, description, time_sec, time_pct))
cat("\n")
cat("TOTAL STARTUP TIME: ", round(time_overall, 2), " seconds\n")
cat("================================================================================\n\n")
# ============================================================================
# OPTIMIZATION RECOMMENDATIONS
# ============================================================================
cat("OPTIMIZATION RECOMMENDATIONS:\n")
cat("================================================================================\n\n")
# Flag slow steps (>5 seconds or >10% of total)
slow_steps <- benchmarks |>
filter(time_sec > 5 | time_pct > 10) |>
arrange(desc(time_sec))
if (nrow(slow_steps) > 0) {
cat("CRITICAL BOTTLENECKS (>5s or >10%):\n\n")
for (i in 1:nrow(slow_steps)) {
row <- slow_steps[i, ]
cat(" •", row$step, "(", row$time_sec, "s,", row$time_pct, "%)\n")
cat(" Description:", row$description, "\n")
if (grepl("libraries", row$step)) {
cat(" Recommendation: Load packages only in server() if possible. Use lazy loading.\n")
} else if (grepl("download", row$step)) {
cat(" Recommendation: Cache downloaded files locally or use precomputed versions.\n")
} else if (grepl("gtfsrouter|tidytransit", row$step)) {
cat(" Recommendation: Pre-compute and cache GTFS timetable. Consider lazy loading for session.\n")
} else if (grepl("calenviro|ej", row$step)) {
cat(" Recommendation: Pre-filter to SF boundary. Store as .gpkg or parquet locally.\n")
}
cat("\n")
}
} else {
cat("No critical bottlenecks detected (all steps < 5s).\n\n")
}
# Create visualization
p <- ggplot(benchmarks, aes(x = reorder(step, -time_sec), y = time_sec, fill = time_pct)) +
geom_col() +
geom_text(aes(label = paste0(round(time_sec, 2), "s")), vjust = -0.3, size = 3) +
scale_fill_gradient(low = "green", high = "red", name = "% of Total") +
labs(
title = "Shiny App Startup Performance Profile",
subtitle = paste0("Total time: ", round(time_overall, 2), " seconds"),
x = "Loading Step",
y = "Time (seconds)"
) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1, size = 9),
plot.title = element_text(face = "bold", size = 14),
legend.position = "right"
)
print(p)
ggsave("/Users/diegoellis/Desktop/Projects/Postdoc/Biodiversity_Access_Indicator/SF_biodiv_access_shiny/startup_benchmark.png",
p, width = 12, height = 6, dpi = 150)
cat("\nBenchmark plot saved to: startup_benchmark.png\n")
# ============================================================================
# Export benchmark data
# ============================================================================
write_csv(benchmarks,
"/Users/diegoellis/Desktop/Projects/Postdoc/Biodiversity_Access_Indicator/SF_biodiv_access_shiny/startup_benchmarks.csv")
cat("Benchmark data saved to: startup_benchmarks.csv\n\n")