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.
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.
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.
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:
4.15 37.41The 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.
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:
0.67 of the variation follows the lineOne 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.
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:
classroom 4.77
online 3.7Curves 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.
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:
residuals average to zero: True
typical miss: 6.1 pointsA 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
-
Previous: Heatmaps
Loading playground…
Check your understanding
Regression Plots Quiz
What does regplot add to a scatter plot?
1 / 10