Get census API
tidycensus::census_api_key(Sys.getenv("census_api"))
## To install your API key for use in future sessions, run this function with `install = TRUE`.
Load libraries
library(tidycensus)
library(sf)
## Linking to GEOS 3.10.2, GDAL 3.4.1, PROJ 8.2.1; sf_use_s2() is TRUE
library(tmap)
## The legacy packages maptools, rgdal, and rgeos, underpinning the sp package,
## which was just loaded, will retire in October 2023.
## Please refer to R-spatial evolution reports for details, especially
## https://r-spatial.org/r/2023/05/15/evolution4.html.
## It may be desirable to make the sf package available;
## package maintainers should consider adding sf to Suggests:.
## The sp package is now running under evolution status 2
## (status 2 uses the sf package in place of rgdal)
library(jsonlite)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.2 ✔ readr 2.1.4
## ✔ forcats 1.0.0 ✔ stringr 1.5.0
## ✔ ggplot2 3.4.2 ✔ tibble 3.2.1
## ✔ lubridate 1.9.2 ✔ tidyr 1.3.0
## ✔ purrr 1.0.1
## ── 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 /home/rstudio/Mod 1
library(yelpr)
library(knitr)
library(tigris)
## To enable caching of data, set `options(tigris_use_cache = TRUE)`
## in your R script or .Rprofile.
Get census tract polygons and census data to get yelp data for chatham County
#### Tract polygons for the Yelp query
tract <- suppressMessages(
get_acs(geography = "tract", # or "block group", "county", "state" etc.
state = "GA",
county = c("Chatham"),
variables = c(hhincome = 'B19019_001',
race.tot = "B02001_001",
race.white = "B02001_002",
race.black = 'B02001_003'
),
year = 2019,
survey = "acs5", # American Community Survey 5-year estimate
geometry = TRUE, # returns sf objects
output = "wide") # wide vs. long
)
##
|
| | 0%
|
|= | 1%
|
|= | 2%
|
|== | 3%
|
|=== | 4%
|
|==== | 5%
|
|===== | 7%
|
|====== | 8%
|
|====== | 9%
|
|======= | 10%
|
|======= | 11%
|
|======== | 12%
|
|========= | 13%
|
|========== | 14%
|
|=========== | 15%
|
|=========== | 16%
|
|============ | 17%
|
|============= | 18%
|
|============= | 19%
|
|============== | 20%
|
|============== | 21%
|
|=============== | 21%
|
|================ | 23%
|
|================= | 24%
|
|================== | 26%
|
|=================== | 27%
|
|=================== | 28%
|
|==================== | 29%
|
|===================== | 30%
|
|====================== | 31%
|
|======================= | 32%
|
|======================= | 33%
|
|======================== | 34%
|
|========================= | 36%
|
|========================== | 37%
|
|========================== | 38%
|
|============================ | 40%
|
|============================== | 42%
|
|================================ | 46%
|
|================================= | 47%
|
|=================================== | 49%
|
|===================================== | 52%
|
|===================================== | 53%
|
|====================================== | 54%
|
|======================================= | 56%
|
|======================================== | 57%
|
|========================================= | 58%
|
|========================================== | 59%
|
|========================================== | 60%
|
|============================================ | 63%
|
|============================================= | 64%
|
|============================================= | 65%
|
|=============================================== | 67%
|
|================================================= | 70%
|
|================================================== | 71%
|
|================================================== | 72%
|
|=================================================== | 73%
|
|==================================================== | 74%
|
|====================================================== | 77%
|
|======================================================= | 78%
|
|======================================================== | 80%
|
|========================================================= | 81%
|
|========================================================= | 82%
|
|========================================================== | 83%
|
|=========================================================== | 84%
|
|=========================================================== | 85%
|
|============================================================ | 86%
|
|============================================================= | 87%
|
|============================================================== | 88%
|
|============================================================== | 89%
|
|=============================================================== | 90%
|
|================================================================ | 92%
|
|================================================================= | 93%
|
|================================================================== | 94%
|
|================================================================== | 95%
|
|=================================================================== | 95%
|
|==================================================================== | 97%
|
|===================================================================== | 99%
|
|======================================================================| 100%
chatham <- counties(13) %>%
filter(NAME=='Chatham')
## Retrieving data for the year 2021
##
|
| | 0%
|
| | 1%
|
|= | 1%
|
|= | 2%
|
|== | 2%
|
|== | 3%
|
|== | 4%
|
|=== | 4%
|
|=== | 5%
|
|==== | 5%
|
|==== | 6%
|
|===== | 6%
|
|===== | 7%
|
|===== | 8%
|
|====== | 8%
|
|====== | 9%
|
|======= | 9%
|
|======= | 10%
|
|======= | 11%
|
|======== | 11%
|
|======== | 12%
|
|========= | 12%
|
|========= | 13%
|
|========== | 14%
|
|========== | 15%
|
|=========== | 15%
|
|=========== | 16%
|
|============ | 16%
|
|============ | 17%
|
|============ | 18%
|
|============= | 18%
|
|============= | 19%
|
|============== | 19%
|
|============== | 20%
|
|============== | 21%
|
|=============== | 21%
|
|=============== | 22%
|
|================ | 22%
|
|================ | 23%
|
|================= | 24%
|
|================= | 25%
|
|================== | 25%
|
|================== | 26%
|
|=================== | 26%
|
|=================== | 27%
|
|=================== | 28%
|
|==================== | 28%
|
|==================== | 29%
|
|===================== | 29%
|
|===================== | 30%
|
|===================== | 31%
|
|====================== | 31%
|
|====================== | 32%
|
|======================= | 32%
|
|======================= | 33%
|
|======================== | 34%
|
|======================== | 35%
|
|========================= | 35%
|
|========================= | 36%
|
|========================== | 37%
|
|========================== | 38%
|
|=========================== | 38%
|
|=========================== | 39%
|
|============================ | 39%
|
|============================ | 40%
|
|============================ | 41%
|
|============================= | 41%
|
|============================= | 42%
|
|============================== | 43%
|
|============================== | 44%
|
|=============================== | 44%
|
|=============================== | 45%
|
|================================ | 45%
|
|================================ | 46%
|
|================================= | 46%
|
|================================= | 47%
|
|================================= | 48%
|
|================================== | 48%
|
|================================== | 49%
|
|=================================== | 49%
|
|=================================== | 50%
|
|=================================== | 51%
|
|==================================== | 51%
|
|==================================== | 52%
|
|===================================== | 52%
|
|===================================== | 53%
|
|===================================== | 54%
|
|====================================== | 54%
|
|====================================== | 55%
|
|======================================= | 55%
|
|======================================= | 56%
|
|======================================== | 56%
|
|======================================== | 57%
|
|======================================== | 58%
|
|========================================= | 58%
|
|========================================= | 59%
|
|========================================== | 59%
|
|========================================== | 60%
|
|========================================== | 61%
|
|=========================================== | 61%
|
|=========================================== | 62%
|
|============================================ | 62%
|
|============================================ | 63%
|
|============================================= | 64%
|
|============================================= | 65%
|
|============================================== | 65%
|
|============================================== | 66%
|
|=============================================== | 66%
|
|=============================================== | 67%
|
|=============================================== | 68%
|
|================================================ | 68%
|
|================================================ | 69%
|
|================================================= | 69%
|
|================================================= | 70%
|
|================================================= | 71%
|
|================================================== | 71%
|
|================================================== | 72%
|
|=================================================== | 72%
|
|=================================================== | 73%
|
|==================================================== | 74%
|
|==================================================== | 75%
|
|===================================================== | 75%
|
|===================================================== | 76%
|
|====================================================== | 76%
|
|====================================================== | 77%
|
|====================================================== | 78%
|
|======================================================= | 78%
|
|======================================================= | 79%
|
|======================================================== | 79%
|
|======================================================== | 80%
|
|======================================================== | 81%
|
|========================================================= | 81%
|
|========================================================= | 82%
|
|========================================================== | 82%
|
|========================================================== | 83%
|
|========================================================== | 84%
|
|=========================================================== | 84%
|
|=========================================================== | 85%
|
|============================================================ | 85%
|
|============================================================ | 86%
|
|============================================================= | 86%
|
|============================================================= | 87%
|
|============================================================= | 88%
|
|============================================================== | 88%
|
|============================================================== | 89%
|
|=============================================================== | 89%
|
|=============================================================== | 90%
|
|=============================================================== | 91%
|
|================================================================ | 91%
|
|================================================================ | 92%
|
|================================================================= | 92%
|
|================================================================= | 93%
|
|================================================================= | 94%
|
|================================================================== | 94%
|
|================================================================== | 95%
|
|=================================================================== | 95%
|
|=================================================================== | 96%
|
|==================================================================== | 96%
|
|==================================================================== | 97%
|
|==================================================================== | 98%
|
|===================================================================== | 98%
|
|===================================================================== | 99%
|
|======================================================================| 99%
|
|======================================================================| 100%
chatham
## Simple feature collection with 1 feature and 17 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -81.3917 ymin: 31.7052 xmax: -80.78296 ymax: 32.23759
## Geodetic CRS: NAD83
## STATEFP COUNTYFP COUNTYNS GEOID NAME NAMELSAD LSAD CLASSFP MTFCC
## 1 13 051 01694477 13051 Chatham Chatham County 06 H1 G4020
## CSAFP CBSAFP METDIVFP FUNCSTAT ALAND AWATER INTPTLAT INTPTLON
## 1 496 42340 <NA> A 1121786258 509839331 +31.9804037 -081.0851840
## geometry
## 1 MULTIPOLYGON (((-80.94245 3...
# View the data
message(sprintf("nrow: %s, ncol: %s", nrow(tract), ncol(tract)))
## nrow: 72, ncol: 11
tract %>% head() %>% knitr::kable() # Ignore this kable() function. This function is for neatly displaying tables on HTML document.
| GEOID | NAME | hhincomeE | hhincomeM | race.totE | race.totM | race.whiteE | race.whiteM | race.blackE | race.blackM | geometry |
|---|---|---|---|---|---|---|---|---|---|---|
| 13051003000 | Census Tract 30, Chatham County, Georgia | 72670 | 6319 | 1778 | 222 | 1634 | 223 | 63 | 52 | MULTIPOLYGON (((-81.09447 3… |
| 13051001100 | Census Tract 11, Chatham County, Georgia | 37277 | 4631 | 1865 | 257 | 731 | 133 | 933 | 256 | MULTIPOLYGON (((-81.08994 3… |
| 13051000100 | Census Tract 1, Chatham County, Georgia | 12048 | 2073 | 1600 | 249 | 451 | 186 | 941 | 205 | MULTIPOLYGON (((-81.1124 32… |
| 13051011300 | Census Tract 113, Chatham County, Georgia | 32833 | 12400 | 2180 | 403 | 1132 | 244 | 942 | 335 | MULTIPOLYGON (((-81.10259 3… |
| 13051002900 | Census Tract 29, Chatham County, Georgia | 94375 | 12144 | 2858 | 221 | 2639 | 214 | 110 | 107 | MULTIPOLYGON (((-81.10376 3… |
| 13051011200 | Census Tract 112, Chatham County, Georgia | 61477 | 11352 | 1612 | 243 | 1367 | 245 | 166 | 83 | MULTIPOLYGON (((-81.10058 3… |
Removing rows that we don’t need from the df
# Retaining only those I want.
# Notice that select function can also change names when it selects columns.
tract <- tract %>%
select(GEOID,
hhincome = hhincomeE,
race.tot = race.totE,
race.white = race.whiteE,
race.black = race.blackE)
# remove one census tract with no population and over water
tract <- tract %>%
filter(!is.na(hhincome))
tmap_mode("view")
## tmap mode set to interactive viewing
tm_shape(tract) + tm_borders() + tm_shape(chatham) + tm_borders(col = 'red')
Create a function that selects one census tract and calculates the centroid and then creates a bounding box
# Function: Get tract-wise radius
get_r <- function(poly, epsg_id){
#---------------------
# Takes: a single POLYGON or LINESTRTING
# Outputs: distance between the centroid of the boundingbox and a corner of the bounding box
#---------------------
# Get bounding box of a given polygon
bb <- st_bbox(poly)
# Get lat & long coordinates of any one corner of the bounding box.
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()
# Get the distance between bb_p and c
r <- st_distance(bb_corner, bb_center)
# Multiply 1.1 to make the circle a bit larger than the Census Tract.
# See the Yelp explanation of their radius parameter to see why we do this.
bb_center$radius <- r*1.2
return(bb_center)
}
Apply this function to every tract in the county using a for loop function
## Using a loop -----------------------------------------------------------------
# Creating an empty vector of NA.
# Results will fill this vector
epsg_id <- 4326
r4all_loop <- vector("list", nrow(tract))
# Starting a for-loop
for (i in 1:nrow(tract)){
r4all_loop[[i]] <- tract %>%
st_transform(crs = epsg_id) %>%
st_geometry() %>%
.[[i]] %>%
get_r(epsg_id = epsg_id)
}
r4all_loop <- bind_rows(r4all_loop)
# Using a functional -----------------------------------------------------------
# We use a functional (sapply) to apply this custom function to each Census Tract.
r4all_apply <- tract %>%
st_geometry() %>%
st_transform(crs = epsg_id) %>%
lapply(., function(x) get_r(x, epsg_id = epsg_id))
r4all_apply <- bind_rows(r4all_apply)
# Are these two identical?
identical(r4all_apply, r4all_loop)
## [1] TRUE
# Appending X Y coordinates as seprate columns
ready_4_yelp <- r4all_apply %>%
mutate(x = st_coordinates(.)[,1],
y = st_coordinates(.)[,2])
tmap_mode('view')
## tmap mode set to interactive viewing
# Select the first 10 rows
ready_4_yelp[1:5,] %>%
# Draw a buffer centered at the centroid of Tract polygons.
# Radius of the buffer is the radius we just calculated using loop
st_buffer(., dist = .$radius) %>%
# Display this buffer in red
tm_shape(.) + tm_polygons(alpha = 0.5, col = 'red') +
# Display the original polygon in blue
tm_shape(tract[1:5,]) + tm_borders(col= 'blue')
which_tract <- 1
test <- business_search(api_key = yelp_api, # like we did for census, store your api key
categories = 'restaurants', # return only restaurants
latitude = ready_4_yelp$y[which_tract],
longitude = ready_4_yelp$x[which_tract],
offset = 0, # 1st page, 1st obs
radius = round(ready_4_yelp$radius[which_tract]), # radius requires integer value
limit = 50) # how many business per page
## No encoding supplied: defaulting to UTF-8.
lapply(test, head)
## $businesses
## id alias
## 1 epNX8WfOZsyvSST0FBWVOw green-truck-neighborhood-pub-savannah
## 2 jerars0XMnDgyOQ2W1LVSg al-salaam-deli-savannah
## 3 aMNH4HRg70g_RjhYGVt7cA lisas-chinese-take-out-savannah
## 4 4WFvhPco5HOVFDXX-D0MAw krystal-savannah-5
## 5 V6i0-Dxv90h9_uQz-kgW3w waters-cafe-savannah
## 6 5tKBUPCnfk7D-7AgCmKP9A barakas-homestyle-soul-and-seafood-restaurant-savannah
## name
## 1 Green Truck Neighborhood Pub
## 2 Al Salaam Deli
## 3 Lisa's Chinese Take-Out
## 4 Krystal
## 5 Waters Cafe
## 6 Baraka's Homestyle Soul & Seafood Restaurant
## image_url
## 1 https://s3-media2.fl.yelpcdn.com/bphoto/jZtjMcoJw8OoY3RQ5gig1A/o.jpg
## 2 https://s3-media1.fl.yelpcdn.com/bphoto/x-3kuna19bKohL1OMioULA/o.jpg
## 3 https://s3-media2.fl.yelpcdn.com/bphoto/20pjxCKlR-N9i_HrNpLYjA/o.jpg
## 4 https://s3-media2.fl.yelpcdn.com/bphoto/9kYIbCWw-rUKn-B41BGk2Q/o.jpg
## 5 https://s3-media2.fl.yelpcdn.com/bphoto/Iu874lOraeCDUhHReD8IGw/o.jpg
## 6 https://s3-media1.fl.yelpcdn.com/bphoto/F8ESdinH-mHJ_XXLAW2LNw/o.jpg
## is_closed
## 1 FALSE
## 2 FALSE
## 3 FALSE
## 4 FALSE
## 5 FALSE
## 6 FALSE
## url
## 1 https://www.yelp.com/biz/green-truck-neighborhood-pub-savannah?adjust_creative=rHmJcLYuBDkzuIyV3DCu2A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=rHmJcLYuBDkzuIyV3DCu2A
## 2 https://www.yelp.com/biz/al-salaam-deli-savannah?adjust_creative=rHmJcLYuBDkzuIyV3DCu2A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=rHmJcLYuBDkzuIyV3DCu2A
## 3 https://www.yelp.com/biz/lisas-chinese-take-out-savannah?adjust_creative=rHmJcLYuBDkzuIyV3DCu2A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=rHmJcLYuBDkzuIyV3DCu2A
## 4 https://www.yelp.com/biz/krystal-savannah-5?adjust_creative=rHmJcLYuBDkzuIyV3DCu2A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=rHmJcLYuBDkzuIyV3DCu2A
## 5 https://www.yelp.com/biz/waters-cafe-savannah?adjust_creative=rHmJcLYuBDkzuIyV3DCu2A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=rHmJcLYuBDkzuIyV3DCu2A
## 6 https://www.yelp.com/biz/barakas-homestyle-soul-and-seafood-restaurant-savannah?adjust_creative=rHmJcLYuBDkzuIyV3DCu2A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=rHmJcLYuBDkzuIyV3DCu2A
## review_count categories rating
## 1 876 pubs, burgers, newamerican, Pubs, Burgers, American (New) 4.5
## 2 176 mideastern, Middle Eastern 4.5
## 3 12 chinese, Chinese 4.0
## 4 32 burgers, hotdogs, Burgers, Fast Food 2.0
## 5 2 sandwiches, coffee, Sandwiches, Coffee & Tea 5.0
## 6 2 seafood, soulfood, Seafood, Soul Food 4.5
## coordinates.latitude coordinates.longitude transactions price
## 1 32.05278 -81.09653 delivery $$
## 2 32.05385 -81.09664 delivery $
## 3 32.05063 -81.08465 delivery $
## 4 32.04510 -81.07650 delivery, pickup $
## 5 32.05061 -81.08464 <NA>
## 6 32.05506 -81.08293 $
## location.address1 location.address2 location.address3 location.city
## 1 2430 Habersham St <NA> Savannah
## 2 2311 Habersham St Savannah
## 3 2315 Waters Ave Savannah
## 4 2804 Bee Rd Savannah
## 5 2317 Waters Ave <NA> Savannah
## 6 1801 Waters Ave Savannah
## location.zip_code location.country location.state
## 1 31401 US GA
## 2 31401 US GA
## 3 31404 US GA
## 4 31401 US GA
## 5 31404 US GA
## 6 31404 US GA
## location.display_address phone display_phone distance
## 1 2430 Habersham St, Savannah, GA 31401 +19122345885 (912) 234-5885 1390.2412
## 2 2311 Habersham St, Savannah, GA 31401 +19124470400 (912) 447-0400 1474.4738
## 3 2315 Waters Ave, Savannah, GA 31404 +19122368228 (912) 236-8228 681.8999
## 4 2804 Bee Rd, Savannah, GA 31401 +19123524598 (912) 352-4598 856.7913
## 5 2317 Waters Ave, Savannah, GA 31404 +19124174929 (912) 417-4929 679.7212
## 6 1801 Waters Ave, Savannah, GA 31404 1186.6089
##
## $total
## [1] 8
##
## $region
## $region$center
## $region$center$longitude
## [1] -81.08555
##
## $region$center$latitude
## [1] 32.04456
names(test)
## [1] "businesses" "total" "region"
# Business
paste0("is it a data.frame?: ", is.data.frame(test$businesses), ", ",
" how many rows?: ", nrow(test$businesses), ", ",
" how many columns?: ", ncol(test$businesses))
## [1] "is it a data.frame?: TRUE, how many rows?: 8, how many columns?: 16"
Okay, we learned that we need to make multiple requests even for just
one Census Tract. We can create another function to make this process
automatic. We will design this function such that it takes a row from
ready_4_yelp, make the initial API request, calculate how
many times more it need to request, complete the remaining request,
gather the responses from the multiple requests.
-
# FUNCTION
get_yelp <- function(tract, category){
# ----------------------------------
# Gets one row of tract information (1,) and category name (str),
# Outputs a list of business data.frame
Sys.sleep(1)
n <- 1
# First request --------------------------------------------------------------
resp <- business_search(api_key = yelp_api,
categories = category,
latitude = tract$y,
longitude = tract$x,
offset = (n - 1) * 50, # = 0 when n = 1
radius = round(tract$radius),
limit = 50)
# Calculate how many requests are needed in total
required_n <- ceiling(resp$total/50)
# out is where the results will be appended to.
out <- vector("list", required_n)
# Store the business information to nth slot in out
out[[n]] <- resp$businesses
# Change the name of the elements to the total required_n
# This is to know if there are more than 1000 businesses,
# we know how many.
names(out)[n] <- required_n
# Throw error if more than 1000
if (resp$total >= 1000)
{
# glue formats string by inserting {n} with what's currently stored in object n.
print(glue::glue("{n}th row has >= 1000 businesses."))
# Stop before going into the loop because we need to
# break down Census Tract to something smaller.
return(out)
}
else
{
# add 1 to n
n <- n + 1
# Now we know required_n -----------------------------------------------------
# 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
# Merge all elements in the list into a single data frame
out <- out %>% bind_rows()
return(out)
}
}
# Apply the function for the first Census Tract
yelp_first_tract <- get_yelp(ready_4_yelp[1,], "restaurants") %>%
as_tibble()
## No encoding supplied: defaulting to UTF-8.
# Print
yelp_first_tract %>% print
## # A tibble: 8 × 16
## id alias name image_url is_closed url review_count categories rating
## <chr> <chr> <chr> <chr> <lgl> <chr> <int> <list> <dbl>
## 1 epNX8WfO… gree… Gree… "https:/… FALSE http… 876 <df> 4.5
## 2 jerars0X… al-s… Al S… "https:/… FALSE http… 176 <df> 4.5
## 3 aMNH4HRg… lisa… Lisa… "https:/… FALSE http… 12 <df> 4
## 4 4WFvhPco… krys… Krys… "https:/… FALSE http… 32 <df> 2
## 5 V6i0-Dxv… wate… Wate… "https:/… FALSE http… 2 <df> 5
## 6 5tKBUPCn… bara… Bara… "https:/… FALSE http… 2 <df> 4.5
## 7 UyMUsGQj… off-… Off … "" FALSE http… 3 <df> 4.5
## 8 zvq_CXSX… chin… Chin… "" FALSE http… 1 <df> 4
## # ℹ 7 more variables: coordinates <df[,2]>, transactions <list>, price <chr>,
## # location <df[,8]>, phone <chr>, display_phone <chr>, distance <dbl>
# Prepare a collector
yelp_all_list <- vector("list", nrow(ready_4_yelp))
# Looping through all Census Tracts
for (row in 1:nrow(ready_4_yelp)){
yelp_all_list[[row]] <- suppressMessages(get_yelp(ready_4_yelp[row,], "restaurants"))
if (row %% 10 == 0){
print(paste0("Current row: ", row))
}
}
## [1] "Current row: 10"
## [1] "Current row: 20"
## [1] "Current row: 30"
## [1] "Current row: 40"
## [1] "Current row: 50"
## [1] "Current row: 60"
# Collapsing the list into a data.frame
yelp_all <- yelp_all_list %>% bind_rows() %>% as_tibble()
# print
yelp_all %>% print(width=1000)
## # A tibble: 6,352 × 16
## id alias
## <chr> <chr>
## 1 epNX8WfOZsyvSST0FBWVOw green-truck-neighborhood-pub-savannah
## 2 jerars0XMnDgyOQ2W1LVSg al-salaam-deli-savannah
## 3 aMNH4HRg70g_RjhYGVt7cA lisas-chinese-take-out-savannah
## 4 4WFvhPco5HOVFDXX-D0MAw krystal-savannah-5
## 5 V6i0-Dxv90h9_uQz-kgW3w waters-cafe-savannah
## 6 5tKBUPCnfk7D-7AgCmKP9A barakas-homestyle-soul-and-seafood-restaurant-savannah
## 7 UyMUsGQju_UeV1ZHxm6E8w off-the-grill-savannah
## 8 zvq_CXSXGpcABljIftU99A china-fun-ii-savannah
## 9 n4OAAFaySDfJNtAQWXtRcg the-olde-pink-house-savannah
## 10 ii6Q4wrFPHenfbmiU5MtHw alligator-soul-savannah-2
## name
## <chr>
## 1 Green Truck Neighborhood Pub
## 2 Al Salaam Deli
## 3 Lisa's Chinese Take-Out
## 4 Krystal
## 5 Waters Cafe
## 6 Baraka's Homestyle Soul & Seafood Restaurant
## 7 Off the Grill
## 8 China Fun II
## 9 The Olde Pink House
## 10 Alligator Soul
## image_url
## <chr>
## 1 "https://s3-media2.fl.yelpcdn.com/bphoto/jZtjMcoJw8OoY3RQ5gig1A/o.jpg"
## 2 "https://s3-media1.fl.yelpcdn.com/bphoto/x-3kuna19bKohL1OMioULA/o.jpg"
## 3 "https://s3-media2.fl.yelpcdn.com/bphoto/20pjxCKlR-N9i_HrNpLYjA/o.jpg"
## 4 "https://s3-media2.fl.yelpcdn.com/bphoto/9kYIbCWw-rUKn-B41BGk2Q/o.jpg"
## 5 "https://s3-media2.fl.yelpcdn.com/bphoto/Iu874lOraeCDUhHReD8IGw/o.jpg"
## 6 "https://s3-media1.fl.yelpcdn.com/bphoto/F8ESdinH-mHJ_XXLAW2LNw/o.jpg"
## 7 ""
## 8 ""
## 9 "https://s3-media3.fl.yelpcdn.com/bphoto/g2sPtnAplvWGWD1ib8t9Ug/o.jpg"
## 10 "https://s3-media2.fl.yelpcdn.com/bphoto/_rLOln1ewP5OSl28nMeM3A/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/green-truck-neighborhood-pub-savannah?adjust_creati…
## 2 https://www.yelp.com/biz/al-salaam-deli-savannah?adjust_creative=rHmJcLYuBDk…
## 3 https://www.yelp.com/biz/lisas-chinese-take-out-savannah?adjust_creative=rHm…
## 4 https://www.yelp.com/biz/krystal-savannah-5?adjust_creative=rHmJcLYuBDkzuIyV…
## 5 https://www.yelp.com/biz/waters-cafe-savannah?adjust_creative=rHmJcLYuBDkzuI…
## 6 https://www.yelp.com/biz/barakas-homestyle-soul-and-seafood-restaurant-savan…
## 7 https://www.yelp.com/biz/off-the-grill-savannah?adjust_creative=rHmJcLYuBDkz…
## 8 https://www.yelp.com/biz/china-fun-ii-savannah?adjust_creative=rHmJcLYuBDkzu…
## 9 https://www.yelp.com/biz/the-olde-pink-house-savannah?adjust_creative=rHmJcL…
## 10 https://www.yelp.com/biz/alligator-soul-savannah-2?adjust_creative=rHmJcLYuB…
## review_count categories rating coordinates$latitude $longitude transactions
## <int> <list> <dbl> <dbl> <dbl> <list>
## 1 876 <df [3 × 2]> 4.5 32.1 -81.1 <chr [1]>
## 2 176 <df [1 × 2]> 4.5 32.1 -81.1 <chr [1]>
## 3 12 <df [1 × 2]> 4 32.1 -81.1 <chr [1]>
## 4 32 <df [2 × 2]> 2 32.0 -81.1 <chr [2]>
## 5 2 <df [2 × 2]> 5 32.1 -81.1 <chr [0]>
## 6 2 <df [2 × 2]> 4.5 32.1 -81.1 <chr [0]>
## 7 3 <df [1 × 2]> 4.5 32.1 -81.1 <chr [1]>
## 8 1 <df [1 × 2]> 4 32.1 -81.1 <chr [1]>
## 9 5812 <df [2 × 2]> 4.5 32.1 -81.1 <chr [1]>
## 10 1206 <df [3 × 2]> 4.5 32.1 -81.1 <chr [1]>
## price location$address1 $address2 $address3 $city $zip_code
## <chr> <chr> <chr> <chr> <chr> <chr>
## 1 $$ 2430 Habersham St <NA> "" Savannah 31401
## 2 $ 2311 Habersham St "" "" Savannah 31401
## 3 $ 2315 Waters Ave "" "" Savannah 31404
## 4 $ 2804 Bee Rd "" "" Savannah 31401
## 5 <NA> 2317 Waters Ave "" <NA> Savannah 31404
## 6 $ 1801 Waters Ave "" "" Savannah 31404
## 7 <NA> 2015 Waters Ave <NA> <NA> Savannah 31404
## 8 <NA> 1519 E Anderson St "" "" Savannah 31404
## 9 $$ 23 Abercorn St "" "" Savannah 31401
## 10 $$$ 114 Barnard St Lower Level "" "Lower Level" Savannah 31401
## $country $state $display_address phone display_phone distance
## <chr> <chr> <list> <chr> <chr> <dbl>
## 1 US GA <chr [2]> "+19122345885" "(912) 234-5885" 1390.
## 2 US GA <chr [2]> "+19124470400" "(912) 447-0400" 1474.
## 3 US GA <chr [2]> "+19122368228" "(912) 236-8228" 682.
## 4 US GA <chr [2]> "+19123524598" "(912) 352-4598" 857.
## 5 US GA <chr [2]> "+19124174929" "(912) 417-4929" 680.
## 6 US GA <chr [2]> "" "" 1187.
## 7 US GA <chr [2]> "+19122321218" "(912) 232-1218" 979.
## 8 US GA <chr [2]> "+19122325151" "(912) 232-5151" 1651.
## 9 US GA <chr [2]> "+19122324286" "(912) 232-4286" 1614.
## 10 US GA <chr [3]> "+19122327899" "(912) 232-7899" 1807.
## # ℹ 6,342 more rows
Repeat for second business category
# Appending X Y coordinates as seprate columns
ready_4_yelp2 <- r4all_apply %>%
mutate(x = st_coordinates(.)[,1],
y = st_coordinates(.)[,2])
tmap_mode('view')
## tmap mode set to interactive viewing
# Select the first 10 rows
ready_4_yelp2[1:5,] %>%
# Draw a buffer centered at the centroid of Tract polygons.
# Radius of the buffer is the radius we just calculated using loop
st_buffer(., dist = .$radius) %>%
# Display this buffer in red
tm_shape(.) + tm_polygons(alpha = 0.5, col = 'red') +
# Display the original polygon in blue
tm_shape(tract[1:5,]) + tm_borders(col= 'blue')
which_tract2 <- 1
test2 <- business_search(api_key = yelp_api, # like we did for census, store your api key
categories = 'hiking', # return only hiking
latitude = ready_4_yelp$y[which_tract],
longitude = ready_4_yelp$x[which_tract],
offset = 0, # 1st page, 1st obs
radius = round(ready_4_yelp2$radius[which_tract2]), # radius requires integer value
limit = 50) # how many business per page
## No encoding supplied: defaulting to UTF-8.
lapply(test2, head)
## $businesses
## list()
##
## $total
## [1] 0
##
## $region
## $region$center
## $region$center$longitude
## [1] -81.08555
##
## $region$center$latitude
## [1] 32.04456
names(test2)
## [1] "businesses" "total" "region"
# Business
paste0("is it a data.frame?: ", is.data.frame(test2$businesses), ", ",
" how many rows?: ", nrow(test2$businesses), ", ",
" how many columns?: ", ncol(test2$businesses))
## [1] "is it a data.frame?: FALSE, how many rows?: , how many columns?: "
Okay, we learned that we need to make multiple requests even for just
one Census Tract. We can create another function to make this process
automatic. We will design this function such that it takes a row from
ready_4_yelp, make the initial API request, calculate how
many times more it need to request, complete the remaining request,
gather the responses from the multiple requests.
-
# FUNCTION
get_yelp2 <- function(tract, category){
# ----------------------------------
# Gets one row of tract information (1,) and category name (str),
# Outputs a list of business data.frame
Sys.sleep(1)
n <- 1
# First request --------------------------------------------------------------
resp <- business_search(api_key = yelp_api,
categories = category,
latitude = tract$y,
longitude = tract$x,
offset = (n - 1) * 50, # = 0 when n = 1
radius = round(tract$radius),
limit = 50)
# Calculate how many requests are needed in total
required_n <- ceiling(resp$total/50)
# out is where the results will be appended to.
out <- vector("list", required_n)
# Store the business information to nth slot in out
out[[n]] <- resp$businesses
# Change the name of the elements to the total required_n
# This is to know if there are more than 1000 businesses,
# we know how many.
names(out)[n] <- required_n
# Throw error if more than 1000
if (resp$total >= 1000)
{
# glue formats string by inserting {n} with what's currently stored in object n.
print(glue::glue("{n}th row has >= 1000 businesses."))
# Stop before going into the loop because we need to
# break down Census Tract to something smaller.
return(out)
}
else
{
# add 1 to n
n <- n + 1
# Now we know required_n -----------------------------------------------------
# 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
# Merge all elements in the list into a single data frame
out <- out %>% bind_rows()
return(out)
}
}
# Apply the function for the first Census Tract
yelp_first_tract2 <- get_yelp2(ready_4_yelp2[1,], "hiking") %>%
as_tibble()
## No encoding supplied: defaulting to UTF-8.
# Print
yelp_first_tract2 %>% print
## # A tibble: 0 × 0
# Prepare a collector
yelp_all_list2 <- vector("list", nrow(ready_4_yelp2))
# Looping through all Census Tracts
for (row in 1:nrow(ready_4_yelp2)){
yelp_all_list2[[row]] <- suppressMessages(get_yelp2(ready_4_yelp2[row,], "hiking"))
if (row %% 10 == 0){
print(paste0("Current row: ", row))
}
}
## [1] "Current row: 10"
## [1] "Current row: 20"
## [1] "Current row: 30"
## [1] "Current row: 40"
## [1] "Current row: 50"
## [1] "Current row: 60"
# Collapsing the list into a data.frame
yelp_all2 <- yelp_all_list2 %>% bind_rows() %>% as_tibble()
# print
yelp_all2 %>% print(width=1000)
## # A tibble: 47 × 15
## id alias
## <chr> <chr>
## 1 KsCxpiJARXKZdscN5e_orQ lake-mayer-park-savannah
## 2 KsCxpiJARXKZdscN5e_orQ lake-mayer-park-savannah
## 3 qaKObHb0607aKmr73crcVA lotts-island-army-airfield-rv-park-savannah
## 4 jVlLATZ1hNSLVQbRAbRqzA chatham-county-wetlands-preserve-savannah
## 5 XnohW-sieClJ1tRHh4I8dw green-creek-trail-richmond-hill
## 6 vrKXd9rvRElgQfYNU13lUQ sterling-creek-park-richmond-hill
## 7 qaKObHb0607aKmr73crcVA lotts-island-army-airfield-rv-park-savannah
## 8 2bZo3N6xd8eKTL1UwTz3uA skidaway-island-state-park-savannah
## 9 HAA04zYTn259FySbcn49zA wormsloe-historic-site-savannah
## 10 KsCxpiJARXKZdscN5e_orQ lake-mayer-park-savannah
## name
## <chr>
## 1 Lake Mayer Park
## 2 Lake Mayer Park
## 3 Lotts Island Army Airfield RV Park
## 4 Chatham County Wetlands Preserve
## 5 Green Creek Trail
## 6 Sterling Creek Park
## 7 Lotts Island Army Airfield RV Park
## 8 Skidaway Island State Park
## 9 Wormsloe Historic Site
## 10 Lake Mayer Park
## image_url
## <chr>
## 1 https://s3-media4.fl.yelpcdn.com/bphoto/nVwZXlKnD0mbttazV8TjKg/o.jpg
## 2 https://s3-media4.fl.yelpcdn.com/bphoto/nVwZXlKnD0mbttazV8TjKg/o.jpg
## 3 https://s3-media3.fl.yelpcdn.com/bphoto/E1KjK7qa1ZedBG7XUmsJDg/o.jpg
## 4 https://s3-media4.fl.yelpcdn.com/bphoto/wfTYmANojAjb97HEmB0MpA/o.jpg
## 5 https://s3-media1.fl.yelpcdn.com/bphoto/yFT7c19oBxg_fonGr7GNAg/o.jpg
## 6 https://s3-media3.fl.yelpcdn.com/bphoto/sBfOarpaqQGEGmKRQj7KyQ/o.jpg
## 7 https://s3-media3.fl.yelpcdn.com/bphoto/E1KjK7qa1ZedBG7XUmsJDg/o.jpg
## 8 https://s3-media2.fl.yelpcdn.com/bphoto/OL74I5Jyk0zExM0i6qUJYA/o.jpg
## 9 https://s3-media1.fl.yelpcdn.com/bphoto/Sd1Nb6BF2x9n87YdLE659Q/o.jpg
## 10 https://s3-media4.fl.yelpcdn.com/bphoto/nVwZXlKnD0mbttazV8TjKg/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/lake-mayer-park-savannah?adjust_creative=rHmJcLYuBD…
## 2 https://www.yelp.com/biz/lake-mayer-park-savannah?adjust_creative=rHmJcLYuBD…
## 3 https://www.yelp.com/biz/lotts-island-army-airfield-rv-park-savannah?adjust_…
## 4 https://www.yelp.com/biz/chatham-county-wetlands-preserve-savannah?adjust_cr…
## 5 https://www.yelp.com/biz/green-creek-trail-richmond-hill?adjust_creative=rHm…
## 6 https://www.yelp.com/biz/sterling-creek-park-richmond-hill?adjust_creative=r…
## 7 https://www.yelp.com/biz/lotts-island-army-airfield-rv-park-savannah?adjust_…
## 8 https://www.yelp.com/biz/skidaway-island-state-park-savannah?adjust_creative…
## 9 https://www.yelp.com/biz/wormsloe-historic-site-savannah?adjust_creative=rHm…
## 10 https://www.yelp.com/biz/lake-mayer-park-savannah?adjust_creative=rHmJcLYuBD…
## review_count categories rating coordinates$latitude $longitude transactions
## <int> <list> <dbl> <dbl> <dbl> <list>
## 1 27 <df [3 × 2]> 4 32.0 -81.1 <list [0]>
## 2 27 <df [3 × 2]> 4 32.0 -81.1 <list [0]>
## 3 4 <df [3 × 2]> 3 32.0 -81.1 <list [0]>
## 4 3 <df [2 × 2]> 4.5 32.0 -81.3 <list [0]>
## 5 4 <df [1 × 2]> 4.5 31.9 -81.3 <list [0]>
## 6 3 <df [3 × 2]> 4.5 31.9 -81.3 <list [0]>
## 7 4 <df [3 × 2]> 3 32.0 -81.1 <list [0]>
## 8 98 <df [3 × 2]> 4.5 32.0 -81.1 <list [0]>
## 9 225 <df [2 × 2]> 4 32.0 -81.1 <list [0]>
## 10 27 <df [3 × 2]> 4 32.0 -81.1 <list [0]>
## location$address1 $address2 $address3 $city
## <chr> <chr> <chr> <chr>
## 1 1850 E Montgomery Cross "" "" Savannah
## 2 1850 E Montgomery Cross "" "" Savannah
## 3 1200 Lott Island Rd "" "" Savannah
## 4 6811 Basin Rd "" <NA> Savannah
## 5 GA-144 "" <NA> Richmond Hill
## 6 Elbow Swamp Rd & Sterling Creek Rd "" <NA> Richmond Hill
## 7 1200 Lott Island Rd "" "" Savannah
## 8 52 Diamond Cswy "" "" Savannah
## 9 7601 Skidaway Rd "" "" Savannah
## 10 1850 E Montgomery Cross "" "" Savannah
## $zip_code $country $state $display_address phone display_phone
## <chr> <chr> <chr> <list> <chr> <chr>
## 1 31406 US GA <chr [2]> "+19126526780" "(912) 652-6780"
## 2 31406 US GA <chr [2]> "+19126526780" "(912) 652-6780"
## 3 31419 US GA <chr [2]> "+19123159554" "(912) 315-9554"
## 4 31401 US GA <chr [2]> "" ""
## 5 31324 US GA <chr [2]> "" ""
## 6 31324 US GA <chr [2]> "" ""
## 7 31419 US GA <chr [2]> "+19123159554" "(912) 315-9554"
## 8 31411 US GA <chr [2]> "+19125982300" "(912) 598-2300"
## 9 31406 US GA <chr [2]> "+19123533023" "(912) 353-3023"
## 10 31406 US GA <chr [2]> "+19126526780" "(912) 652-6780"
## distance
## <dbl>
## 1 3082.
## 2 1803.
## 3 5091.
## 4 4823.
## 5 7173.
## 6 7593.
## 7 8006.
## 8 6802.
## 9 9910.
## 10 11933.
## # ℹ 37 more rows
bind two df together
yelp_all_c <- bind_rows(yelp_all,yelp_all2)
map them
yelp_sf <- yelp_all_c %>%
mutate(x = .$coordinates$longitude,
y = .$coordinates$latitude) %>%
filter(!is.na(x) & !is.na(y)) %>%
st_as_sf(coords = c("x", "y"), crs = 4326)
# Map
tm_shape(yelp_sf) +
tm_dots(col = "review_count", style="quantile")
Questions 1. What’s the county and state of your choice? Chatham county in Georgia (home of Savannah!)
How many businesses are there in total? There are 5,536 business.
How many businesses are there for each business category? There are 2768 restaurants and 47 hiking.
Upon visual inspection, can you see any noticeable spatial patterns to the way they are distributed across the county (e.g., clustering of businesses at some parts of the county)? There are clusters in downtown Savannah and on the coastal vacation islands. The populations are higher in these areas. The hiking trails are closer to the coast where the nature is undistrbed by city development.