Understanding Scatter Plot in Python
Learn how to create and read scatter plots in Python using Matplotlib and Seaborn. Covers correlation, color encoding, regression lines, and ML uses.
A scatter plot places a dot at every (x, y) pair in a dataset. The resulting cloud of points reveals whether two numerical variables are related, how tightly, and in which direction — making scatter plots indispensable for exploratory data analysis and machine learning workflows.
This chapter covers:
- What scatter plots show and how to read them
- Creating scatter plots with Matplotlib and Seaborn
- Customizing colors, sizes, and transparency
- Encoding a third variable with color or size (bubble charts)
- Plotting multiple groups with a legend
- Adding a regression trend line
- Common machine learning use cases
What a Scatter Plot Shows
Each point represents one observation. The horizontal axis holds one variable, the vertical axis holds another. The overall shape of the cloud tells you the correlation between the two variables.
Reading the Pattern
| Pattern | Meaning |
|---|---|
| Points rise from left to right | Positive correlation — as X increases, Y tends to increase |
| Points fall from left to right | Negative correlation — as X increases, Y tends to decrease |
| No discernible shape | No linear correlation between the variables |
| Tight, narrow band | Strong correlation |
| Wide, diffuse cloud | Weak correlation |
| Points far from the main cloud | Outliers — worth investigating |
The Pearson correlation coefficient r summarises this pattern as a single number from -1 (perfect negative) to +1 (perfect positive). A value near 0 means no linear relationship. Scatter plots let you see what r cannot tell you — for example, two datasets can share the same r while having completely different shapes (see Anscombe's quartet).
Creating a Scatter Plot with Matplotlib
Matplotlib's plt.scatter() is the most flexible option. Install Matplotlib if you have not already:
pip install matplotlib numpyBasic Example
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=42)
# Simulate hours studied vs exam score
hours = rng.uniform(1, 10, 40)
score = 5 * hours + rng.normal(scale=8, size=40)
plt.scatter(hours, score)
plt.xlabel('Hours Studied')
plt.ylabel('Exam Score')
plt.title('Hours Studied vs Exam Score')
plt.tight_layout()
plt.show()The positive slope in the resulting cloud shows that more study hours correlate with higher exam scores.
Customizing Marker Color and Size
The three most useful parameters for plt.scatter() are:
c— color name, hex string, or array of values (mapped through a colormap)s— marker size in points squared (default 20); pass a scalar or arrayalpha— transparency from 0 (invisible) to 1 (solid); use 0.4–0.7 for overlapping points
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=7)
x = rng.normal(loc=5, scale=2, size=60)
y = rng.normal(loc=5, scale=2, size=60)
plt.scatter(x, y, c='steelblue', s=80, alpha=0.6, edgecolors='white', linewidths=0.5)
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Customized Scatter Plot')
plt.tight_layout()
plt.show()edgecolors='white' with linewidths=0.5 adds a thin white border around each dot, making individual points easier to distinguish when they overlap.
Encoding a Third Variable with Color
Pass an array to c to color each point by a third numeric variable. Add plt.colorbar() so readers know what the colors represent:
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=3)
x = rng.random(50)
y = rng.random(50)
temperature = rng.uniform(15, 35, 50) # third variable, e.g. temperature in °C
scatter = plt.scatter(x, y, c=temperature, cmap='coolwarm', s=80, alpha=0.8)
plt.colorbar(scatter, label='Temperature (°C)')
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.title('Sensor Readings by Temperature')
plt.tight_layout()
plt.show()Use perceptually uniform colormaps — 'viridis', 'plasma', 'cividis', or 'coolwarm' — rather than 'jet' or 'rainbow', which distort perception and are not color-blind friendly.
Encoding a Third Variable with Bubble Size
Pass an array to s to make each marker's area proportional to a third variable — this is called a bubble chart:
import matplotlib.pyplot as plt
import numpy as np
countries = ['USA', 'China', 'Japan', 'Germany', 'UK']
gdp = [25.5, 18.0, 4.2, 4.1, 3.1] # trillion USD
life_exp = [76.4, 77.1, 84.3, 80.6, 81.3] # years
population = [334, 1412, 125, 84, 67] # millions — encoded as size
# Scale population to a visible marker area range
sizes = [p * 1.5 for p in population]
plt.scatter(gdp, life_exp, s=sizes, alpha=0.6, edgecolors='black', linewidths=0.8)
for i, name in enumerate(countries):
plt.annotate(name, (gdp[i], life_exp[i]), textcoords='offset points',
xytext=(6, 4), fontsize=9)
plt.xlabel('GDP (trillion USD)')
plt.ylabel('Life Expectancy (years)')
plt.title('GDP vs Life Expectancy (bubble size = population)')
plt.tight_layout()
plt.show()Creating a Scatter Plot with Seaborn
Seaborn's sns.scatterplot() works directly with Pandas DataFrames and adds features like automatic grouping by a categorical column and a built-in hue parameter for color encoding.
Install Seaborn first:
pip install seaborn pandasBasic Seaborn Scatter Plot
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
data = pd.DataFrame({
'hours': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
'score': [45, 50, 55, 60, 65, 70, 72, 80, 85, 92],
})
sns.scatterplot(data=data, x='hours', y='score')
plt.xlabel('Hours Studied')
plt.ylabel('Exam Score')
plt.title('Hours Studied vs Exam Score')
plt.tight_layout()
plt.show()Color-Coding Groups with hue
The hue parameter automatically assigns a different color to each category and adds a legend:
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
data = pd.DataFrame({
'sepal_length': [5.1, 4.9, 6.3, 5.8, 7.0, 6.4, 6.3, 5.8, 7.1, 6.3],
'sepal_width': [3.5, 3.0, 2.9, 2.7, 3.2, 3.2, 3.3, 2.7, 3.0, 2.9],
'species': ['setosa', 'setosa', 'versicolor', 'versicolor',
'virginica', 'virginica', 'virginica', 'versicolor',
'virginica', 'virginica'],
})
sns.scatterplot(data=data, x='sepal_length', y='sepal_width', hue='species')
plt.title('Iris: Sepal Length vs Sepal Width')
plt.tight_layout()
plt.show()Seaborn automatically creates the legend. This is equivalent to calling plt.scatter() multiple times with different colors.
Adding a Regression Line with sns.regplot()
sns.regplot() combines a scatter plot with a fitted regression line and a confidence band:
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=10)
x = np.linspace(1, 10, 30)
y = 3 * x + rng.normal(scale=4, size=30)
data = pd.DataFrame({'x': x, 'y': y})
sns.regplot(data=data, x='x', y='y', scatter_kws={'alpha': 0.6}, line_kws={'color': 'red'})
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Scatter Plot with Regression Line')
plt.tight_layout()
plt.show()The shaded area around the line is a 95% confidence interval. Use ci=None to remove it.
Plotting Multiple Groups
With Matplotlib
Call plt.scatter() once per group and set label= on each call:
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=0)
groups = {
'Group A': (2, 3),
'Group B': (6, 6),
'Group C': (9, 2),
}
for name, (cx, cy) in groups.items():
x = rng.normal(loc=cx, scale=0.6, size=30)
y = rng.normal(loc=cy, scale=0.6, size=30)
plt.scatter(x, y, s=50, alpha=0.7, label=name)
plt.legend()
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Three Distinct Clusters')
plt.tight_layout()
plt.show()Each scatter() call automatically picks the next color from Matplotlib's default color cycle.
With Seaborn
Pass a DataFrame and use hue= and optionally style= to distinguish groups:
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=1)
rows = []
for group, (cx, cy) in [('A', (2, 3)), ('B', (6, 6)), ('C', (9, 2))]:
for _ in range(25):
rows.append({'x': rng.normal(cx, 0.6), 'y': rng.normal(cy, 0.6), 'group': group})
df = pd.DataFrame(rows)
sns.scatterplot(data=df, x='x', y='y', hue='group', style='group')
plt.title('Three Clusters — Seaborn Multi-Group')
plt.tight_layout()
plt.show()style='group' assigns a distinct marker shape to each group in addition to the color, which helps readers who print in black and white.
Scatter Plots in Machine Learning
Scatter plots are not just for exploration — they are part of the core ML workflow.
1. Checking for Linear Relationships Before Regression
Before training a linear regression model, plot input features against the target. A roughly linear scatter means linear regression is appropriate:
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=5)
house_size = rng.uniform(50, 300, 60) # square metres
house_price = 2000 * house_size + rng.normal(scale=40000, size=60) # EUR
plt.scatter(house_size, house_price, alpha=0.6, s=50)
plt.xlabel('House Size (m²)')
plt.ylabel('Price (EUR)')
plt.title('House Size vs Price — linear pattern suggests linear regression')
plt.tight_layout()
plt.show()If the scatter shows a curve rather than a line, you may need polynomial features or a different model.
2. Visualizing Clusters After K-Means
After running a clustering algorithm such as k-means, color each point by its cluster label to confirm separation:
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=8)
# Simulate cluster assignments from k-means
centers = [(1, 1), (5, 5), (9, 1)]
X, labels = [], []
for i, (cx, cy) in enumerate(centers):
X.extend(zip(rng.normal(cx, 0.7, 30), rng.normal(cy, 0.7, 30)))
labels.extend([i] * 30)
X = np.array(X)
labels = np.array(labels)
colors = ['tab:blue', 'tab:orange', 'tab:green']
for k in range(3):
mask = labels == k
plt.scatter(X[mask, 0], X[mask, 1], c=colors[k], s=50, alpha=0.7, label=f'Cluster {k}')
plt.legend()
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.title('K-Means Cluster Assignments')
plt.tight_layout()
plt.show()Well-separated clouds confirm that the algorithm found meaningful groupings.
3. Evaluating Regression Model Predictions
Plot actual vs predicted values after training a model. A perfect model produces points along the diagonal y = x line:
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=2)
# Simulate actual and predicted values from a trained model
actual = rng.uniform(10, 100, 50)
predicted = actual + rng.normal(scale=8, size=50) # model with some noise
plt.scatter(actual, predicted, alpha=0.6, s=60, edgecolors='black', linewidths=0.5)
# Draw the ideal y = x line
lim = [min(actual.min(), predicted.min()) - 5, max(actual.max(), predicted.max()) + 5]
plt.plot(lim, lim, 'r--', linewidth=1.5, label='Perfect prediction')
plt.xlim(lim)
plt.ylim(lim)
plt.xlabel('Actual Values')
plt.ylabel('Predicted Values')
plt.title('Actual vs Predicted — regression model evaluation')
plt.legend()
plt.tight_layout()
plt.show()Points scattered randomly around the diagonal (no systematic bow or fan shape) mean the model's errors are unbiased.
4. Visualizing Dimensionality Reduction (PCA / t-SNE)
After reducing high-dimensional data to two dimensions with PCA or t-SNE, a scatter plot is the natural way to display the result. Each point is one observation; color indicates its class label:
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=20)
# Simulate 2-D PCA output for three classes
class_data = {
'Class 0': ((-3, 0), 0.8),
'Class 1': ((0, 3), 0.8),
'Class 2': ((3, 0), 0.8),
}
for label, ((cx, cy), spread) in class_data.items():
x = rng.normal(cx, spread, 40)
y = rng.normal(cy, spread, 40)
plt.scatter(x, y, s=30, alpha=0.7, label=label)
plt.legend()
plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.title('PCA Projection — 2D visualization of high-dimensional data')
plt.tight_layout()
plt.show()Clusters that separate cleanly after reduction suggest that the classes are genuinely distinguishable by the original features.
Saving Scatter Plots to a File
Use plt.savefig() before plt.show() — calling show() first clears the figure:
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(seed=99)
x = rng.random(50)
y = rng.random(50)
plt.scatter(x, y, alpha=0.7, s=60)
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Scatter Plot')
plt.tight_layout()
plt.savefig('scatter.png', dpi=150) # raster — good for web
plt.savefig('scatter.pdf') # vector — best for publications
plt.show()Use dpi=300 for print-quality PNG images.
When to Use Each Library
| Situation | Recommended tool |
|---|---|
| Quick one-off plot with NumPy arrays | matplotlib.pyplot.scatter() |
| Working with Pandas DataFrames | seaborn.scatterplot() |
| Need per-point color or size encoding | matplotlib.pyplot.scatter() |
| Want automatic grouping by a column | seaborn.scatterplot(hue=...) |
| Want a built-in regression line | seaborn.regplot() |
| Deep Matplotlib customisation | fig, ax = plt.subplots() then ax.scatter() |
For a complete deep-dive into Matplotlib scatter plot parameters — including log scales, annotations, marker shapes, and scatter() vs plot() comparisons — see the Matplotlib Scatter Plots chapter.
Common Gotchas
Variable not defined. Each code snippet in this chapter is self-contained. If you combine snippets, make sure x and y are defined in the same script before calling plt.scatter().
Figure not cleared between plots. After plt.show(), Matplotlib clears the figure. If you are running snippets in a Jupyter notebook, each cell creates a fresh figure automatically. In a plain Python script, call plt.figure() to start a new plot if you want multiple separate charts.
Overplotting. With many points, all stacked on top of each other, the chart looks like a filled blob. Fix it with alpha=0.3 to show density, or switch to plt.hexbin() for 2-D histogram binning.
No colorbar. If you pass an array to c, always add plt.colorbar() — without it, readers cannot decode the color scale.
Related Chapters
- Matplotlib Scatter Plots — full Matplotlib scatter reference: log axes, annotations, bubble charts, edge colors,
scatter()vsplot() - Linear Regression — fit and interpret a linear model in Python
- K-Means Clustering — partition data into groups and visualize with scatter plots
- Data Distribution — understand the shape of your data before modeling
- Matplotlib Histograms — visualize the distribution of a single variable
- Matplotlib Line Plots — trends over a continuous ordered variable
- Train / Test Split — split data before training and evaluating models