Linear Models (lm)

Reviewed & published by Brayan K

A linear model fits the best straight-line relationship between a response and one or more predictors — R's lm() function makes regression a single, readable line of code.

Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll fit a model with lm(), read its coefficients and R-squared, make predictions on new data, and build models with multiple predictors.

What You'll Learn in This Lesson

1️⃣ Fitting a Model

lm(response ~ predictor, data = df) fits the model. coef() pulls out the intercept and slope, which define the best-fit line.

# lm() fits a linear model. The formula is: response ~ predictor.

df <- data.frame(
  hours = c(1, 2, 3, 4, 5),
  score = c(52, 61, 68, 80, 89)
)

model <- lm(score ~ hours, data = df)
coef(model)          # intercept and slope
# (Intercept)       hours
#        42.1         9.3

# Read it as: score ≈ 42.1 + 9.3 * hours
# (each extra hour adds about 9.3 points)

2️⃣ Reading the Model with summary()

summary(model) reports the full results — coefficient estimates, their p-values, and R-squared, the share of variation the model explains.

df <- data.frame(
  hours = c(1, 2, 3, 4, 5),
  score = c(52, 61, 68, 80, 89)
)
model <- lm(score ~ hours, data = df)

# summary() gives the full picture: coefficients, p-values, and R-squared.
summary(model)$r.squared      # how much variation is explained (0-1)
# -> [1] 0.9941...

# Extract pieces:
summary(model)$coefficients[, "Estimate"]   # the fitted coefficients

3️⃣ Predicting and Multiple Predictors

predict(model, newdata) applies the fitted rule to new cases. Add predictors with + in the formula — mpg ~ wt + hp uses both weight and horsepower.

df <- data.frame(
  hours = c(1, 2, 3, 4, 5),
  score = c(52, 61, 68, 80, 89)
)
model <- lm(score ~ hours, data = df)

# predict() applies the model to NEW data.
new_data <- data.frame(hours = c(6, 7))
predict(model, new_data)
#    1     2
# 97.9 107.2

# Multiple predictors use + in the formula:
mtcars_model <- lm(mpg ~ wt + hp, data = mtcars)
round(coef(mtcars_model), 3)
# (Intercept)          wt          hp
#      37.227      -3.878      -0.032

Your turn. Fill in the ___ blank, run it, and compare with the expected output.

# 🎯 YOUR TURN — replace each ___ then run it.

ads <- data.frame(
  spend = c(10, 20, 30, 40, 50),
  sales = c(100, 140, 190, 230, 280)
)

# 1) Fit a model predicting sales from spend
model <- lm(___, data = ads)   # 👉 replace ___ with sales ~ spend

# 2) Print the slope (the "spend" coefficient), rounded to 2 decimals
cat("Slope:", round(coef(model)["spend"], 2), "\n")

# 3) Predict sales for a spend of 60
cat("Predicted at 60:", round(predict(model, data.frame(spend = 60)), 1), "\n")

# ✅ Expected output:
# Slope: 4.5
# Predicted at 60: 323

Mini-Challenge: Model and Report

Write it from the outline, run it, and check it against the example output. Fitting, reporting R-squared, and predicting is the full regression workflow in miniature.

# 🎯 MINI-CHALLENGE: model and report.
#
# Use this data:
#   temp <- c(15, 18, 20, 25, 30, 35)
#   sales <- c(20, 24, 27, 36, 45, 52)   (ice cream sales)
#
# 1. Fit lm(sales ~ temp).
# 2. Print the intercept and slope, each rounded to 2 decimals.
# 3. Print the R-squared from summary(model)$r.squared, rounded to 3.
# 4. Predict sales when temp = 28 with predict().
#
# ✅ Example output (approximate):
#    Intercept: -7.5   Slope: 1.71
#    R-squared: 0.996
#    At 28 degrees: 40.4

# your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Linear Models

TaskR Syntax
Fit a modellm(y ~ x, data = df)
Coefficientscoef(model)
Full reportsummary(model)
R-squaredsummary(model)$r.squared
Predictpredict(model, newdata)
Multiple predictorslm(y ~ a + b)

🎉 Lesson Complete!

Practice quiz

How do you read the formula in lm(score ~ hours)?

  • hours predicted from score
  • score predicted from hours
  • score equals hours
  • hours times score

Answer: score predicted from hours. The form is response ~ predictor, so score is modelled from hours.

Which function pulls out a model's intercept and slope?

  • coef(model)
  • slope(model)
  • params(model)
  • lm(model)

Answer: coef(model). coef() returns the fitted coefficients (intercept and slope per predictor).

What does R-squared measure?

  • The number of predictors
  • The slope of the line
  • The p-value
  • The fraction of variation the model explains

Answer: The fraction of variation the model explains. R-squared, from 0 to 1, is the share of the response's variation explained.

How do you get R-squared from a fitted model?

  • coef(model)$r2
  • summary(model)$r.squared
  • rsq(model)
  • model$r.squared

Answer: summary(model)$r.squared. summary(model)$r.squared returns the R-squared value.

Which function applies a fitted model to new data?

  • forecast()
  • apply()
  • predict()
  • fit()

Answer: predict(). predict(model, newdata) computes predictions for new cases.

How do you add a second predictor in a formula?

  • mpg ~ wt + hp
  • mpg ~ wt, hp
  • mpg ~ wt & hp
  • mpg ~ wt * hp only

Answer: mpg ~ wt + hp. Add predictors with +, e.g. mpg ~ wt + hp uses both.

What must newdata in predict() contain?

  • The response column
  • Random values
  • Nothing
  • The same predictor column names as the formula

Answer: The same predictor column names as the formula. newdata must use the same predictor names the model was trained on.

If the slope for hours is 9.1, what does it mean?

  • Each extra hour adds about 9.1 to the response
  • The intercept is 9.1
  • R-squared is 9.1
  • There are 9.1 predictors

Answer: Each extra hour adds about 9.1 to the response. A slope is the change in response per one-unit increase in that predictor.

What does the intercept represent?

  • The maximum response
  • The predicted response when all predictors are zero
  • The error term
  • The number of rows

Answer: The predicted response when all predictors are zero. The intercept is the model's predicted value when predictors equal zero.

Why is predicting far outside the training range risky?

  • It is always exact
  • predict() refuses to run
  • Extrapolation may not hold beyond the data
  • It changes the coefficients

Answer: Extrapolation may not hold beyond the data. The linear relationship that held within your data may not continue beyond it.

Continue this course

Frequently asked questions

What does lm() do and how do I read its formula?

lm() fits a linear regression — it finds the straight-line relationship that best predicts a response variable from one or more predictors. The formula syntax is response ~ predictor, read as 'response explained by predictor'. So lm(score ~ hours) models score as a function of hours. The result is an object holding the fitted coefficients (an intercept and a slope per predictor) plus diagnostics you can inspect with summary().

How do I interpret the intercept and slope?

The intercept is the model's predicted value when all predictors are zero — the line's starting height. Each slope (coefficient) tells you how much the response changes for a one-unit increase in that predictor, holding others fixed. If the 'hours' slope is 9.1, then each extra hour of study is associated with about 9.1 more points. Signs matter: a negative slope means the response falls as the predictor rises.

What is R-squared and what counts as 'good'?

R-squared, in summary(model)$r.squared, is the fraction of the response's variation that the model explains, from 0 to 1. An R-squared of 0.85 means the model accounts for 85% of the variability. Higher is generally better, but 'good' depends entirely on the field — physics experiments may expect 0.99, while human-behaviour data might be impressive at 0.3. Don't chase R-squared blindly; a model can fit the sample well yet generalise poorly.

How do I make predictions with a fitted model?

Use predict(model, newdata), where newdata is a data frame with the same predictor column names the model was trained on. For lm(score ~ hours), you'd pass data.frame(hours = c(6, 7)) to predict scores for 6 and 7 hours. Be cautious about predicting far outside the range of your training data (extrapolation) — the linear relationship that held within your data may not continue beyond it.