5.4.19 · D5Scientific Computing (Python)

Question bank — Publication-quality figures — LaTeX labels, colormaps, DPI

1,530 words7 min readBack to topic

True or false — justify

Cranking dpi from 300 to 600 makes your text physically bigger on the printed page.
False. DPI only changes how many pixels fill the same inch canvas — sharper, not bigger. Physical text size is fixed by figsize (inches) and font size (points, 1 pt = 1/72 in), which DPI never touches.
A vector PDF has "infinite DPI".
True in spirit. A PDF stores shapes (lines, glyph outlines) not pixels, so there is no pixel grid to look blocky — it re-renders sharp at any zoom. "Infinite DPI" is shorthand for "resolution-independent".
viridis is a good default because it has lots of bright, vivid colors.
False — that reasoning describes jet. viridis is good because its brightness rises monotonically (perceptually uniform), so it stays honest in grayscale and for colorblind readers. Vividness is not the goal; monotonic brightness is.
Saving a dense photographic heatmap as PDF is the sharpest choice.
False. A PDF would embed millions of tiny rectangles → huge file, slow to open, no sharper. Vector wins only for line art; dense raster data belongs in PNG/TIFF at high DPI.
Setting figsize=(3.5, 2.6) guarantees the saved image is 3.5 inches wide when opened.
Half true. It fixes the intended physical size, but savefig.bbox="tight" trims margins and can shrink the actual inch dimensions. The 3.5 in is a target, not a locked output width once trimming is on.
text.usetex=True and mathtext produce identical-looking output.
False. mathtext is matplotlib's built-in TeX-like engine; usetex=True calls a real LaTeX install so fonts (e.g. Computer Modern) match your paper exactly. The math renders similarly but the fonts and spacing differ.
The string "$\alpha$" (no r prefix) is safe as long as it contains a valid LaTeX command.
False. Python parses escapes before LaTeX sees the string: \a becomes a bell character, \n a newline. Valid LaTeX doesn't help — the corruption happens in Python first.
A colorbar is optional decoration you can drop to save space.
False. The colorbar is the axis for color — without it, the mapping from color to number is unknown and the plot is unreadable. It carries meaning, not decoration.
Doubling both figsize dimensions at fixed DPI quadruples the pixel count.
True. Pixels ; doubling and multiplies each factor by 2, so area . (Text also doubles in physical size, which is usually not what you want.)
coolwarm is always a safer choice than viridis.
False. coolwarm is diverging — it assumes a meaningful center. On plain low→high data with no special middle it invents a fake "neutral zone" and misleads. Match the colormap type to your data, not by "safety".

Spot the error

fig.savefig("f.png"); fig, ax = plt.subplots(figsize=(3.5,2.6)) — order looks fine?
Error: you saved before creating and drawing the figure. figsize must be set at subplots time, and savefig must come after all plotting. Order is reversed.
ax.set_xlabel("$\lambda$ (nm)") — what's wrong?
Missing the ==raw-string r==. Python turns \l into a mangled escape sequence before LaTeX runs. Fix: r"$\lambda$ (nm)".
ax.set_title(r"E = mc^2") renders mc^2 as plain text with a literal caret — why?
No ==$...$== math delimiters. Outside dollar signs everything is upright text and ^ is a literal character. Fix: r"$E = mc^2$".
plt.rcParams["savefig.dpi"] = 600 but the printed 3.5-in column figure text is unreadably tiny. Where's the real bug?
DPI wasn't the problem — ==figsize was left at the default 6.4 in==. On a 3.5 in column it shrinks ~45%, dragging a 9 pt font down to ~5 pt. Set figsize=(3.5, 2.6) first.
im = ax.imshow(Z, cmap="jet"); fig.colorbar(im) — the colorbar is present, so is this fine?
No. jet's non-monotonic brightness makes even a labeled colorbar deceptive — a bright band mid-scale reads as a peak that isn't there. A correct colorbar can't fix a dishonest colormap. Use viridis.
ax.imshow(Z) without origin="lower" for data where row 0 is the smallest y.
Error: imshow defaults to origin="upper", flipping your data top-to-bottom so low y appears on top. Add origin="lower" so the axis matches the data's orientation.

Why questions

Why are font sizes measured in points and not pixels?
Points (1/72 in) are tied to physical inches, so a 9 pt label keeps its real printed size on paper regardless of DPI. Pixels would change apparent size whenever DPI changed.
Why must figsize be chosen before font sizes when matching a journal column?
Because font size is fixed in points-per-inch; the inch canvas (figsize) determines how large those points look on the page. Set the canvas first, then the fonts sit at the right physical scale.
Why does dimensional analysis of give pixels?
The "inch" in the numerator cancels the "inch" in the denominator, leaving pure dots (= pixels). This proves DPI and figsize are coupled factors, not independent knobs. See Dimensional analysis.
Why prefer setting these knobs via rcParams rather than per-plot arguments?
rcParams sets them once globally so every figure in the project is consistent and the styling is reproducible from a single block — the heart of Reproducible research and rcParams.
Why does grayscale printing expose a bad colormap that looks fine in color?
Grayscale collapses everything to brightness alone. A perceptually uniform map like viridis still ranks correctly; jet's non-monotonic brightness makes two different values print as the same gray, erasing information.
Why can a diverging colormap mislead on strictly positive data?
Its neutral midpoint implies a "zero/baseline" that the data doesn't have, so your eye reads a meaningless split as if it were significant. Diverging maps assume a real center. See Colormaps and color theory in visualization.
Why is savefig.bbox="tight" convenient but a subtle trap for exact sizing?
It trims whitespace, so the saved image is slightly smaller than the declared figsize. Convenient for margins, but it means the output inches no longer exactly equal figsize.

Edge cases

You need a figure for a talk slide, not a journal. Does the 300-dpi rule still apply?
Not necessarily — screens are ~96–150 dpi and slides are viewed large, so bigger figsize and larger fonts matter more than 300 dpi. The principle (fix physical size first) holds; the exact number doesn't.
Your data has exactly one meaningful value (all constant). Which colormap?
Neither type helps — a single value maps to one color, so the colorbar is a flat band. The "trap" is expecting a colormap to reveal structure that isn't in the data.
A journal demands EPS but your figure has a semi-transparent overlay. What breaks?
EPS (a vector format) has poor/no true transparency support, so alpha blending may flatten or drop. This is a raster-vs-vector edge: transparency-heavy figures often need PDF or high-DPI PNG. See Raster vs vector graphics.
text.usetex=True on a machine with no LaTeX installed — what happens?
Rendering fails with an error, not a silent fallback. usetex requires a real external LaTeX toolchain; without it, use the default mathtext engine instead.
You set figsize in centimeters by habit: figsize=(9, 6.6). What goes wrong?
matplotlib always interprets figsize in inches, so a "9 cm" figure becomes a 9-inch monster (~23 cm). Convert: divide cm by 2.54 first.
Data range is 0 to 1 but nearly all values cluster near 0. Does viridis still read honestly?
The colormap stays perceptually uniform, but a linear scale wastes most colors on empty high-end. The honesty problem is now the scale (consider log/normalization), not the colormap choice.

Recall One-line self-test

If you can answer "does DPI change physical size?" (no), "why raw strings?" (Python eats backslashes first), and "why viridis over jet?" (monotonic brightness = honest in grayscale/colorblind) without hesitating, you've disarmed the three biggest traps. ::: The rest are variations on those same three ideas: size vs sharpness, string parsing order, and perceptual honesty.

Connections