Middlebury’s Pathways

Quantitative Analysis of Campus
Path Networks Using R



Introduction

Project Overview

This project analyzes the outdoor walking infrastructure at Middlebury College’s campus. Nodes represent physical facilities on the Middlebury College campus, and links represent walking infrastructure connecting them.

Through network visualizations and quantitative analysis, this project explores the following questions:

1. What is the distribution of types of walking infrastructure on campus? Are certain types of walking infrastructure clustered anywhere?
2. Which paths are most important on campus?
3. Which dormitories or other housing buildings are closest and furthest from dining halls?

Results from this analysis highlight the paths on campus that should be prioritized for maintenance, repairs, snow-clearing, etc., and identify areas where additional paths may make navigating campus more efficient. Results also show which housing options may be most desirable based on proximity to dining halls and other facilities.

Code for all visualizations and analysis is available throughout this report, and can be viewed by clicking the Show buttons on the upper right corners of any output. You can click the Show button here to view setup code including libraries, importing of network data, etc.

# Import required libraries
library(tidyverse)
library(dplyr)
library(igraph)
library(statnet)
library(ggplot2)
library(knitr)
library(shiny)

# Set output margins
par(mar = c(0, 0, 0, 0))

# Load CSV for edgelist and edge attributes
MiddleburyPaths_Master <- read.csv("MiddPathsEdgelist.csv", header = T, check.names = F)

# Cleanup the imported CSV by removing duplicate links between nodes
MiddleburyPaths_Master$Node_A <- as.character(MiddleburyPaths_Master$Node_A)
MiddleburyPaths_Master$Node_B <- as.character(MiddleburyPaths_Master$Node_B)

MiddleburyPaths_Master <- MiddleburyPaths_Master %>%
  mutate(pair = pmin(Node_A, Node_B, na.rm = TRUE)) %>% 
  mutate(pair = paste0(pmin(Node_A, Node_B), "-", pmax(Node_A, Node_B))) %>%
  distinct(pair, .keep_all = TRUE) %>%
  select(-pair)

# Create graph object for the path network using data from edgelist CSV
MiddleburyPathNetwork_Master <- graph_from_data_frame(as.matrix(MiddleburyPaths_Master[, c("Node_A", "Node_B")]), directed = F)

# Add edge attribute "Class" to graph object using data from edgelist CSV
E(MiddleburyPathNetwork_Master)$Class <- MiddleburyPaths_Master$Class

# Load CSV for node attributes
MiddleburyBuildingsAttributes <- read.csv("NodeAttributes.csv", header = T, check.names = F)

# Create matrix object for node locations using coordinate data from node attribute CSV
nodeCoordinates <- as.matrix(MiddleburyBuildingsAttributes[match(V(MiddleburyPathNetwork_Master)$name, MiddleburyBuildingsAttributes$Node_ID), c("Xcoord", "Ycoord")])

Project Data

Data for node locations was identified manually using OpenStreetMap and then exported into a csv file before being loaded into R. Data for links was collected manually using Middlebury College’s interactive campus map which displays all walking paths, sidewalks and private and public roads. The scope of data collected includes only walkable paths and legal, marked street crossings that are part of the contiguous campus. For example, the Marbleworks office and other support buildings throughout town were omitted.

# Create variables for the size and order of the entire network
fullNetwork_size <- gsize(MiddleburyPathNetwork_Master)
fullNetwork_order <- gorder(MiddleburyPathNetwork_Master)

# Create a dataframe of the summary values for size and order of the entire network
fullNetwork_summary <- data.frame(
  Metric = c("Network Size (Total number of edges)", "Network Order (Total number of nodes)"),
  Value = c(fullNetwork_size, fullNetwork_order))

# Output a summary table
kable(fullNetwork_summary, col.names = c("Metric", "Value"), caption = "Full Pathway Network Size and Order")
Full Pathway Network Size and Order
Metric Value
Network Size (Total number of edges) 136
Network Order (Total number of nodes) 81


Nodes have spatial attributes, so their locations when plotted are true to their actual physical locations on a map. All attributes for nodes include: Ycoord - the Y coordinate of the node, Xcoord - the X coordinate of the node, Type - the type of building (e.g., dining hall, dormitory, academic building, etc.). Links also have one attribute: Class - the type of link (e.g., paved path, public sidewalk, protected road, etc.). While all links in this analysis are displayed as straight lines, some do have visible curvature in overhead imagery. However, the straight paths indicated in visualizations are a relatively close approximation to the actual positions and lengths of all paths.

Methodology

text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text

text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text

Findings

Distribution of Walking Infrastructure Types

Walking infrastructure on the Middlebury campus is composed of paved paths, unpaved paths, public sidewalks, protected roads, and street crossings. In this data, links have been classified according to their type. This is important because different types of links are subject to different concerns. For example, paved and unpaved campus paths are maintained by the College, while public sidewalks are maintained by the local government.

# Count the number of links of each class using the original CSV
fullNetwork_LinkClassCounts <- MiddleburyPaths_Master %>%
  group_by(Class) %>%
  summarise(count = n())

# Plot the number of links by class
ggplot(fullNetwork_LinkClassCounts, aes(x = reorder(Class, count), y = count)) +
  geom_bar(stat = "identity", fill = "#003882") +
  geom_text(aes(label = count), 
            hjust = 1.25, vjust = 0.5, 
            size = 8, fontface = "bold", 
            color = "gray") +
  labs(x = "Link Class", y = "Number of Links") + 
  theme_minimal() +
  theme(
    axis.text.y = element_text(size = 10),
    axis.text.x = element_text(angle = 0, hjust = 1, size = 10),
    axis.title.y = element_text(size = 14, margin = margin(r = 20)),
    axis.title.x = element_text(size = 14, margin = margin(t = 20))
  ) + 
  coord_flip()


When

# Create network with only paved path links
edges_to_remove <- E(MiddleburyPathNetwork_Master)[Class != "Paved Path"]
filteredNetwork_PavedPath <- delete_edges(MiddleburyPathNetwork_Master, edges_to_remove)

# Create network with only unpaved path links
edges_to_remove <- E(MiddleburyPathNetwork_Master)[Class != "Unpaved Path"]
filteredNetworkUnpavedPath <- delete_edges(MiddleburyPathNetwork_Master, edges_to_remove)

# Create network with only public sidewalk links
edges_to_remove <- E(MiddleburyPathNetwork_Master)[Class != "Public Sidewalk"]
filteredNetworkPublicSidewalk <- delete_edges(MiddleburyPathNetwork_Master, edges_to_remove)

# Create network with only protected road links
edges_to_remove <- E(MiddleburyPathNetwork_Master)[Class != "Protected Road"]
filteredNetworkProtectedRoad <- delete_edges(MiddleburyPathNetwork_Master, edges_to_remove)

# Create network with only street crossing links
edges_to_remove <- E(MiddleburyPathNetwork_Master)[Class != "Street Crossing"]
filteredNetworkStreetCrossing <- delete_edges(MiddleburyPathNetwork_Master, edges_to_remove)

# Plot the filtered network
plot(filteredNetworkStreetCrossing,
     vertex.size = 3,
     vertex.shape = "square",
     vertex.color = "#003882",
     vertex.frame.color = "white",
     vertex.label.cex = 0.3,
     vertex.label.family = "Helvetica",
     vertex.label.color = "black",
     vertex.label.dist = 0.4,
     vertex.label.degree = 180,
     edge.curved = 0,
     edge.width = 1,
     margin = 0,
     layout = nodeCoordinates)

# Define the UI for interactive network plot
ui <- fluidPage(
  titlePanel("Types of Links"),
  sidebarLayout(
    sidebarPanel(
      selectInput("graph_choice", "Select Class of Links:", 
                  choices = c("Paved Paths" = "filteredNetwork_PavedPath", 
                              "Unpaved Paths" = "filteredNetworkUnpavedPath", 
                              "Public Sidewalks" = "filteredNetworkPublicSidewalk",
                              "Protected Roads" = "filteredNetworkProtectedRoad",
                              "Street Crossings" = "filteredNetworkStreetCrossing"),
                  width = "150px"),
      style = "font-size: 12px; margin-bottom: 20px;"
    ),
    mainPanel(
      plotOutput("graph_plot", height = "800px", width = "800px")
    )
  )
)

# Define the server for interactive network plot
server <- function(input, output) {
  output$graph_plot <- renderPlot({
    graph <- switch(input$graph_choice,
                    filteredNetwork_PavedPath = filteredNetwork_PavedPath,
                    filteredNetworkUnpavedPath = filteredNetworkUnpavedPath,
                    filteredNetworkPublicSidewalk = filteredNetworkPublicSidewalk,
                    filteredNetworkProtectedRoad = filteredNetworkProtectedRoad,
                    filteredNetworkStreetCrossing = filteredNetworkStreetCrossing)
    plot(graph,
     vertex.size = 3,
     vertex.shape = "square",
     vertex.color = "#003882",
     vertex.frame.color = "white",
     vertex.label = NA,
     edge.curved = 0,
     edge.width = 1,
     margin = 0,
     layout = nodeCoordinates)
  })
}

# Output the interactive Shiny App
shinyApp(ui = ui, server = server)
Shiny applications not supported in static R Markdown documents