Predicting Property Value: An Analysis of Washington, DC Residential Sales

Haleluya Tesfamariam, 10/12/2026

Dataset Source: opendata.dc.gov/cama

Project Overview

Using the Computer Assisted Mass Appraisal (CAMA) Residential dataset from Open Data DC, this project aims to analyze how physical property characteristics (gross building area, bathroom count) influence home valuations (sale price), and to evaluate how these valuation trends vary across different property quality grades in Washington, DC. The methodology involves cleaning the raw dataset to remove unrepresentative $0 transactions, subsetting the data to 4 core variables (PRICE, GBA, BATHRM, and GRADE_D), fitting a multiple linear regression model (PRICE ~ GBA + BATHRM) using standard R functions, and visualizing sales distributions using a customized ggplot2 scatterplot formatted with non-default colors, modified themes, and appropriate labels. Ultimately, the project uses linear regression outputs (p-values, adjusted R-squared, and diagnostic plots) and empirical data visualizations to assess structural price disparities and demonstrate how data-driven modeling can uncover market drivers within urban real estate.

*Please note Grade_D represents “Grade Description”.

Loading Necessary Libraries

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.3     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dplyr)
library(ggplot2)
library(readr)

Importing the DC CAMA csv File using readr

raw_housing_data <- readr::read_csv("C:/Users/Selomie/Downloads/RESIDENTIAL_(CAMA).csv")
Rows: 109343 Columns: 39
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (13): SSL, HEAT_D, AC, SALEDATE, QUALIFIED, STYLE_D, STRUCT_D, GRADE_D, ...
dbl (26): BATHRM, HF_BATHRM, HEAT, NUM_UNITS, ROOMS, BEDRM, AYB, YR_RMDL, EY...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(raw_housing_data)
# A tibble: 6 × 39
  SSL    BATHRM HF_BATHRM  HEAT HEAT_D AC    NUM_UNITS ROOMS BEDRM   AYB YR_RMDL
  <chr>   <dbl>     <dbl> <dbl> <chr>  <chr>     <dbl> <dbl> <dbl> <dbl>   <dbl>
1 0093 …      3         1     1 Force… Y             2    10     3  1978    2015
2 0093 …      3         1     7 Warm … Y             2    10     3  1978    2022
3 0093 …     NA        NA    NA <NA>   <NA>         NA    NA    NA  2021      NA
4 0093 …     NA        NA    NA <NA>   <NA>         NA    NA    NA  2021      NA
5 0094 …      2         0    13 Hot W… Y             2     8     3  1926      NA
6 0096 …      4         1     1 Force… Y             2    11     6  1890    1993
# ℹ 28 more variables: EYB <dbl>, STORIES <dbl>, SALEDATE <chr>, PRICE <dbl>,
#   QUALIFIED <chr>, SALE_NUM <dbl>, GBA <dbl>, BLDG_NUM <dbl>, STYLE <dbl>,
#   STYLE_D <chr>, STRUCT <dbl>, STRUCT_D <chr>, GRADE <dbl>, GRADE_D <chr>,
#   CNDTN <dbl>, CNDTN_D <chr>, EXTWALL <dbl>, EXTWALL_D <chr>, ROOF <dbl>,
#   ROOF_D <chr>, INTWALL <dbl>, INTWALL_D <chr>, KITCHENS <dbl>,
#   FIREPLACES <dbl>, USECODE <dbl>, LANDAREA <dbl>, GIS_LAST_MOD_DTTM <chr>,
#   OBJECTID <dbl>

Cleaning Raw Data

Below I will (1) filter out properties with a PRICE of $0 or missing values, and (2) select only the core variables needed for analysis PRICE, GBA, BATHRM, GRADE_D. Using “!is.na” to identify valid values for the variables above.

cleaned_housing_data <- raw_housing_data %>%
  filter(PRICE > 0, !is.na(PRICE), !is.na(GBA), !is.na(BATHRM)) %>%
  select(PRICE, GBA, BATHRM, GRADE_D)
head(cleaned_housing_data)
# A tibble: 6 × 4
    PRICE   GBA BATHRM GRADE_D     
    <dbl> <dbl>  <dbl> <chr>       
1 1625000  1344      3 Good Quality
2 1600000  2016      3 Very Good   
3  800000  1728      2 Good Quality
4  875000  2852      4 Good Quality
5 2700000  4050      4 Excellent   
6 1749000  2508      3 Good Quality

Multiple Linear Regression Analysis

Running a multiple linear regression predicting PRICE using GBA (square footage) and BATHRM. Mathematically we have two independent variables acting on PRICE. (m1 x GBA) is the multiplier for square footage. It tells us how much the price increases for every 1 additional sqft, holding bathroom count constant. (m2 x BATHRM) tells us how much the price increases for every additional bathroom, holding square footage constant. Together the model is able to explain variation in price as its related to both square footage and bathroom count.

regression_model <- lm(PRICE ~ GBA + BATHRM, data = cleaned_housing_data)
summary(regression_model)

Call:
lm(formula = PRICE ~ GBA + BATHRM, data = cleaned_housing_data)

Residuals:
     Min       1Q   Median       3Q      Max 
-3955719  -270429   -21748   228832 23345381 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept) -3.587e+05  6.685e+03  -53.66   <2e-16 ***
GBA          4.890e+02  4.366e+00  112.00   <2e-16 ***
BATHRM       1.237e+05  3.354e+03   36.88   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 681700 on 63202 degrees of freedom
Multiple R-squared:  0.3748,    Adjusted R-squared:  0.3748 
F-statistic: 1.894e+04 on 2 and 63202 DF,  p-value: < 2.2e-16

Summary Statistics

According to the model’s output, every additional square foot of gross building area (GBA) adds about $489.04 to the market value of a DC home. This is highly consistent with Realtor.com’s published data of citywide $/sqft of $479 (Realtor.com Data). Additionally, the model predicts that each additional bathroom adds about $123,690.33 to the final home price, this significant premium likely reflects bathrooms’ correlation with overall property luxury in urban real estate rathar the raw price of a bathroom on it’s own.

The model produced an adjusted R^2 of 0.3748 (37.48%) which shows that roughly 37.5% of the variation in DC home price sales can be attributed to square footage and bathroom count.

Both GBA and BATHRM have p-values of 0.000…2 (2.2e-16), since both are well below the 0.05 threshold, we can confidently say that both square footage and bathroom count are statistically significant predictors of a home’s sale price in Washington, DC.

par(mfrow = c(2, 2))
plot(regression_model)

par(mfrow = c(1,1))

Filtering Grade Data

Below, I filter the housing dataset to remove categories with missing values (“No Data”), redundant or overly fragmented tiers (the four “Exceptional” levels), and categories with insufficient observations (“Low Quality”). This retains the core property grade classifications (GRADE_D), such as Average, Above Average, Good, and Excellent, allowing us to cleanly evaluate how square footage (GBA) and bathroom count (BATHRM) influence home valuations across distinct structural quality tiers in Washington, DC.

filtered_grades_data <- cleaned_housing_data %>%
  filter(
    !is.na(GRADE_D),
    !GRADE_D %in% c("No Data", "Low Quality", "Exceptional-A", "Exceptional-B", "Exceptional-C", "Exceptional-D","Above Average","Very Good", "Fair Quality")
  )

Plotting Each Grade Side-by-Side

Below, I generate a faceted scatterplot using ggplot2 to visualize the relationship between square footage, sale price, and property grade side by side.

facet_graphs <- ggplot(filtered_grades_data, aes(x = GBA, y = PRICE, color = GRADE_D)) +
  geom_point(alpha = 0.4, size = 1.5) +
  facet_wrap(~ GRADE_D) +
  scale_color_brewer(palette = "Spectral") +
  theme_minimal() +
  theme(legend.position = "none") + 
  labs(
    title = "DC Housing Prices by Size Across Property Grades",
    subtitle = "Analyzing valuation trends faceted by structural quality",
    x = "Gross Building Area (Sqft)",
    y = "Sale Price ($)",
    caption = "Source: Open Data DC (CAMA Residential)"
  )

Assessing the Impact of Square Footage on Home Price by Home Grade

Using a trend line (using the geom_smooth() function) we can identify which grades of homes’ final sales prices are impacted most by square footage. We can see visually that square footage has an increasing impact on final sale price as the quality of the home overall improves. Homes graded as “Average” show a modest increase in final price for every square foot added, while sales price of homes graded “Excellent” and “Superior” are more significantly impacted by square footage.

facet_graphs +
  geom_smooth(method = "lm", se = FALSE, color = "black", linewidth = 0.8)
`geom_smooth()` using formula = 'y ~ x'

Technical Challenges and Implications

  • Collinearity: Larger homes naturally tend to have more bathrooms, meaning GBA and BATHRM share a strong relationship. This can make it difficult for the regression model to isolate the distinct individual effect of each variable on price.
  • Incomplete Data: Removing missing values and $0 sales filters out unrecorded or off-market transfers, which may introduce selection bias and omit valuable property records.

  • Non-Geographic Data: The model omits spatial features such as neighborhood, quadrant, or proximity to transit, missing the crucial impact that location plays in urban real estate pricing.

  • Lack of Data in Many of the Grades: Tiers like “Low Quality” or specific “Exceptional” categories had too few observations to analyze reliably, requiring their exclusion and limiting the findings to a narrower subset of grades.

Conclusion

To conclude this study, I analyzed Washington, DC residential real estate to determine what drives home sale prices. By constructing a multiple linear regression model, I found that a home’s square footage and bathroom count explain roughly 37.5 percent of its final price. On average, each additional square foot adds about $489, and each extra bathroom contributes approximately $123,690. Both characteristics proved to be statistically significant, reliable predictors for citywide valuations.

However, a standard baseline model assumes every property behaves uniformly, overlooking how a home’s assigned grade alters valuation dynamics. By separating properties into grade tiers using faceted visualizations and trend lines, I discovered that square footage does not impact all homes equally. For standard or average properties, expanding building area yields a modest price increase. In contrast, for luxury and superior homes, the financial return per square foot is significantly higher.

Ultimately, I conclude that while size and basic amenities establish the baseline market value in Washington, DC, a property’s overall grade acts as a powerful economic amplifier. Quality fundamentally shifts the marginal value of physical space, demonstrating that buyers place a much higher monetary premium on square footage when the property tier is elevated.