This assignment is divided into three main sections.
In the first section, you will select two Census Tracts within Fulton and DeKalb Counties, GA — one that you believe is the most walkable and another that is the least walkable. You may choose any tracts within these two counties. If the area you want to analyze is not well represented by a single tract, you may select multiple adjacent tracts (e.g., two contiguous tracts as one “walkable area”). The definition of walkable is up to you — it can be based on your personal experience (e.g., places where you’ve had particularly good or bad walking experiences), Walk Score data, or any combination of criteria. After making your selections, provide a brief explanation of why you chose those tracts.
The second section is the core of this assignment. You will prepare OpenStreetMap (OSM) data, download Google Street View (GSV) images, and apply the computer vision technique covered in class — semantic segmentation.
In the third section, you will summarize and analyze the results. After applying computer vision to the images, you will obtain pixel counts for 19 different object categories. Using the data, you will:
Importing the necessary packages is part of this assignment. Add any required packages to the code chunk below as you progress through the tasks.
library("tidytransit")
library("dplyr")
##
## 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
library(gtfsrouter)
## Warning: package 'gtfsrouter' was built under R version 4.5.2
## Registered S3 method overwritten by 'gtfsrouter':
## method from
## summary.gtfs gtfsio
library(sf)
## Linking to GEOS 3.13.1, GDAL 3.11.0, PROJ 9.6.0; sf_use_s2() is TRUE
library(tidycensus)
library(mapview)
library(leafsync)
library(leaflet)
library(dbscan)
##
## Attaching package: 'dbscan'
## The following object is masked from 'package:stats':
##
## as.dendrogram
library(sfnetworks)
library(tigris)
## To enable caching of data, set `options(tigris_use_cache = TRUE)`
## in your R script or .Rprofile.
library(tidygraph)
##
## Attaching package: 'tidygraph'
## The following object is masked from 'package:stats':
##
## filter
library(plotly)
## Loading required package: ggplot2
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
library(osmdata)
## Data (c) OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright
library(here)
## here() starts at C:/Users/HojungYu/OneDrive/GT_Semester3/Urban_Analytics
library(magrittr)
library(purrr)
##
## Attaching package: 'purrr'
## The following object is masked from 'package:magrittr':
##
## set_names
library(tmap)
library(ggplot2)
library(units)
## udunits database from C:/Users/HojungYu/AppData/Local/R/win-library/4.5/units/share/udunits/udunits2.xml
Use the Census Tract map in the following code chunk to identify the GEOIDs of the tracts you consider walkable and unwalkable.
# TASK ////////////////////////////////////////////////////////////////////////
# Set up your api key here
census_api_key(Sys.getenv("CENSUS_API"))
## To install your API key for use in future sessions, run this function with `install = TRUE`.
# //TASK //////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
# Download Census Tract polygon for Fulton and DeKalb
tract <- get_acs("tract",
variables = c('pop' = 'B01001_001'),
year = 2023,
state = "GA",
county = c("Fulton", "DeKalb"),
geometry = TRUE)
## Getting data from the 2019-2023 5-year ACS
## Downloading feature geometry from the Census website. To cache shapefiles for use in future sessions, set `options(tigris_use_cache = TRUE)`.
## | | | 0% | |= | 1% | |= | 2% | |== | 2% | |== | 3% | |=== | 4% | |=== | 5% | |==== | 6% | |===== | 7% | |====== | 8% | |====== | 9% | |======= | 9% | |======= | 10% | |======== | 11% | |========= | 12% | |========= | 13% | |========== | 14% | |========== | 15% | |=========== | 15% | |=========== | 16% | |============ | 17% | |============ | 18% | |============= | 18% | |============= | 19% | |============== | 20% | |============== | 21% | |=============== | 21% | |=============== | 22% | |================ | 23% | |================= | 24% | |================= | 25% | |================== | 26% | |=================== | 27% | |==================== | 28% | |==================== | 29% | |===================== | 29% | |===================== | 30% | |====================== | 31% | |====================== | 32% | |======================= | 32% | |======================= | 33% | |======================== | 34% | |======================== | 35% | |========================= | 35% | |========================= | 36% | |========================== | 37% | |=========================== | 38% | |=========================== | 39% | |============================ | 40% | |============================= | 41% | |============================= | 42% | |============================== | 43% | |=============================== | 44% | |=============================== | 45% | |================================ | 46% | |================================= | 47% | |================================= | 48% | |================================== | 48% | |================================== | 49% | |=================================== | 50% | |=================================== | 51% | |==================================== | 51% | |===================================== | 52% | |===================================== | 53% | |====================================== | 54% | |======================================= | 55% | |======================================= | 56% | |======================================== | 57% | |========================================= | 58% | |========================================= | 59% | |========================================== | 59% | |========================================== | 60% | |=========================================== | 61% | |=========================================== | 62% | |============================================ | 62% | |============================================ | 63% | |============================================= | 64% | |============================================= | 65% | |============================================== | 65% | |============================================== | 66% | |=============================================== | 67% | |=============================================== | 68% | |================================================ | 68% | |================================================ | 69% | |================================================= | 70% | |================================================= | 71% | |================================================== | 71% | |================================================== | 72% | |=================================================== | 73% | |==================================================== | 74% | |==================================================== | 75% | |===================================================== | 76% | |====================================================== | 77% | |======================================================= | 78% | |======================================================= | 79% | |======================================================== | 79% | |======================================================== | 80% | |========================================================= | 81% | |========================================================= | 82% | |========================================================== | 82% | |========================================================== | 83% | |=========================================================== | 84% | |============================================================ | 85% | |============================================================ | 86% | |============================================================= | 87% | |============================================================== | 88% | |============================================================== | 89% | |=============================================================== | 90% | |================================================================ | 91% | |================================================================ | 92% | |================================================================= | 93% | |================================================================== | 94% | |================================================================== | 95% | |=================================================================== | 96% | |==================================================================== | 97% | |==================================================================== | 98% | |===================================================================== | 98% | |===================================================================== | 99% | |======================================================================| 100%
tmap_mode("view")
## ℹ tmap modes "plot" - "view"
## ℹ toggle with `tmap::ttm()`
## This message is displayed once per session.
tm_basemap("OpenStreetMap") +
tm_shape(tract) +
tm_polygons(fill_alpha = 0.2)
# =========== NO MODIFY ZONE ENDS HERE ========================================
Once you have the GEOIDs, create two Census Tract objects – one representing your most walkable area and the other your least walkable area.
# TASK ////////////////////////////////////////////////////////////////////////
# 1. Specify the GEOIDs of your walkable and unwalkable Census Tracts.
# e.g., tr_id_walkable <- c("13121001205", "13121001206")
# 2. Extract the selected Census Tracts using `tr_id_walkable` and `tr_id_unwalkable`
# For the walkable Census Tract(s)
tr_id_walkable <- c("13121001205", "13121001206")
tract_walkable <- tract %>%
filter(GEOID %in% tr_id_walkable)
# For the unwalkable Census Tract(s)
tr_id_unwalkable <- c("13121004200", "13121006200")
tract_unwalkable <- tract %>%
filter(GEOID %in% tr_id_unwalkable)
# //TASK //////////////////////////////////////////////////////////////////////
# TASK ////////////////////////////////////////////////////////////////////////
# Create an interactive map showing `tract_walkable` and `tract_unwalkable`
tmap_mode("view")
## ℹ tmap modes "plot" - "view"
tm_basemap("OpenStreetMap") +
tm_shape(tract_walkable) +
tm_polygons(col = "green", alpha = 0.4, border.col = NA) +
tm_shape(tract_unwalkable) +
tm_polygons(col = "red", alpha = 0.4, border.col = NA)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use 'fill' for the fill color of polygons/symbols
## (instead of 'col'), and 'col' for the outlines (instead of 'border.col').[v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.[v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.
# //TASK //////////////////////////////////////////////////////////////////////
Provide a brief description of your selected Census Tracts. Why do you consider these tracts walkable or unwalkable? What factors do you think contribute to their walkability?
I considered the Midtown area as a walkable place based on my own experience. Since I live in Midtown, I walk there very frequently without using a car. Along the streets, many shops face the sidewalk, and the quality of the sidewalks is well maintained. I also see many people walking their dogs.
For an unwalkable place, I considered the West End area. When I visited the West End to go to Dollar Tree, I saw several empty stores. I also noticed cracks in the sidewalks and a lot of trash. It was in late summer, and I felt thermal discomfort because there were few trees nearby.
To obtain the OSM network for your selected Census Tracts: (1) Create
bounding boxes. (2) Use the bounding boxes to download OSM data. (3)
Convert the data into an sfnetwork object and clean it.
# TASK ////////////////////////////////////////////////////////////////////////
# Create one bounding box (`tract_walkable_bb`) for your walkable Census Tract(s) and another (`tract_unwalkable_bb`) for your unwalkable Census Tract(s).
# For the walkable Census Tract(s)
tract_walkable_bb <- st_bbox(tract_walkable)
# For the unwalkable Census Tract(s)
tract_unwalkable_bb <- st_bbox(tract_unwalkable)
# //TASK //////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
# Get OSM data for the two bounding boxes
osm_walkable <- opq(bbox = tract_walkable_bb) %>%
add_osm_feature(key = 'highway',
value = c("primary", "secondary", "tertiary", "residential")) %>%
osmdata_sf() %>%
osm_poly2line()
osm_unwalkable <- opq(bbox = tract_unwalkable_bb) %>%
add_osm_feature(key = 'highway',
value = c("primary", "secondary", "tertiary", "residential")) %>%
osmdata_sf() %>%
osm_poly2line()
# =========== NO MODIFY ZONE ENDS HERE ========================================
# TASK ////////////////////////////////////////////////////////////////////////
# 1. Convert `osm_walkable` and `osm_unwalkable` into sfnetwork objects (as undirected networks),
# 2. Clean the network by (1) deleting parallel lines and loops, (2) creating missing nodes, and (3) removing pseudo nodes (make sure the `summarise_attributes` argument is set to 'first' when doing so).
net_walkable <- osm_walkable$osm_lines %>%
# Drop redundant columns
select(osm_id, highway) %>%
as_sfnetwork(directed = FALSE) %>% #undirected networks
activate("edges") %>% # deleting parallel lines
filter(!edge_is_multiple()) %>%
filter(!edge_is_loop()) %>%
convert(sfnetworks::to_spatial_subdivision) %>% # missing nodes
convert(sfnetworks::to_spatial_smooth,
.clean=TRUE,
summarise_attributes="first") # removing nodes
## Warning: to_spatial_subdivision assumes attributes are constant over geometries
net_unwalkable <- osm_unwalkable$osm_lines %>%
# Drop redundant columns
select(osm_id, highway) %>%
as_sfnetwork(directed = FALSE) %>% #undirected networks
activate("edges") %>% # deleting parallel lines
filter(!edge_is_multiple()) %>%
filter(!edge_is_loop()) %>%
convert(sfnetworks::to_spatial_subdivision) %>% # missing nodes
convert(sfnetworks::to_spatial_smooth,
.clean=TRUE,
summarise_attributes="first") # removing nodes
## Warning: to_spatial_subdivision assumes attributes are constant over geometries
# //TASK //////////////////////////////////////////////////////////////////////
# TASK //////////////////////////////////////////////////////////////////////
# Using `net_walkable` and`net_unwalkable`,
# 1. Activate the edge component of each network.
# 2. Create a `length` column.
# 3. Filter out short (<300 feet) segments.
# 4. Randomly Sample 100 rows per road type.
# 5. Assign the results to `edges_walkable` and `edges_unwalkable`, respectively.
set.seed(123)
# OSM for the walkable part
edges_walkable <- net_walkable %>%
activate("edges") %>%
mutate(length=st_length(geometry),
length=as.numeric(set_units(length,"ft"))
) %>%
filter(length >= 300) %>%
group_by(highway) %>%
slice_sample(n=100, replace=TRUE)%>%
ungroup() %>%
st_as_sf()
# OSM for the unwalkable part
edges_unwalkable <- net_unwalkable %>%
activate("edges") %>%
mutate(length=st_length(geometry),
length=as.numeric(set_units(length,"ft"))
) %>%
filter(length >= 300) %>%
group_by(highway) %>%
slice_sample(n=100, replace=TRUE)%>%
ungroup() %>%
st_as_sf()
# //TASK //////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
# Merge the two
edges <- bind_rows(edges_walkable %>% mutate(is_walkable = TRUE),
edges_unwalkable %>% mutate(is_walkable = FALSE)) %>%
mutate(edge_id = seq(1,nrow(.)))
# =========== NO MODIFY ZONE ENDS HERE ========================================
getAzimuth() function.In this assignment, you will collect two GSV images per road segment, as illustrated in the figure below. To do this, you will define a function that extracts the coordinates of the midpoint and the azimuths in both directions.
If you can’t see this image, try changing the markdown editing mode from ‘Source’ to ‘Visual’ (you can find the buttons in the top-left corner of this source pane).
getAzimuth <- function(line){
# TASK ////////////////////////////////////////////////////////////////////////
# 1. Use the `st_line_sample()` function to sample three points at locations 0.48, 0.5, and 0.52 along the line. These points will be used to calculate the azimuth.
# 2. Use `st_cast()` function to convert the 'MULTIPOINT' object into a 'POINT' object.
# 3. Extract coordinates using `st_coordinates()`.
# 4. Assign the coordinates of the midpoint to `mid_p`.
# 5. Calculate the azimuths from the midpoint in both directions and save them as `mid_azi_1` and `mid_azi_2`, respectively.
# 1-3
mid_p3 <- line %>%
st_line_sample(sample = c(0.48, 0.5, 0.52)) %>%
st_cast("POINT") %>%
st_coordinates()
# 4
mid_p <- mid_p3[2,]
# 5
mid_azi_1 <- atan2(mid_p3[1,"X"] - mid_p3[2, "X"],
mid_p3[1,"Y"] - mid_p3[2, "Y"])*180/pi
mid_azi_2 <- atan2(mid_p3[3,"X"] - mid_p3[2, "X"],
mid_p3[3,"Y"] - mid_p3[2, "Y"])*180/pi
# //TASK //////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
return(tribble(
~type, ~X, ~Y, ~azi,
"mid1", mid_p["X"], mid_p["Y"], mid_azi_1,
"mid2", mid_p["X"], mid_p["Y"], mid_azi_2))
# =========== NO MODIFY ZONE ENDS HERE ========================================
}
Apply the getAzimuth() function to the
edges object. Once this step is complete, your data will be
ready for downloading GSV images.
# TASK ////////////////////////////////////////////////////////////////////////
# Apply getAzimuth() function to all edges.
# Remember that you need to pass edges object to st_geometry() before you apply getAzimuth()
edges_azi <- edges %>%
st_geometry() %>%
map_df(getAzimuth, .progress=T)
# //TASK //////////////////////////////////////////////////////////////////////
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
edges_azi <- edges_azi %>%
bind_cols(edges %>%
st_drop_geometry() %>%
slice(rep(1:nrow(edges),each=2))) %>%
st_as_sf(coords = c("X", "Y"), crs = 4326, remove=FALSE) %>%
mutate(img_id = seq(1, nrow(.)))
# =========== NO MODIFY ZONE ENDS HERE ========================================
getImage <- function(iterrow){
# This function takes one row of `edges_azi` and downloads GSV image using the information from the row.
# TASK ////////////////////////////////////////////////////////////////////////
# 1. Extract required information from the row of `edges_azi`
# 2. Format the full URL and store it in `request`. Refer to this page: https://developers.google.com/maps/documentation/streetview/request-streetview
# 3. Format the full path (including the file name) of the image being downloaded and store it in `fpath`
type <- iterrow$type
location <- paste0(iterrow$Y %>% round(5), ",", iterrow$X %>% round(5))
heading <- iterrow$azi %>% round(1)
edge_id <- iterrow$edge_id
img_id <- iterrow$img_id
key <- Sys.getenv("API_KEY_GSV")
endpoint <- "https://maps.googleapis.com/maps/api/streetview"
request <- glue::glue("{endpoint}?size=640x640&location={location}&heading={heading}&fov=90&pitch=0&key={key}")
fname <- glue::glue("GSV-nid_{img_id}-eid_{edge_id}-type_{type}-Location_{location}-heading_{heading}.jpg") # Don't change this code for fname
fpath <- file.path("GSV_images", fname)
# //TASK //////////////////////////////////////////////////////////////////////
furl <- request
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
# Download images
if (!file.exists(fpath)){
download.file(furl, fpath, mode = 'wb')
}
Sys.sleep(1)
# =========== NO MODIFY ZONE ENDS HERE ========================================
}
Before you download GSV images, make sure the row
number in edges_azi is not too large! Each row corresponds
to one GSV image, so if the row count exceeds your API quota, consider
selecting different Census Tracts.
You do not want to run the following code chunk more than once, so the code chunk option
eval=FALSEis set to prevent the API call from executing again when knitting the script.
# =========== NO MODIFICATION ZONE STARTS HERE ===============================
for (i in seq(1,nrow(edges_azi))){
getImage(edges_azi[i,])
}
# =========== NO MODIFY ZONE ENDS HERE ========================================
ZIP THE DOWNLOADED IMAGES AND NAME IT ‘gsv_images.zip’ FOR STEP 6.
Use this Google Colab script to apply the pretrained semantic segmentation model to your GSV images.
Once all of the images are processed and saved in your Colab session
as a CSV file, download the CSV file and merge it back to
edges_azi.
# TASK ////////////////////////////////////////////////////////////////////////
# Read the downloaded CSV file containing the semantic segmentation results.
seg_output <- read.csv("seg_output.csv")
# //TASK ////////////////////////////////////////////////////////////////////////
# TASK ////////////////////////////////////////////////////////////////////////
# 1. Join the `seg_output` data to `edges_azi` with 'img_id'.
# 2. Calculate the proportion of predicted pixels for the following categories: `building`, `sky`, `road`, and `sidewalk`. If there are other categories you are interested in, feel free to include their proportions as well.
# 3. Calculate the proportion of greenness using the `vegetation` and `terrain` categories.
# 4. Calculate the building-to-street ratio. For the street, use `road` and `sidewalk` pixels; including `car` pixels is optional.
edges_seg_output <- edges_azi %>%
left_join(seg_output, by="img_id") %>%
mutate(
tot_pixels=road+sidewalk+building+wall+fence+pole+traffic.light+traffic.sign+vegetation+terrain+sky+person+rider+car+truck+bus+train+motorcycle+bicycle,
# 2. Prop of each
prop_building = building / tot_pixels,
prop_sky = sky / tot_pixels,
prop_road = road / tot_pixels,
prop_sidewalk=sidewalk/tot_pixels,
# 3. Greenness
prop_green = (vegetation + terrain) / tot_pixels,
# 4. bd to street
street_pixels = road + sidewalk + car,
bd_st_ratio = if_else(street_pixels > 0, building / street_pixels, NA_real_)
)
# //TASK ////////////////////////////////////////////////////////////////////////
At the beginning of this assignment, you specified walkable and unwalkable Census Tracts. The key focus of this section is the comparison between these two types of tracts.
Create interactive maps showing the proportion of sidewalk, greenness, and the building-to-street ratio for both walkable and unwalkable areas. In total, you will produce 6 maps. Provide a brief description of your findings.
In Midtown, the proportion of sidewalk is higher along Peachtree Street NE, while Peachtree Street NW shows a lower sidewalk proportion. In contrast, the West End has a more varied distribution of sidewalk proportion. The sidewalk ratio is higher along Lee Street SW; however, it is not consistent along the entire corridor. Oak Street SW and Joseph E. Lowery Boulevard also show a higher proportion of sidewalk coverage.
# TASK ////////////////////////////////////////////////////////////////////////
# Plot interactive map(s)
# As long as you can deliver the message clearly, you can use any format/package you want.
edges_walkable <- edges_seg_output %>% filter(is_walkable == TRUE)
edges_unwalkable <- edges_seg_output %>% filter(is_walkable == FALSE)
# Sidewalk Walkable
tmap_mode("view")
## ℹ tmap modes "plot" - "view"
tm_basemap("OpenStreetMap") +
tm_shape(edges_walkable) +
tm_dots(
col = "prop_sidewalk",
palette = "brewer.blues",
style="jenks",
n=5,
size=1,
title = "Sidewalk proportion"
) +
tm_layout(title = "Sidewalk proportion – WALKABLE")
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: instead of `style = "jenks"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'n', 'palette' (rename to 'values') to
## 'tm_scale_intervals(<HERE>)'[tm_dots()] Argument `title` unknown.[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
# //TASK //////////////////////////////////////////////////////////////////////
# Sidewalk unWalkable
tmap_mode("view")
## ℹ tmap modes "plot" - "view"
tm_basemap("OpenStreetMap") +
tm_shape(edges_unwalkable) +
tm_dots(
col = "prop_sidewalk",
palette = "brewer.blues",
style="jenks",
n=5,
size=1,
title = "Sidewalk proportion"
) +
tm_layout(title = "Sidewalk proportion – UNWALKABLE")
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: instead of `style = "jenks"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'n', 'palette' (rename to 'values') to
## 'tm_scale_intervals(<HERE>)'[tm_dots()] Argument `title` unknown.[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
In Midtown, similar to the sidewalk proportion, Peachtree Street Northeast shows a higher proportion of greenery, while the cross streets, such as 4th Street Northwest, have less. Tech Square also shows lower vegetation compared to other streets, which was a somewhat surprising result.
In West End, Lee Street Southwest shows lower levels of greenery. However, it was somewhat surprising to see higher greenery overally in the surrounding neighborhoods and near the college campus.
#Greenery Walkable
tm_basemap("OpenStreetMap") +
tm_shape(edges_walkable) +
tm_dots(
col = "prop_green",
palette = "brewer.greens",
style="jenks",
n=5,
size=1,
title = "Greenness proportion"
) +
tm_layout(title = "Greenness – WALKABLE")
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: instead of `style = "jenks"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'n', 'palette' (rename to 'values') to
## 'tm_scale_intervals(<HERE>)'
## [tm_dots()] Argument `title` unknown.
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
#Greenery Unwalkable
tm_basemap("OpenStreetMap") +
tm_shape(edges_unwalkable) +
tm_dots(
col = "prop_green",
palette = "brewer.greens",
style="jenks",
n=5,
size=1,
title = "Greenness proportion"
) +
tm_layout(title = "Greenness – UNWALKABLE")
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: instead of `style = "jenks"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'n', 'palette' (rename to 'values') to
## 'tm_scale_intervals(<HERE>)'
## [tm_dots()] Argument `title` unknown.
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
In Midtown, horizontal streets such as 3rd Street Northeast, Armstread Place Northwest, and 4th Street Northwest show a higher building-to-street ratio. This is because these roads are narrower than the vertical streets, such as Peachtree Street Northwest. Overall, Juniper Street Northeast shows a lower building-to-street ratio because it is a residential area with low-rise apartment buildings.
In West End, the pattern generally matches expectations. The neighborhood streets show a lower proportion because they are single-family housing areas. In contrast, larger roads such as Ralph David Abernathy Freeway and Lee Street Southwest show a higher building-to-street ratio.
# Building-to-ratio Walkable
tm_basemap("OpenStreetMap") +
tm_shape(edges_walkable) +
tm_dots(
col = "bd_st_ratio",
palette = "brewer.reds",
style="jenks",
n=5,
size = 1,
title = "Building-to-street ratio"
) +
tm_layout(title = "Building-to-street ratio – WALKABLE")
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: instead of `style = "jenks"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'n', 'palette' (rename to 'values') to
## 'tm_scale_intervals(<HERE>)'
## [tm_dots()] Argument `title` unknown.
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
# Building-to-ratio Unwalkable
tm_basemap("OpenStreetMap") +
tm_shape(edges_unwalkable) +
tm_dots(
col = "bd_st_ratio",
palette = "brewer.reds",
style="jenks",
n=5,
size = 1,
title = "Building-to-street ratio – UNWALKABLE")
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: instead of `style = "jenks"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'n', 'palette' (rename to 'values') to
## 'tm_scale_intervals(<HERE>)'
## [tm_dots()] Argument `title` unknown.
Create boxplots for the proportion of each category (building, sky, road, sidewalk, greenness, and any additional categories of interest) and the building-to-street ratio for walkable and unwalkable tracts. Each plot should compare walkable and unwalkable tracts. In total, you will produce 6 or more boxplots. Provide a brief description of your findings.
We can see clear differences in the proportion of sidewalks between the two areas: Midtown has a higher sidewalk proportion than West End. However, the proportions of greenery and sky are higher in West End than in Midtown, presumably because West End contains more neighborhood areas than dense urban areas. This pattern is also reflected in the building proportion. The proportion of road is similar between the two tracts, but the building-to-street ratio is higher in Midtown, highlighting its more urban characteristics.
# TASK ////////////////////////////////////////////////////////////////////////
# Create boxplot(s) using ggplot2 package.
library(dplyr)
library(tidyr)
##
## Attaching package: 'tidyr'
## The following object is masked from 'package:magrittr':
##
## extract
library(ggplot2)
# Prepare datasets
boxdat <- edges_seg_output %>%
st_drop_geometry() %>%
mutate(
walkability = if_else(is_walkable, TRUE, FALSE)
)
# Choose variables to plot
vars_to_plot <- c(
"prop_building",
"prop_sky",
"prop_road",
"prop_sidewalk",
"prop_green", # greenness
"bd_st_ratio" # building-to-street
# add more here if you have them, e.g. "prop_car", "prop_tree", ...
)
# Long format for faceted boxplots
box_long <- boxdat %>%
select(walkability, all_of(vars_to_plot)) %>%
pivot_longer(
cols = -walkability,
names_to = "variable",
values_to = "value"
) %>%
filter(!is.na(value))
ggplot(box_long, aes(x = walkability, y = value, fill = walkability)) +
geom_boxplot(outlier.alpha = 0.4) +
facet_wrap(~ variable, scales = "free_y") +
scale_fill_manual(values = c("TRUE" = "#1b9e77", "FALSE" = "#d95f02")) +
labs(
x = NULL,
y = "Proportion / Ratio",
fill = "Walkability",
title = "Distribution of Streetscape Composition by Walkability"
) +
theme_minimal(base_size = 12) +
theme(
legend.position = "bottom",
axis.text.x = element_text(angle = 15, hjust = 1)
)
# //TASK //////////////////////////////////////////////////////////////////////
Perform t-tests on the mean proportion of each category (building, sky, road, sidewalk, greenness, and any additional categories of interest) as well as the building-to-street ratio between street segments in the walkable and unwalkable tracts. This will result in 6 or more t-test results. Provide a brief description of your findings.
All results are statistically significant, with z-values greater than 2.
The proportion of buildings is higher in Midtown than in West End, whereas the proportion of sky is higher in West End. The proportion of road is similar between the two tracts. The proportion of sidewalk is higher in Midtown, and the building-to-street ratio is also substantially higher there. Greenery is more prevalent in West End than in Midtown, as noted in Analysis 2, likely because the West End tract includes more residential neighborhoods with trees and yards.
Overall, these results show that Midtown has a higher sidewalk proportion, which is a positive characteristic for walkability. However, the greenery and building proportion indicators suggest that Midtown is not necessarily better than the non-walkable area in all respects. Some studies suggest that a balanced building proportion can support walkability by enhancing enclosure and perceived safety. In this context, the proportion of sky in Midtown appears relatively low compared with West End.
# TASK ////////////////////////////////////////////////////////////////////////
# Perform t-tests and report both the differences in means and their statistical significance.
# As long as you can deliver the message clearly, you can use any format/package you want.
# Prep datasets
dat <- edges_seg_output %>%
st_drop_geometry() %>%
mutate(
walkability = if_else(is_walkable, TRUE, FALSE)
)
# Choose variables to plot
vars_to_test <- c(
"prop_building",
"prop_sky",
"prop_road",
"prop_sidewalk",
"prop_green", # greenness
"bd_st_ratio" # building-to-street
# add more here if you have them, e.g. "prop_car", "prop_tree", ...
)
# Run t-tests
t_results <- map_dfr(vars_to_test, function(v) {
df_v <- dat %>%
select(walkability, value = all_of(v)) %>%
filter(!is.na(value))
tt <- t.test(value ~ walkability, data = df_v)
tibble(
variable = v,
mean_walkable = mean(df_v$value[df_v$walkability == TRUE], na.rm = TRUE),
mean_unwalkable = mean(df_v$value[df_v$walkability == FALSE], na.rm = TRUE),
t_statistic = unname(tt$statistic),
df = unname(tt$parameter),
p_value = tt$p.value
)
})
t_results
## # A tibble: 6 × 6
## variable mean_walkable mean_unwalkable t_statistic df p_value
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 prop_building 0.221 0.0509 -15.4 174. 3.08e-34
## 2 prop_sky 0.114 0.227 10.9 321. 7.03e-24
## 3 prop_road 0.342 0.361 2.65 201. 8.81e- 3
## 4 prop_sidewalk 0.0510 0.0357 -5.01 238. 1.05e- 6
## 5 prop_green 0.225 0.287 4.23 305. 3.11e- 5
## 6 bd_st_ratio 0.604 0.123 -7.52 135. 6.90e-12
# //TASK //////////////////////////////////////////////////////////////////////