Skip to main content
MahiDev — Learn. Build. Become AI-ready.

Lesson 13

Regression Plots

Fit and draw a trend line with sns.regplot and sns.lmplot, read the confidence band, try a curved fit with order, and check the leftovers with residplot.

Intermediate25 min
Regression Plots hero Module 26 Lesson 13Regression Plots hero Module 26 Lesson 13

A scatter plot shows that two measurements move together. A regression plot adds the straight line that fits the dots best, plus a shaded band showing how sure that line is. Here every extra hour of study goes with about 4.2 more points on the quiz.

Scatter of hours against score with a fitted line and a shaded confidence bandScatter of hours against score with a fitted line and a shaded confidence band

A line through the dots

regplot draws the scatter and the least-squares line in one call. np.polyfit with degree 1 finds the same line as numbers: the slope is points per hour and the intercept is where the line meets zero hours.

python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

rng = np.random.default_rng(26)
study = pd.DataFrame({
    "track": rng.choice(["Python", "Data", "Web"], 120),
    "mode": rng.choice(["online", "classroom"], 120),
    "week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()

sns.regplot(data=study, x="hours", y="score")
plt.show()

slope, intercept = np.polyfit(study["hours"], study["score"], 1)
print(round(slope, 2), round(intercept, 2))

Output:

text
4.15 37.41

The band and the styling

The band is a 95% confidence interval for the line, found by resampling. It is narrow in the middle, where most dots are, and wider at the ends. ci=None removes it; scatter_kws and line_kws pass styling to the dots and the line.

python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

rng = np.random.default_rng(26)
study = pd.DataFrame({
    "track": rng.choice(["Python", "Data", "Web"], 120),
    "mode": rng.choice(["online", "classroom"], 120),
    "week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()

sns.regplot(data=study, x="hours", y="score", ci=None,
            scatter_kws={"s": 18, "alpha": 0.5}, line_kws={"color": "#EC4899", "lw": 3})
plt.show()

print(round(study["hours"].corr(study["score"]) ** 2, 2), "of the variation follows the line")

Output:

text
0.67 of the variation follows the line

One line per group with lmplot

lmplot is the figure-level version. hue fits a separate line for each group, and col puts each fit in its own panel. Compare the slopes: here the classroom line is steeper, so each extra hour goes with a bigger gain for classroom learners.

python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

rng = np.random.default_rng(26)
study = pd.DataFrame({
    "track": rng.choice(["Python", "Data", "Web"], 120),
    "mode": rng.choice(["online", "classroom"], 120),
    "week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()

g = sns.lmplot(data=study, x="hours", y="score", hue="mode", height=4, aspect=1.2)
plt.show()

for mode, rows in study.groupby("mode"):
    print(mode, round(np.polyfit(rows["hours"], rows["score"], 1)[0], 2))

Output:

text
classroom 4.77
online 3.7

Curves and residuals

order=2 fits a curve instead of a straight line. residplot shows what the straight line missed: each dot's distance above or below it. Residuals scattered evenly around zero mean a straight line is a fair summary.

python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

rng = np.random.default_rng(26)
study = pd.DataFrame({
    "track": rng.choice(["Python", "Data", "Web"], 120),
    "mode": rng.choice(["online", "classroom"], 120),
    "week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()

fig, axes = plt.subplots(1, 2, figsize=(11, 4))
sns.regplot(data=study, x="hours", y="score", order=2, ci=None, ax=axes[0])
axes[0].set_title("order=2")
sns.residplot(data=study, x="hours", y="score", ax=axes[1])
axes[1].set_title("residuals of the straight line")
plt.show()

slope, intercept = np.polyfit(study["hours"], study["score"], 1)
residuals = study["score"] - (slope * study["hours"] + intercept)
print("residuals average to zero:", abs(residuals.mean()) < 1e-9)
print("typical miss:", round(residuals.std(), 1), "points")

Output:

text
residuals average to zero: True
typical miss: 6.1 points

A line describes the table, not the cause. More hours and higher scores go together here, but a chart alone cannot prove that one produces the other.

Key takeaways

  • regplot draws a scatter with the best straight line and a 95% band

  • np.polyfit(x, y, 1) gives the same slope and intercept as numbers

  • lmplot is figure-level: hue fits a line per group, col a panel per group

  • order=2 fits a curve; residplot shows what the line missed

  • Next: Pair Plots and Joint Plots

  • Previous: Heatmaps

Loading playground…

Check your understanding

Regression Plots Quiz

What does regplot add to a scatter plot?

1 / 10