library(arrow); library(dplyr); library(sf)
##
## Attaching package: 'arrow'
## The following object is masked from 'package:utils':
##
## timestamp
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
## Linking to GEOS 3.13.0, GDAL 3.8.5, PROJ 9.5.1; sf_use_s2() is TRUE
core_medical_regex <- "Hospital|Emergency|Urgent Care|Planned Parenthood|Reproductive|General Practice|Family Medicine"
detroit_raw <- open_dataset("DetroitData") %>%
filter(toupper(CITY) %in% c("DETROIT", "HIGHLAND PARK")) %>%
select(LOCATION_NAME, LATITUDE, LONGITUDE, CATEGORY_TAGS, SUB_CATEGORY, STREET_ADDRESS) %>%
collect() %>%
filter(!is.na(LATITUDE) & !is.na(LONGITUDE))
clinics_sf <- detroit_raw %>%
filter(grepl(core_medical_regex, paste(LOCATION_NAME, CATEGORY_TAGS, SUB_CATEGORY), ignore.case = TRUE)) %>%
filter(!grepl("Mental Health|Psychiat|Dental|Dentist|Dermatology|Optometry", SUB_CATEGORY, ignore.case = TRUE)) %>%
mutate(Facility_Type = case_when(
grepl("Hospital", SUB_CATEGORY, ignore.case = TRUE) ~ "Hospital",
grepl("Urgent Care|Emergency", LOCATION_NAME, ignore.case = TRUE) ~ "Emergency/Urgent Care",
grepl("Planned Parenthood|Reproductive", LOCATION_NAME, ignore.case = TRUE) ~ "Specialized Clinic",
TRUE ~ "Primary Care"
)) %>%
group_by(STREET_ADDRESS) %>% slice(1) %>% ungroup() %>%
st_as_sf(coords = c("LONGITUDE", "LATITUDE"), crs = 4326) %>% st_transform(3857)
bus_sf <- detroit_raw %>%
filter(grepl("Bus", paste(CATEGORY_TAGS, SUB_CATEGORY), ignore.case = TRUE)) %>%
st_as_sf(coords = c("LONGITUDE", "LATITUDE"), crs = 4326) %>% st_transform(3857)
nearest_idx <- st_nearest_feature(clinics_sf, bus_sf)
clinics_sf$dist_m <- as.numeric(st_distance(clinics_sf, bus_sf[nearest_idx, ], by_element = TRUE))
clinics_sf$Access_Status <- ifelse(clinics_sf$dist_m > 400, "Transit Isolated", "Accessible")
table(clinics_sf$Facility_Type)
##
## Emergency/Urgent Care Hospital Primary Care
## 16 53 31
## Specialized Clinic
## 2
library(leaflet)
nearest_bus_pts <- bus_sf[st_nearest_feature(clinics_sf, bus_sf), ]
spider_lines <- st_sfc(lapply(1:nrow(clinics_sf), function(i) {
st_linestring(rbind(st_coordinates(st_transform(clinics_sf[i,], 4326)),
st_coordinates(st_transform(nearest_bus_pts[i,], 4326))))
}), crs = 4326) %>% st_sf()
clinics_4326 <- st_transform(clinics_sf, 4326)
bus_4326 <- st_transform(bus_sf, 4326)
spider_4326 <- st_transform(spider_lines, 4326)
target_colors <- c("#E31A1C", "#FF7F00", "#6A3D9A", "#1F78B4")
target_levels <- c("Hospital", "Emergency/Urgent Care", "Specialized Clinic", "Primary Care")
pal <- colorFactor(palette = target_colors, levels = target_levels)
leaflet() %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(
data = bus_4326,
radius = 1.5,
color = "#999999",
stroke = FALSE,
fillOpacity = 0.5,
group = "Bus Network"
) %>%
addCircleMarkers(
data = clinics_4326,
radius = ~sqrt(dist_m) * 0.8,
color = ~pal(Facility_Type),
fillOpacity = 0.7,
stroke = TRUE,
weight = 1,
popup = ~paste0("<b>", LOCATION_NAME, "</b><br>Dist: ", round(dist_m), "m"),
group = "Facilities"
) %>%
addPolylines(
data = spider_4326,
color = "#222222", # Very dark gray for maximum contrast
weight = 3, # Bold stroke
opacity = 0.8,
group = "Access Gaps"
) %>%
addLegend(
pal = pal,
values = target_levels,
title = "Facility Type",
position = "bottomright"
)
library(ggplot2)
ggplot() +
geom_sf(data = bus_sf, color = "gray90", size = 0.05, alpha = 0.3) +
geom_sf(data = clinics_sf, aes(size = dist_m, color = Facility_Type), alpha = 0.6) +
scale_size_area(
max_size = 8,
breaks = c(0, 250, 500, 1000), # Defines the specific ranges in the legend
labels = c("0m (Direct)", "250m", "500m (Isolated)", "1km+"),
name = "Walking Distance"
) +
scale_color_manual(
values = c("Hospital" = "#E31A1C",
"Emergency/Urgent Care" = "#FF7F00",
"Specialized Clinic" = "#6A3D9A",
"Primary Care" = "#1F78B4"),
name = "Facility Type"
) +
theme_minimal() +
guides(
color = guide_legend(override.aes = list(size = 5), order = 1),
size = guide_legend(override.aes = list(color = "gray40"), order = 2)
) +
labs(
title = "Detroit Healthcare: Transit Access Burden",
subtitle = "Analysis of core medical infrastructure relative to 6,000+ bus stops",
caption = "Distance calculated as straight-line meters to nearest transit node"
) +
theme(
text = element_text(face = "bold"),
panel.grid = element_blank(),
legend.position = "right",
plot.title = element_text(size = 16),
panel.background = element_rect(fill = "#fafafa", color = NA)
)

library(gt)
clinics_sf %>%
st_drop_geometry() %>%
group_by(Facility_Type) %>%
summarise(
Total = n(),
`Isolated Count` = sum(dist_m > 400),
`Access Rate (%)` = (sum(dist_m <= 400) / n()) * 100
) %>%
gt() %>%
tab_header(title = "Detroit Healthcare Transit Access Audit") %>%
fmt_number(columns = `Access Rate (%)`, decimals = 1) %>%
data_color(columns = `Access Rate (%)`, colors = scales::col_numeric(palette = "RdYlGn", domain = c(50, 100)))
## Warning: Since gt v0.9.0, the `colors` argument has been deprecated.
## • Please use the `fn` argument instead.
## This warning is displayed once every 8 hours.
| Detroit Healthcare Transit Access Audit |
| Facility_Type |
Total |
Isolated Count |
Access Rate (%) |
| Emergency/Urgent Care |
16 |
4 |
75.0 |
| Hospital |
53 |
14 |
73.6 |
| Primary Care |
31 |
1 |
96.8 |
| Specialized Clinic |
2 |
0 |
100.0 |
library(ggplot2)
detroit_colors <- c(
"Hospital" = "#E31A1C",
"Emergency/Urgent Care" = "#FF7F00",
"Specialized Clinic" = "#6A3D9A",
"Primary Care" = "#1F78B4"
)
ggplot(clinics_sf, aes(x = Facility_Type, y = dist_m, fill = Facility_Type)) +
geom_violin(alpha = 0.2, color = NA) +
geom_jitter(aes(color = Facility_Type), width = 0.25, alpha = 0.4, size = 1.2) +
geom_hline(yintercept = 400, linetype = "dashed", color = "#ff4e50", linewidth = 1.2) +
annotate("text", x = 0.6, y = 440, label = "400m Isolation Threshold",
color = "#ff4e50", fontface = "bold", hjust = 0) +
coord_cartesian(ylim = c(0, 1000)) +
scale_fill_manual(values = detroit_colors) +
scale_color_manual(values = detroit_colors) +
theme_minimal() +
theme(
legend.position = "none",
text = element_text(face = "bold"),
axis.text.x = element_text(size = 10),
panel.grid.minor = element_blank(),
panel.grid.major.x = element_blank()
) +
labs(
title = "DETROIT HEALTHCARE ACCESS DISTRIBUTION",
subtitle = "Meters to nearest bus stop by high-stakes facility type",
y = "Distance (Meters)",
x = ""
)
