City of Marietta is located northwest to the city of Atlanta and the county seat of Cobb County. According to US Census 2020, Marietta had a population of 60,792 in 2020, making it the 14th largest city in the State of Georgia. The city of Marietta intersects with 34 Census Tracts. To identify Census Tracts that intersects with the geographic extent of Marietta and visualize them, we can follow these steps using R.
# Load Required Packages
library(tidycensus)
library(tidyverse)
library(tmap)
library(sf)
# Load Census API
tidycensus::census_api_key(Sys.getenv("census_api"))
# Load the geometry of Census Tracts within Cobb County from ACS
tract <- suppressMessages(
get_acs(geography = "tract",
state = "GA",
county = c("Cobb"), # Marietta is the county seat of Cobb County
variables = c(hhincome = 'B19019_001'), #Must insert a variable to load ACS Data
year = 2021,
survey = "acs5", # ACS 5-year estimate
geometry = TRUE, # Returns sf objects
output = "wide")
)
# Load the geometry of Marietta
marietta <- tigris::places('GA') %>%
filter(NAME == 'Marietta')
By running the below codes, we can identify that there are 34 Census Tracts that intersects with the geographic extent of Marietta.
# Identify Census Tracts that intersects with the geographic extent of Marietta
tract_marietta <- tract[marietta, ]
# Calculate Numbers of Census Tracts that intersects with Marietta
message(sprintf("nrow: %s, ncol: %s", nrow(tract_marietta), ncol(tract_marietta)))
## nrow: 34, ncol: 5
# visualize Census tracts that intersects with Marietta along with the City boundary of Marietta
tract_marietta <- tract_marietta %>%
select(GEOID,
hhincome = hhincomeE) # change the column name hhincomeE to hhincome
tmap_mode("view") # switch to interactive viewing
tm_shape(tract_marietta) + tm_borders(lwd = 2) + # visualize census tracts and Ma
tm_shape(marietta) + tm_polygons(col = 'red', alpha = 0.4)
According to Data USA, Marietta’s young and affluent consumer base is reflected in the City’s median age of 35.2 and a median household income of $67,589 in 2022. Accordingly, I wish to navigate the landscape of two businesses, skincare and day spa, which is highly preferred by young, high-income consumers. We can follow these steps using R to understand the spatial and business analytics dynamics of skincare and day spa businesses in Marietta.
To define a search area for Yelpr within 34 census tracts, we use a custom function to create a buffer around each census tract by calculating the centroid of each census tract and the distance of a corner of the bounding box (bb) to the centroid.
# Calibrate a function to identify census-tract-wise radius
get_r <- function(poly, epsg_id){
bb <- st_bbox(poly) # Get bb of a given polygon
bb_corner <- st_point(c(bb[1], bb[2])) %>% st_sfc(crs = epsg_id) # Lat/lon of a corner of the bb
# 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) # r = distance between bb_p and c
bb_center$radius <- r*1.1 # Multiply 1.1 to make the circle larger than the Census Tract.
return(bb_center)
}
# Create buffers for all census tracts
epsg_id <- 4326 # To define GCS
r4all_apply <- tract_marietta %>% # Apply function with laaply to create buffers for each census tracts
st_geometry() %>%
st_transform(crs = epsg_id) %>%
lapply(., function(x) get_r(x, epsg_id = epsg_id))
r4all_apply <- bind_rows(r4all_apply) # combines all results returned by lapply into a single data frame
# Save lat/lon at separate columns
ready_4_yelp <- r4all_apply %>%
mutate(x = st_coordinates(.)[,1],
y = st_coordinates(.)[,2])
tmap_mode('view') # Activate interactive viewing Mode
ready_4_yelp %>% # Visualize the buffers of 34 census tracts
st_buffer(., dist = .$radius) %>% # Radius of the buffer is the radius we just calculated using loop
tm_shape(.) + tm_polygons(alpha = 0.5, col = 'red') + # Buffers will be shown in Red
tm_shape(tract_marietta) + tm_borders(col= 'blue') # Census Tract boundaries will be shown in Blue
Now, by using this buffer, we can follow these steps using Yelpr package from R to retrieve all businesses within the defined search area from the Yelp database.
# load required Packaged
library(yelpr)
# FUNCTION
get_yelp <- function(tract, category){ # according to tract and category, create a list of business dataframe
Sys.sleep(1)
n <- 1
# 1st request --------------------------------------------------------------
resp <- business_search(api_key = Sys.getenv("yelp_api_2"),
categories = category,
latitude = tract$y,
longitude = tract$x,
offset = (n - 1) * 50, # = 0 when n = 1
radius = round(tract$radius), # buffer
limit = 50)
required_n <- ceiling(resp$total/50) # Identify Total Requests
out <- vector("list", required_n) # Creates an empty list to store the results
out[[n]] <- resp$businesses # Stores the results in the list
names(out)[n] <- required_n # Sets the name of the list element to required_n to track the number of pages
if (resp$total >= 500) # Show error if more than 500
{
print(glue::glue("{n}th row has >= 500 businesses.")) # print an error result
return(out) # no additional request, stop the loop
}
else
{
n <- n + 1 # add 1 to n
while(n <= required_n){ # While-loop
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
} # Loop Through Remaining Pages
out <- out %>% bind_rows() # Merge all elements in the list into a single data frame
return(out)
}
}
# Apply the function for the first Census Tract
yelp_skincare_all_list <- vector("list", nrow(ready_4_yelp)) # Make a vector to store the data
for (row in 1:nrow(ready_4_yelp)){ # Looping through all Census Tracts (for-loop)
yelp_skincare_all_list[[row]] <- suppressMessages(get_yelp(ready_4_yelp[row,], "skincare"))
print(paste0("Current row: ", row))
}
yelp_skincare_all <- yelp_skincare_all_list %>% bind_rows() %>% as_tibble() # Merge all elements in the list into a single data frame
yelp_skincare_all %>% print(width=1000) #print
From this task, we can identify there are 538 Skincare businesses located in Marietta.
message(sprintf("nrow: %s, ncol: %s", nrow(yelp_skincare_all), ncol(yelp_skincare_all)))
## nrow: 538, ncol: 18
yelp_spas_all_list <- vector("list", nrow(ready_4_yelp)) # Make a vector to store the data
for (row in 1:nrow(ready_4_yelp)){ # Looping through all Census Tracts (for-loop)
yelp_spas_all_list[[row]] <- suppressMessages(get_yelp(ready_4_yelp[row,], "spas"))
print(paste0("Current row: ", row))
}
## [1] "Current row: 1"
## [1] "Current row: 2"
## [1] "Current row: 3"
## [1] "Current row: 4"
## [1] "Current row: 5"
## [1] "Current row: 6"
## [1] "Current row: 7"
## [1] "Current row: 8"
## [1] "Current row: 9"
## [1] "Current row: 10"
## [1] "Current row: 11"
## [1] "Current row: 12"
## [1] "Current row: 13"
## [1] "Current row: 14"
## [1] "Current row: 15"
## [1] "Current row: 16"
## [1] "Current row: 17"
## [1] "Current row: 18"
## [1] "Current row: 19"
## [1] "Current row: 20"
## [1] "Current row: 21"
## [1] "Current row: 22"
## [1] "Current row: 23"
## [1] "Current row: 24"
## [1] "Current row: 25"
## [1] "Current row: 26"
## [1] "Current row: 27"
## [1] "Current row: 28"
## [1] "Current row: 29"
## [1] "Current row: 30"
## [1] "Current row: 31"
## [1] "Current row: 32"
## [1] "Current row: 33"
## [1] "Current row: 34"
yelp_spas_all <- yelp_spas_all_list %>% bind_rows() %>% as_tibble() # Merge all elements in the list into a single data frame
yelp_spas_all %>% print(width=1000) # print
## # A tibble: 180 × 18
## id alias
## <chr> <chr>
## 1 IpmF7zMXOl4aiqtLwJasRQ evene-day-spa-marietta
## 2 pKMsjMOct2NfB28N1K5RZQ future-perfect-massage-atlanta
## 3 VdfR0Dp1-UYWpVTHs-tOBQ advanced-skin-care-marietta
## 4 fwF5feu5woAc4ZteoGFIMQ angela-michael-skincare-and-spa-dunwoody
## 5 NUczjrecxjJQaP-WcvlJpw own-you-artistry-and-skin-marietta
## 6 rGBUSPIBhsAwDwz6Yycn-g make-up-and-motivation-marietta
## 7 l-fIWju3V0h0OGZMzUgEXQ the-retreat-day-spa-marietta-2
## 8 2aD8dfXJqtmbtrlFbau1ow aromomtherapy-mobile-spa-morrow
## 9 oWR0pTahKWgC1dVJOOxELw wcw-body-studio-coming-soon-atlanta
## 10 f6sUrfBV-Otdp-LbH5uuog jojo-brazilian-wax-marietta
## name
## <chr>
## 1 Evene Day Spa
## 2 Future Perfect Massage
## 3 Advanced Skin Care
## 4 Angela Michael Skincare & Spa
## 5 Own You Artistry & Skin
## 6 Make-Up & Motivation
## 7 The Retreat Day Spa
## 8 Aromomtherapy Mobile Spa
## 9 WCW Body Studio - COMING SOON
## 10 JOJO brazilian wax
## image_url
## <chr>
## 1 https://s3-media3.fl.yelpcdn.com/bphoto/Plvi5vhLgbWqm5uFwuc7dA/o.jpg
## 2 https://s3-media4.fl.yelpcdn.com/bphoto/511uhUwowoHukAQQD5iZRg/o.jpg
## 3 https://s3-media2.fl.yelpcdn.com/bphoto/c0pLjiRwOIW5We2YPBcN4Q/o.jpg
## 4 https://s3-media3.fl.yelpcdn.com/bphoto/hTTPSZiXJuDxSHAj6JbiRA/o.jpg
## 5 https://s3-media4.fl.yelpcdn.com/bphoto/fWkoVdS_YH57LxOAtiTMaQ/o.jpg
## 6 https://s3-media1.fl.yelpcdn.com/bphoto/ztmQv1MWnNDT4gCZ_njJSA/o.jpg
## 7 https://s3-media4.fl.yelpcdn.com/bphoto/Z4CCivAhd4J7khEeR6J48A/o.jpg
## 8 https://s3-media4.fl.yelpcdn.com/bphoto/mjhtJ2Qa3G-Rue8zVofndg/o.jpg
## 9 https://s3-media2.fl.yelpcdn.com/bphoto/nEXQ-IHZ3J2-ne-89J51_g/o.jpg
## 10 https://s3-media1.fl.yelpcdn.com/bphoto/SinqLKp8kYVZy5Zn-KrIyw/o.jpg
## is_closed
## <lgl>
## 1 FALSE
## 2 FALSE
## 3 FALSE
## 4 FALSE
## 5 FALSE
## 6 FALSE
## 7 FALSE
## 8 FALSE
## 9 FALSE
## 10 FALSE
## url
## <chr>
## 1 https://www.yelp.com/biz/evene-day-spa-marietta?adjust_creative=SkW8jFlIgl9N…
## 2 https://www.yelp.com/biz/future-perfect-massage-atlanta?adjust_creative=SkW8…
## 3 https://www.yelp.com/biz/advanced-skin-care-marietta?adjust_creative=SkW8jFl…
## 4 https://www.yelp.com/biz/angela-michael-skincare-and-spa-dunwoody?adjust_cre…
## 5 https://www.yelp.com/biz/own-you-artistry-and-skin-marietta?adjust_creative=…
## 6 https://www.yelp.com/biz/make-up-and-motivation-marietta?adjust_creative=SkW…
## 7 https://www.yelp.com/biz/the-retreat-day-spa-marietta-2?adjust_creative=SkW8…
## 8 https://www.yelp.com/biz/aromomtherapy-mobile-spa-morrow?adjust_creative=SkW…
## 9 https://www.yelp.com/biz/wcw-body-studio-coming-soon-atlanta?adjust_creative…
## 10 https://www.yelp.com/biz/jojo-brazilian-wax-marietta?adjust_creative=SkW8jFl…
## review_count categories rating coordinates$latitude $longitude transactions
## <int> <list> <dbl> <dbl> <dbl> <list>
## 1 53 <df [3 × 2]> 3.5 34.0 -84.5 <list [0]>
## 2 15 <df [3 × 2]> 5 33.7 -84.4 <list [0]>
## 3 7 <df [3 × 2]> 5 34.0 -84.5 <list [0]>
## 4 21 <df [3 × 2]> 4.5 34.0 -84.3 <list [0]>
## 5 1 <df [3 × 2]> 5 34.0 -84.5 <list [0]>
## 6 0 <df [3 × 2]> 0 33.9 -84.5 <list [0]>
## 7 0 <df [3 × 2]> 0 34.0 -84.5 <list [0]>
## 8 0 <df [3 × 2]> 0 33.6 -84.3 <list [0]>
## 9 0 <df [2 × 2]> 0 33.7 -84.4 <list [0]>
## 10 0 <df [2 × 2]> 0 34.0 -84.5 <list [0]>
## price location$address1 $address2 $address3 $city $zip_code $country
## <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 $$ "20 Atlanta St SE" "" <NA> Marietta 30060 US
## 2 $$ "226 Powell St SE" "" "" Atlanta 30316 US
## 3 $$ "711 Canton Rd" "Ste 400-B" "" Marietta 30060 US
## 4 $$ "2500-C Mt Vernon Rd" "" "" Dunwoody 30338 US
## 5 <NA> "720 Roswell St NE" "Ste 150" "" Marietta 30060 US
## 6 <NA> "54 Park St SE" "" "" Marietta 30060 US
## 7 <NA> "55 Atlanta St SE" "Ste 112" <NA> Marietta 30060 US
## 8 <NA> <NA> <NA> <NA> Morrow 30260 US
## 9 <NA> "" <NA> "" Atlanta 30312 US
## 10 <NA> <NA> <NA> "" Marietta 30060 US
## $state $display_address phone display_phone distance
## <chr> <list> <chr> <chr> <dbl>
## 1 GA <chr [2]> "+17704371133" "(770) 437-1133" 1853.
## 2 GA <chr [2]> "+16783047687" "(678) 304-7687" 28555.
## 3 GA <chr [3]> "+17707715155" "(770) 771-5155" 1354.
## 4 GA <chr [2]> "+16787319611" "(678) 731-9611" 21670.
## 5 GA <chr [3]> "+16788255054" "(678) 825-5054" 1478.
## 6 GA <chr [2]> "+16789000020" "(678) 900-0020" 1499.
## 7 GA <chr [3]> "+16782167350" "(678) 216-7350" 1911.
## 8 GA <chr [1]> "+14046662494" "(404) 666-2494" 45965.
## 9 GA <chr [1]> "" "" 28418.
## 10 GA <chr [1]> "+14044726317" "(404) 472-6317" 1636.
## business_hours attributes$business_temp_closed
## <list> <lgl>
## 1 <df [1 × 3]> NA
## 2 <df [1 × 3]> NA
## 3 <df [1 × 3]> NA
## 4 <df [1 × 3]> NA
## 5 <df [1 × 3]> NA
## 6 <df [1 × 3]> NA
## 7 <df [1 × 3]> NA
## 8 <df [1 × 3]> NA
## 9 <df [1 × 3]> NA
## 10 <df [1 × 3]> NA
## $menu_url $waitlist_reservation $open24_hours
## <chr> <lgl> <lgl>
## 1 <NA> NA NA
## 2 <NA> NA NA
## 3 https://advancedskincarepro.com/services/ NA NA
## 4 <NA> NA NA
## 5 <NA> NA NA
## 6 <NA> NA NA
## 7 <NA> NA NA
## 8 <NA> NA NA
## 9 <NA> NA NA
## 10 <NA> NA NA
## # ℹ 170 more rows
From this task, we can identify there are 180 Skincare businesses located in Marietta.
message(sprintf("nrow: %s, ncol: %s", nrow(yelp_spas_all), ncol(yelp_spas_all)))
## nrow: 180, ncol: 18
Also, we can identify there are 718 Day Spa and Skincare businesses located in Marietta by combining two database.
yelp_two_biz_all <- bind_rows(yelp_skincare_all, yelp_spas_all)
message(sprintf("nrow: %s, ncol: %s", nrow(yelp_two_biz_all), ncol(yelp_two_biz_all)))
## nrow: 718, ncol: 18
To visualize locations of Day Spa and Skincare businesses located in Marietta, we need to convert the data frames to spatial data frames.
# Converting data frame into spatial data frame
yelp_skincare_sf <- yelp_skincare_all %>%
mutate(x = .$coordinates$longitude,
y = .$coordinates$latitude) %>%
filter(!is.na(x) & !is.na(y)) %>%
st_as_sf(coords = c("x", "y"), crs = 4326)
yelp_spas_sf <- yelp_spas_all %>%
mutate(x = .$coordinates$longitude,
y = .$coordinates$latitude) %>%
filter(!is.na(x) & !is.na(y)) %>%
st_as_sf(coords = c("x", "y"), crs = 4326)
The visualization indicates that most skincare shops are located in downtown Marietta as well as commercial districts along I-75. Especially, shops with higher review counts are primarily situated within downtown and northern Marietta.
# Map
tm_shape(yelp_skincare_sf) +
tm_dots(col = "review_count", style="pretty")
A similar pattern is shown for day spa businesses. Many day spa shops are found in downtown Marietta and commercial districts along I-75, and spa shops with higher review counts spas are located in downtown as well as the western Marietta. We might also say that, given the similarities in their locations, many skincare and day spa shops cluster together in strategic business districts.
# Map
tm_shape(yelp_spas_sf) +
tm_dots(col = "review_count", style="pretty")
US Census (n.d.) American Community Survey 2022: 5-year estimates Data USA (n.d.) Data USA: Marietta, GA. https://datausa.io/profile/geo/marietta-ga/