Logically, connecting scatter plot points with a line is the same as marking specific points on a line plot with a marker, so you can just use plot
(which is mentioned elsewhere on this page). You can set marker facecolor, edgecolor and size along with line style, color and width all in the same plot()
call.
import matplotlib.pyplot as plt
x = list(range(7))
y = [9, 5, 2, 4, 6, 7, 1]
plt.plot(x, y, marker='^', mfc='r', mec='r', ms=6, ls='--', c='b', lw=2)
With that being said, using scatter
+ plot
is a little different from defining markers in a plot
call like above because scatter
creates a list of collections (which points to scatter points). You can check it using ax.lines
and ax.collections
. So if you have to change marker properties after plotting the figure, you'll have to access it via .collections
while with plot
, everything is stored in ax.lines
.
import random
plt.plot(x, y, '--b')
plt.scatter(x, y, s=36, c='r', marker='^', zorder=2)
plt.gca().lines # <Axes.ArtistList of 1 lines>
plt.gca().collections # <Axes.ArtistList of 1 collections>
plt.plot(x, y, marker='^', mfc='r', mec='r', ms=6, ls='--', c='b')
plt.gca().lines # <Axes.ArtistList of 1 lines>
plt.gca().collections # <Axes.ArtistList of 0 collections>
An immediate consequence I found rather important was that the scatter
+ plot
syntax consumes much more memory than just using plot()
. This becomes rather important if you're creating many figures in a loop. The following memory profiling example shows that plot
with markers consumes over 3 times less peak size of memory blocks (tested on Python 3.12.0 and matplotlib 3.8.0).
# .\profiling.py
import tracemalloc
import random
import matplotlib.pyplot as plt
def plot_markers(x, y, ax):
ax.plot(x, y, marker='^', mfc='r', mec='r', ms=6, ls='--', c='b')
def scatter_plot(x, y, ax):
ax.plot(x, y, '--b')
ax.scatter(x, y, s=36, c='r', marker='^', zorder=2)
if __name__ == '__main__':
x = list(range(10000))
y = [random.random() for _ in range(10000)]
for func in (plot_markers, scatter_plot):
fig, ax = plt.subplots()
tracemalloc.start()
func(x, y, ax)
size, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
plt.close(fig)
print(f"{func.__name__}: size={size/1024:.2f}KB, peak={peak/1024:.2f}KB.")
> py .\profiling.py
plot_markers: size=445.83KB, peak=534.86KB.
scatter_plot: size=636.88KB, peak=1914.20KB.
show()
after calling bothscatter()
andplot()
first? – Falco