Python
Data Visualization
Scatter Plot
Matplotlib
Programming Tutorial

How to do a scatter plot with empty circles in Python?

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

Hollow circle markers are useful when a scatter plot is dense and filled markers would hide overlap. In Matplotlib, the core trick is to draw markers with no face color and a visible edge color, then tune size, line width, and transparency for the actual data density.

Create Hollow Markers with plt.scatter

The standard pattern is facecolors="none" together with an explicit edge color.

python
1import numpy as np
2import matplotlib.pyplot as plt
3
4rng = np.random.default_rng(42)
5x = rng.normal(size=100)
6y = 0.8 * x + rng.normal(scale=0.7, size=100)
7
8plt.figure(figsize=(7, 5))
9plt.scatter(
10    x,
11    y,
12    s=70,
13    facecolors="none",
14    edgecolors="tab:blue",
15    linewidths=1.4,
16)
17plt.xlabel("x")
18plt.ylabel("y")
19plt.title("Scatter Plot with Empty Circles")
20plt.grid(alpha=0.25)
21plt.tight_layout()
22plt.show()

This is usually enough for a clean exploratory plot.

Tune Marker Size and Transparency for Dense Data

A style that works for 100 points may fail badly for 10,000. For dense plots, reduce marker size and add alpha so outlines do not become a solid blob.

python
1import numpy as np
2import matplotlib.pyplot as plt
3
4rng = np.random.default_rng(7)
5x = rng.uniform(-3, 3, 1500)
6y = np.sin(x) + rng.normal(scale=0.35, size=1500)
7
8plt.figure(figsize=(8, 5))
9plt.scatter(
10    x,
11    y,
12    s=18,
13    facecolors="none",
14    edgecolors="black",
15    linewidths=0.6,
16    alpha=0.5,
17)
18plt.tight_layout()
19plt.show()

The goal is not just hollow markers. The goal is readable density.

Plot Multiple Groups with Different Edge Colors

If categories matter, draw each group separately and use color only on the outline. That keeps the chart lighter while preserving group identity.

python
1import numpy as np
2import matplotlib.pyplot as plt
3
4rng = np.random.default_rng(123)
5
6x1 = rng.normal(-1.5, 0.7, 80)
7y1 = rng.normal(1.0, 0.6, 80)
8x2 = rng.normal(1.2, 0.8, 80)
9y2 = rng.normal(-0.8, 0.7, 80)
10
11plt.figure(figsize=(7, 5))
12plt.scatter(x1, y1, s=60, facecolors="none", edgecolors="tab:green", linewidths=1.2, label="Class A")
13plt.scatter(x2, y2, s=60, facecolors="none", edgecolors="tab:red", linewidths=1.2, label="Class B")
14plt.legend()
15plt.tight_layout()
16plt.show()

This pattern is common in scientific figures where filled shapes would make overlapping classes hard to inspect.

Work from a Pandas DataFrame

If your data already lives in pandas, pass the columns directly to Matplotlib.

python
1import pandas as pd
2import matplotlib.pyplot as plt
3
4df = pd.DataFrame(
5    {
6        "height": [160, 165, 170, 175, 180, 185],
7        "weight": [55, 60, 66, 73, 81, 88],
8    }
9)
10
11plt.figure(figsize=(6, 4))
12plt.scatter(
13    df["height"],
14    df["weight"],
15    s=80,
16    facecolors="none",
17    edgecolors="tab:purple",
18    linewidths=1.5,
19)
20plt.xlabel("Height")
21plt.ylabel("Weight")
22plt.tight_layout()
23plt.show()

The marker settings stay the same. Only the data source changes.

Export with Enough Resolution

Thin outlines can disappear in slides or documents if the exported image uses low DPI. Save with higher resolution and test the final output size.

python
plt.savefig("scatter_hollow.png", dpi=300, bbox_inches="tight")

If the plot will appear on a dark background, verify that your edge color still has enough contrast.

Add a Regression Line Without Losing Marker Clarity

If you need a trend line, draw it separately and keep the markers hollow. That preserves the visual benefit of empty circles while still communicating direction.

python
1coef = np.polyfit(x, y, 1)
2line_x = np.linspace(x.min(), x.max(), 100)
3line_y = coef[0] * line_x + coef[1]
4plt.plot(line_x, line_y, color="tab:orange", linewidth=2)

Common Pitfalls

  • Forgetting facecolors="none", which produces normal filled markers instead of empty circles.
  • Using outlines that are too thin to survive export or presentation scaling.
  • Keeping marker sizes too large for dense data, which creates heavy visual clutter.
  • Encoding multiple categories with nearly identical edge colors.
  • Assuming one marker style works for both exploratory plots and publication-quality figures.

Summary

  • Use plt.scatter with facecolors="none" to create hollow circles.
  • Set edgecolors and linewidths explicitly so the markers remain visible.
  • Reduce size and add transparency when plotting dense data.
  • Plot categories separately when color encodes class membership.
  • Export at higher DPI if thin outlines need to remain sharp.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.