Plots with millions of points or real-time updates can take seconds per frame or produce huge files unless you know where the time goes.
Rendering cost grows with the number of artists and vertices, so the big wins are drawing fewer, simpler things:
- Use one artist, not many: one
plotcall with many points is far faster than thousands of separate calls. For many line segments, use aLineCollection. - Prefer `plot(..., "o")` to `scatter` when markers share one size and color.
- Path simplification:
path.simplify(on by default) removes vertices that do not change the rendered line;path.simplify_thresholdtrades accuracy for speed.agg.path.chunksize(for example 10000) helps Agg with very long lines. - Marker subsampling:
markevery=10draws a marker every tenth point. - Decimate first: for 10 million samples, aggregate (resample,
hexbin,hist2d, datashader) to what the screen can show.
For vector output, rasterized=True on a dense artist embeds it as an image inside the SVG/PDF while text and axes stay vector, which keeps files small; dpi in savefig controls that image's resolution.
Blitting makes live updates fast: draw the static background once, cache it with canvas.copy_from_bbox(fig.bbox), then for each frame restore_region, redraw only the changing artists with ax.draw_artist, and canvas.blit. Mark animated artists with animated=True so the normal draw skips them. FuncAnimation(blit=True) does exactly this.
Measure before optimizing: time fig.canvas.draw() and compare.
Example
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import time, os
rng = np.random.default_rng(0)
x = rng.normal(size=200_000); y = rng.normal(size=200_000)
def timed_draw(fig):
t = time.perf_counter(); fig.canvas.draw()
return time.perf_counter() - t
fig1, ax1 = plt.subplots(); ax1.scatter(x, y, s=1)
fig2, ax2 = plt.subplots(); ax2.plot(x, y, ",", linestyle="none")
print(timed_draw(fig2) < timed_draw(fig1)) # True: one Line2D beats a collection
fig3, ax3 = plt.subplots(); ax3.plot(x, y, ",", linestyle="none", rasterized=True)
fig2.savefig("vector.pdf"); fig3.savefig("raster_inside.pdf", dpi=150)
print(os.path.getsize("raster_inside.pdf") < os.path.getsize("vector.pdf")) # TrueRemember
- Fewer artists and vertices is the main lever; aggregate huge data first
rasterized=Truekeeps vector files small for dense layers- Blitting redraws only changing artists over a cached background
Watch out for
- Calling
plotin a Python loop once per point, creating thousands of artists.