Analysis of Urban Tree Cover: City of Buffalo 2024
Ecological Benefits Analysis
Author
Jack Mernitz
Published
December 3, 2024
Introduction
Trees are often overlooked as a component of urban spaces. With so much focus on the growth of infrastructure, living space, and work spaces, green places are left little room to grow. Tree leaf area coverage, while possibly annoying for lawn owners in the fall, is directly related to shade capacity and other environmental benefits of trees. Permeable surfaces where trees grow also reduce runoff, trees cool the surrounding air in sufficient density, and the mental and aesthetic benefits of green spaces in urban areas for residents and tourists are notable. Urban forestry bureaus seek to classify and present some of these benefits to the public.
Quantifying and estimating the possible value of these services in relation to tree occurrence seemed like an interesting topic to delve into. Vacant spots and stumps of former trees cannot provide benefits any longer. While an intensive endeavor to replace fully grown trees, my project seeks to enumerate how useful it could be to increase tree cover. Parsing data to assign relative values for comparable trees will assist in more accurate estimation of services. Individual versus collective benefit will also be questioned, but only after calculation of total possible benefits are complete.
Materials and methods
The Dataset
Publicly available data from the City of Buffalo Department of Public Works: Bureau of Forestry exists as a list of tree inventory within the City proper. This includes multiple categories to represent trees’ ecosystem services. Downloading and processing this data is necessary, as there are 133,229 entries in the dataset. The data is organized into 28 columns, and each row is a tree site. It is a daily updated dataset existing since 2018.
Install and Load Packages
Code
install.packages("mapview")install.packages("webshot")install.packages("hrbrthemes")library(tidyverse)library(dplyr)library(ggplot2)library(hrbrthemes)library(mapview)library(webshot)library(sf)library(kableExtra)library(htmlwidgets)library(widgetframe)knitr::opts_chunk$set(widgetframe_widgets_dir ='widgets' ) knitr::opts_chunk$set(cache=TRUE) # cache the results for quick compiling
tree_data %>%slice(1:5) %>%#show only 1:5 rowskable(digits=2,align="c")%>%#make table and round to two digitskable_styling(bootstrap_options =c("striped", "hover", "condensed", "responsive"))
Editing
Botanical.Name
Common.Name
DBH
Total.Yearly.Eco.Benefits....
Stormwater.Benefits....
Stormwater.Gallons.Saved
Greenhouse.CO2.Benefits....
CO2.Avoided..in.lbs..
CO2.Sequestered..in.lbs..
Energy.Benefits....
kWh.Saved
Therms.Saved
Air.Quality.Benefits....
Pollutants.Saved..in.lbs..
Property.Benefits....
Leaf.Surface.Area..in.sq..ft..
Address
Street
Side
Site
Council.District
Park.Name
Latitude
Longitude
Site.ID
Location
location
Buffalo
VACANT
VACANT
0
0
0
0
0
0
0
0
0
0
0
0
0
0
250
Minnesota Av
Front
1
University
0
42.95
-78.82
73961
(42.9466215137, -78.8202132436)
POINT (-78.8202132436 42.9466215137)
Buffalo
VACANT
VACANT
0
0
0
0
0
0
0
0
0
0
0
0
0
0
722
Eggert Rd
Side
1
University
0
42.95
-78.80
75828
(42.9480502373, -78.8037997246799)
POINT (-78.8037997246799 42.9480502373)
Buffalo
VACANT
VACANT
0
0
0
0
0
0
0
0
0
0
0
0
0
0
164
Rounds Av
Front
1
University
0
42.95
-78.81
75879
(42.9474045086, -78.8074540366)
POINT (-78.8074540366 42.9474045086)
Buffalo
VACANT
VACANT
0
0
0
0
0
0
0
0
0
0
0
0
0
0
41
Rounds Av
Front
1
University
0
42.95
-78.81
75922
(42.9473611754, -78.8120223129)
POINT (-78.8120223129 42.9473611754)
Buffalo
VACANT
VACANT
0
0
0
0
0
0
0
0
0
0
0
0
0
0
559
Potomac Av
Front
1
Delaware
0
42.92
-78.88
50106
(42.92413835322631, -78.87850124740001)
POINT (-78.87850124740001 42.92413835322631)
Filter out ‘unsuitable vacant’ and ‘0’ sites.
Code
tree_suitable <- tree_data %>%filter(tree_data[,2] !="unsuitable vacant")tree_filtered <- tree_suitable %>%filter(tree_suitable[,3] !="0")print(paste("The length of tree_filtered is ", nrow(tree_data) -nrow(tree_filtered), "rows smaller."))
[1] "The length of tree_filtered is 566 rows smaller."
Code
rm(tree_data)rm(tree_suitable)
Resulting data split into vacant, stump, and used sites and compared.
treeonly_data %>%# Count occurrences of each tree species col3count(!!sym(names(treeonly_data)[3])) %>%arrange(n) %>%# Sort by counttop_n(15, n) %>%mutate(!!names(treeonly_data)[3] :=factor(!!sym(names(treeonly_data)[3]), levels =unique(!!sym(names(treeonly_data)[3])))) %>%# Factor for ordered axisggplot(aes(x =!!sym(names(treeonly_data)[3]), y = n)) +# Use the count (n) on the y-axisgeom_segment(aes(x =!!sym(names(treeonly_data)[3]), xend =!!sym(names(treeonly_data)[3]), y =0, yend = n), color ="gray") +# Add segments (bars)geom_point(size =3, color ="#654194") +# Add points (dots)coord_flip() +# Flip coordinates to make the plot horizontal (lolipop graph)theme_ipsum() +# Apply hrbrthemes styletheme(panel.grid.minor.y =element_blank(),panel.grid.major.y =element_blank(),legend.position ="none"# Hide the legend ) +xlab("")
Code
top_15_counts <- treeonly_data %>%count(!!sym(names(treeonly_data)[3])) %>%arrange(desc(n)) %>%# Sort by count in descending orderslice_max(n, n =15) #sum of 15 greatesttotal_count <-sum(top_15_counts$n)# Add the proportion columntop_15_counts <- top_15_counts %>%mutate(proportion = n / total_count)portion_total <- total_count/70163
From this, we can also see the top 15 species only make up 57.8% of the total tree count.
Assessment of resource values by species.
Code
top_eco <- treeonly_data %>%group_by(!!sym(names(treeonly_data)[3])) %>%# Group by speciessummarise(total =sum(!!sym(names(treeonly_data)[5]), na.rm =TRUE)) %>%arrange(desc(total)) %>%# Sort by total value in descending orderslice_max(total, n =15) # Keep top 15 categories by total sum# Bar graph for top totals from column 5top_eco %>%ggplot(aes(x =!!sym(names(top_eco)[1]), y = total, fill =as.factor(!!sym(names(top_eco)[1])))) +# Use category for x-axis and fill by categorygeom_bar(stat ="identity") +# Bar graph with stat = "identity" to use the actual totalsgeom_text(aes(label = total), angle =90, hjust = .4, size =5) +scale_y_continuous(limits =c(0, 1300000)) +theme(axis.text.x =element_blank(), # Remove x-axis tick labelsaxis.ticks.x =element_blank() # Remove x-axis ticks ) +labs(fill ="Common Name", title ="Ecological Yearly Benefits of Top 15 Species") +xlab("Trees") +ylab("Benefits in Dollars")
This shows the most ecologically beneficial tree species and their associated value. We’ll also assume people do not want to go to the trouble of removing stumps first, so let’s focus on vacant spots. We know that people will want to pick popular choices, so we’ll use the 15 most popular species to assign trees to vacant sites.
Results
Proportionate species distribution assigned to vacant sites.
Code
# Calculate how many times each species should be assigned to vacant_datavacant_count <-nrow(vacant_data) # matches# Calculate numb of times each species should appear in vacant_datatop_15_counts <- top_15_counts %>%mutate(count_vacant =round(proportion * vacant_count))# Create a vector of species names, repeated according to count_vacantreassigned_data <-rep(top_15_counts$Common.Name, top_15_counts$count_vacant)# Randomly shuffle the species names in vacant_reassigned_dataset.seed(426) # Set the seed to ensure reproducibilityreassigned_data <-sample(reassigned_data)# Adjust the length of vacant_reassigned_data to match vacant_data# Ensure it has exactly the same number of observations as vacant_data (60559)reassigned_data <-sample(reassigned_data, vacant_count, replace =TRUE)# Replace the "Common.Name" column (column 3) in vacant_data with reassigned speciesvacant_data$Common.Name <- reassigned_datavacant_data %>%head(5) %>%kable() %>%kable_styling(bootstrap_options =c("striped", "hover", "condensed", "responsive"))
Editing
Botanical.Name
Common.Name
DBH
Total.Yearly.Eco.Benefits....
Stormwater.Benefits....
Stormwater.Gallons.Saved
Greenhouse.CO2.Benefits....
CO2.Avoided..in.lbs..
CO2.Sequestered..in.lbs..
Energy.Benefits....
kWh.Saved
Therms.Saved
Air.Quality.Benefits....
Pollutants.Saved..in.lbs..
Property.Benefits....
Leaf.Surface.Area..in.sq..ft..
Address
Street
Side
Site
Council.District
Park.Name
Latitude
Longitude
Site.ID
Location
location
Buffalo
VACANT
MAPLE, SILVER
0
0
0
0
0
0
0
0
0
0
0
0
0
0
250
Minnesota Av
Front
1
University
0
42.94662
-78.82021
73961
(42.9466215137, -78.8202132436)
POINT (-78.8202132436 42.9466215137)
Buffalo
VACANT
MAPLE, CRIMSON KING
0
0
0
0
0
0
0
0
0
0
0
0
0
0
722
Eggert Rd
Side
1
University
0
42.94805
-78.80380
75828
(42.9480502373, -78.8037997246799)
POINT (-78.8037997246799 42.9480502373)
Buffalo
VACANT
OAK, SWAMP WHITE
0
0
0
0
0
0
0
0
0
0
0
0
0
0
164
Rounds Av
Front
1
University
0
42.94740
-78.80745
75879
(42.9474045086, -78.8074540366)
POINT (-78.8074540366 42.9474045086)
Buffalo
VACANT
MAPLE, NORWAY
0
0
0
0
0
0
0
0
0
0
0
0
0
0
41
Rounds Av
Front
1
University
0
42.94736
-78.81202
75922
(42.9473611754, -78.8120223129)
POINT (-78.8120223129 42.9473611754)
Buffalo
VACANT
MAPLE, CRIMSON KING
0
0
0
0
0
0
0
0
0
0
0
0
0
0
559
Potomac Av
Front
1
Delaware
0
42.92414
-78.87850
50106
(42.92413835322631, -78.87850124740001)
POINT (-78.87850124740001 42.92413835322631)
We now have reassigned the 15 most populous trees to the vacant_data dataset.
Take totals data and find eco value per tree.
Code
eco_summary <- treeonly_data %>%group_by(!!sym(names(treeonly_data)[3])) %>%# Group by species (Common.Name)summarise(total_benefit =sum(!!sym(names(treeonly_data)[5]), na.rm =TRUE), # Sum of eco benefitcount =n(), # Occurrences of each speciesavg_benefit = total_benefit / count # Calculate the average ecological value ) %>%arrange(desc(count)) %>%# Sort by countslice_max(count, n =15) # Top 15 species based on count#Displayeco_summary
##Project environmental benefits of vacant site data replanted
Code
# Join the eco_summary with vacant_data based on the species (Common.Name)vacant_eco <- vacant_data %>%left_join(eco_summary %>%select(!!sym(names(eco_summary)[1]), avg_benefit), # Select species and avg_benefitby =c("Common.Name"=names(eco_summary)[1])) %>%# Join on species namemutate(!!sym(names(vacant_data)[5]) := avg_benefit) # Replace the 5th column with avg_benefit# Checked data, missing lat/longvacant_eco <- vacant_eco %>%filter(Latitude !=0)# View the updated vacant data with the avg_benefit in the 5th columnvacant_eco %>%head(5) %>%kable() %>%kable_styling(bootstrap_options =c("striped", "hover", "condensed", "responsive"))
Editing
Botanical.Name
Common.Name
DBH
Total.Yearly.Eco.Benefits....
Stormwater.Benefits....
Stormwater.Gallons.Saved
Greenhouse.CO2.Benefits....
CO2.Avoided..in.lbs..
CO2.Sequestered..in.lbs..
Energy.Benefits....
kWh.Saved
Therms.Saved
Air.Quality.Benefits....
Pollutants.Saved..in.lbs..
Property.Benefits....
Leaf.Surface.Area..in.sq..ft..
Address
Street
Side
Site
Council.District
Park.Name
Latitude
Longitude
Site.ID
Location
location
avg_benefit
Buffalo
VACANT
MAPLE, SILVER
0
189.40818
0
0
0
0
0
0
0
0
0
0
0
0
250
Minnesota Av
Front
1
University
0
42.94662
-78.82021
73961
(42.9466215137, -78.8202132436)
POINT (-78.8202132436 42.9466215137)
189.40818
Buffalo
VACANT
MAPLE, CRIMSON KING
0
118.69964
0
0
0
0
0
0
0
0
0
0
0
0
722
Eggert Rd
Side
1
University
0
42.94805
-78.80380
75828
(42.9480502373, -78.8037997246799)
POINT (-78.8037997246799 42.9480502373)
118.69964
Buffalo
VACANT
OAK, SWAMP WHITE
0
73.51569
0
0
0
0
0
0
0
0
0
0
0
0
164
Rounds Av
Front
1
University
0
42.94740
-78.80745
75879
(42.9474045086, -78.8074540366)
POINT (-78.8074540366 42.9474045086)
73.51569
Buffalo
VACANT
MAPLE, NORWAY
0
132.88977
0
0
0
0
0
0
0
0
0
0
0
0
41
Rounds Av
Front
1
University
0
42.94736
-78.81202
75922
(42.9473611754, -78.8120223129)
POINT (-78.8120223129 42.9473611754)
132.88977
Buffalo
VACANT
MAPLE, CRIMSON KING
0
118.69964
0
0
0
0
0
0
0
0
0
0
0
0
559
Potomac Av
Front
1
Delaware
0
42.92414
-78.87850
50106
(42.92413835322631, -78.87850124740001)
POINT (-78.87850124740001 42.92413835322631)
118.69964
Total ecological benefit increase
Code
# Sum the total ecological benefit of vacant_ecototal_ecological_benefit <-sum(vacant_eco$avg_benefit, na.rm =TRUE)total_ecological_benefit <-format(total_ecological_benefit, nsmall =2, big.mark =",", prefix ="$")# Print the total ecological benefitprint(total_ecological_benefit)
[1] "7,573,792.78"
##Let’s map the new trees!
Code
vacant_eco_sf <- vacant_eco %>%st_as_sf(coords =c("Longitude", "Latitude"), crs =4326) # Convert to sf object# Use mapview to visualize#map <- #mapview(vacant_eco_sf, zcol = "avg_benefit", legend = TRUE)#mapshot(map, file = "eco_map.png")
Conclusions
Interesting analyses from count and total ecological benefits alone. Although higher in overall count, crabapple and planetree are not in the highest ecological benefit categories. That section is dominated by maple species. From this analysis, assuming everyone who has a vacant slot plants a tree (distributed from top 15), by the time they reach maturity another 7,573,792.78 of ecological benefits could come from those 60,000 trees every year. However, that is with the assumption that all trees have the same average value, not accounting for trees of different ages or variation within species.
Unfortunately this webpage’s server is not powerful enough to display the 60,500 points of data (arguably a dataset too large to use for this project, in hindsight). The pre-rendered map graphic is from my personal computer, captured via screenshot. Due to the randomizing of tree assignment per vacant site, the projected ecological benefits are slightly altered but remain within the $7.6 million range. It would be interesting to continue this analysis for tree canopy coverage and localized (cooled) temperature data at street level across the city.
References
“Bureau of Forestry | Buffalo, NY.” n.d. Accessed December 2, 2024. https://www.buffalony.gov/358/Bureau-of-Forestry.
“Figure 4.-Urban Forest Species Composition as a Percentage of All…” n.d. ResearchGate. Accessed November 30, 2024. https://www.researchgate.net/figure/Urban-forest-species-composition-as-a-percentage-of-all-trees-Philadelphia-2012_fig3_321777064.
Holtz, Yan. n.d. “Dendrogram Customization with R and Ggraph.” Accessed November 30, 2024. https://www.r-graph-gallery.com/335-custom-ggraph-dendrogram.html.
Ziter, Carly D., Eric J. Pedersen, Christopher J. Kucharik, and Monica G. Turner. 2019. “Scale-Dependent Interactions between Tree Canopy Cover and Impervious Surfaces Reduce Daytime Urban Heat during Summer.” Proceedings of the National Academy of Sciences 116 (15): 7575–80. https://doi.org/10.1073/pnas.1817561116.