Simple Linear
Regression with R
Build, interpret, evaluate and diagnose a regression model that predicts house price from house area.
Download the practice dataset
The dataset contains 40 houses with area measured in square feet and price measured in lakh. Values include realistic variation around an increasing linear relationship.
Only the CSV dataset is downloadable from this article.
read.csv().1. Import and inspect the dataset
# Display the current working directory
getwd()
# Optional: choose the directory containing the CSV
# setwd("D:/R_Practical")
# Import data
houses <- read.csv(
"house_area_price.csv",
stringsAsFactors = FALSE
)
# Inspect data
head(houses)
tail(houses)
dim(houses)
names(houses)
str(houses)
summary(houses)
Inspection confirms that Area_sqft and Price_Lakh are numerical and that each row represents one house. The identifier House_ID labels observations but should not be used as a numerical predictor.
# Check data quality
colSums(is.na(houses))
sum(duplicated(houses$House_ID))
# Retain valid observations for modelling
model_data <- houses[
complete.cases(
houses[c("Area_sqft", "Price_Lakh")]
),
]
2. Explore the relationship before modelling
A scatter plot reveals direction, approximate form, unusual observations and possible changes in spread. A linear model is plausible when the cloud of points follows a roughly straight pattern.
plot( model_data$Area_sqft, model_data$Price_Lakh, main = "House Area and Price", xlab = "Area (square feet)", ylab = "Price (lakh)", pch = 19, col = "#1769FF" )
Correlation
correlation <- cor( model_data$Area_sqft, model_data$Price_Lakh, method = "pearson" ) print(correlation)
Pearson correlation ranges from −1 to +1. A positive value indicates that larger areas tend to be associated with higher prices. A value close to ±1 indicates a strong linear association, while a value near zero indicates weak linear association.
3. Fit the regression model using lm()
R's lm() function fits linear models. The formula places the response on the left and the predictor on the right.
price_model <- lm( Price_Lakh ~ Area_sqft, data = model_data ) print(price_model) summary(price_model)
The model estimates the intercept and slope by ordinary least squares, selecting the line that minimizes the sum of squared residuals.
# Extract coefficients coefficients <- coef(price_model) intercept <- coefficients[1] slope <- coefficients[2] print(intercept) print(slope)
4. Interpret the regression coefficients
Intercept
The intercept is the predicted house price when area equals zero. Because a zero-square-foot house is outside the useful data range, the intercept mainly positions the regression line and may not have a meaningful real-world interpretation.
Slope
The slope estimates the expected change in house price for every additional square foot of area. If the fitted slope is 0.05, then an increase of 100 square feet corresponds to an estimated price increase of:
price_change_for_100_sqft <- slope * 100 print(price_change_for_100_sqft)
5. Understand the model summary
summary(price_model) reports coefficient estimates, standard errors, t statistics, p-values, residual information, residual standard error, R², adjusted R² and the overall F test.
| Output | Meaning | Interpretation |
|---|---|---|
| Estimate | Fitted intercept or slope | Magnitude and direction of the relationship |
| Std. Error | Estimated uncertainty of a coefficient | Smaller values imply more precise estimates, relative to scale |
| t value | Estimate divided by standard error | Evidence against a zero coefficient |
| Pr(>|t|) | Coefficient p-value | Small values indicate evidence of a non-zero linear association under model assumptions |
| Residual standard error | Typical residual size in response units | Approximate unexplained prediction error in lakh |
| Multiple R-squared | Proportion of response variance explained | Higher values indicate closer in-sample fit |
| F-statistic | Overall model test | For one predictor, it tests the same null slope as the coefficient t test |
6. Draw the fitted regression line
plot(
model_data$Area_sqft,
model_data$Price_Lakh,
main = "Simple Linear Regression: Area vs Price",
xlab = "Area (square feet)",
ylab = "Price (lakh)",
pch = 19,
col = "#1769FF"
)
abline(
price_model,
col = "#FF6B57",
lwd = 3
)
legend(
"topleft",
legend = c("Observed houses", "Regression line"),
col = c("#1769FF", "#FF6B57"),
pch = c(19, NA),
lty = c(NA, 1),
lwd = c(NA, 3),
bty = "n"
)
Vertical distances from the points to the line are residuals. Points above the line have positive residuals because observed price exceeds predicted price; points below the line have negative residuals.
7. Generate fitted values and residuals
model_data$Predicted_Price <- fitted(
price_model
)
model_data$Residual <- residuals(
price_model
)
head(
model_data[
c(
"House_ID",
"Price_Lakh",
"Predicted_Price",
"Residual"
)
]
)
Fitted values describe the model's in-sample estimates. Residuals are essential for diagnostics because they reveal patterns not captured by the fitted line.
8. Evaluate model accuracy
R² measures explained variation, while MAE and RMSE express prediction error in the original response unit.
actual <- model_data$Price_Lakh predicted <- predict(price_model) mae <- mean(abs(actual - predicted)) mse <- mean((actual - predicted)^2) rmse <- sqrt(mse) r_squared <- summary(price_model)$r.squared accuracy <- c( MAE = mae, MSE = mse, RMSE = rmse, R_Squared = r_squared ) round(accuracy, 4)
| Metric | Meaning | Preferred direction |
|---|---|---|
| MAE | Average absolute prediction error | Lower is better |
| MSE | Average squared prediction error | Lower is better |
| RMSE | Square root of MSE, expressed in lakh | Lower is better |
| R² | Fraction of price variance explained by area | Closer to 1 indicates stronger in-sample fit |
These in-sample measures evaluate the same data used to fit the model and may be optimistic. Prediction for new cases should be evaluated using held-out data or cross-validation.
9. Predict price for new houses
new_houses <- data.frame( Area_sqft = c(1250, 1750, 2250) ) point_predictions <- predict( price_model, newdata = new_houses ) cbind(new_houses, point_predictions)
Confidence interval for the mean response
predict( price_model, newdata = new_houses, interval = "confidence", level = 0.95 )
A confidence interval estimates the mean price for houses at a specified area.
Prediction interval for an individual house
predict( price_model, newdata = new_houses, interval = "prediction", level = 0.95 )
A prediction interval is wider because it includes uncertainty about both the regression line and the variation among individual houses.
10. Check regression assumptions
Standard diagnostic plots
par(mfrow = c(2, 2)) plot(price_model) par(mfrow = c(1, 1))
| Diagnostic plot | What to examine |
|---|---|
| Residuals vs Fitted | No systematic curve and roughly constant vertical spread |
| Normal Q–Q | Residual points approximately follow the reference line |
| Scale–Location | Similar residual spread across fitted values |
| Residuals vs Leverage | Potential influential observations and Cook's distance |
# Additional influence measures cooks_distance <- cooks.distance(price_model) which(cooks_distance > 4 / nrow(model_data)) # Largest absolute studentized residuals studentized <- rstudent(price_model) order(abs(studentized), decreasing = TRUE)[1:5]
11. Optional train–test evaluation using base R
A train–test split gives a more realistic estimate of performance on unseen observations. A fixed seed makes the split reproducible.
set.seed(42)
train_index <- sample(
seq_len(nrow(model_data)),
size = floor(0.80 * nrow(model_data))
)
train_data <- model_data[train_index, ]
test_data <- model_data[-train_index, ]
train_model <- lm(
Price_Lakh ~ Area_sqft,
data = train_data
)
test_predictions <- predict(
train_model,
newdata = test_data
)
test_mae <- mean(
abs(test_data$Price_Lakh - test_predictions)
)
test_rmse <- sqrt(
mean(
(test_data$Price_Lakh - test_predictions)^2
)
)
print(c(Test_MAE = test_mae, Test_RMSE = test_rmse))
With only 40 records, test results depend noticeably on which observations enter the test set. Repeated cross-validation is generally more stable, but the split above demonstrates the essential separation between training and evaluation.
Interpretation and reporting template
- State the predictor, response, sample size and units.
- Describe the scatter plot and report Pearson correlation.
- Write the fitted equation using the estimated intercept and slope.
- Interpret the slope in meaningful units, such as price change per 100 square feet.
- Report the slope estimate, uncertainty and p-value.
- Report R² together with MAE or RMSE.
- Discuss residual diagnostics and influential observations.
- Separate interpolation from extrapolation.
- State that association does not establish causation.
- Recognize omitted predictors such as location, age, quality and amenities.
Practice questions
- Calculate the Pearson correlation between area and price.
- Write the fitted regression equation from the model coefficients.
- Interpret the slope per one square foot and per 100 square feet.
- Predict price for houses of 1400, 1900 and 2400 square feet.
- Compare confidence intervals with prediction intervals.
- Identify the observation with the largest absolute residual.
- Identify houses whose Cook's distance exceeds
4/n. - Perform an 80:20 train–test split and report test MAE and RMSE.
- Explain why high R² does not prove causation.
- Suggest variables needed for a more realistic multiple-regression model.