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

Lesson 14

Scatter Plots

Plot one dot per observation with plt.scatter to see whether two measurements move together, and read the association honestly.

Intermediate25 min
Scatter Plots hero Module 25 Lesson 14Scatter Plots hero Module 25 Lesson 14

A scatter plot puts one dot on the chart for every observation, using one measurement for its x position and another for its y. Nothing is joined up, because there is no order to follow. What you are looking for is the shape of the cloud: does y tend to rise with x, fall with x, or ignore it entirely?

A scatter plot of practice hours against score, rising to the rightA scatter plot of practice hours against score, rising to the right

One dot per observation

plt.scatter(x, y) needs two lists of the same length, one value from each measurement per observation. The first learner is the pair made of the first hours value and the first score.

python
import matplotlib.pyplot as plt

hours = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
score = [41, 48, 44, 56, 61, 58, 69, 72, 68, 79, 84, 81]

plt.scatter(hours, score)
plt.title("Practice against score")
plt.xlabel("hours of practice")
plt.ylabel("score")
plt.show()

print("observations:", len(hours))
print("first learner:", hours[0], "hours scored", score[0])

Output:

text
observations: 12
first learner: 2 hours scored 41

Reading the cloud

Three shapes cover most cases. Dots drifting up to the right are a positive association: high x goes with high y. Drifting down to the right is a negative association. A shapeless blob means no association, and that is a real finding too, worth saying out loud rather than hiding.

python
import matplotlib.pyplot as plt

speed = [30, 40, 50, 60, 70, 80, 90, 100, 110, 120]
litres = [7.8, 6.9, 6.1, 5.8, 5.6, 5.9, 6.4, 7.2, 8.1, 9.4]

plt.scatter(speed, litres, color="#EC4899")
plt.title("Fuel used against speed")
plt.xlabel("kilometres per hour")
plt.ylabel("litres per 100 km")
plt.show()

cheapest = litres.index(min(litres))
print("least fuel used at", speed[cheapest], "km/h")
print("the shape is a curve, not a straight line")

Output:

text
least fuel used at 70 km/h
the shape is a curve, not a straight line

This one is neither simply positive nor negative. It falls, bottoms out, and climbs again, which a straight-line summary would have hidden completely. Look at the cloud before you trust a single number.

Two groups on one chart

Call scatter twice and the second group gets its own colour. Label both and the reader can see whether the groups behave the same way, which is usually a more interesting question than the overall shape.

python
import matplotlib.pyplot as plt

morning_hours = [2, 3, 5, 6, 8, 9, 11, 12]
morning_score = [44, 49, 58, 63, 71, 74, 82, 86]
evening_hours = [2, 4, 5, 7, 8, 10, 11, 13]
evening_score = [38, 43, 47, 55, 54, 63, 66, 72]

plt.scatter(morning_hours, morning_score, color="#7C3AED", label="morning group")
plt.scatter(evening_hours, evening_score, color="#F59E0B", label="evening group")
plt.title("Both groups improve, from different starting points")
plt.xlabel("hours of practice")
plt.ylabel("score")
plt.legend()
plt.show()

print("morning average:", round(sum(morning_score) / len(morning_score), 1))
print("evening average:", round(sum(evening_score) / len(evening_score), 1))

Output:

text
morning average: 65.9
evening average: 54.8
Two coloured clouds of dots both rising to the rightTwo coloured clouds of dots both rising to the right

Overlapping dots

Where many observations share nearly the same values, the dots pile up and you cannot tell ten from a hundred. alpha makes them translucent, so dense areas simply look darker, and a smaller marker size helps too.

python
import matplotlib.pyplot as plt
import numpy as np

generator = np.random.default_rng(7)
x = generator.normal(50, 8, 600)
y = x * 0.8 + generator.normal(0, 6, 600)

plt.scatter(x, y, alpha=0.25, s=18, color="#2563EB")
plt.title("600 observations, drawn faintly")
plt.xlabel("measurement one")
plt.ylabel("measurement two")
plt.show()

print("points drawn:", len(x))
print("darker areas hold more observations")

Output:

text
points drawn: 600
darker areas hold more observations

default_rng(7) fixes the random seed, so this chart looks the same every time you run it. Do that whenever you show a random example to someone else.

Key takeaways

  • scatter draws one dot per observation and joins nothing

  • Look for a rising cloud, a falling cloud, or no shape at all

  • A second scatter call compares two groups on one chart

  • alpha and a small marker size keep crowded clouds readable

  • Next: Colour Maps and Dot Sizes

  • Previous: Stacked Bar Charts

Loading playground…

Check your understanding

Scatter Plots Quiz

What does one dot on a scatter plot represent?

1 / 10