title: "STAT 3280 Final Project: A Graphical Exploration of UVA Off-Grounds Housing Options" author: "Isabel Xiao" date: "2024-11-24" output: html_document runtime: shiny ---
{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE)
I remember my first year of college, excited but very anxious in my efforts to make friends, keep up with classes, and adjust to life on my own. It was around the end of October where I just started to feel like I was finding my place in college, making friends with not only other first years but also upperclassmen who gave me much needed advice for navigating life at UVA. One specific piece of advice echoed to me by many of my upperclassmen friends was that I needed to get on my search for second year housing as early as possible, or my options would soon run out. I remember visiting their apartments off grounds, and as the school year went on, the housing warnings grew, not to mention the horror stories that were told to me by my RA and upperclassmen friends surrounding terrible landlords, roommate drama, and rat infestations. Around November, I finally settled on a nice, albeit far and expensive apartment on the far side of the Corner neighborhood. In addition to all of the advice I was given, the thing that helped me with my housing search the most was the UVA off grounds housing website, which displayed tables of various listings for apartments and houses around UVA, along with a helpful map that displayed the locations of each. During my search, I remember being frustrated at how expensive some of my top choices were, especially in the areas that I preferred. Once I got through my second year, I had gone through a toxic roommate situation, a desperate search for a sub-leaser, and I had moved 3 times (once for a summer sublet, once for the fall, and once for the spring). At that point, I had much more familiarity with the nature of the UVA off-grounds housing options, like which management companies I had good experiences with, and whether I liked living in a 2 bedroom apartment or a house with 6 other girls. As a result, for this project I thought it would be really interesting to do an exploration of different off-grounds housing options and see if there were trends in pricing, neighborhood, management, and other variables. Having been through the long process of finding housing and moving around different neighborhoods near UVA, I've gathered some assumptions about the housing climate around UVA from my experiences. For example, throughout my search, I've generally assumed that rentals on JPA are cheaper the further you get away from grounds, while rentals on the Corner are going to be expensive no matter where you are. Additionally, I also know that from my experience, apartments managed by MSC are usually more expensive than apartments managed by BMC or CBS. Through this project, I aim to explore if the data collected supports some of these assumptions or not. Additionally, I'm also interested in whether or not the UVA Off-Grounds Housing website is able to provide a comprehensive database of housing options that are 'desirable' for UVA students, like low rent and close distance from school buildings. Does the data contain lots of listings in close neighborhoods such as JPA and the Corner? Or are there listings from all over Charlottesville? Are most apartments managed by companies? Which companies own the most listings? These are some of the questions I aim to explore. I'm excited to see if the data challenges or supports my assumptions, and even makes me think of the reasoning behind the assumptions I have. I used the SimpleScraper web extension to gather data on 114 listings on the UVA off-grounds housing website. My dataset contains the name of each listing, address, number of bedrooms, total price, price per bedroom, maximum residents, square feet, management, housing type, neighborhood, and latitude and longitude. Through all of these plots, I used colors from the plasma color palette, which is designed to be perceptually uniform and improve readability for people with forms of color blindness.
```{r, include=FALSE}
library(tidyverse) library(tidygeocoder) library(ggplot2) library(ggthemes) library(ggalluvial) library(plotly) library(treemapify) library(fmsb) library(viridis) library(bslib) library(shiny) library(RColorBrewer) uvahousing = read.csv("uvahousing_FINAL.csv")
uvahousing <- uvahousing %>% mutate( Ownership = casewhen( ManagementCompany %in% c("MSC", "Real Property Management", "Woodard Properties", "Inv Group LLC", "Arlington Park Townhomes", "Atrium", "Breeden Property Management", "CBS", "Cathcart Property Management", "Dominion Realty Partners", "Godfrey Property Management", "Fogelman Properties", "Frye Properties", "Metzger & Company Inc", "NewCastle Management", "RPM", "Royal Blue Properties", "Shamrock Corporation", "Thalhimer Multifamily", "The New House Company", "The Scion Group", "University Apartments LLC", "University Partners", "Vest Residential") ~ "Company", TRUE ~ "Landlord" ) )
uvahousing2 = uvahousing uvahousing2 <- uvahousing %>% mutate(TotalPrice = gsub("\$|\s", "", TotalPrice)) %>% mutate(TotalPrice = gsub("\,|\s", "", TotalPrice)) %>% separate(NumBedrooms, into = c("BedroomsLow", "BedroomsHigh"), sep = "-", fill = "right", convert = TRUE) %>% separate(NumBathrooms, into = c("BathroomsLow", "BathroomsHigh"), sep = "-", fill = "right", convert = TRUE) %>% separate(TotalPrice, into = c("PriceLow", "PriceHigh"), sep = "-", fill = "right", convert = TRUE) %>% mutate( BedroomsHigh = ifelse(is.na(BedroomsHigh), BedroomsLow, BedroomsHigh), BathroomsHigh = ifelse(is.na(BathroomsHigh), BathroomsLow, BathroomsHigh), PriceHigh = ifelse(is.na(PriceHigh), PriceLow, PriceHigh) ) %>% rowwise() %>% do(data.frame( Name = .$Name, Address = .$Address, HousingType = .$HousingType, Neighborhood = .$Neighborhood, DistanceUVA = .$DistanceUVA, SqFeet = .$SqFeet, Ownership = .$Ownership, ManagementCompany = .$ManagementCompany, NumBedrooms = c(.$BedroomsLow, .$BedroomsHigh), NumBathrooms = c(.$BathroomsLow, .$BathroomsHigh), TotalPrice = c(.$PriceLow, .$PriceHigh) )) %>% ungroup() %>% mutate(PricePerBedroom = as.numeric(TotalPrice) / as.numeric(NumBedrooms))
uvahousing = uvahousing %>% distinct(Name, .keepall = TRUE) uvahousing2 <- uva_housing2[!duplicated(uva_housing2), ]
write.csv(uvahousing2, "uvahousing2.csv")
uvahousing2 = read.csv("uvahousing2.csv")
uvahousing2$PricePerBedroom = as.numeric(uvahousing2$PricePerBedroom) uvahousing2$TotalPrice = as.numeric(uvahousing2$TotalPrice)
uvahousing2 = uvahousing2 %>% mutate(ListingID = rownumber())
```
This is a simple map displaying the locations of all of my listings. Each dot is colored by housing type, and the hover over displays information about that listing including number of bedrooms, number of bathrooms, and price per bedroom. I labeled each listing as either a townhouse (T), house (H), apartment in a complex (AC), or apartment in a house (AH), which includes duplexes and any units in a house divided into multiple units. I was able to get the latitude and longitude from each address by using the tidygeocoder package. As we can see, the amount of listings in each housing type seem to be equal amongst the Corner and JPA neighborhoods. The full interactive map is here: https://public.tableau.com/app/profile/isabel.xiao5094/viz/MapofUVAOffGroundsHousingListings/Dashboard1#1
What's interesting is that there are quite a few listings that are very far from UVA if we zoom out enough. When I saw this, my first instinct was to explore how similar these listings were to the ones near UVA in price, which brings us to the plot below.
```{r, warning=FALSE, echo=FALSE}
uvahousingplot3 = uvahousing2 %>% filter(DistanceUVA <= 20)
uvahousingplot3$Neighborhood <- factor( uvahousingplot3$Neighborhood, levels = c("JPA", "Corner", "Lewis Mountain", "Barracks", "Starr Hill", "Venable", "10th St", "Eagles Landing", "Downtown", "North Downtown", "Other/Far") # Replace with your specific order )
scatterplot = plotly(uvahousingplot3, x=~DistanceUVA, y=~PricePerBedroom, color=~Neighborhood, type='scatter', mode='markers', colors ='plasma', frame = ~Neighborhood)
scatterplot %>% layout( title = 'Price per Bedroom vs Distance from UVA', xaxis = list(title = 'Distance from UVA', gridcolor = "gray", zerolinecolor = "gray"), yaxis = list(title = 'Price Per Bedroom', gridcolor = "gray"), plotbgcolor = "black", paperbgcolor = "black", font = list( family = "Arial, sans-serif", size = 12, color = "white" ), showlegend = FALSE, updatemenus = list( list( x = 0, # Adjust x position (0 to 1, left to right) y = 1 ) ) )
```
Before making this plot, I had a theory that the price per bedroom would decrease as the distance from UVA increased. I figured that some management companies would lower the price of listings that were further away as a trade off. However, this graph shows there's not really any relationship between the 2 variables. Although I was surprised at first, it made more sense the more I thought about it, since UVA isn't the only determinant of housing prices around Charlottesville. Speaking from my own experience, it's mostly undergraduate students that choose housing closer to school, while many graduate students, hospital workers, professors, and families whose parents work at UVA tend to pick housing that's further away, whether its to avoid the noise of the parties thrown by undergraduates, or because they don't mind paying higher prices and commuting to work/school. It makes sense that management companies would charge the same amount for further listings as they know these demographics can pay the same amount or even more.
```{r, echo=FALSE} uvahousingplot3 = uva_housing2 %>% filter(Neighborhood != "Other/Far")
scatterplot = plotly(uvahousingplot3, x=~DistanceUVA, y=~PricePerBedroom, color=~Neighborhood, type='scatter', mode='markers', colors ='plasma')
scatterplot %>% layout( title = 'Price per Bedroom vs Distance from UVA', xaxis = list(title = 'Distance from UVA', gridcolor = "gray", zerolinecolor = "gray"), yaxis = list(title = 'Price Per Bedroom', gridcolor = "gray"), plotbgcolor = "black", paperbgcolor = "black", font = list( family = "Arial, sans-serif", size = 12, color = "white" ) ) ```
Even when I remove the listings that are in faraway neighborhoods from UVA, we can see that there is still actually no trend. Rent is expensive everywhere in Charlottesville!
Next, I wanted to see if the UVA off grounds housing website was a good representation of the most desired neighborhoods to live in by undergraduate students. Most undergraduates look for housing on either JPA or the Corner, as the walk from those neighborhoods to UVA is the shortest. Other than that, graduate students often live near the Barracks shopping center, since that's where the law and business school are, while Eagles Landing is also a common choice for graduate students since the condos are often clean and reliable, and the bus routes are accessible.
{r, echo=FALSE} ggplot(uva_housing, aes(x=Neighborhood, fill=Neighborhood)) + geom_bar(stat="count") + labs(x="Neighborhood", y="Count", title="Count of UVA Off-Grounds Housing Neighborhoods") + theme(axis.text.x = element_text(angle = 20, color = "white"), axis.text.y = element_text(color = "white"), axis.title = element_text(color = "white"), plot.title = element_text(hjust = 0.5, color = "white"), panel.grid.major.x = element_line(color="#242323"), panel.grid.major.y = element_line(color="#242323"), panel.grid.minor.x = element_line(color="#242323"), panel.grid.minor.y = element_line(color="#242323"), panel.background = element_rect(fill = "black"), plot.background = element_rect(fill = "black"), legend.background = element_rect(fill = "black"), legend.text = element_text(color = "white"), legend.title = element_text(color = "white"), legend.position = "none") + scale_fill_viridis_d(option="plasma")
From this plot, it makes sense that the Corner and JPA would have the majority of listings. I was initially surprised to see that the Other/Far category also had a significant number of listings. However, it does make some sense, as it's important to note that the UVA Off-Grounds housing site allows anyone to make an account and add listings as long as they're verified, not just official management companies. It's possible that some companies or homeowners looking to rent out a room figured it may be worth a shot to post their listings to potentially attract students, professors, or UVA workers that don't mind a long commmute.
I also thought it would be interesting to see if the neighborhood and the price per bedroom had a relationship. This shows the price distribution for each neighborhood listed in the data. From my own experience, I thought that that prices for listings on the Corner were generally higher than listings on JPA, since the Corner is where all the frats and bars are, and it's closer to all the popular restaurants and shops that students frequent. This graph does actually confirm this idea, as the median price for JPA is lower than that for the Corner. It also makes sense that there would be a wide range of pricings for the Other/Far category, since that encompasses multiple neighborhoods not necessarily near each other. However, I realize can only make sound assumptions about JPA, the Corner, and the Other/Far category, as those are the 3 categories I have a significant amount of observations for. I would not be able to say that the median price for rentals in the 10th St neighborhood is necessarily the highest, because I only have 2 listings in that category. Despite this, this graph still conveys some interesting information about housing prices in the neighborhoods with more listings.
{r, echo=FALSE} ggplot(uva_housing2, aes(x=Price_Per_Bedroom, y=Neighborhood, fill=Neighborhood)) + geom_boxplot(color="white") + labs(x="Rent Price Per Bedroom", y="Neighborhood", title="Rent Price Distribution by Neighborhood") + scale_fill_viridis_d(option="plasma") + theme(axis.text.x = element_text(color = "white"), axis.text.y = element_text(color = "white"), axis.title = element_text(color = "white"), plot.title = element_text(hjust = 0.5, color = "white"), panel.grid.major.x = element_line(color="#242323"), panel.grid.major.y = element_line(color="#242323"), panel.grid.minor.x = element_line(color="#242323"), panel.grid.minor.y = element_line(color="#242323"), panel.background = element_rect(fill = "black"), plot.background = element_rect(fill = "black"), legend.background = element_rect(fill = "black"), legend.text = element_text(color = "white"), legend.title = element_text(color = "white"), legend.position = "none")
Moving on from looking at the neighborhoods of the listings, I was also very interested in the management companies and landlords that own the rentals around UVA. From my own experience, I initially felt that MSC, CBS, and Woodard Properties are the management companies that owned the most property around UVA. I decided to get each unique management company/landlord from the data and sort them based on how many rentals they owned in the data. Then, I took the top 6 and created this bar graph, which shows that the top 6 management companies own a little more than half of the listings in the data. CBS, MSC, and Woodard Properties are all on there, confirming my initial feeling.
```{r, echo=FALSE} uvahousing %>% groupby(Management_Company) %>% summarise(count=n()) %>% arrange(., -count)
uvahousing <- uvahousing %>% mutate( RelevantManagement = casewhen( ManagementCompany %in% c("Real Property Management", "Woodard Properties", "MSC", "Inv Group LLC", "CBS", "Godfrey Property Management") ~ ManagementCompany, TRUE ~ "Other" ) )
uvahousing <- uvahousing %>% mutate( Top6 = casewhen( ManagementCompany %in% c("Real Property Management", "Woodard Properties", "MSC", "Inv Group LLC", "CBS", "Godfrey Property Management") ~ "Top 6", TRUE ~ "Other" ) )
ggplot(uvahousing, aes(y=Top6, fill=RelevantManagement)) + geombar(stat="count") + labs(y="Management", x="Count", title="Count of Top 6 Management Companies") + scalefillviridisd(option="plasma", name="Top 6 Management") + theme(axis.text.x = elementtext(color = "white"), axis.text.y = elementtext(color = "white"), axis.title = elementtext(color = "white"), plot.title = elementtext(hjust = 0.5, color = "white"), panel.grid.major.x = elementline(color="#242323"), panel.grid.major.y = elementline(color="#242323"), panel.grid.minor.x = elementline(color="#242323"), panel.grid.minor.y = elementline(color="#242323"), panel.background = elementrect(fill = "black"), plot.background = elementrect(fill = "black"), legend.background = elementrect(fill = "black"), legend.text = elementtext(color = "white"), legend.title = element_text(color = "white"))
```
Additionally, I wanted to explore the relationship between management and type of housing. I created another column in the data that displays whether the listing is owned by a management company or an individual landlord. From this plot, we can see that the majority of listings are owned by a management company, which own the majority of listings in apartment complexes. Companies also own the majority of listings that are townhouses and apartments in houses, but the ownership of the listings are houses is split almost equally. Individual landlords may be more likely to already have a house that they want to rent a room out in.
```{r, echo=FALSE}
uvahousing2 <- uvahousing2 %>% mutate(HousingAbbreviation = casewhen( HousingType == "Apartment Complex" ~ "AC", HousingType == "House" ~ "H", HousingType == "Townhouse" ~ "T", HousingType == "Apartment in House" ~ "AH", TRUE ~ "Other" # In case there are other values, we use "Other" as a default ))
bright_colors <- c("#F0F921FF", "#FCA636FF", "#E16462FF", "#6A00A8FF") # Bright colors from the plasma palette
ggplot(uvahousing2, aes(axis1 = Ownership, axis2 = HousingType)) + geomalluvium(aes(fill=factor(HousingType)), width = 0, reverse=FALSE, alpha=0.8) + guides(fill='none') + geomstratum(width=1/8, reverse=FALSE, color="#242323") + geomtext(color="black", stat = "stratum", aes(label= afterstat(stratum)), reverse = FALSE, angle=20, size=3) + scalexcontinuous(breaks = 1:3, labels = c("Ownership", "Housing Type", "Number of Bedrooms")) + ggtitle("Alluvial Plot of Housing Types by Ownership") + scalefillmanual(values=brightcolors) + # Use plasma color palette theme( axis.text.x = elementtext(color = "white"), axis.text.y = elementtext(color = "white"), axis.title = elementtext(color = "white"), plot.title = elementtext(hjust = 0.5, color = "white"), panel.grid.major.x = elementline(color = "#242323"), panel.grid.major.y = elementline(color = "#242323"), panel.grid.minor.x = elementline(color = "#242323"), panel.grid.minor.y = elementline(color = "#242323"), panel.background = elementrect(fill = "black"), plot.background = elementrect(fill = "black"), legend.background = elementrect(fill = "black"), legend.text = elementtext(color = "white"), legend.title = element_text(color = "white") )
```
Now that we've explored a bit of the housing type variable, I naturally thought it would be good to look at the price distribution for each housing type as well. I chose to create a violin plot for this variable as opposed to the box plots in the price distribution by neighborhood graph because I had more listings in each housing type category, so I thought it would be more worth it to show the densities of each housing type. From this plot, it looks like the price range for listings that are houses is the highest, while the range for townhouses is the shortest. I think it's also worth noting that all of the categories reach peak density at around $1000 per bedroom; it seems like most listings are going to be around $1000 monthly rent per bedroom, which I personally feel is quite expensive.
```{r, echo=FALSE} uvahousing2 <- uvahousing2 %>% mutate(HousingType = casewhen( HousingType == "AC" ~ "Apartment Complex", HousingType == "T" ~ "Townhouse", HousingType == "AH" ~ "Apartment in House", HousingType == "H" ~ "House", TRUE ~ Housing_Type # Keeps original value if not matched ))
bright_colors <- c("#FCA636FF", "#B12A90FF", "#6A00A8FF", "#0D0887FF")
ggplot(uvahousing2, aes(factor(HousingType), PricePerBedroom, fill=HousingType)) + geomviolin(alpha=0.8) + geomjitter(color="white", width = 0.1, alpha=0.4, size=1.8) + labs(x="Housing Type", y="Price Per Bedroom", title="Rent Price Distribution by Housing Type", fill="Housing Type") + theme(axis.text.x = elementtext(color = "white"), axis.text.y = elementtext(color = "white"), axis.title = elementtext(color = "white"), plot.title = elementtext(hjust = 0.5, color = "white"), panel.grid.major.x = elementline(color = "#242323"), panel.grid.major.y = elementline(color = "#242323"), panel.grid.minor.x = elementline(color = "#242323"), panel.grid.minor.y = elementline(color = "#242323"), panel.background = elementrect(fill = "black"), plot.background = elementrect(fill = "black"), legend.background = elementrect(fill = "black"), legend.text = elementtext(color = "white"), legend.title = elementtext(color = "white"), legend.position = "none") + scalefillmanual(values=bright_colors) ```
Throughout my housing search and my time talking to fellow students who live off-grounds, one question that has been brought up quite frequently is 'How much do you pay for rent?' Many times I've gotten answers that have been nowhere near what I'm able to afford, with some of my classmates paying over $1100 for rent. As a result, I was curious to see if I could explore the characteristics of particularly expensive listings to see if there were similarities among them. This Shiny App allows you to select a minimum price and view the characteristics of listings above that price, such as management company, neighborhood, and housing type. What's interesting is that for listings above $1000, it looks like CBS and MSC are the 2 companies with a significant amount of those listings. Additionally, apartment complexes make up the most amount of listings above $1000, and this rarely changes as you increase the price.
```{r, echo=FALSE} uvahousing2 = uvahousing2 %>% rename(Management = Management_Company)
ui <- fluidPage( #"Graphs", sidebarLayout( sidebarPanel( sliderInput("minPrice", "Minimum Price", min = 500, max=2400, value=1000, step=100), HTML("
Move the slider to display characteristics of listings above this minimum rent price.
") ), mainPanel( plotOutput("companiesPlot"), plotOutput("neighborhoodPlot"), plotOutput("housingTypePlot") ) ) )server <- function(input, output) { output$companiesPlot <- renderPlot({ filtereddata <- uvahousing2 %>% filter(PricePerBedroom >= input$minPrice)
data_summary <- filtered_data %>%
group_by(Management) %>%
summarise(Count = n())
bright_colors <- c("#0D0887FF", "#B12A90FF", "#56106EFF", "#FCA636FF", "#6A00A8FF")
continuous_palette <- colorRampPalette(bright_colors)
unique_management <- unique(data_summary$Management) # Get unique categories
random_colors <- sample(bright_colors, length(unique_management), replace=TRUE) # Sample colors
# Map the random colors to each Management category
color_mapping <- setNames(random_colors, unique_management)
# Treemap
ggplot(data_summary, aes(area = Count, fill = Management, label = Management)) +
geom_treemap() +
geom_treemap_text(colour = "white", place = "centre", grow = TRUE) +
labs(title = "Management of Listings Above Minimum Price") +
theme(axis.text.x = element_text(angle = 20, color = "white"),
axis.text.y = element_text(color = "white"),
axis.title = element_text(color = "white"),
plot.title = element_text(hjust = 0.5, color = "white"),
panel.grid.major.x = element_line(color="#242323"),
panel.grid.major.y = element_line(color="#242323"),
panel.grid.minor.x = element_line(color="#242323"),
panel.grid.minor.y = element_line(color="#242323"),
panel.background = element_rect(fill = "black"),
plot.background = element_rect(fill = "black"),
legend.background = element_rect(fill = "black"),
legend.text = element_text(color = "white"),
legend.title = element_text(color = "white"),
legend.position = "none") +
scale_fill_manual(values=color_mapping) + # Repeating Stata colors
guides(fill = "none")
})
output$neighborhoodPlot <- renderPlot({ filtereddata <- uvahousing2 %>% filter(PricePerBedroom >= input$minPrice)
overall_avg_price <- mean(filtered_data$Price_Per_Bedroom)
ggplot(filtered_data, aes(x = Neighborhood, y = Price_Per_Bedroom, color = Neighborhood)) +
geom_hline(yintercept = overall_avg_price, linetype = "dashed", color = "#F0F921FF", size = 1) + # Horizontal average line
geom_segment(aes(x = Neighborhood,
xend = Neighborhood,
y = overall_avg_price,
yend = Price_Per_Bedroom),
color = "white",
size = 0.8) + # Lollipop stem
geom_point(size = 5) + # Lollipop head
labs(
title = paste("Prices by Neighborhood with Average Price Line of: ", round(overall_avg_price, 2)),
x = "Neighborhood",
y = "Price Per Bedroom"
) +
theme(
axis.text.x = element_text(color = "white"),
axis.text.y = element_text(color = "white"),
axis.title = element_text(color = "white"),
plot.title = element_text(hjust = 0.5, color = "white"),
panel.grid.major.x = element_line(color="#242323"),
panel.grid.major.y = element_line(color="#242323"),
panel.grid.minor.x = element_line(color="#242323"),
panel.grid.minor.y = element_line(color="#242323"),
panel.background = element_rect(fill = "black"),
plot.background = element_rect(fill = "black"),
legend.background = element_rect(fill = "black"),
legend.text = element_text(color = "white"),
legend.title = element_text(color = "white"),
legend.position = "none"
) + scale_color_viridis_d(option="plasma")
})
output$housingTypePlot <- renderPlot({ filtereddata <- uvahousing2 %>% filter(PricePerBedroom >= input$minPrice)
# Bar Graph
ggplot(filtered_data, aes(x=Housing_Type, fill=Housing_Type)) +
geom_bar(stat="count") +
labs(x="Housing Type", y="Count", title="Housing Type of Listings Above Minimum Price") +
theme(axis.text.x = element_text(color = "white"),
axis.text.y = element_text(color = "white"),
axis.title = element_text(color = "white"),
plot.title = element_text(hjust = 0.5, color = "white"),
panel.grid.major.x = element_line(color="#242323"),
panel.grid.major.y = element_line(color="#242323"),
panel.grid.minor.x = element_line(color="#242323"),
panel.grid.minor.y = element_line(color="#242323"),
panel.background = element_rect(fill = "black"),
plot.background = element_rect(fill = "black"),
legend.background = element_rect(fill = "black"),
legend.text = element_text(color = "white"),
legend.title = element_text(color = "white"),
legend.position = "none") +
scale_fill_viridis_d(option="plasma")
})
} shinyApp(ui = ui, server = server) ```
Lastly, this shiny app allows us to filter listings based on bedroom, rent, and other preferences one might have. I've also added another tab that compares up to 5 listings graphically with a lollipop plot. I thought this would be a cool way to get a look at individual listings and compare them as if we were still on our housing search and deciding between choices.
```{r, echo=FALSE} uvahousing2 <- uvahousing2 %>% mutate(ListingID = rownumber())
uvahousing2 <- uvahousing2 %>% mutate(ListingID2 = paste("ListingID: ", row_number()))
ui <- fluidPage(
navsetcardunderline( navpanel("Listing Comparison Table", sidebarLayout( sidebarPanel( numericInput("priceHigh", "High end price/bedroom", 800, min = 0), numericInput("priceLow", "Low end price/bedroom", 400, min = 0), selectInput("neighborhood", "Select Neighborhood", choices = c("All", unique(uvahousing2$Neighborhood)), multiple = FALSE), sliderInput("distanceuva", "Distance from UVA (miles)", min = 0, max = 10, step = 0.1, value = 0.1), sliderInput("numbedrooms", "Number of bedrooms", min = 1, max = 4, value = 2), selectInput("housingtype", "Select Housing Type", choices = c("All", unique(uvahousing2$Housing_Type)), multiple = FALSE) ), mainPanel( tableOutput("ListingSelector") ) )
),
nav_panel("Top 3 Listing Comparison",
sidebarLayout(
sidebarPanel(
numericInput("pricePreferred", "Preferred Price", 800, min = 0),
numericInput("priceRange", "Price Range (preferred price plus/minus)", 200, min = 0),
selectInput("neighborhood", "Select Neighborhood",
choices = c("All", unique(uva_housing2$Neighborhood)),
multiple = FALSE),
sliderInput("distance_uva", "Distance from UVA (miles)", min = 0, max = 10, step = 0.1, value = 1),
sliderInput("num_bedrooms", "Number of bedrooms", min = 1, max = 4, value = 2),
sliderInput("num_bathrooms", "Number of bathrooms", min = 1, max = 4, value = 2),
selectInput("housing_type", "Select Housing Type",
choices = c("All", unique(uva_housing2$Housing_Type)),
multiple = FALSE),
),
mainPanel(
uiOutput("checkboxUI"),
plotOutput("bedroomPlot"),
plotOutput("bathroomPlot"),
plotOutput("distancePlot"),
plotOutput("pricePlot")
)
)
)
) )
server <- function(input, output, session) { output$ListingSelector <- renderTable({ uvahousing2 %>% filter(PricePerBedroom <= input$priceHigh, PricePerBedroom >= input$priceLow, NumBedrooms == input$numbedrooms, (Neighborhood == input$neighborhood | input$neighborhood == "All"), (HousingType == input$housingtype | input$housingtype == "All"), DistanceUVA <= input$distanceuva) })
# Reactive filtered data based on user preferences filtereddata <- reactive({ uvahousing2 %>% filter(PricePerBedroom <= input$pricePreferred + input$priceRange, PricePerBedroom >= input$pricePreferred - input$priceRange, NumBedrooms <= input$numbedrooms, (Neighborhood == input$neighborhood | input$neighborhood == "All"), (HousingType == input$housingtype | input$housingtype == "All"), DistanceUVA <= input$distance_uva) })
# Generate checkboxes for filtered listings output$checkboxUI <- renderUI({ availablelistings <- filtereddata() checkboxGroupInput( "selectedlistings", label = NULL, choices = setNames(availablelistings$ListingID, paste("Name: ", availablelistings$Name, " Price:", availablelistings$PricePerBedroom, "Beds:", availablelistings$NumBedrooms, "Bathrooms:", availablelistings$NumBathrooms, "Dist:", round(availablelistings$Distance_UVA, 2), "mi")), selected = NULL ) })
# Observe the selected listings to enforce a maximum of 5 selections observeEvent(input$selectedlistings, { if (length(input$selectedlistings) > 5) { # Remove the last selected item if more than 5 are selected updateCheckboxGroupInput( session, "selectedlistings", selected = head(input$selectedlistings, 5) ) showNotification("You can only select up to 3 listings.", type = "warning") } })
# Lollipop plot function
plotvariable <- function(data, xvar, preferredvalue, title) { ggplot(data, aesstring(y = "ListingID2", x = xvar)) + geomsegment(aesstring(xend = xvar, yend = "ListingID2"), color = "black") + geomvline(xintercept = preferredvalue, linetype = "dashed", color = "#F0F921FF", linewidth = 1) + geompoint(size = 8, color = "#6A00A8FF") + geomtext(aesstring(label = xvar), vjust = -0.5, color = "white") + geomsegment(aes(x = preferredvalue, xend = !!sym(xvar), y = ListingID2, yend = ListingID2), color = "#FCA636FF", linetype = "dotted", linewidth = 1) + scalexcontinuous(limits = c(preferredvalue - 2, preferredvalue + 2)) + # Center on preferred value theme(axis.text.x = elementtext(color = "white"), axis.text.y = elementtext(color = "white"), axis.title = elementtext(color = "white"), plot.title = elementtext(hjust = 0.5, color = "white"), panel.grid.major.x = elementline(color="#242323"), panel.grid.major.y = elementline(color="#242323"), panel.grid.minor.x = elementline(color="#242323"), panel.grid.minor.y = elementline(color="#242323"), panel.background = elementrect(fill = "black"), plot.background = elementrect(fill = "black"), legend.background = elementrect(fill = "black"), legend.text = elementtext(color = "white"), legend.title = elementtext(color = "white"), legend.position = "none") + labs(title = title, x = title, y = NULL) }
# Lollipop plots output$bedroomPlot <- renderPlot({ selectedlistings <- filtereddata() %>% filter(ListingID %in% input$selectedlistings) plotvariable(selectedlistings, "NumBedrooms", input$numbedrooms, "Number of Bedrooms") })
output$bathroomPlot <- renderPlot({ selectedlistings <- filtereddata() %>% filter(ListingID %in% input$selectedlistings) plotvariable(selectedlistings, "NumBathrooms", input$numbathrooms, "Number of Bathrooms") })
output$distancePlot <- renderPlot({ selectedlistings <- filtereddata() %>% filter(ListingID %in% input$selectedlistings) plotvariable(selectedlistings, "DistanceUVA", input$distanceuva, "Distance from UVA (miles)") })
output$pricePlot <- renderPlot({ selectedlistings <- filtereddata() %>% filter(ListingID %in% input$selectedlistings)
# Set x-axis range based on input price range
price_min <- input$pricePreferred - input$priceRange
price_max <- input$pricePreferred + input$priceRange
# Generate plot
ggplot(selected_listings, aes_string(y = "Listing_ID2", x = "Price_Per_Bedroom")) +
geom_segment(aes_string(xend = "Price_Per_Bedroom", yend = "Listing_ID2"), color = "black") +
geom_vline(xintercept = input$pricePreferred, linetype = "dashed", color = "#F0F921FF", linewidth = 1) + # Red dashed line at preferred price
geom_point(size = 8, color = "#6A00A8FF") +
geom_text(aes_string(label = "Price_Per_Bedroom"), vjust = -0.5, color = "white") +
geom_segment(aes(x = Price_Per_Bedroom, xend = input$pricePreferred, y = Listing_ID2, yend = Listing_ID2),
color = "#FCA636FF", linetype = "dotted", linewidth = 1) +
scale_x_continuous(limits = c(price_min, price_max)) + # X-axis interval from preferred - range to preferred + range
theme(axis.text.x = element_text(color = "white"),
axis.text.y = element_text(color = "white"),
axis.title = element_text(color = "white"),
plot.title = element_text(hjust = 0.5, color = "white"),
panel.grid.major.x = element_line(color="#242323"),
panel.grid.major.y = element_line(color="#242323"),
panel.grid.minor.x = element_line(color="#242323"),
panel.grid.minor.y = element_line(color="#242323"),
panel.background = element_rect(fill = "black"),
plot.background = element_rect(fill = "black"),
legend.background = element_rect(fill = "black"),
legend.text = element_text(color = "white"),
legend.title = element_text(color = "white"),
legend.position = "none") +
labs(title = "Price Per Bedroom", x = "Price Per Bedroom", y = NULL)
}) }
shinyApp(ui = ui, server = server)
```
Overall, I feel that these plots were able to both challenge and support some of my assumptions about the UVA off grounds housing options. What was particularly interesting was that they made me think about how undergraduate students are not the only factor influencing housing, which goes back to the lack of a relationship between distance and pricing. Also, I would say that my initial impression of MSC owning many properties around UVA is pretty supported. Generally, it seems like rent around grounds is quite expensive no matter where you go, but some neighborhoods offer more affordable options than others. Additionally, it seems like the UVA Off Grounds housing site does a decent job at offering listings of varying prices and in 'desirable' neighborhoods by students, even though the amount of listings that are in very far areas might not be ideal for most students. I think gathering more data and updating the data set as more listings come out could give a better representation of the housing environment here. This would be an extension I'd like to make to this project in the future. Additionally, it might be interesting to incorporate this data with listings from other websites, and even cross-reference with neighborhood crime rates and housing prices not just for rentals to see if there is potentially a trend there. I found this data to be quite interesting and I would love to explore further in this way.