library(tidycensus)
## Warning: package 'tidycensus' was built under R version 4.4.3
library(sf)
## Warning: package 'sf' was built under R version 4.4.3
## Linking to GEOS 3.13.0, GDAL 3.10.1, PROJ 9.5.1; sf_use_s2() is TRUE
library(tmap)
## Breaking News: tmap 3.x is retiring. Please test v4, e.g. with
## remotes::install_github('r-tmap/tmap')
library(jsonlite)
## Warning: package 'jsonlite' was built under R version 4.4.3
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ purrr::flatten() masks jsonlite::flatten()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(httr)
library(jsonlite)
library(reshape2)
##
## Attaching package: 'reshape2'
##
## The following object is masked from 'package:tidyr':
##
## smiths
library(here)
## here() starts at C:/Users/HojungYu/OneDrive/GT_Semester3/Urban_Analytics
library(knitr)
library(dplyr)
I chose ‘coffe_shop’ and ‘pub’ for my assignment.
Buford is my area of interest.
bg <- suppressMessages(
tidycensus::get_acs(geography = "block group", # or "block", "tract", "county", "state" etc.
state = "GA",
county = c("Barrow","Gwinnett"),
variables = c(hhincome = 'B19013_001'),
year = 2023,
survey = "acs5", # we typically use American Community Survey 5-year estimates
geometry = TRUE, # we need an sf object
output = "wide") # wide vs. long
)
## | | | 0% | |= | 1% | |= | 2% | |== | 3% | |=== | 4% | |=== | 5% | |==== | 6% | |===== | 7% | |====== | 8% | |======= | 9% | |======= | 10% | |======== | 11% | |======== | 12% | |========= | 13% | |========== | 15% | |=========== | 16% | |============ | 17% | |============ | 18% | |============= | 18% | |============== | 19% | |============== | 20% | |=============== | 21% | |================ | 22% | |================= | 24% | |================== | 26% | |=================== | 27% | |==================== | 29% | |===================== | 30% | |====================== | 32% | |======================= | 33% | |======================== | 34% | |========================= | 35% | |========================= | 36% | |========================== | 37% | |========================== | 38% | |=========================== | 39% | |============================ | 40% | |============================= | 41% | |============================== | 42% | |=============================== | 45% | |================================ | 46% | |================================= | 47% | |================================= | 48% | |================================== | 49% | |=================================== | 50% | |==================================== | 51% | |===================================== | 53% | |======================================= | 56% | |======================================== | 57% | |========================================== | 60% | |========================================== | 61% | |=========================================== | 62% | |============================================ | 63% | |============================================= | 65% | |============================================== | 66% | |=============================================== | 68% | |================================================= | 70% | |================================================== | 72% | |==================================================== | 74% | |===================================================== | 75% | |===================================================== | 76% | |====================================================== | 78% | |======================================================= | 79% | |======================================================== | 80% | |========================================================= | 81% | |========================================================== | 83% | |============================================================ | 86% | |============================================================= | 86% | |============================================================== | 88% | |============================================================== | 89% | |=============================================================== | 90% | |=============================================================== | 91% | |================================================================ | 92% | |================================================================== | 94% | |=================================================================== | 95% | |=================================================================== | 96% | |==================================================================== | 97% | |===================================================================== | 99% | |======================================================================| 99% | |======================================================================| 100%
# City boundary
buford <- tigris::places('GA') %>% filter(NAME == 'Buford')
## Retrieving data for the year 2022
## | | | 0% | |= | 1% | |= | 2% | |== | 2% | |== | 3% | |=== | 4% | |==== | 5% | |==== | 6% | |===== | 7% | |====== | 9% | |======= | 10% | |======== | 11% | |========= | 12% | |========= | 13% | |========== | 14% | |=========== | 15% | |=========== | 16% | |============ | 17% | |============ | 18% | |============= | 18% | |============== | 19% | |============== | 20% | |=============== | 21% | |=============== | 22% | |================ | 23% | |================= | 24% | |================= | 25% | |================== | 26% | |=================== | 27% | |=================== | 28% | |==================== | 29% | |===================== | 29% | |===================== | 30% | |====================== | 31% | |======================= | 32% | |======================= | 33% | |======================== | 35% | |========================= | 36% | |========================== | 36% | |========================== | 37% | |========================== | 38% | |=========================== | 38% | |============================ | 39% | |============================ | 40% | |============================= | 41% | |============================= | 42% | |============================== | 42% | |============================== | 43% | |=============================== | 44% | |================================ | 45% | |================================ | 46% | |================================= | 47% | |================================= | 48% | |================================== | 48% | |================================== | 49% | |=================================== | 50% | |=================================== | 51% | |==================================== | 51% | |===================================== | 52% | |===================================== | 53% | |====================================== | 54% | |====================================== | 55% | |======================================= | 56% | |======================================== | 57% | |======================================== | 58% | |========================================= | 58% | |========================================== | 60% | |=========================================== | 61% | |============================================ | 62% | |============================================ | 63% | |============================================ | 64% | |============================================= | 65% | |============================================== | 65% | |============================================== | 66% | |=============================================== | 67% | |=============================================== | 68% | |================================================ | 68% | |================================================ | 69% | |================================================= | 70% | |================================================= | 71% | |================================================== | 71% | |================================================== | 72% | |=================================================== | 72% | |=================================================== | 73% | |===================================================== | 76% | |====================================================== | 77% | |======================================================= | 78% | |======================================================= | 79% | |========================================================= | 82% | |========================================================== | 83% | |========================================================== | 84% | |=========================================================== | 84% | |=========================================================== | 85% | |============================================================ | 86% | |============================================================= | 87% | |============================================================= | 88% | |============================================================== | 88% | |============================================================== | 89% | |=============================================================== | 89% | |=============================================================== | 90% | |================================================================ | 92% | |================================================================= | 92% | |================================================================= | 93% | |================================================================== | 94% | |=================================================================== | 95% | |==================================================================== | 97% | |==================================================================== | 98% | |===================================================================== | 98% | |===================================================================== | 99% | |======================================================================| 100%
library("mapview")
## Warning: package 'mapview' was built under R version 4.4.3
# Get BGs intersecting with the City of Decatur boundary
bg_buford <- bg[buford,]
mapview(bg_buford)
I selected method 2 for dividing my area of interest.
# Define EPSG codes for GCS (WGS 84) and PCS (WGS 84 / UTM zone 16N)
# Since my city is Savannah,
gcs_id <- 4326
pcs_id <- 32616 # WGS 84 / UTM zone 16N
# Get bbox for Buford
buford_bb <- buford %>% st_transform(pcs_id) %>% st_bbox()
# Find coordinates for the four sides of the bbox
west <- buford_bb[1]
east <- buford_bb[3]
south <- buford_bb[2]
north <- buford_bb[4]
# Split the bbox into a grid
fishnet_n <- 8
dist <- abs(east - west)/fishnet_n
# Fishnet points
fish_x <- seq(from = west, to = east, by = dist)
fish_y <- seq(from = south, to = north, by = dist)
fishnet <- expand.grid(fish_x, fish_y) %>%
rename(x = Var1, y = Var2) %>%
st_as_sf(coords = c('x', 'y'), crs = pcs_id)
fishnet$x <- fishnet %>% st_transform(gcs_id) %>% st_coordinates() %>% .[,1]
fishnet$y <- fishnet %>% st_transform(gcs_id) %>% st_coordinates() %>% .[,2]
fishnet$r <- dist*(2^(1/2)/2)
# step 1
pts_ll <- fishnet |>
st_drop_geometry() |>
st_as_sf(coords = c("x", "y"), crs = gcs_id)
# step 2
pts_utm <- pts_ll |> st_transform(pcs_id)
bufs <- st_buffer(pts_utm, dist = fishnet$r)
# step 3
tmap_mode("view")
## tmap mode set to interactive viewing
tm_basemap("CartoDB.Positron") +
tm_shape(st_transform(buford, gcs_id)) +
tm_borders(lwd = 2, col = "black") +
tm_shape(st_transform(bufs, gcs_id)) +
tm_polygons(alpha = 0.2, col = "red", border.col = NA) +
tm_shape(pts_ll) +
tm_dots(size = 0.02, col = "blue") +
mapview(bufs)
library(httr)
google_api_key <- Sys.getenv("API_KEY")
# function
nearbySearch <- function(lat, lon, radius, types_vec, fieldmask_vec, google_api_key){
endpoint <- "https://places.googleapis.com/v1/places:searchNearby"
body <- list(
includedTypes = as.list(types_vec),
locationRestriction = list(
circle = list(
center = list(latitude = lat, longitude = lon),
radius = radius
)
),
rankPreference = "DISTANCE"
)
resp <- POST(
endpoint,
add_headers(
"Content-Type" = "application/json",
"X-Goog-Api-Key" = google_api_key,
"X-Goog-FieldMask" = paste(fieldmask_vec, collapse = ",")),
body = body,
encode = "json"
)
#Parse the API response into a data frame.
data <- content(resp, as="text") %>%
jsonlite::fromJSON(flatten = T) %>%
as.data.frame()
if (nrow(data) == 20){
print("WARNING: The response has 20 rows! Consider using a smaller spatial unit.")
}
return(data)
}
bg_buford_xy <- fishnet
# allocate the list first
n <- nrow(bg_buford_xy)
coffee_list <- vector("list", n)
bar_list <- vector("list", n)
for (i in seq_len(nrow(bg_buford_xy))) {
# COFFEE
coffee_list[[i]] <- nearbySearch(
lat = bg_buford_xy$y[i],
lon = bg_buford_xy$x[i],
radius = bg_buford_xy$r[i],
types_vec = 'coffee_shop', # coffee shop and pub
fieldmask_vec = c("places.id",
"places.displayName",
"places.formattedAddress",
"places.location",
"places.types",
"places.priceLevel",
"places.rating",
"places.userRatingCount"),
google_api_key = google_api_key
)
Sys.sleep(1) # throttle requests to respect QPS quotas and avoid 429 rate-limit errors
# PUB
bar_list[[i]] <- nearbySearch(
lat = bg_buford_xy$y[i],
lon = bg_buford_xy$x[i],
radius = bg_buford_xy$r[i],
types_vec = 'bar', # coffee shop and pub
fieldmask_vec = c("places.id",
"places.displayName",
"places.formattedAddress",
"places.location",
"places.types",
"places.priceLevel",
"places.rating",
"places.userRatingCount"),
google_api_key = google_api_key
)
Sys.sleep(1)
}
# Combine all data frames
coffee_all <- dplyr::bind_rows(coffee_list) |>
mutate(source_type = "coffee_shop") |>
distinct(places.id, .keep_all = TRUE) # dedupe across overlapping buffers
bar_all <- dplyr::bind_rows(bar_list) |>
mutate(source_type = "bar") |>
distinct(places.id, .keep_all = TRUE)
# Export the POI into an RDS file
saveRDS(coffee_all, here::here("buford_poi_coffee_shops.rds"))
saveRDS(bar_all, here::here("buford_poi_bars.rds"))
# Convert the data to an sf object using XY coordinates
coffee_all_sf <- coffee_all %>%
rename(x = places.location.longitude, y = places.location.latitude) %>%
filter(!is.na(x) & !is.na(y)) %>%
st_as_sf(coords = c("x", "y"), crs = 4326)
bar_all_sf <- bar_all %>%
rename(x = places.location.longitude, y = places.location.latitude) %>%
filter(!is.na(x) & !is.na(y)) %>%
st_as_sf(coords = c("x", "y"), crs = 4326)
coffee_all_sf$type <- "Coffee"
bar_all_sf$type <- "Bar"
poi_sf <- rbind(coffee_all_sf, bar_all_sf)
# Map
tm_shape(poi_sf) +
tm_dots(col = "type",
size = "places.userRatingCount",
palette = c("Coffee" = "brown", "Bar" = "darkblue"),
border.lwd=0.5,
popup.vars = c("Name" = "places.displayName.text",
"Address" = "places.formattedAddress",
"Rating" = "places.rating",
"Rating Count" = "places.userRatingCount",
"Price Level" = "places.priceLevel")) +
tm_shape(buford) +
tm_borders()
## Legend for symbol sizes not available in view mode.
I chose Buford city in Gwinnett County. It is famous for the largest mall in the state of Georgia, Mall of Georgia. It is also known for its small-town charm and local restaurants.
I selected ‘coffee_shop’ and ‘bar’ to see the difference between nightlife and day-life.
60 rows are in my ‘poi_sf’.
Around the Mall of Georgia, I noticed a concentration of POIs with high numbers of reviews. The area has more bars than coffee shops. It reflects place’s role as a shopping destination where visitors are more likely to engage in dining and nightlife activities than in coffee-centered activities such as studying or working.
In contrast, near Sugar hills and golden Pkwy, coffee shops outnumber bars. This is because the area functions more as local community hub, where daytime activities and neighborhood interactions are more prominent.
Overall, coffee shops are usually clustered near community center while bars are spread out along major roads. These spatial patterns highlight the differing rhythms of suburban life, which can be further investigated through analyzing the common behavior pattern of day-life and nightlife of suburban life.