Vacancy and Land Use - Sharswood

URBS 4000: Urban Studies Thesis

Author

Jed Chew

Published

December 8, 2025

Project Title: Mixed by Design

A Case Study of the Philadelphia Housing Authority’s (PHA) Choice Neighborhoods Redevelopment of Sharswood

For my Urban Studies thesis, I am researching the PHA’s Choice Neighborhoods redevelopment of the Sharswood neighborhood in North Philadelphia. I have two main research questions about the process and outcome of this redevelopment:

(1) the process by which the PHA aligned the politics, finance, and design for the redevelopment of Sharswood; and 

(2) the early redevelopment outcomes relative to the Choice Neighborhoods Initiative (CNI) vision of mixed-partners, mixed-use, and mixed-income


Part 1: Vacancy Data

Vacant Property Indicators from Open Data Philly

  • Data Source: https://opendataphilly.org/datasets/vacant-property-indicators/
  • Date Downloaded: Oct 20, 2025
  • Department: Office of Innovation and Technology in cooperation with Department of Licenses and Inspections, Office of Property Assessment, Philadelphia Land Bank, and Philadelphia Water Department.
  • Description: The location of properties across Philadelphia that are likely to be a vacant lot or vacant building based on an assessment of City of Philadelphia administrative datasets.
  • Types of GeoJSON Files: Buildings, Land, and Points

Step 1: Load Raw Data as csv files

  • Philadelphia census tracts, block groups, and neighborhood boundaries
# Load required packages
library(tidyverse)
library(tidycensus)
library(janitor)
library(sf)
library(tigris)
library(scales)
library(ggnewscale)
library(patchwork)
library(RColorBrewer)
library(viridisLite)
library(units)
library(knitr)
library(caret)
# Load spatial data
census_tracts <- tracts(state = "PA", county = "Philadelphia", year = 2020, class = "sf", cb = TRUE, progress = FALSE)
block_grps <- block_groups(state = "PA", county = "Philadelphia", year = 2020, class = "sf",cb = TRUE, progress = FALSE)

# Filter Block Groups for Sharswood
ids <- c("421010138001","421010138002","421010139001",
         "421010139002","421010139003")

sharswood_bg_sf <- block_grps |>
 filter(GEOID %in% ids) |> 
  st_transform(2272) # PA State Plane (in US Survey Feet)

Step 2: Get Philadelphia Demographic Data using tidycensus

# Load all available variables for ACS 5-year 2022
acs_vars_2022 <- load_variables(2022, "acs5", cache = TRUE)
# Get tract-level demographic data from 2022 ACS 5-Yr Estimates for Philadelphia
phl_tract_2022_data <- get_acs(
  geography = "tract",
  variables = c(
    total_pop = "B01003_001",
    median_income = "B19013_001",
    poverty = "B17001_001",
    White     = "B03002_003",
    Black     = "B03002_004",
    Hispanic  = "B03002_012"
  ),
  state = "PA",
  county = "Philadelphia",
  year = 2022,
  survey = "acs5",
  output = "wide",
  geometry = TRUE
)

# Clean the county names to remove state name and "County" 
phl_tract_2022_clean <- phl_tract_2022_data |> 
  separate(
    NAME, 
    into = c("tract_name", "county_name", "state_name"), 
    sep = "; "
  ) |> 
  mutate(
    tract_name = str_remove(tract_name, "Census Tract "),
    county_name = str_remove(county_name, " County")
  )

phl_tract_2022_summary <- phl_tract_2022_clean |>
  select(GEOID, tract_name, county_name, total_popE, median_incomeE)

Step 3: Filter Data for Sharswood

# Filter Census Tracts for Sharswood
sharswood_2022_tract_sf <- phl_tract_2022_summary |> 
  filter(tract_name %in% c("138", "139"))

sharswood_2022_tract <- sharswood_2022_tract_sf |> 
  st_drop_geometry()

Sharswood Land Parcels

# Filter Property Parcels for Sharswood
prop_parcels <- st_read("data/DOR_Parcel.geojson") |>
  st_transform(st_crs(sharswood_bg_sf))

sharswood_parcels <- st_filter(prop_parcels, sharswood_bg_sf, .predicate = st_intersects)

st_write(
  sharswood_parcels, "data/sharswood_parcels.geojson",
  driver = "GeoJSON",
  delete_dsn = TRUE,
  layer_options = c("RFC7946=YES","COORDINATE_PRECISION=6","WRITE_BBOX=NO")
)

Vacancy Data for Sharswood

# Spatially Filter Vacancy Data for Sharswood
vacant_bldgs <- st_read("data/Vacant_Indicators_Bldg.geojson")
vacant_land <- st_read("data/Vacant_Indicators_Land.geojson")
vacant_pts <- st_read("data/Vacant_Indicators_Points.geojson")
sharswood_land_parcels <- st_read("data/sharswood_parcels.geojson")

sharswood_zip <- 19121

sharswood_vacant_bldgs <- vacant_bldgs |> 
  st_transform(st_crs(sharswood_bg_sf)) |> 
  filter(zipcode == sharswood_zip) |> 
  mutate(bldg_desc = str_squish(bldg_desc)) |>
  filter(
    !is.na(bldg_desc), # filter out NAs
    !str_detect(bldg_desc, regex("\\bschool\\b", ignore_case = TRUE)), # filter out Girard College
    !str_detect(bldg_desc, regex("\\bland\\b", ignore_case = TRUE)) # filter out Land
  ) |> 
  mutate(
    # Remove "MASONRY" and tidy up
    bldg_desc_clean = str_squish(str_remove(bldg_desc, 
                                            regex("MASONRY", ignore_case = TRUE))),
    # Create broader categories
    bldg_type = case_when(
      str_detect(bldg_desc_clean, 
                 regex("APT\\s*2-4", ignore_case = TRUE)) ~ "Apt 2-4 units",
      str_detect(bldg_desc_clean, 
                 regex("ROW\\s*\\d", ignore_case = TRUE)) ~ "Rowhouse",
      str_detect(bldg_desc_clean, 
                 regex("ROW\\s*CONV", ignore_case = TRUE)) ~ "Rowhouse Convert/Apt",
      str_detect(bldg_desc_clean, 
                 regex("SEMI", ignore_case = TRUE)) ~ "Semi-Detached",
      str_detect(bldg_desc_clean, 
                 regex("STORE", ignore_case = TRUE)) ~ "Store",
      str_detect(bldg_desc_clean, 
                 regex("STR/OFF\\+APTS", ignore_case = TRUE)) ~ "Store/Office + Apts",
      str_detect(bldg_desc_clean, 
                 regex("HSE\\s+WORSHIP", ignore_case = TRUE)) ~ "Hse of Worship",
      TRUE ~ "Other")
  ) |>
  st_filter(sharswood_bg_sf, .predicate = st_intersects)

sharswood_vacant_land <- vacant_land |> 
  st_transform(st_crs(sharswood_bg_sf)) |> 
  filter(ZIPCODE == sharswood_zip,
         BLDG_DESC == "VAC LAND RES < ACRE") |> 
  st_filter(sharswood_bg_sf, .predicate = st_intersects)

sharswood_vacant_pts <- vacant_pts |> 
  st_transform(st_crs(sharswood_bg_sf)) |> 
  filter(ZIPCODE == sharswood_zip) |> 
  st_filter(sharswood_bg_sf, .predicate = st_within)
# Vacancy in Table Format
sharswood_vacant_bldgs_df <- sharswood_vacant_bldgs |> 
  st_drop_geometry() |> 
  select(objectid, address, owner1, bldg_desc, bldg_type, zoningbasedistrict)

sharswood_vacant_land_df <- sharswood_vacant_land |> 
  st_drop_geometry() |> 
  select(OBJECTID, ADDRESS, OWNER1, BLDG_DESC, ZONINGBASEDISTRICT)

sharswood_vacant_pts_df <- sharswood_vacant_pts |> 
  st_drop_geometry() |> 
  select(OBJECTID, ADDRESS, OWNER1, BLDG_DESC, ZONINGBASEDISTRICT)
# Count Vacant Land by Owner
sharswood_vacant_land_owner <- sharswood_vacant_land |> 
  mutate(owner_std = str_to_title(str_squish(OWNER1))) |> 
  add_count(owner_std, name = "owner_n")  |> 
  mutate(
    owner_grp = case_when(
      is.na(owner_std) | owner_std == "" ~ "Unknown",
      owner_n == 1                       ~ "Individual Owners",
      TRUE                               ~ owner_std
    )
  ) |> 
  select(-owner_n)

sharswood_vacant_land_owner_df <- sharswood_vacant_land_owner |> 
  st_drop_geometry() |> 
  select(OBJECTID, ADDRESS, OWNER1, BLDG_DESC, owner_grp, ZONINGBASEDISTRICT)

Step 4: Plot Maps of Vacant Buildings, Land, and Points in Sharswood

# Build palettes that expand to any number of categories
n_bldg <- dplyr::n_distinct(sharswood_vacant_bldgs$bldg_type)
# Blue → teal → green
bldg_cols <- colorRampPalette(
  c("#084081", "#2b8cbe", "#4eb3d3", "#7bccc4", "#43a2ca", "#2ca25f", "#238b45", "#006d2c")
)(n_bldg)

n_land <- dplyr::n_distinct(sharswood_vacant_land$ZONINGBASEDISTRICT)
land_cols <- colorRampPalette(brewer.pal(3, "Reds"))(n_land)

ggplot() +
  geom_sf(data = sharswood_land_parcels, fill = NA, color = "gray60", linewidth = 0.4) +
  geom_sf(data = sharswood_vacant_bldgs, 
          aes(fill = bldg_type), 
          color = "grey10", linewidth = 0.08) +
  scale_fill_manual(values = bldg_cols, name = "Vacant Buildings - Type") +
  guides(fill = guide_legend(order = 1)) +
  new_scale_fill() +
  geom_sf(data = sharswood_vacant_land,
          aes(fill = ZONINGBASEDISTRICT),
          color = "grey30", linewidth = 0.2, alpha = 0.65) +
  scale_fill_manual(values = land_cols, name = "Vacant Land - Zoning") +
  guides(fill = guide_legend(order = 2)) +
  labs(
    title = "Vacant Land and Buildings in Sharswood (2025)",
    subtitle = "Data Source: Philadelphia Office of Innovation and Technology",
    caption = "Vacant Land updated 3/24/25; Vacant Buildings updated 7/7/25",
  ) +
  theme_void() +
  theme(
    legend.position = "right",
    legend.title = element_text(size = 11, face = "bold"),
    legend.text  = element_text(size = 9),
    plot.title   = element_text(face = "bold"),
    plot.caption = element_text(hjust = 0, size = 9, margin = margin(t = 6))
  )

Zoning Codes:

Source: Philadelphia Zoning Code Quick Guide (Dec 2022), https://www.phila.gov/media/20220909084529/ZONING-QUICK-GUIDE_PCPC_9_9_22.pdf

  • CMX-2: Commercial Mixed-Use (small-scale)
  • RM-1: Residential Multifamily (low-rise)
  • RSA-5: Residential Single-Family Attached (rowhomes)
# Vacant Land by Ownership
n_land_owner <- dplyr::n_distinct(sharswood_vacant_land_owner$owner_grp)
land_owner_cols <- colorRampPalette(brewer.pal(8, "Set1"))(n_land_owner)

ggplot() +
  geom_sf(data = sharswood_land_parcels, fill = NA, color = "gray60", linewidth = 0.4) +
  geom_sf(data = sharswood_vacant_land_owner,
          aes(fill = owner_grp),
          color = "grey30", linewidth = 0.2, alpha = 0.65) +
  scale_fill_manual(values = land_owner_cols, name = "Vacant Land - Ownership") +
  guides(fill = guide_legend(order = 2)) +
  labs(
    title = "Vacant Land in Sharswood by Ownership (2025)",
    subtitle = "Data Source: Philadelphia Office of Innovation and Technology",
    caption = "Vacant Land updated 3/24/25"
  ) +
  theme_void() +
  theme(
    legend.position = "right",
    legend.title = element_text(size = 11, face = "bold"),
    legend.text  = element_text(size = 9),
    plot.title   = element_text(face = "bold"),
    plot.caption = element_text(hjust = 0, size = 9, margin = margin(t = 6))
  )

Part 2: Land-Use Data

# Color Palette from PCPC
landuse_pal <- c(
  "Residential Low"                     = "#ffffcc", # 255,255,204
  "Residential Medium"                  = "#f5ff12", # 245,255,18
  "Residential High"                    = "#ffaa00", # 255,170,0
  "Commercial Consumer"                 = "#e60000", # 230,0,0
  "Commercial Business/Professional"    = "#a80000", # 168,0,0
  "Mixed Commercial/Residential"        = "#e89579", # 232,149,121
  "Industrial"                          = "#8c00ff", # 140,0,255
  "Civic/Institution"                   = "#0070ff", # 0,112,255
  "Transportation"                      = "#cccccc", # 204,204,204
  "Culture/Recreation"                  = "#00ffe6", # 0,255,230
  "Active Recreation"                   = "#9bccb3", # 155,204,179  
  "Park/Open Space"                     = "#63cc00", # 99,204,0
  "Cemetery"                            = "#6b7e93", # 107,126,147  
  "Water"                               = "#bfe8ff", # 191,232,255
  "Vacant"                              = "#a87000", # 168,112,0
  "Other/Unknown"                       = "#d2d2d2"  # 210,210,210 
)

2023 Sharswood Land Use

# Remove the GeoJSON object-size limit
Sys.setenv(OGR_GEOJSON_MAX_OBJ_SIZE = "0")
curr_land_use <- st_read("data/Land_Use.geojson") |> 
  st_make_valid()

# Spatial Filter for Sharswood
sharswood_curr_land_use <- curr_land_use |> 
  st_transform(st_crs(sharswood_bg_sf)) |> 
  st_crop(sharswood_bg_sf) |> 
  st_filter(sharswood_bg_sf, .predicate = st_within) |> 
  select(objectid, c_dig2, year, Shape__Area, Shape__Length)

# Rename Column Headings
sharswood_curr_land_use_clean <- sharswood_curr_land_use |> 
  filter(c_dig2 > 0) |> 
  mutate(
    land_use = case_when(
      c_dig2 == 11 ~ "Residential Low",
      c_dig2 == 12 ~ "Residential Medium",
      c_dig2 == 13 ~ "Residential High",
      c_dig2 == 21 ~ "Commercial Consumer",
      c_dig2 == 22 ~ "Commercial Business/Professional",
      c_dig2 == 23 ~ "Mixed Commercial/Residential",
      c_dig2 == 31 ~ "Industrial",
      c_dig2 == 41 ~ "Civic/Institution",
      c_dig2 == 51 ~ "Transportation",
      c_dig2 == 61 ~ "Culture/Recreation",
      c_dig2 == 62 ~ "Active Recreation",
      c_dig2 == 71 ~ "Park/Open Space",
      c_dig2 == 72 ~ "Cemetery",
      c_dig2 == 81 ~ "Water",
      c_dig2 == 91 ~ "Vacant",
      c_dig2 == 92 ~ "Other/Unknown"
      )
    )
# Create New Geojson File for 2023 Land Use
st_write(
  sharswood_curr_land_use_clean, "data/sharswood_land_use_clean.geojson",
  driver = "GeoJSON",
  delete_dsn = TRUE,
  layer_options = c("RFC7946=YES","COORDINATE_PRECISION=6","WRITE_BBOX=NO")
)
# Map 2023 Sharswood Land-Use
sharswood_2023_land_use <- st_read("data/sharswood_land_use_clean.geojson")

ggplot(sharswood_2023_land_use) +
  geom_sf(aes(fill = land_use), color = NA) +
  scale_fill_manual(values = landuse_pal, name = "Land Use") +
  labs(title = "2023 Land Use in Sharswood",
       subtitle = "Data Source: Philadelphia Dept of Planning and Devt",
       caption = "Residential High/Low/Medium refers to Density") +
  theme_void() +
  theme(
    legend.position = "right",
    legend.title = element_text(size = 11, face = "bold"),
    legend.text  = element_text(size = 9),
    plot.title   = element_text(face = "bold"),
    plot.caption = element_text(hjust = 0, size = 9, margin = margin(t = 6))
  )

2013 Sharswood Land Use

land_use_2012_18 <- st_read("data/Land_Use_2012_2018.geojson",
                            wkt_filter = filt_wkt)

# Spatial Filter for Sharswood
sharswood_historical_land_use <- land_use_2012_18 |> 
  st_make_valid() |> 
  st_transform(st_crs(sharswood_bg_sf)) |> 
  st_crop(sharswood_bg_sf) |> 
  st_filter(sharswood_bg_sf, .predicate = st_within)

# Rename Column Headings
sharswood_historical_land_use_clean <- sharswood_historical_land_use |> 
  filter(C_DIG2 > 0) |> 
  mutate(
    land_use = case_when(
      C_DIG2 == 11 ~ "Residential Low",
      C_DIG2 == 12 ~ "Residential Medium",
      C_DIG2 == 13 ~ "Residential High",
      C_DIG2 == 21 ~ "Commercial Consumer",
      C_DIG2 == 22 ~ "Commercial Business/Professional",
      C_DIG2 == 23 ~ "Mixed Commercial/Residential",
      C_DIG2 == 31 ~ "Industrial",
      C_DIG2 == 41 ~ "Civic/Institution",
      C_DIG2 == 51 ~ "Transportation",
      C_DIG2 == 61 ~ "Culture/Recreation",
      C_DIG2 == 62 ~ "Active Recreation",
      C_DIG2 == 71 ~ "Park/Open Space",
      C_DIG2 == 72 ~ "Cemetery",
      C_DIG2 == 81 ~ "Water",
      C_DIG2 == 91 ~ "Vacant",
      C_DIG2 == 92 ~ "Other/Unknown"
      )
    )
# Create New Geojson File for 2013 Land Use
st_write(
  sharswood_historical_land_use_clean, "data/sharswood_2013_land_use.geojson",
  driver = "GeoJSON",
  delete_dsn = TRUE,
  layer_options = c("RFC7946=YES","COORDINATE_PRECISION=6","WRITE_BBOX=NO")
)
# Map 2013 Sharswood Land-Use
sharswood_2013_land_use <- st_read("data/sharswood_2013_land_use.geojson")

ggplot(sharswood_2013_land_use) +
  geom_sf(aes(fill = land_use), color = NA) +
  scale_fill_manual(values = landuse_pal, name = "Land Use") +
  labs(title = "2013 Land Use in Sharswood",
       subtitle = "Data Source: Philadelphia Dept of Planning and Devt",
       caption = "Residential High/Low/Medium refers to Density") +
  theme_void() +
  theme(
    legend.position = "right",
    legend.title = element_text(size = 11, face = "bold"),
    legend.text  = element_text(size = 9),
    plot.title   = element_text(face = "bold"),
    plot.caption = element_text(hjust = 0, size = 9, margin = margin(t = 6))
  )