[1, 2] Choose two categories of businesses, Choose a city

Categories of businesses chosen in this assignment

Cafes and Bars

Study area

Suwanee city, GA

[3] Get Census Tracts

This chunk fetches the Census Tracts for Suwanee city, GA, and computes the radius and centroid point based on tract geometry.

city <- tigris::places('GA') %>% 
  filter(NAME == 'Suwanee') 

tract <- suppressMessages(
  get_acs(geography = "tract",
          state = "GA",
          variables = c(hhincome = 'B19019_001'),
          year = 2021,
          survey = "acs5", # American Community Survey 5-year estimate
          geometry = TRUE, 
          output = "wide") 
)

tract_city <- tract[city,]

# Function to calculate radius based on bounding box geometry
get_r <- function(poly, epsg_id){
  bb <- st_bbox(poly)
  bb_corner <- st_point(c(bb[1], bb[2])) %>% st_sfc(crs = epsg_id)
  # Get centroid of the bb
  bb_center_x <- (bb[3]+bb[1])/2
  bb_center_y <- (bb[4]+bb[2])/2
  bb_center <- st_point(c(bb_center_x, bb_center_y)) %>% st_sfc(crs = epsg_id) %>% st_sf()
  
  r <- st_distance(bb_corner, bb_center)
  bb_center$radius <- r*1.1
  return(bb_center)
}

epsg_id <- 4326
tract_city.loop <- vector("list", nrow(tract_city))

for (i in 1:nrow(tract_city)){
  tract_city.loop [[i]] <- tract_city %>% 
    st_transform(crs = epsg_id) %>% 
    st_geometry() %>% 
    .[[i]] %>% 
    get_r(epsg_id = epsg_id)
}

tract_city <- bind_rows(tract_city.loop)

# Add centroid coordinates
tract_city %<>% mutate(
  x = st_coordinates(st_centroid(tract_city$geometry))[,1],
  y = st_coordinates(st_centroid(tract_city$geometry))[,2]
)

[4] Get business data from Yelp

Get business data from Yelp using yelpr package for the selected city & business categories

# Function to retrieve business data from Yelp for specific categories and Census Tracts
get_yelp <- function(tract, category){
  Sys.sleep(1)
  n <- 1
  # First request ---
  resp <- business_search(api_key = Sys.getenv("yelp_api"), 
                          categories = category, 
                          latitude = tract$y, 
                          longitude = tract$x, 
                          offset = (n - 1) * 50,
                          radius = round(tract$radius), 
                          limit = 50)

  # Calculate how many requests are needed in total
  required_n <- ceiling(resp$total/50)
  
  # Message when there are no outcomes
  if (required_n == 0) {
    print("No businesses found for this tract.")
    return(NULL)
  }
  
  out <- vector("list", required_n)
  out[[n]] <- resp$businesses

  names(out)[n] <- required_n
  
  # If more than 1,000
  if (resp$total >= 1000)
  {
    print(glue::glue("{n}th row has >= 1000 businesses."))
    return(out)
  } 
  else 
  {
    # add 1 to n
    n <- n + 1

    # Starting a loop
    while(n <= required_n){
      resp <- business_search(api_key = Sys.getenv("yelp_api"), 
                              categories = category, 
                              latitude = tract$y, 
                              longitude = tract$x, 
                              offset = (n - 1) * 50, 
                              radius = round(tract$radius), 
                              limit = 50)
      
      out[[n]] <- resp$businesses
      
      n <- n + 1
    } #<< end of while loop
    
    out <- out %>% bind_rows()
    return(out)
  }
}

# Initialize an empty list to store Yelp data for bars for each tract
yelp_bar_list <- vector("list", nrow(tract_city))
# Loop over each row (Census Tract) and retrieve Yelp data for bars
for (row in 1:nrow(tract_city)){
  yelp_bar_list[[row]] <- suppressMessages(get_yelp(tract_city[row,], "bars"))
  print(paste0("Current row: ", row))
}
# Combine all Yelp data for bars into a single data frame
yelp_bars <- yelp_bar_list %>% bind_rows() %>% as_tibble()

# Initialize an empty list to store Yelp data for cafes for each tract
yelp_cafe_list <- vector("list", nrow(tract_city))
# Loop over each row (Census Tract) and retrieve Yelp data for cafes
for (row in 1:nrow(tract_city)){
  yelp_cafe_list[[row]] <- suppressMessages(get_yelp(tract_city[row,], "cafes"))
  print(paste0("Current row: ", row))
}
# Combine all Yelp data for cafes into a single data frame
yelp_cafes <- yelp_cafe_list %>% bind_rows() %>% as_tibble()

# Add a new column 'type' to differentiate cafes from bars
yelp_cafes %<>% mutate(type = "cafe")
yelp_bars %<>% mutate(type = "bar")

# Combine the two data frames (cafes and bars) into a single data frame
yelp_all_pois <- rbind(yelp_cafes, yelp_bars)

[5] Create two maps

One shows the location of Yelp businesses (bars and cafes), and the other shows the city boundary and Census Tracts.

## [1] Location of Yelp businesses
# Convert the Yelp data frame to an sf object
yelp_sf <- yelp_all_pois %>% 
  mutate(x = .$coordinates$longitude,
         y = .$coordinates$latitude) %>% 
  filter(!is.na(x) & !is.na(y)) %>% 
  st_as_sf(coords = c("x", "y"), crs = 4326)

tmap_mode("view")
## tmap mode set to interactive viewing
# Plot the Yelp business locations, categorized by type (bar or cafe)
tm_shape(yelp_sf) +
  tm_dots(col = "type", style="quantile")
## [2] City boundary and Census Tracts
# Create an sf object for the city boundary and Census Tracts
tract_city_sf <- st_intersection(tract, st_transform(city, st_crs(tract)))
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
# Plot the Census Tracts and city boundary on the map
tm_shape(tract_city_sf) +
  tm_borders(col = "blue", lwd = 2) + # Census Tracts with borders
  tm_shape(city) +
  tm_borders(col = "red", lwd = 3) +  # City boundary in red
  tm_fill(alpha = 0.7)  # Semi-transparent fill for the city boundary

[6] Questions

Which city did you choose?

Suwanee, GA

How many businesses are there in total?

173

How many businesses are there for each business category?

Bars: 124 Cafes: 49

Upon visual inspection, can you see any noticeable spatial patterns to the way they are distributed across the city (e.g., clustering of businesses at some parts of the city)?

There are noticeable concentrations of both cafes and bars in certain areas. Businesses, for example, tend to be located near major roads (e.g., I-85, Route 316 and 23) or commercial centers. In contrast, there are fewer businesses scattered across the northern and western parts of the city, which could indicate more residential or less commercialized areas.

(Optional) Are there any other interesting findings?

There are about twice as many bars as cafes. Given that Suwanee is known for its higher levels of education and high-quality residential areas, one might expect cafes to be more prevalent, as they often cater to lifestyles associated with such demographics. Cafes typically provide spaces for socializing, working, and studying; in contrast, the higher number of bars may suggest a different social dynamic or could be an indicator of certain areas being more commercially oriented toward nightlife or entertainment than anticipated.