Written and maintained by CASRAI Editorial Board
Last updated
The most common bad data visualization example in research papers is a bar chart whose y-axis does not start at zero. Peer-reviewed work on this pattern finds that such axes can visually exaggerate effect sizes, and that adding explicit cues does not remove the effect. This guide shows how a truncated chart is built in matplotlib and ggplot2 (both free), how to rebuild it honestly, and how to audit any figure before it goes into a manuscript or report.
What bad data visualization examples are used for in research
Researchers meet bad data visualization examples in two places: when reviewing their own figures before submission, and when judging figures in work they cite. The usual examples (also searched as bad examples of data visualization or examples of bad data visualization) are truncated axes, dual axes with unrelated scales, cherry-picked time ranges, too many pie slices, 3D effects and missing labels or units. This page ties one distortion, the truncated y-axis, to published evidence and to the exact plotting calls that create and fix it.
What the evidence says about truncated axes
Correll, Bertini and Franconeri, in “Truncating the Y-Axis: Threat or Menace?” (CHI 2020, arXiv 1907.02035), report that bar charts with y-axes that do not begin at zero can visually exaggerate effect sizes. They also report that the subjective impact of axis truncation is persistent across visualization designs. They note that advice on whether to truncate the y-axis can be equivocal for other visualization types, and suggest designers consider the scale of the meaningful effect sizes and variation they intend to communicate. Treat a zero baseline as the default for bars, and justify any exception in the figure legend.
Access the tools (official sources)
Both tools below are free and open. Use only the official documentation for installation steps.
- matplotlib (Python): documentation at matplotlib.org/stable. The function used here is Axes.set_ylim. For an introduction to the pyplot import, see the CASRAI guide on import matplotlib.pyplot as plt.
- ggplot2 (R): documentation at ggplot2.tidyverse.org. The function used here is coord_cartesian().
Step by step: build a truncated bar chart, then fix it
The values below are made-up demonstration numbers, not data from any study. Two groups differ by only 2%, which is the situation where truncation misleads most.
In matplotlib
- Create the bars:
fig, ax = plt.subplots()thenax.bar(["Control", "Treatment"], [1000, 1020]). - Make the distortion:
ax.set_ylim(990, 1030). The bottom argument is in data coordinates, so the bars now start at 990 and the 20-unit difference fills half of the plot height. Calling set_ylim also switches autoscaling off for that axis (the auto argument defaults to False). - Fix it: replace that call with
ax.set_ylim(0, 1100). Both bars now start at a zero baseline and the 2% difference looks like a 2% difference. - If a zoomed view is genuinely needed, keep the zero-based chart as the main panel and add a labelled inset or a second panel, with the axis range stated in the legend.
In ggplot2
- Create the bars:
ggplot(df, aes(group, value)) + geom_col(). - Make the distortion with a zoom: add
+ coord_cartesian(ylim = c(990, 1030)). - Fix it: use
coord_cartesian(ylim = c(0, 1100)), or remove the call so the bars start at zero. - Check which mechanism you used.
coord_cartesian()zooms without changing the underlying data. Setting limits on a scale instead (for examplescale_y_continuous(limits = ...)) converts all values outside the range to NA, so data is dropped, with warnings.
Output and how to read it
Read a bar chart in this order: where does the baseline sit, what are the units, how many observations are behind each bar, and is there an error bar or interval? If the lowest tick label is not zero, the bar heights cannot be compared as ratios. For distributions, use a chart type that shows the shape rather than a single bar, such as a histogram; for relationships, see scatterplot interpretation. To judge whether a visible gap matters, compare it with the effect size and its interval, not with how tall the bars look.
Other bad data visualization examples to check for
- Dual axes: two y-axes with unrelated scales can make unrelated series appear to move together. Label both axes and consider two stacked panels.
- Cherry-picked time ranges: a line chart that starts or ends at a convenient point. State the full range used and why.
- Too many pie slices, and 3D effects: they make magnitudes hard to compare. A sorted bar chart or a Pareto chart is easier to read.
- Missing context: no units, no n, no source, no indication of uncertainty.
For positive design practice, see the CASRAI guide on designing effective scientific figures.
Pre-publication audit checklist for a research figure
- Do bar charts start at zero? If not, is the exception stated in the legend?
- Are units, sample size and the source of the data given?
- Are error bars or intervals shown, and is their meaning (standard deviation, standard error, confidence interval) defined?
- Is the full time range shown, or is the choice of range explained?
- If both axes are scaled, is each labelled and is the scaling choice justified?
- In R, were limits applied with
coord_cartesian()(zoom) rather than a scale limit (drops data)?
Troubleshooting
- Bars disappear or a warning about removed rows appears in ggplot2: you likely set scale limits rather than coordinate limits. Switch to
coord_cartesian(ylim = ...)so no data is converted to NA. - A small margin appears below zero: ggplot2’s
expandargument defaults to TRUE and adds a small expansion; setexpand = FALSEto use the limits exactly. - The matplotlib axis does not update after new data is added: set_ylim turned autoscaling off for that axis, so call it again with the new range.
- The axis appears upside down: in matplotlib, bottom may be greater than top, which inverts the axis. Swap the two arguments.
Free alternatives
| Tool | Language | Call that sets the axis range | Source |
|---|---|---|---|
| matplotlib | Python | Axes.set_ylim(bottom, top) | matplotlib.org documentation |
| ggplot2 | R | coord_cartesian(ylim = …) for zoom; scale limits drop data | ggplot2.tidyverse.org documentation |
FAQ
What is an example of bad data visualization?
A bar chart with a y-axis that does not begin at zero is the standard example, because it can visually exaggerate effect sizes. Dual axes with unrelated scales, cherry-picked time ranges, crowded pie charts and 3D effects are the other frequent ones.
Is a truncated y-axis always wrong?
Not always. Correll, Bertini and Franconeri note that advice on truncating the y-axis can be equivocal for visualization types other than bar charts. For bar charts, a zero baseline is the safe default, and any truncation should be disclosed.
Does labelling the axis fix a truncated bar chart?
Not reliably. The same study reports that the subjective impact of truncation is persistent across visualization designs.
How do I zoom a plot in R without losing data?
Use coord_cartesian(ylim = ...). Setting limits on a scale converts values outside the range to NA; the ggplot2 documentation says this converts them to NA, so those data are dropped.
Sources (official documentation and research, checked 2026-10-29)
- Correll, M., Bertini, E., Franconeri, S. “Truncating the Y-Axis: Threat or Menace?” arxiv.org/abs/1907.02035 (CHI 2020, DOI 10.1145/3313831.3376222). Checked 2026-10-29.
- matplotlib, Axes.set_ylim. Checked 2026-10-29.
- ggplot2, coord_cartesian. Checked 2026-10-29.







