The dataset “Boston” comes with package MASS. When you load the package, it automatically loads the dataset as well.
library(MASS) # install.packages("MASS")
Boston # see the data
head(Boston) # another way to see the data
# ?Boston # see explanation for variables
# 1 crim per capita crime rate by town.
# 2 zn proportion of residential land zoned for lots over 25,000 sq.ft.
# 3 indus proportion of non-retail business acres per town.
# 4 chas Charles River dummy variable (= 1 if tract bounds river; 0 otherwise).
# 5 nox nitrogen oxides concentration (parts per 10 million).
# 6 rm average number of rooms per dwelling.
# 7 age proportion of owner-occupied units built prior to 1940.
# 8 dis weighted mean of distances to five Boston employment centres.
# 9 rad index of accessibility to radial highways.
# 10 tax full-value property-tax rate per $10,000.
# 11 ptratio pupil-teacher ratio by town.
# 12 black 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town.
# 13 lstat lower status of the population (percent).
# 14 medv median value of owner-occupied homes in \$1000s.
# 13 lstat = lower status of the population (percent)
# Proportion of population that is lower status
# = 1/2 (proportion of adults without,
# some high school education and proportion of male
# workers classified as laborers). The logarithmic
# specification implies that socioeconomic status
# distinctions mean more in the upper brackets of
# society than in the lower classes. Source: 1970 U. S. Census
##----------------------------------------
### Data Prep
library(tidyverse) # install.packages("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.2 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ✖ dplyr::select() masks MASS::select()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# Turn Boston into tibble
Boston <- as_tibble(Boston)
Boston
# Rename "medv" column as "resp"
Boston2 <- Boston %>% rename(resp=medv)
Boston2
# move "resp" columnm to the 1st column
Boston2 <- Boston2 %>% relocate(resp)
Boston2
# turn "chas" column into 0/1 factor
Boston3 <- Boston2 %>% mutate( chas=as.factor(chas) )
Boston3library(plotly) # install.packages('plotly', repos='https://cran.case.edu/')
# 1. Prepare data with a 2-level categorical factor
DF = Boston3
# 2. Fit the model (2 continuous predictors + 1 categorical predictor)
Fit01 <- lm(resp ~ zn + rm + chas, data = DF)
summary(Fit01)##
## Call:
## lm(formula = resp ~ zn + rm + chas, data = DF)
##
## Residuals:
## Min 1Q Median 3Q Max
## -24.705 -3.287 0.239 2.778 39.400
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -30.46972 2.65423 -11.480 < 2e-16 ***
## zn 0.06657 0.01285 5.182 3.19e-07 ***
## rm 8.26353 0.42787 19.313 < 2e-16 ***
## chas1 4.52123 1.12556 4.017 6.80e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6.38 on 502 degrees of freedom
## Multiple R-squared: 0.5217, Adjusted R-squared: 0.5189
## F-statistic: 182.5 on 3 and 502 DF, p-value: < 2.2e-16
# 3. Setup the coordinate grid for the continuous variables
x_range <- seq(min(DF$zn), max(DF$zn), length.out = 30)
y_range <- seq(min(DF$rm), max(DF$rm), length.out = 30)
grid <- expand.grid(zn = x_range, rm = y_range)
# 4. Generate distinct prediction matrices for both levels
grid_A <- transform(grid, chas = factor("0", levels=levels(DF$chas)))
grid_B <- transform(grid, chas = factor("1", levels=levels(DF$chas)))
z_A <- matrix(predict(Fit01, newdata = grid_A), nrow = 30, ncol = 30)
z_B <- matrix(predict(Fit01, newdata = grid_B), nrow = 30, ncol = 30)
# 5. Define matching colors (V-Engine = Orange, Straight = Blue)
colors_pts <- c("#E69F00", "#56B4E9")
color_A_pln <- list(c(0, 1), c("#E69F00", "#E69F00"))
color_B_pln <- list(c(0, 1), c("#56B4E9", "#56B4E9"))
# 6. Build the 3D plot
plot_ly(DF, x = ~zn, y = ~rm, z = ~resp) %>%
# Scatter points
add_markers(color = ~chas, colors=colors_pts, marker=list(size=6), opacity=0.9) %>%
# V-Engine plane
add_surface(x = x_range, y = y_range, z = z_A, name = "chas 0 plane",
colorscale = color_A_pln, opacity = 0.4, showscale = FALSE) %>%
# Straight-Engine plane
add_surface(x = x_range, y = y_range, z = z_B, name = "chas 1 plane",
colorscale = color_B_pln, opacity = 0.4, showscale = FALSE) %>%
# Labels and style
layout(scene = list(
xaxis = list(title = "zn"),
yaxis = list(title = "rn"),
zaxis = list(title = "resp")
))\[\begin{align*} Y = -30.5 + 0.067 (zn) + 8.26 (rm) + 4.52 (chas) \end{align*}\]
##--------------------------------------
## 1. Data loading and prep
library(MASS) # install.packages("MASS")
Boston # see the data
head(Boston) # another way to see the data
# ?Boston # see explanation for variables
# 1 crim per capita crime rate by town.
# 2 zn proportion of residential land zoned for lots over 25,000 sq.ft.
# 3 indus proportion of non-retail business acres per town.
# 4 chas Charles River dummy variable (= 1 if tract bounds river; 0 otherwise).
# 5 nox nitrogen oxides concentration (parts per 10 million).
# 6 rm average number of rooms per dwelling.
# 7 age proportion of owner-occupied units built prior to 1940.
# 8 dis weighted mean of distances to five Boston employment centres.
# 9 rad index of accessibility to radial highways.
# 10 tax full-value property-tax rate per $10,000.
# 11 ptratio pupil-teacher ratio by town.
# 12 black 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town.
# 13 lstat lower status of the population (percent).
# 14 medv median value of owner-occupied homes in \$1000s.
# 13 lstat = lower status of the population (percent)
# Proportion of population that is lower status
# = 1/2 (proportion of adults without,
# some high school education and proportion of male
# workers classified as laborers). The logarithmic
# specification implies that socioeconomic status
# distinctions mean more in the upper brackets of
# society than in the lower classes. Source: 1970 U. S. Census
##----------------------------------------
### Data Prep
library(tidyverse) # install.packages("tidyverse")
# Turn Boston into tibble
Boston <- as_tibble(Boston)
Boston
# Rename "medv" column as "resp"
Boston2 <- Boston %>% rename(resp=medv)
Boston2
# move "resp" columnm to the 1st column
Boston2 <- Boston2 %>% relocate(resp)
Boston2
# turn "chas" column into 0/1 factor
Boston3 <- Boston2 %>% mutate( chas=as.factor(chas) )
Boston3
##--------------------------------------
## 2. Three variable model with surface plot
library(plotly) # install.packages('plotly', repos='https://cran.case.edu/')
# a. Prepare data with a 2-level categorical factor
DF = Boston3
# b. Fit the model (2 continuous predictors + 1 categorical predictor)
Fit01 <- lm(resp ~ zn + rm + chas, data = DF)
summary(Fit01)
# c. Setup the coordinate grid for the continuous variables
x_range <- seq(min(DF$zn), max(DF$zn), length.out = 30)
y_range <- seq(min(DF$rm), max(DF$rm), length.out = 30)
grid <- expand.grid(zn = x_range, rm = y_range)
# d. Generate distinct prediction matrices for both levels
grid_A <- transform(grid, chas = factor("0", levels=levels(DF$chas)))
grid_B <- transform(grid, chas = factor("1", levels=levels(DF$chas)))
z_A <- matrix(predict(Fit01, newdata = grid_A), nrow = 30, ncol = 30)
z_B <- matrix(predict(Fit01, newdata = grid_B), nrow = 30, ncol = 30)
# e. Define matching colors (V-Engine = Orange, Straight = Blue)
colors_pts <- c("#E69F00", "#56B4E9")
color_A_pln <- list(c(0, 1), c("#E69F00", "#E69F00"))
color_B_pln <- list(c(0, 1), c("#56B4E9", "#56B4E9"))
# f. Build the 3D plot
plot_ly(DF, x = ~zn, y = ~rm, z = ~resp) %>%
# Scatter points
add_markers(color = ~chas, colors=colors_pts, marker=list(size=6), opacity=0.9) %>%
# V-Engine plane
add_surface(x = x_range, y = y_range, z = z_A, name = "chas 0 plane",
colorscale = color_A_pln, opacity = 0.4, showscale = FALSE) %>%
# Straight-Engine plane
add_surface(x = x_range, y = y_range, z = z_B, name = "chas 1 plane",
colorscale = color_B_pln, opacity = 0.4, showscale = FALSE) %>%
# Labels and style
layout(scene = list(
xaxis = list(title = "zn"),
yaxis = list(title = "rn"),
zaxis = list(title = "resp")
))
# Y = -30.5 + 0.067 (zn) + 8.26 (rm) + 4.52 (chas)
##--------------------------------------
# Scatter Plots for each variables
plot(DF$zn, DF$resp)
plot(DF$rm, DF$resp)
plot(DF$chas, DF$resp)