# Section 1: In this study, the goal is to use Multiple Linear Regression to investigate one central question: What environmental and neighborhood characteristics are most strongly associated with home prices, and how can these factors be used to estimate residential property values and inform future policies?

# My primary statistical modeling approach was using a multiple linear regression model with variables that most directly relate to the research question. I focused on factors such as pupil-to-teacher ratios, nitric oxide concentrations, and lower-status population percentages to examine the relationships between neighborhood and environmental characteristics with housing prices. I used the findings from this regression model to support recommendations for future city policies, with air quality and access to education being two areas that warrant further investigation due to their relationships with housing costs. In addition, the expanded model included interactions between various factors, such as nitric oxide concentration influencing the relationship between the average number of rooms and housing values. The main policy takeaway for the city planning board is that initiatives to improve environmental quality and educational resources across Boston neighborhoods could have important implications for neighborhood development and housing affordability.
# Section 2: Exploratory Data Analysis

# install.packages("caret", repos = "https://cloud.r-project.org")
# install.packages("car", repos = "https://cloud.r-project.org")
# install.packages("tidyverse", repos = "https://cloud.r-project.org")
library(tidyverse) # 1. Load required packages 
library(caret)
library(car)
df <- read.csv("HousingData.csv") # Import Housing Data csv 

head(df) # Display the first 6 rows
##      CRIM ZN INDUS CHAS   NOX    RM  AGE    DIS RAD TAX PTRATIO      B LSTAT
## 1 0.00632 18  2.31    0 0.538 6.575 65.2 4.0900   1 296    15.3 396.90  4.98
## 2 0.02731  0  7.07    0 0.469 6.421 78.9 4.9671   2 242    17.8 396.90  9.14
## 3 0.02729  0  7.07    0 0.469 7.185 61.1 4.9671   2 242    17.8 392.83  4.03
## 4 0.03237  0  2.18    0 0.458 6.998 45.8 6.0622   3 222    18.7 394.63  2.94
## 5 0.06905  0  2.18    0 0.458 7.147 54.2 6.0622   3 222    18.7 396.90    NA
## 6 0.02985  0  2.18    0 0.458 6.430 58.7 6.0622   3 222    18.7 394.12  5.21
##   MEDV
## 1 24.0
## 2 21.6
## 3 34.7
## 4 33.4
## 5 36.2
## 6 28.7
summary(df) # Summarize variable distributions 
##       CRIM                ZN             INDUS            CHAS        
##  Min.   : 0.00632   Min.   :  0.00   Min.   : 0.46   Min.   :0.00000  
##  1st Qu.: 0.08190   1st Qu.:  0.00   1st Qu.: 5.19   1st Qu.:0.00000  
##  Median : 0.25372   Median :  0.00   Median : 9.69   Median :0.00000  
##  Mean   : 3.61187   Mean   : 11.21   Mean   :11.08   Mean   :0.06996  
##  3rd Qu.: 3.56026   3rd Qu.: 12.50   3rd Qu.:18.10   3rd Qu.:0.00000  
##  Max.   :88.97620   Max.   :100.00   Max.   :27.74   Max.   :1.00000  
##  NAs    :20         NAs    :20       NAs    :20      NAs    :20       
##       NOX               RM             AGE              DIS        
##  Min.   :0.3850   Min.   :3.561   Min.   :  2.90   Min.   : 1.130  
##  1st Qu.:0.4490   1st Qu.:5.886   1st Qu.: 45.17   1st Qu.: 2.100  
##  Median :0.5380   Median :6.208   Median : 76.80   Median : 3.207  
##  Mean   :0.5547   Mean   :6.285   Mean   : 68.52   Mean   : 3.795  
##  3rd Qu.:0.6240   3rd Qu.:6.623   3rd Qu.: 93.97   3rd Qu.: 5.188  
##  Max.   :0.8710   Max.   :8.780   Max.   :100.00   Max.   :12.127  
##                                   NAs    :20                       
##       RAD              TAX           PTRATIO            B         
##  Min.   : 1.000   Min.   :187.0   Min.   :12.60   Min.   :  0.32  
##  1st Qu.: 4.000   1st Qu.:279.0   1st Qu.:17.40   1st Qu.:375.38  
##  Median : 5.000   Median :330.0   Median :19.05   Median :391.44  
##  Mean   : 9.549   Mean   :408.2   Mean   :18.46   Mean   :356.67  
##  3rd Qu.:24.000   3rd Qu.:666.0   3rd Qu.:20.20   3rd Qu.:396.23  
##  Max.   :24.000   Max.   :711.0   Max.   :22.00   Max.   :396.90  
##                                                                   
##      LSTAT             MEDV      
##  Min.   : 1.730   Min.   : 5.00  
##  1st Qu.: 7.125   1st Qu.:17.02  
##  Median :11.430   Median :21.20  
##  Mean   :12.715   Mean   :22.53  
##  3rd Qu.:16.955   3rd Qu.:25.00  
##  Max.   :37.970   Max.   :50.00  
##  NAs    :20
str(df)
## 'data.frame':    506 obs. of  14 variables:
##  $ CRIM   : num  0.00632 0.02731 0.02729 0.03237 0.06905 ...
##  $ ZN     : num  18 0 0 0 0 0 12.5 12.5 12.5 12.5 ...
##  $ INDUS  : num  2.31 7.07 7.07 2.18 2.18 2.18 7.87 7.87 7.87 7.87 ...
##  $ CHAS   : int  0 0 0 0 0 0 NA 0 0 NA ...
##  $ NOX    : num  0.538 0.469 0.469 0.458 0.458 0.458 0.524 0.524 0.524 0.524 ...
##  $ RM     : num  6.58 6.42 7.18 7 7.15 ...
##  $ AGE    : num  65.2 78.9 61.1 45.8 54.2 58.7 66.6 96.1 100 85.9 ...
##  $ DIS    : num  4.09 4.97 4.97 6.06 6.06 ...
##  $ RAD    : int  1 2 2 3 3 3 5 5 5 5 ...
##  $ TAX    : int  296 242 242 222 222 222 311 311 311 311 ...
##  $ PTRATIO: num  15.3 17.8 17.8 18.7 18.7 18.7 15.2 15.2 15.2 15.2 ...
##  $ B      : num  397 397 393 395 397 ...
##  $ LSTAT  : num  4.98 9.14 4.03 2.94 NA ...
##  $ MEDV   : num  24 21.6 34.7 33.4 36.2 28.7 22.9 27.1 16.5 18.9 ...
names(df) # Display column names 
##  [1] "CRIM"    "ZN"      "INDUS"   "CHAS"    "NOX"     "RM"      "AGE"    
##  [8] "DIS"     "RAD"     "TAX"     "PTRATIO" "B"       "LSTAT"   "MEDV"
# HOUSE AGE vs PROPERTY PRICE 
ggplot(df, aes(x = AGE, y = MEDV)) + # Relationship between house age and property price 
  geom_point() +
  geom_smooth(method = "lm", se = TRUE) +
  labs(
    title = "Property Price vs. House Age",
    x = "% of Homes Built Before 1940",
    y = "Median Home Value ($1,000s)"
  ) +
  theme_minimal()

# NUMBER OF ROOMS vs PROPERTY PRICE 
ggplot(df, aes(x = RM, y = MEDV)) + # Relationship between number of rooms and property price 
  geom_point() +
  geom_smooth(method = "lm", se = TRUE) +
  labs(
    title = "Property Price vs. Number of Rooms",
    x = "Number of Rooms",
    y = "Median Home Value ($1,000s)"
  ) +
  theme_minimal()

# NITROGEN OXIDE CONCENTRATION vs PROPERTY PRICE
ggplot(df, aes(x = NOX, y = MEDV)) + # Environmental metric impact on property price 
  geom_point() +
  geom_smooth(method = "lm", se = TRUE) +
  labs(
    title = "Property Price vs. Nitrogen Oxide Concentration",
    x = "Nitrogen Oxide Concentration",
    y = "Median Home Value ($1,000s)"
  ) +
  theme_minimal()

# NEIGHBORHOOD LOWER STATUS POPULATION vs PROPERTY PRICE
ggplot(df, aes(x = LSTAT, y = MEDV)) + # Neighborhood factors impact on property price 
  geom_point() +
  geom_smooth(method = "lm", se = TRUE) +
  labs(
    title = "Property Price vs. Lower Neighborhood Status",
    x = "Percentage of Lower-Status Neighborhood Population",
    y = "Median Home Value ($1,000s)"
  ) +
  theme_minimal()

# SCHOOL RATING vs PROPERTY PRICE
ggplot(df, aes(x = PTRATIO, y = MEDV)) + # Educational factor impact on property price 
  geom_point() +
  geom_smooth(method = "lm", se = TRUE) +
  labs(
    title = "Property Price vs. Pupil-Teacher Ratio",
    x = "Pupil to Teacher Ratio",
    y = "Median Home Value ($1,000s)"
  ) +
  theme_minimal()

# CRIME RATE vs PROPERTY PRICE
ggplot(df, aes(x = CRIM, y = MEDV)) + # Crime rate impact on property price 
  geom_point() +
  geom_smooth(method = "lm", se = TRUE) +
  labs(
    title = "Property Price vs. Crime Rate",
    x = "Crime Rate",
    y = "Median Home Value ($1,000s)"
  ) +
  theme_minimal()

# Section 3: Model Building & Selection

set.seed(42)
train_index <- caret::createDataPartition(
  y = df$MEDV,
  p = 0.80,
  list = FALSE
)

train <- df[train_index, ]
test <- df[-train_index, ]

nrow(train) # Check that 80-20 split functions correctly (407-99)
## [1] 407
nrow(test)
## [1] 99
# Baseline Model 1 without interaction terms 
model1 <- lm(MEDV ~ RM + AGE + NOX + CRIM + PTRATIO + LSTAT, data = train)

summary(model1)
## 
## Call:
## lm(formula = MEDV ~ RM + AGE + NOX + CRIM + PTRATIO + LSTAT, 
##     data = train)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -13.5060  -3.0007  -0.4043   1.8420  28.8216 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 16.53159    4.71265   3.508  0.00051 ***
## RM           5.00076    0.49464  10.110  < 2e-16 ***
## AGE          0.01849    0.01436   1.288  0.19866    
## NOX         -6.90850    3.30502  -2.090  0.03731 *  
## CRIM        -0.09035    0.03017  -2.994  0.00295 ** 
## PTRATIO     -0.94266    0.12917  -7.298 1.97e-12 ***
## LSTAT       -0.42518    0.06147  -6.917 2.19e-11 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 4.728 on 351 degrees of freedom
##   (49 observations deleted due to missingness)
## Multiple R-squared:  0.7172, Adjusted R-squared:  0.7124 
## F-statistic: 148.3 on 6 and 351 DF,  p-value: < 2.2e-16
# Expanded Model 2 with interaction terms with home size 
model2 <- lm(MEDV ~ RM*PTRATIO + RM*LSTAT + RM*NOX + AGE + NOX + CRIM + PTRATIO + LSTAT, data = train)

summary(model2)
## 
## Call:
## lm(formula = MEDV ~ RM * PTRATIO + RM * LSTAT + RM * NOX + AGE + 
##     NOX + CRIM + PTRATIO + LSTAT, data = train)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -8.8422 -2.1696 -0.4935  1.8040 28.4770 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -148.45859   18.53089  -8.011 1.73e-14 ***
## RM            29.93027    2.85452  10.485  < 2e-16 ***
## PTRATIO        5.13058    0.81645   6.284 9.86e-10 ***
## LSTAT          1.10376    0.23593   4.678 4.15e-06 ***
## NOX           65.71571   17.66443   3.720 0.000232 ***
## AGE            0.02455    0.01141   2.150 0.032205 *  
## CRIM          -0.12452    0.02363  -5.271 2.39e-07 ***
## RM:PTRATIO    -0.90118    0.12696  -7.098 7.14e-12 ***
## RM:LSTAT      -0.28209    0.04092  -6.893 2.57e-11 ***
## RM:NOX       -10.99551    2.91261  -3.775 0.000188 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 3.68 on 348 degrees of freedom
##   (49 observations deleted due to missingness)
## Multiple R-squared:  0.8302, Adjusted R-squared:  0.8258 
## F-statistic:   189 on 9 and 348 DF,  p-value: < 2.2e-16
# Interpretation: The interaction terms between the average number of rooms (RM) and the other predictors indicate whether the relationship between housing value and the number of rooms changes depending on these variables. Low p-values and high absolute t-statistics suggest that the corresponding interaction terms are statistically significant. The negative coefficient estimates for the relevant predictors indicate that, holding other variables constant, increases in these predictors are associated with lower predicted housing values. For the interaction terms specifically, a negative coefficient suggests that the effect of the number of rooms on predicted housing value becomes less positive/more negative as the interacting predictor increases.

# The coefficient for the NOX (Nitric Oxide Concentration) stood out the most as a variable in this multiple linear regression model. With a coefficient estimate of 65.7, this indicates that, according to the model, an increase in NOX is associated with an increase in predicted housing value. As a result, this factor needs further investigation to draw stronger conclusions about its relationship with housing prices and determine whether it has any implications for future policy recommendations.

# Predictions
pred1 <- predict(model1, newdata = test)
pred2 <- predict(model2, newdata = test)

# MAE
mae1 <- mean(abs(test$MEDV - pred1), na.rm = TRUE)
mae2 <- mean(abs(test$MEDV - pred2), na.rm = TRUE)

# RMSE
rmse1 <- sqrt(mean((test$MEDV - pred1)^2, na.rm = TRUE))
rmse2 <- sqrt(mean((test$MEDV - pred2)^2, na.rm = TRUE))

# R-squared
r2_1 <- 1 - sum((test$MEDV - pred1)^2, na.rm = TRUE) /
  sum((test$MEDV - mean(test$MEDV, na.rm = TRUE))^2, na.rm = TRUE)

r2_2 <- 1 - sum((test$MEDV - pred2)^2, na.rm = TRUE) /
  sum((test$MEDV - mean(test$MEDV, na.rm = TRUE))^2, na.rm = TRUE)

# Results table
results <- data.frame(
  Model = c("Baseline", "Interaction"),
  MAE = c(mae1, mae2),
  RMSE = c(rmse1, rmse2),
  R_squared = c(r2_1, r2_2)
)

missing_rows <- df[!complete.cases(df), ] # Troubleshooting: View rows with NA values
View(missing_rows)

print(results)
##         Model      MAE     RMSE R_squared
## 1    Baseline 3.869352 5.856873 0.6797927
## 2 Interaction 3.065204 4.778176 0.7868801
# Section 4: Assumption Testing & Influential Observations 

# Interaction model
plot(model2, which = 1)

plot(model2, which = 2)

# Heteroscedasticity Assessment: There is no substantial evidence of heteroscedasticity in the observations based on the Residuals vs. Fitted plot. The residuals appear to be relatively evenly distributed around zero across the range of fitted housing values with no pronounced curve or funnel shape. This suggests that the model maintains relatively constant residual variance across predicted housing prices.

# Normality Assessment: Based on the Normal Q-Q Residuals plot, there is evidence that the residuals are not normally distributed. The standardized residuals deviate from the diagonal reference line, particularly toward the higher theoretical quantiles. This suggests that the residuals do not follow a normal distribution.

# Cook's Distance
cooks <- cooks.distance(model2)

plot(cooks,
     type = "h",
     main = "Cook's Distance",
     xlab = "Observation",
     ylab = "Cook's Distance")

sort(cooks, decreasing = TRUE)[1:5] # Obtain top 5 Cook Distance observations
##       366       365       371       372       381 
## 0.4015849 0.3917210 0.2132627 0.1136842 0.0836675
train[c(366, 365, 371, 372, 381,367), ] # Observe top 5 highest Cook's distance observations 
##        CRIM ZN INDUS CHAS   NOX    RM  AGE    DIS RAD TAX PTRATIO      B LSTAT
## 453 5.09017  0  18.1    0 0.713 6.297 91.8 2.3682  24 666    20.2 385.09 17.27
## 450 7.52601  0  18.1    0 0.713 6.417 98.3 2.1850  24 666    20.2 304.21 19.31
## 460 6.80117  0  18.1    0 0.713 6.081 84.4 2.7175  24 666    20.2 396.90 14.70
## 463 6.65492  0  18.1    0 0.713 6.317 83.0 2.7344  24 666    20.2 396.90 13.99
## 474 4.64689  0  18.1    0 0.614 6.980 67.6 2.5329  24 666    20.2 374.68    NA
## 455 9.51363  0  18.1    0 0.713 6.728 94.1 2.4961  24 666    20.2   6.68 18.71
##     MEDV
## 453 16.1
## 450 13.0
## 460 20.0
## 463 19.5
## 474 29.8
## 455 14.9
# Influential Observations: Based on the Cook's Distance graph and further inspection of observations with high influence, I believe that these extreme observations should be retained. There are no apparent errors or discrepancies in the data for these observations, suggesting that they represent legitimate variation within the dataset. Removing these observations could eliminate meaningful information and make the dataset less representative. For example, removing data points with high crime rates would diminish the true trends of factors and housing costs across Boston. Therefore, I recommend retaining these observations in the regression models. 
# Section 5: Limitations and Policy Implications 

#1. Based on the model coefficients, the results suggest that factors such as nitric oxide concentration and pupil-to-teacher ratios need further investigation. Both variables yielded positive coefficients in the MLR model, indicating that higher NOX concentrations and PTRATIOs are associated with higher predicted housing values, holding other variables constant. As a result, policies targeting areas of Boston with high nitric oxide concentrations and pupil-to-teacher ratios could have potential implications for housing affordability and neighborhood development. For example, expanding educational resources and hiring additional teachers could help address differences in educational opportunities across neighborhoods. Additionally, investing in green spaces and improving environmental quality could improve neighborhood conditions. The MLR Model suggests that these factors are worth possibly investigating further and could be imrpotant to consider by the city planning board for future policies. 

#2. The limitations of this model include the fact that the dataset contains a limited number of Boston-area housing observations that may be outdated. With a broader goal of understanding how factors such as urban infrastructure and environmental characteristics affect home prices, the data may be sensitive to Boston-specific factors that are not accounted for by the model and may not represent overall housing market trends. In addition, there are a limited number of explanatory variables included in the linear regression models that could potentially impact model performance. For example, the goal is to investigate how square footage interacts with environmental factors, but the variable used in the model is the average number of rooms per dwelling since there is no explicit square footage measure. As a result, this model does not account for the actual overall size of a home.

# Another limitation is that several potentially significant characteristics that could influence housing prices are not included in the dataset. Factors such as education quality, access to public transportation, and proximity to green spaces, could all contribute to property value. Leaving out these variables may limit the model's ability to fully explain variations in housing prices and could influence the estimated coefficients of the variables that are included. Incorporating these additional characteristics could provide a more comprehensive understanding of the relationship between neighborhood characteristics and housing values. As a result, these omitted variables and dataset-specific constraints could be holding back the model's ability to accurately gauge the impacts of the MLR variables on home prices.