Project 2 — NYC Housing Prices

Author

Patricio Romero

Published

October 12, 2026

Introduction

For Project 2, I reviewed the datasets presented in the Week 5 Discussion 5A posts. These posts were used to identify three different datasets that could be prepared and analyzed independently.

This report focuses on the NYC Housing Average Prices dataset. The dataset was selected to examine housing prices in New York City and identify differences across the available geographic areas.

Housing prices are an important economic topic because they affect housing affordability and the cost of living. Analyzing housing price data can help identify patterns and differences in the New York City housing market.

Dataset Selection

The NYC Housing Average Prices dataset was selected as the third of the three datasets required for Project 2.

Discussion 5A was used to identify the dataset. The original CSV file will be preserved, and the tidying, transformation, and analysis will be completed as part of Project 2.

The objective is to organize the housing price information into a tidy structure that makes it easier to compare average prices across the available boroughs and time periods.

Data Source

Dataset: NYC Housing Average Prices
Original file: NYC_Housing_Average_Prices_Wide.csv
File format: CSV
Dataset category: Housing and Real Estate Data

The original CSV file is stored in:

Data /NYC_Housing_Average_Prices_Wide.csv

The original publisher and official dataset webpage will be documented after verifying the source.

Approach

The purpose of this analysis is to compare average housing prices across New York City boroughs and examine differences in housing prices over time.

The original data will be imported from the CSV file. I will use dplyr to select the required variables, standardize column names, convert housing price fields to numeric values, and address missing or inconsistent observations.

I will review the original wide-format dataset to identify the columns containing housing price measurements and the variables identifying boroughs or time periods.

I will then use pivot_longer() from the tidyr package to transform the appropriate columns into a tidy structure.

The transformed dataset will organize the housing price observations into variables that can be used for comparison and analysis. All analysis and visualizations will use the transformed dataset.

Business Questions

This analysis will address two questions:

  1. How do average housing prices vary across the five New York City boroughs?
  2. Which New York City borough has experienced the greatest change in average housing prices over time?

Planned Analysis

The analysis will include:

  • A review of the available housing price data.
  • A comparison of average housing prices across the five New York City boroughs.
  • A calculation of summary statistics for housing prices.
  • An examination of housing price changes over time, if multiple periods are available.
  • An identification of the borough with the greatest housing price change, if supported by the data.
  • Two or three clearly labeled visualizations created with ggplot2.
  • A brief interpretation of the principal findings.

Expected Outcome

The analysis will help identify differences in average housing prices across New York City boroughs.

If the dataset contains multiple time periods, the analysis will also examine how housing prices have changed over time and which borough experienced the greatest change.

The results will demonstrate how transforming a wide-format dataset into a tidy structure makes housing price data easier to organize, compare, and analyze.

Data Loading and Initial Exploration

The first step is to load the NYC Housing Average Prices dataset and review its structure.

This helps identify the available columns, borough names, housing price measurements, time periods, and missing values.

Code
# Load the required packages
library(readr)
library(dplyr)
library(tidyr)
library(ggplot2)

# Import the original dataset
housing <- read_csv(
  "Data /NYC_Housing_Average_Prices_Wide.csv",
  show_col_types = FALSE
)

# Check the number of rows and columns
dim(housing)
[1] 24  7
Code
# Display column names
names(housing)
[1] "Year"          "Month"         "Bronx"         "Brooklyn"     
[5] "Manhattan"     "Queens"        "Staten Island"
Code
# Preview the first 10 rows
head(housing, 10)
# A tibble: 10 × 7
    Year Month Bronx Brooklyn Manhattan Queens `Staten Island`
   <dbl> <chr> <dbl>    <dbl>     <dbl>  <dbl>           <dbl>
 1  2024 Jan     565      948      1108    650             675
 2  2024 Feb     568      952      1112    654             678
 3  2024 Mar     572      958      1118    660             683
 4  2024 Apr     575      965      1122    663             688
 5  2024 May     579      972      1128    668             693
 6  2024 Jun     582      978      1132    671             697
 7  2024 Jul     586      982      1137    675             701
 8  2024 Aug     584      979      1135    672             700
 9  2024 Sep     588      988      1141    678             705
10  2024 Oct     590      992      1144    681             708
Code
# Check column types and dataset structure
glimpse(housing)
Rows: 24
Columns: 7
$ Year            <dbl> 2024, 2024, 2024, 2024, 2024, 2024, 2024, 2024, 2024, …
$ Month           <chr> "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug"…
$ Bronx           <dbl> 565, 568, 572, 575, 579, 582, 586, 584, 588, 590, 587,…
$ Brooklyn        <dbl> 948, 952, 958, 965, 972, 978, 982, 979, 988, 992, 989,…
$ Manhattan       <dbl> 1108, 1112, 1118, 1122, 1128, 1132, 1137, 1135, 1141, …
$ Queens          <dbl> 650, 654, 660, 663, 668, 671, 675, 672, 678, 681, 679,…
$ `Staten Island` <dbl> 675, 678, 683, 688, 693, 697, 701, 700, 705, 708, 706,…

Initial Dataset Summary

The dataset contains housing price information for New York City.

The initial exploration helps identify the available geographic areas, price variables, and time periods.

The next step is to review missing values and prepare the dataset for cleaning and transformation.

Missing Values Analysis

The first step is to check for missing housing price values.

This helps identify whether the dataset contains incomplete records before the data is transformed.

Code
# Check missing values in the five borough columns
missing_summary <- housing %>%
  select(Bronx, Brooklyn, Manhattan, Queens, `Staten Island`) %>%
  summarise(
    across(
      everything(),
      list(
        Missing = ~sum(is.na(.x)),
        Available = ~sum(!is.na(.x))
      )
    )
  ) %>%
  pivot_longer(
    everything(),
    names_to = c("Borough", ".value"),
    names_pattern = "^(.*)_(Missing|Available)$"
  ) %>%
  mutate(
    Missing_Percent = round(
      100 * Missing / (Missing + Available), 1
    )
  )

knitr::kable(
  missing_summary,
  col.names = c(
    "Borough",
    "Missing Values",
    "Available Values",
    "Missing (%)"
  ),
  caption = "Missing Housing Price Values by Borough"
)
Missing Housing Price Values by Borough
Borough Missing Values Available Values Missing (%)
Bronx 0 24 0
Brooklyn 0 24 0
Manhattan 0 24 0
Queens 0 24 0
Staten Island 0 24 0

Missing values are not replaced with zero. Only available housing price observations are used in the analysis.

Data Cleaning and Transformation

The original dataset stores the five boroughs in separate columns.

To make the data easier to analyze, the borough columns are transformed from wide to long format using pivot_longer().

Code
# Create a clean dataset
housing_clean <- housing %>%
  select(
    Year, Month,
    Bronx, Brooklyn, Manhattan,
    Queens, `Staten Island`
  ) %>%
  mutate(
    Year = as.integer(Year),
    Month = factor(
      Month,
      levels = month.abb,
      ordered = TRUE
    )
  )

# Transform wide data into long format
housing_long <- housing_clean %>%
  pivot_longer(
    cols = c(
      Bronx, Brooklyn, Manhattan,
      Queens, `Staten Island`
    ),
    names_to = "Borough",
    values_to = "Average_Price"
  ) %>%
  filter(!is.na(Average_Price)) %>%
  mutate(
    Date = as.Date(
      paste(Year, as.integer(Month), "01", sep = "-")
    )
  )

# Display the transformed data
head(housing_long, 10)
# A tibble: 10 × 5
    Year Month Borough       Average_Price Date      
   <int> <ord> <chr>                 <dbl> <date>    
 1  2024 Jan   Bronx                   565 2024-01-01
 2  2024 Jan   Brooklyn                948 2024-01-01
 3  2024 Jan   Manhattan              1108 2024-01-01
 4  2024 Jan   Queens                  650 2024-01-01
 5  2024 Jan   Staten Island           675 2024-01-01
 6  2024 Feb   Bronx                   568 2024-02-01
 7  2024 Feb   Brooklyn                952 2024-02-01
 8  2024 Feb   Manhattan              1112 2024-02-01
 9  2024 Feb   Queens                  654 2024-02-01
10  2024 Feb   Staten Island           678 2024-02-01
Code
# Summarize the transformation
transformation_summary <- tibble(
  Step = c(
    "Original Rows",
    "Original Columns",
    "Long Format Observations"
  ),
  Count = c(
    nrow(housing),
    ncol(housing),
    nrow(housing_long)
  )
)

knitr::kable(
  transformation_summary,
  col.names = c("Step", "Count"),
  caption = "Housing Data Transformation Summary"
)
Housing Data Transformation Summary
Step Count
Original Rows 24
Original Columns 7
Long Format Observations 120

The transformed dataset contains one row for each borough and month.

This structure makes it easier to compare housing prices across locations and time periods.

Transformation Validation

The transformation is checked to confirm that the available housing price observations were preserved.

Code
# Count available prices before transformation
original_count <- housing_clean %>%
  select(
    Bronx, Brooklyn, Manhattan,
    Queens, `Staten Island`
  ) %>%
  summarise(
    across(everything(), ~sum(!is.na(.x)))
  ) %>%
  unlist(use.names = FALSE) %>%
  sum()

# Count observations after transformation
long_count <- nrow(housing_long)

validation <- tibble(
  Dataset = c(
    "Original Wide Data",
    "Transformed Long Data"
  ),
  Observations = c(original_count, long_count)
)

knitr::kable(
  validation,
  col.names = c("Dataset", "Observations"),
  caption = "Transformation Validation"
)
Transformation Validation
Dataset Observations
Original Wide Data 120
Transformed Long Data 120
Code
# Confirm that both counts match
stopifnot(original_count == long_count)

The validation confirms whether the original and transformed datasets contain the same number of available housing price observations.

Summary Statistics

The following table summarizes average housing prices for each borough.

It includes the number of observations, average price, minimum price, and maximum price.

Code
# Calculate summary statistics by borough
housing_summary <- housing_long %>%
  group_by(Borough) %>%
  summarise(
    Observations = n(),
    Average_Price = mean(Average_Price),
    Minimum_Price = min(Average_Price),
    Maximum_Price = max(Average_Price),
    .groups = "drop"
  ) %>%
  arrange(desc(Average_Price))

knitr::kable(
  housing_summary,
  col.names = c(
    "Borough",
    "Observations",
    "Average Price",
    "Minimum Price",
    "Maximum Price"
  ),
  digits = 2,
  caption = "Housing Price Summary Statistics by Borough"
)
Housing Price Summary Statistics by Borough
Borough Observations Average Price Minimum Price Maximum Price
Manhattan 24 1152.62 1152.62 1152.62
Brooklyn 24 1000.08 1000.08 1000.08
Staten Island 24 714.50 714.50 714.50
Queens 24 686.83 686.83 686.83
Bronx 24 594.62 594.62 594.62

These statistics help identify which boroughs have higher or lower average housing prices during the available period.

Housing Price Analysis

Question 1: How do average housing prices vary across the five New York City boroughs?

The first business question compares average housing prices across the five boroughs.

Code
# Compare average prices across boroughs
borough_prices <- housing_long %>%
  group_by(Borough) %>%
  summarise(
    Average_Price = mean(Average_Price),
    .groups = "drop"
  ) %>%
  arrange(desc(Average_Price))

knitr::kable(
  borough_prices,
  col.names = c(
    "Borough",
    "Average Housing Price"
  ),
  digits = 2,
  caption = "Average Housing Prices by Borough"
)
Average Housing Prices by Borough
Borough Average Housing Price
Manhattan 1152.62
Brooklyn 1000.08
Staten Island 714.50
Queens 686.83
Bronx 594.62
Code
# Visualize average housing prices
ggplot(
  borough_prices,
  aes(
    x = reorder(Borough, Average_Price),
    y = Average_Price
  )
) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(
    title = "Average Housing Prices by NYC Borough",
    x = "Borough",
    y = "Average Housing Price"
  ) +
  theme_minimal()

The table and chart show differences in average housing prices across the five boroughs.

Question 2: Which New York City borough has experienced the greatest change in average housing prices over time?

The second business question examines how housing prices changed between the first and last available months.

The percentage change is calculated using the first and last recorded prices for each borough.

Code
# Calculate the change in housing prices
price_change <- housing_long %>%
  arrange(Borough, Date) %>%
  group_by(Borough) %>%
  summarise(
    First_Date = first(Date),
    Last_Date = last(Date),
    First_Price = first(Average_Price),
    Last_Price = last(Average_Price),
    Price_Change = Last_Price - First_Price,
    Percent_Change = if (
      first(Average_Price) != 0
    ) {
      100 * (
        last(Average_Price) - first(Average_Price)
      ) / first(Average_Price)
    } else {
      NA_real_
    },
    .groups = "drop"
  ) %>%
  arrange(desc(Percent_Change))

knitr::kable(
  price_change,
  col.names = c(
    "Borough",
    "First Date",
    "Last Date",
    "First Price",
    "Last Price",
    "Price Change",
    "Change (%)"
  ),
  digits = 2,
  caption = "Housing Price Changes by Borough"
)
Housing Price Changes by Borough
Borough First Date Last Date First Price Last Price Price Change Change (%)
Staten Island 2024-01-01 2025-12-01 675 750 75 11.11
Queens 2024-01-01 2025-12-01 650 718 68 10.46
Brooklyn 2024-01-01 2025-12-01 948 1045 97 10.23
Bronx 2024-01-01 2025-12-01 565 620 55 9.73
Manhattan 2024-01-01 2025-12-01 1108 1192 84 7.58
Code
# Visualize housing price trends
ggplot(
  housing_long,
  aes(
    x = Date,
    y = Average_Price,
    color = Borough
  )
) +
  geom_line(linewidth = 1) +
  geom_point(size = 1.5) +
  labs(
    title = "Monthly Housing Price Trends by Borough",
    x = "Month",
    y = "Average Housing Price",
    color = "Borough"
  ) +
  theme_minimal()

The chart shows how average housing prices changed over time in each borough.

The percentage change table helps identify which borough experienced the greatest increase or decrease during the available period.

Key Findings

The analysis provides two main findings based on the business questions.

Question 1: Housing Prices by Borough

The borough with the highest average housing price is Manhattan, with an average value of 1152.62.

Question 2: Housing Price Changes Over Time

The borough with the greatest percentage increase is Staten Island, with a change of 11.11% between the first and last available months.

These findings are based on the recorded housing prices in the dataset.

Conclusion

The NYC Housing Average Prices dataset provides information about housing prices across the five New York City boroughs.

The analysis compares average housing prices by borough and examines changes over time.

Transforming the dataset from wide to long format made it easier to calculate summary statistics and create visualizations.

The results help identify differences in housing prices and show which borough experienced the greatest percentage increase during the available period.

Video Presentation

This video presents my NYC Housing Analysis for DATA 607 Project 2. It explains the data cleaning and transformation process, the analysis of NYC housing data, and the main findings and conclusions.

YouTube Presentation:
https://youtu.be/ZHzGUdhl1k4

AI Use

ChatGPT was used to help interpret the assignment requirements, organize the planned approach, improve the English writing, and provide guidance for the R code. I will run, review, and verify the data transformations, analysis, visualizations, and final conclusions.