Denver Airbnb Analysis: Mapping the Short-Term Rental Landscape

Author

Ohioma Gabriel Akhihiero

1 Introduction

Denver, Colorado has experienced rapid growth in short-term rentals over the past decade, with Airbnb playing a significant role in reshaping the city’s housing and tourism landscape. This report investigates the spatial distribution of Airbnb listings across Denver’s neighborhoods and explores what factors drive listing prices.

Specifically, this analysis seeks to answer the following questions:

  1. Where are Airbnb listings concentrated across Denver’s neighborhoods?
  2. Which neighborhoods command the highest prices?
  3. What listing characteristics (room type, availability, number of reviews) are associated with higher prices?
  4. How do review patterns vary across the city?

Understanding these patterns is valuable for travelers seeking affordable options, policymakers monitoring housing impacts, and hosts looking to price competitively.

2 Packages Required

library(tidyverse)  # Data manipulation and visualization (includes ggplot2, dplyr, tidyr, readr)
library(sf)         # Spatial data handling and visualization using simple features
library(plotly)     # Interactive graphics and maps
library(scales)     # Formatting axis labels (e.g., dollar signs, commas)
library(knitr)      # Table formatting and report generation

3 Data Preparation

3.1 Data Sources

The data used in this analysis comes from Inside Airbnb, a mission-driven project that collects and publishes publicly available Airbnb listing data. The Denver dataset was collected in early 2025 and includes detailed information on active listings, neighborhood boundaries, guest reviews, and calendar availability.

The following files are used:

  • listings.csv.gz: Detailed listing-level data including price, room type, location, and host information.
  • neighborhoods.geojson: A GeoJSON file containing polygon boundaries for Denver’s Airbnb-defined neighborhoods.
  • reviews.csv.gz: A record of guest reviews including listing ID and date.
  • calendar.csv.gz: Daily availability and price data for each listing.

3.2 Importing the Data

# Import listings data
listings <- read_csv("airbnb_data/listings.csv.gz")

# Import neighborhood spatial data
neighborhoods <- st_read("airbnb_data/neighbourhoods.geojson", quiet = TRUE)

# Import reviews data
reviews <- read_csv("airbnb_data/reviews.csv.gz")

3.3 Assessing and Cleaning the Data

# Get a quick look at the listings data
glimpse(listings)
Rows: 4,910
Columns: 79
$ id                                           <dbl> 360, 364, 592, 686, 1940,…
$ listing_url                                  <chr> "https://www.airbnb.com/r…
$ scrape_id                                    <dbl> 2.025093e+13, 2.025093e+1…
$ last_scraped                                 <date> 2025-09-30, 2025-09-30, …
$ source                                       <chr> "city scrape", "city scra…
$ name                                         <chr> "Denver’s Peaceful Oasis …
$ description                                  <chr> "Enjoy the famous Colorad…
$ neighborhood_overview                        <chr> "The cottage is located i…
$ picture_url                                  <chr> "https://a0.muscache.com/…
$ host_id                                      <dbl> 666, 783, 933, 990, 2150,…
$ host_url                                     <chr> "https://www.airbnb.com/u…
$ host_name                                    <chr> "Jennifer", "Jason", "Jil…
$ host_since                                   <date> 2008-07-08, 2008-07-11, …
$ host_location                                <chr> "Denver, CO", "Denver, CO…
$ host_about                                   <chr> "We are artists and tinke…
$ host_response_time                           <chr> "within an hour", "N/A", …
$ host_response_rate                           <chr> "100%", "N/A", "100%", "1…
$ host_acceptance_rate                         <chr> "99%", "N/A", "93%", "80%…
$ host_is_superhost                            <lgl> TRUE, FALSE, TRUE, TRUE, …
$ host_thumbnail_url                           <chr> "https://a0.muscache.com/…
$ host_picture_url                             <chr> "https://a0.muscache.com/…
$ host_neighbourhood                           <chr> "Highland", "Five Points"…
$ host_listings_count                          <dbl> 3, 1, 2, 1, 9, 1, 3, 4, 2…
$ host_total_listings_count                    <dbl> 4, 1, 3, 4, 11, 1, 4, 4, …
$ host_verifications                           <chr> "['email', 'phone']", "['…
$ host_has_profile_pic                         <lgl> TRUE, TRUE, TRUE, TRUE, T…
$ host_identity_verified                       <lgl> FALSE, TRUE, TRUE, TRUE, …
$ neighbourhood                                <chr> "Neighborhood highlights"…
$ neighbourhood_cleansed                       <chr> "Highland", "Five Points"…
$ neighbourhood_group_cleansed                 <lgl> NA, NA, NA, NA, NA, NA, N…
$ latitude                                     <dbl> 39.76641, 39.76672, 39.75…
$ longitude                                    <dbl> -105.0021, -104.9791, -10…
$ property_type                                <chr> "Entire guesthouse", "Ent…
$ room_type                                    <chr> "Entire home/apt", "Entir…
$ accommodates                                 <dbl> 2, 3, 2, 1, 2, 2, 2, 2, 2…
$ bathrooms                                    <dbl> 1.0, 1.5, 1.0, 1.0, 1.0, …
$ bathrooms_text                               <chr> "1 bath", "1.5 baths", "1…
$ bedrooms                                     <dbl> 2, 1, 1, 1, 0, 1, 1, 1, 1…
$ beds                                         <dbl> 2, 1, 1, 2, 1, 1, 1, 1, 1…
$ amenities                                    <chr> "[\"Heating\", \"Fire pit…
$ price                                        <chr> "$139.00", "$213.00", "$4…
$ minimum_nights                               <dbl> 29, 185, 2, 30, 1, 4, 2, …
$ maximum_nights                               <dbl> 364, 365, 365, 120, 29, 7…
$ minimum_minimum_nights                       <dbl> 29, 185, 2, 30, 1, 4, 2, …
$ maximum_minimum_nights                       <dbl> 29, 185, 2, 30, 4, 4, 2, …
$ minimum_maximum_nights                       <dbl> 364, 365, 365, 120, 1125,…
$ maximum_maximum_nights                       <dbl> 364, 365, 365, 120, 1125,…
$ minimum_nights_avg_ntm                       <dbl> 29.0, 185.0, 2.0, 30.0, 2…
$ maximum_nights_avg_ntm                       <dbl> 364.0, 365.0, 365.0, 120.…
$ calendar_updated                             <lgl> NA, NA, NA, NA, NA, NA, N…
$ has_availability                             <lgl> TRUE, TRUE, TRUE, TRUE, T…
$ availability_30                              <dbl> 0, 23, 12, 15, 13, 14, 18…
$ availability_60                              <dbl> 0, 53, 40, 45, 43, 33, 36…
$ availability_90                              <dbl> 0, 83, 56, 75, 73, 53, 66…
$ availability_365                             <dbl> 120, 358, 232, 164, 348, …
$ calendar_last_scraped                        <date> 2025-09-30, 2025-09-30, …
$ number_of_reviews                            <dbl> 186, 87, 201, 252, 244, 1…
$ number_of_reviews_ltm                        <dbl> 3, 0, 25, 2, 44, 15, 57, …
$ number_of_reviews_l30d                       <dbl> 0, 0, 4, 0, 4, 1, 4, 0, 0…
$ availability_eoy                             <dbl> 0, 86, 56, 78, 77, 53, 69…
$ number_of_reviews_ly                         <dbl> 3, 0, 11, 0, 57, 11, 50, …
$ estimated_occupancy_l365d                    <dbl> 174, 0, 175, 120, 255, 12…
$ estimated_revenue_l365d                      <dbl> 24186, 0, 8050, 4800, 221…
$ first_review                                 <date> 2018-08-13, 2009-05-18, …
$ last_review                                  <date> 2025-08-18, 2016-04-26, …
$ review_scores_rating                         <dbl> 4.98, 4.85, 4.89, 4.77, 4…
$ review_scores_accuracy                       <dbl> 4.97, 4.78, 4.80, 4.75, 4…
$ review_scores_cleanliness                    <dbl> 4.95, 4.81, 4.61, 4.80, 4…
$ review_scores_checkin                        <dbl> 4.99, 4.95, 4.95, 4.86, 4…
$ review_scores_communication                  <dbl> 4.99, 4.96, 4.95, 4.91, 4…
$ review_scores_location                       <dbl> 4.98, 4.65, 4.84, 4.87, 4…
$ review_scores_value                          <dbl> 4.90, 4.71, 4.88, 4.82, 4…
$ license                                      <chr> "2017-BFN-0002177", NA, "…
$ instant_bookable                             <lgl> FALSE, FALSE, FALSE, FALS…
$ calculated_host_listings_count               <dbl> 2, 1, 1, 1, 2, 1, 2, 3, 2…
$ calculated_host_listings_count_entire_homes  <dbl> 2, 1, 0, 0, 2, 1, 2, 1, 2…
$ calculated_host_listings_count_private_rooms <dbl> 0, 0, 1, 1, 0, 0, 0, 2, 0…
$ calculated_host_listings_count_shared_rooms  <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ reviews_per_month                            <dbl> 2.14, 0.44, 0.99, 1.21, 2…
# Select only the columns we need for analysis
listings_clean <- listings |>
  select(id, name, neighbourhood_cleansed, room_type, 
         price, number_of_reviews, review_scores_rating,
         availability_365, latitude, longitude) |>
  mutate(price = as.numeric(gsub("[$,]", "", price))) |>
  filter(!is.na(price), price > 0) |>
  filter(!is.na(neighbourhood_cleansed))

# Check how many rows remain after cleaning
nrow(listings_clean)
[1] 4301
# Show a preview of the cleaned data
head(listings_clean, 10)
# A tibble: 10 × 10
       id name          neighbourhood_cleansed room_type price number_of_reviews
    <dbl> <chr>         <chr>                  <chr>     <dbl>             <dbl>
 1    360 Denver’s Pea… Highland               Entire h…   139               186
 2    364 Lodo / RiNo … Five Points            Entire h…   213                87
 3    592 private       North Park Hill        Private …    46               201
 4    686 Alexandra's … North Capitol Hill     Private …    40               252
 5   1940 Baker Studio… Baker                  Entire h…    87               244
 6  31503 Highland Par… West Highland          Entire h…    77               191
 7  39405 LoHi Secret … Highland               Entire h…   140               771
 8  90307 Comfy King B… South Park Hill        Private …    90               303
 9 154999 Midland Loft… CBD                    Entire h…   102                14
10 192430 Treetop View… South Park Hill        Private …    36               133
# ℹ 4 more variables: review_scores_rating <dbl>, availability_365 <dbl>,
#   latitude <dbl>, longitude <dbl>
# Summary statistics for key variables
listings_clean |>
  summarise(
    total_listings = n(),
    median_price = median(price),
    mean_price = round(mean(price), 2),
    min_price = min(price),
    max_price = max(price),
    avg_reviews = round(mean(number_of_reviews), 1),
    avg_availability = round(mean(availability_365), 1)
  ) |>
  knitr::kable(caption = "Summary Statistics for Denver Airbnb Listings")
Summary Statistics for Denver Airbnb Listings
total_listings median_price mean_price min_price max_price avg_reviews avg_availability
4301 123 163.23 8 5000 74.1 231.9

4 Exploratory Data Analysis

4.1 Listing Distribution by Neighborhood

# Count listings per neighborhood and plot top 15
listings_clean |>
  count(neighbourhood_cleansed, sort = TRUE) |>
  slice_head(n = 15) |>
  ggplot(aes(x = reorder(neighbourhood_cleansed, n), y = n)) +
  geom_col(fill = "#FF5A5F") +
  coord_flip() +
  labs(
    title = "Top 15 Denver Neighborhoods by Airbnb Listing Count",
    x = "Neighborhood",
    y = "Number of Listings"
  ) +
  theme_minimal()

Five Points stands out as the most listing-dense neighborhood in Denver by a wide margin, with Highland coming in second. Most of the top neighborhoods are clustered in the north-central part of the city, which makes sense given their proximity to downtown, dining, and entertainment. This suggests that location near the city center is a strong driver of where hosts choose to list their properties.

4.2 Price by Neighborhood

# Median price per neighborhood, top 15 most expensive
listings_clean |>
  group_by(neighbourhood_cleansed) |>
  summarise(median_price = median(price), count = n()) |>
  filter(count >= 10) |>
  arrange(desc(median_price)) |>
  slice_head(n = 15) |>
  ggplot(aes(x = reorder(neighbourhood_cleansed, median_price), y = median_price)) +
  geom_col(fill = "#00A699") +
  coord_flip() +
  scale_y_continuous(labels = dollar_format()) +
  labs(
    title = "Top 15 Most Expensive Denver Neighborhoods (Median Nightly Price)",
    x = "Neighborhood",
    y = "Median Nightly Price"
  ) +
  theme_minimal()

Belcaro and Cherry Creek sit at the top of the price rankings, with median nightly rates above $200. Interestingly, these neighborhoods did not appear in the top 15 for listing count, meaning they are not where most listings are — they are just where the most expensive ones are. This points to two different kinds of Airbnb markets operating within Denver at the same time.

4.3 Spatial Map of Listings by Neighborhood

# Count listings and median price per neighborhood
neighborhood_stats <- listings_clean |>
  group_by(neighbourhood_cleansed) |>
  summarise(
    count = n(),
    median_price = median(price)
  )

# Merge with spatial data
neighborhoods_merged <- neighborhoods |>
  left_join(neighborhood_stats, 
            by = c("neighbourhood" = "neighbourhood_cleansed"))

# Choropleth map of listing counts
ggplot(neighborhoods_merged) +
  geom_sf(aes(fill = count), color = "white", size = 0.3) +
  scale_fill_viridis_c(option = "plasma", name = "Listing Count") +
  labs(
    title = "Airbnb Listing Density Across Denver Neighborhoods",
    caption = "Source: Inside Airbnb, 2025"
  ) +
  theme_void()

The map makes the listing concentration easy to see. The brightest areas are in north-central Denver, confirming what the bar chart showed about Five Points and Highland. As you move toward the outer edges of the city, listings become much more sparse, suggesting that demand drops off significantly outside of the urban core.

4.4 Spatial Map of Median Price by Neighborhood

# Choropleth map of median price
ggplot(neighborhoods_merged) +
  geom_sf(aes(fill = median_price), color = "white", size = 0.3) +
  scale_fill_viridis_c(option = "magma", name = "Median Price",
                       labels = dollar_format()) +
  labs(
    title = "Median Nightly Airbnb Price Across Denver Neighborhoods",
    caption = "Source: Inside Airbnb, 2025"
  ) +
  theme_void()

The price map tells a different story from the listing density map. The most expensive areas are concentrated in the southeast part of the city, particularly around Belcaro and Cherry Creek, while the north-central neighborhoods with the most listings are actually more moderately priced. The two maps together show that popularity and price do not necessarily go hand in hand in Denver’s Airbnb market.

4.5 Price by Room Type

# Boxplot of price by room type (remove extreme outliers for readability)
listings_clean |>
  filter(price <= 500) |>
  ggplot(aes(x = reorder(room_type, price, median), y = price, fill = room_type)) +
  geom_boxplot(show.legend = FALSE) +
  scale_y_continuous(labels = dollar_format()) +
  scale_fill_manual(values = c("#FF5A5F", "#00A699", "#FC642D", "#484848")) +
  coord_flip() +
  labs(
    title = "Nightly Price Distribution by Room Type",
    subtitle = "Listings priced above $500 excluded for readability",
    x = "Room Type",
    y = "Nightly Price"
  ) +
  theme_minimal()

Entire homes and apartments are priced significantly higher than private or shared rooms, which is expected given the difference in space and privacy. Shared rooms are the most affordable option by far, though they represent a small portion of the overall market. The wide range of prices within the entire home category also stands out — it suggests that not all full-property listings are created equal, and neighborhood likely plays a big role in explaining that variation.

4.6 Review Activity Over Time

# Count reviews per month and plot trend
reviews |>
  mutate(date = as.Date(date),
         month = floor_date(date, "month")) |>
  count(month) |>
  filter(month >= as.Date("2020-01-01")) |>
  ggplot(aes(x = month, y = n)) +
  geom_line(color = "#FF5A5F", linewidth = 1) +
  geom_smooth(se = FALSE, color = "#484848", linetype = "dashed") +
  labs(
    title = "Monthly Airbnb Review Activity in Denver (2020-2025)",
    subtitle = "Each review roughly corresponds to a completed stay",
    x = "Month",
    y = "Number of Reviews"
  ) +
  theme_minimal()

Review activity dropped sharply in early 2020, which lines up with the COVID-19 pandemic and the near-complete halt of travel. Since then, activity has recovered strongly and continues to grow each year. The seasonal peaks visible every summer suggest that Denver sees a consistent spike in Airbnb demand during warmer months, likely driven by outdoor tourism and events.

5 Summary

This analysis examined 4,301 Airbnb listings across Denver’s neighborhoods using data from Inside Airbnb (2025).

Key findings:

  • Five Points and Highland are Denver’s most listing-dense neighborhoods, reflecting their popularity as vibrant, centrally located areas.
  • Belcaro and Cherry Creek command the highest median nightly prices, suggesting that wealth and exclusivity drive pricing more than listing volume.
  • Entire home/apt listings are significantly more expensive than private or shared rooms, giving travelers a clear price-vs-privacy tradeoff.
  • Airbnb activity in Denver has grown substantially since recovering from the COVID-19 dip in 2020, with strong seasonal peaks each summer.

Limitations: This analysis uses a single snapshot of data from 2025 and cannot capture long-term trends in individual neighborhoods. Price outliers (up to $5,000/night) may skew neighborhood averages. Future analysis could incorporate census demographic data to explore relationships between neighborhood income levels and Airbnb pricing.