Skip to the content

Topics Covered

cor() Pearson r Spearman ρ cor.test() lm() summary(lm) predict() Residual Diagnostics
On this page
  1. 1. Correlation Analysis
  2. 2. Simple Linear Regression with lm()
  3. 3. Multiple Linear Regression (preview)
  4. 4. Residual Diagnostics
  5. 5. Comparing Pearson vs Spearman Visually
  6. 6. Standard Regression Workflow
  7. 7. Useful Helper Functions
  8. Key Take-aways

1. Correlation Analysis

1.1 Karl Pearson Correlation Coefficient

Measures the strength of linear association between two numeric variables; range −1 to +1.

# Sample data
hours <- c(2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
marks <- c(50, 55, 62, 66, 72, 75, 80, 85, 90, 95)

cor(hours, marks)                          # Pearson (default)
# [1] 0.9985

# Same explicitly
cor(hours, marks, method = "pearson")
r = +1r ≈ +0.8 r ≈ 0r ≈ −0.8 sign = direction of the relationship · magnitude = how tightly points hug a straight line
Reading Pearson's r at a glance. Remember r measures linear association only — a strong curve can still give r ≈ 0.

1.2 Spearman Rank Correlation

Non-parametric; based on ranks; captures any monotonic relationship (linear or not). Robust to outliers.

cor(hours, marks, method = "spearman")
# [1] 1.000  (perfect monotone increase here)

1.3 Significance Test — cor.test()

cor.test(hours, marks)
# Returns: t-statistic, df, p-value, 95 % CI, sample estimate

cor.test(hours, marks, method = "spearman")
Pearson's product-moment correlation data: hours and marks t = 52.29, df = 8, p-value = 2.0e-11 alternative hypothesis: true correlation is not equal to 0 95 percent confidence interval: 0.9936 0.9997 sample estimates: cor: 0.9985

1.4 Correlation Matrix for Multiple Variables

data(iris)
round(cor(iris[, 1:4]), 3)
#              Sepal.Length Sepal.Width Petal.Length Petal.Width
# Sepal.Length        1.000      -0.118        0.872       0.818
# Sepal.Width        -0.118       1.000       -0.428      -0.366
# Petal.Length        0.872      -0.428        1.000       0.963
# Petal.Width         0.818      -0.366        0.963       1.000

# Visualise it
# install.packages("corrplot")
library(corrplot)
corrplot(cor(iris[, 1:4]), method = "color",
         addCoef.col = "black", tl.col = "black")

1.5 Karl Pearson Coefficient from Scratch

n <- length(hours)
num <- n * sum(hours * marks) - sum(hours) * sum(marks)
den <- sqrt((n * sum(hours^2) - sum(hours)^2) *
            (n * sum(marks^2) - sum(marks)^2))
num / den                                  # equals cor(hours, marks)

1.6 Spearman from Scratch (using ranks)

rx <- rank(hours)
ry <- rank(marks)
cor(rx, ry)                                # equals Spearman ρ
1 - 6 * sum((rx - ry)^2) / (n * (n^2 - 1))  # classical formula
EXAMPLE 1 — Pearson r and its CI
cor.test(iris$Sepal.Length, iris$Petal.Length)
# t = 21.65, p < 2.2e-16,  r = 0.872, 95 % CI (0.827, 0.906)
EXAMPLE 2 — Spearman with tied ranks
x <- c(80, 85, 85, 70, 90)        # tie at 85
y <- c(70, 80, 90, 60, 95)
cor(x, y, method = "spearman")    # 0.975
cor.test(x, y, method = "spearman")

2. Simple Linear Regression with lm()

2.1 Fitting the Model

fit <- lm(marks ~ hours)             # formula: Y ~ X
fit                                  # short print: coefficients only
Call: lm(formula = marks ~ hours) Coefficients: (Intercept) hours 41.09 4.91

Fitted line: \(\hat Y = 41.09 + 4.91\, X\).

2.2 Full Summary

summary(fit)
Call: lm(formula = marks ~ hours) Residuals: Min 1Q Median 3Q Max -0.9091 -0.4318 -0.2273 0.2500 1.4545 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 41.0909 0.6672 61.59 5.4e-12 *** hours 4.9091 0.0939 52.29 2.0e-11 *** --- Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 Residual standard error: 0.8528 on 8 degrees of freedom Multiple R-squared: 0.997, Adjusted R-squared: 0.9967 F-statistic: 2734 on 1 and 8 DF, p-value: 1.98e-11

2.3 Extracting Components

coef(fit)             # coefficients
fitted(fit)           # predicted Y for the training data
resid(fit)            # residuals
fit$model             # data used
confint(fit)          # 95 % CIs for coefficients
anova(fit)            # ANOVA table

# Specific bits
summary(fit)$r.squared     # 0.997
summary(fit)$sigma         # residual SE
deviance(fit)              # SSE (sum of squared residuals)
AIC(fit); BIC(fit)         # information criteria

2.4 Plotting the Regression Line

plot(hours, marks, pch = 19, col = "navy",
     main = "Marks vs Study Hours",
     xlab = "Hours", ylab = "Marks")
abline(fit, col = "red", lwd = 2)
text(3, 90, paste0("R² = ", round(summary(fit)$r.squared, 3)),
     pos = 4, col = "darkred")
ŷ = 41.09 + 4.91 x R² = 0.997 246 810 507090 Study hours Marks
The hours/marks data with the least-squares line. The slope means each extra study hour is associated with about 4.9 more marks; the intercept (41.1) is the predicted score at zero hours.

2.5 Prediction

# Predict for new X
newx <- data.frame(hours = c(5, 7.5, 12))
predict(fit, newx)                           # point estimates
predict(fit, newx, interval = "confidence")  # CI for mean response
predict(fit, newx, interval = "prediction")  # CI for individual response
> predict(fit, newx, interval = "confidence") fit lwr upr 1 65.64 64.93 66.34 2 77.91 77.25 78.57 3 100.00 98.66 101.34 > predict(fit, newx, interval = "prediction") fit lwr upr 1 65.64 63.55 67.72 2 77.91 75.84 79.98 3 100.00 97.62 102.38

Note the prediction intervals are always wider than the confidence intervals — an individual response varies more than a mean.

Always be cautious extrapolating outside the observed range of X.

confidence — for the mean response prediction — for an individual x̄ — bands are narrowest here, flare toward the edges
interval = "confidence" bounds the average response at each x; interval = "prediction" must also allow for individual scatter, so it is always wider — compare the two tables above.

3. Multiple Linear Regression (preview)

data(mtcars)
fit2 <- lm(mpg ~ wt + hp + cyl, data = mtcars)
summary(fit2)

Just add predictors with +. Use * for interactions; poly(x, 2) for polynomial terms.

lm(mpg ~ wt + I(wt^2), data = mtcars)        # quadratic in wt
lm(mpg ~ wt * hp, data = mtcars)             # main effects + wt:hp interaction

4. Residual Diagnostics

par(mfrow = c(2, 2))
plot(fit)
par(mfrow = c(1, 1))

Four standard panels (recall Unit 3):

  1. Residuals vs Fitted — linearity / homoscedasticity.
  2. Normal Q-Q — normality of residuals.
  3. Scale–Location — checks equal variance.
  4. Residuals vs Leverage — influential observations (Cook's distance).

Formal Checks

shapiro.test(resid(fit))             # Normality
# install.packages("car")
library(car)
ncvTest(fit)                         # Heteroscedasticity
durbinWatsonTest(fit)                # Auto-correlation of residuals
vif(fit2)                            # Multicollinearity (multiple regression)

Cook's Distance — Influential Points

cooks <- cooks.distance(fit2)
plot(cooks, type = "h", main = "Cook's Distance", ylab = "")
abline(h = 4 / nrow(mtcars), col = "red", lty = 2)
# Rule of thumb: distance > 4/n is suspect

5. Comparing Pearson vs Spearman Visually

# Generate non-linear monotone data
x <- 1:50
y <- exp(x/15) + rnorm(50, 0, 5)

cor(x, y)                                 # Pearson — weaker (linear only)
cor(x, y, method = "spearman")            # Spearman — closer to 1

plot(x, y, pch = 19, col = "navy",
     main = "Pearson sees the noise, Spearman sees the order")
abline(lm(y ~ x), col = "red", lwd = 2)   # poor linear fit
lines(x, exp(x/15), col = "darkgreen", lwd = 2, lty = 2)  # true curve
Pearson r ≈ 0.92 — the line misses the curve Spearman ρ = 1 — the order never breaks x — the relationship is monotone but not linear
Every step right is a step up, so ranks match perfectly (ρ = 1), but the growth accelerates, so a straight line — and hence Pearson's r — cannot reach 1. This is exactly when Spearman is the better summary.

6. Standard Regression Workflow

  1. Explore the data: summary(), cor(), scatter plots.
  2. Fit the model with lm().
  3. Examine summary() — coefficient p-values, R², overall F.
  4. Diagnose residuals with plot(fit).
  5. Refine — drop non-significant predictors, transform variables (log(), sqrt()), add interactions or polynomial terms.
  6. Validate — predict on held-out data; compute RMSE / MAE.
  7. Report — equation, R², residual SE, key p-values, plot the line + CI.
# Train-test split & RMSE
set.seed(1)
idx   <- sample(seq_len(nrow(mtcars)), 0.7 * nrow(mtcars))
train <- mtcars[idx, ]
test  <- mtcars[-idx, ]

fit <- lm(mpg ~ wt + hp, data = train)
pred <- predict(fit, newdata = test)
rmse <- sqrt(mean((test$mpg - pred)^2))
rmse
EXAMPLE 1 — End-to-end on cars
fit <- lm(mpg ~ wt, data = mtcars)
summary(fit)
plot(mtcars$wt, mtcars$mpg, pch = 19, col = "darkblue",
     main = "MPG vs Weight (mtcars)")
abline(fit, col = "red", lwd = 2)

new <- data.frame(wt = c(2.5, 3.5))
predict(fit, new, interval = "prediction")  # predict MPG for new weights
EXAMPLE 2 — Polynomial regression
x <- 1:50
y <- 0.5 * x^2 + 3 * x + rnorm(50, 0, 20)

fit_lin  <- lm(y ~ x)
fit_quad <- lm(y ~ poly(x, 2))
summary(fit_lin)$adj.r.squared       # lower
summary(fit_quad)$adj.r.squared      # higher

plot(x, y, pch = 19, col = "navy")
abline(fit_lin, col = "red", lwd = 2)
lines(x, predict(fit_quad), col = "green", lwd = 2)
legend("topleft", c("Linear","Quadratic"),
       col = c("red","green"), lty = 1, lwd = 2, bty = "n")

7. Useful Helper Functions

FunctionUse
coef(fit)Coefficients
fitted(fit)Predicted values
resid(fit)Residuals
summary(fit)Full summary
anova(fit)ANOVA decomposition
confint(fit)95 % CIs for coefficients
predict(fit, new, interval)Prediction with CI
plot(fit)4 diagnostic plots
step(fit)Stepwise variable selection (AIC)
update(fit, . ~ . - x)Drop a term from a fitted model

Key Take-aways