Answer
Where should histogram bin edges go when your data are rounded?
The short answer
Yes. For data recorded to a fixed unit, put bin edges halfway between the possible values and use widths that are whole multiples of the unit. A recorded 3 means "somewhere from 2.5 to 3.5", so those edges match what the measurement can tell you. Edges on the recorded values let an arbitrary boundary rule merge two values into one bar. For genuine counts, a bar chart with one bar per value is usually better.
Why edges on the recorded values cause trouble
A histogram bin is an interval, and every interval needs a rule for values that land exactly on an edge. Software picks one convention. R's hist() uses intervals closed on the right, (a, b], and closes the first bin on both sides. NumPy's histogram() uses intervals closed on the left, [a, b), and closes the last bin on both sides so that the maximum is counted.
With continuous data that are measured precisely, almost no value sits exactly on an edge, so the rule hardly matters. With rounded data, every value can sit on an edge if the edges are whole numbers. Then the convention, not the data, decides which bar each value joins. Take five values, 0 to 4, each appearing 20 times, with edges at 0, 1, 2, 3 and 4. That gives four bins for five values, so two values must share one. R's default merges 0 and 1 into the first bar (counts 40, 20, 20, 20); NumPy merges 3 and 4 into the last (20, 20, 20, 40). Neither is a bug: four bins simply cannot show five values one per bar.
Centre the bins on the values you could have recorded
When a measurement is rounded to the nearest unit, the recorded value k stands for every true value from k − 0.5 to k + 0.5. Bin edges at the half-units (2.5 and 3.5 around a recorded 3, for instance) reproduce those intervals exactly. That is the statistical justification: each bin then contains precisely the measurements that were recorded as one value, no value ever sits on an edge, and the closure convention stops mattering. In the five-value example, edges from −0.5 to 4.5 give five equal bars of 20.
There is a second, less obvious benefit. With half-unit edges, the histogram of the rounded values is identical to the histogram you would have drawn from the unrounded measurements, because rounding never moves a value across a bin edge. Rounding has cost you nothing at that resolution. With whole-unit edges, rounding pushes many values into a neighbouring bin, so the picture partly reflects the rounding rather than the quantity being measured.
Showing the full range matters too. Edges that stop at the sample maximum (or start at the minimum) are a convenience of automatic binning, not a requirement. Extending the outer edges by half a unit on each side costs nothing and gives the extreme values bars of their own.
Wider bins: use whole multiples of the rounding unit
If one bar per value is too fine, widen the bins, but keep the width a whole multiple of the rounding unit and keep the edges on half-units. A width of 2 with half-unit edges gives every bin exactly two possible values. A width of 1.5 does not: the bins alternately hold two values and one, so the bars rise and fall in a sawtooth that comes from the binning, not from the data. The same problem appears with automatic rules (Sturges, Scott, Freedman-Diaconis) that suggest a width such as 1.7 for integer data; round the suggestion to a whole number of units before using it.
Are genuine counts different?
Partly. For counts (number of children, number of errors), there is no hidden continuous value between 2 and 3; the value 2 is exact. The half-unit argument about measurement intervals therefore does not apply. The practical argument still does: edges between the possible values give one bar per value and keep boundary values from being merged.
But for counts a histogram is often the wrong tool. A histogram shows density as area over a continuous axis, which suggests values in between. A bar chart of the frequency of each value (in R, barplot(table(x)); or a spike plot with thin lines) is more honest, because the gaps show that nothing lies between the integers. When the counts span hundreds of values, grouping them is fine, again with widths that are whole numbers and edges between values.
See it in R and Python
The code reproduces the five-value example, then simulates 1,000 continuous measurements (mean 10, standard deviation 2) recorded to the nearest whole unit, and compares half-unit edges, whole-unit edges and a bin width that is not a multiple of the unit.
R
set.seed(677365)
# 1. Five values, 20 of each, binned with edges on the values themselves
x <- rep(0:4, each = 20)
hist(x, breaks = 0:4, plot = FALSE)$counts # R default: (a, b], first bin closed
hist(x, breaks = 0:4, right = FALSE, plot = FALSE)$counts # [a, b), last bin closed
hist(x, breaks = seq(-0.5, 4.5, 1), plot = FALSE)$counts # edges halfway between values
# 2. Continuous measurements recorded to the nearest whole unit
z <- rnorm(1000, mean = 10, sd = 2)
r <- round(z)
range(r)
table(r)
# Half-unit edges: binning the rounded values gives exactly the counts of the raw values
half <- seq(min(r) - 0.5, max(r) + 0.5, by = 1)
c_raw_half <- hist(z[z > min(half) & z < max(half)], breaks = half, plot = FALSE)$counts
c_rnd_half <- hist(r, breaks = half, plot = FALSE)$counts
identical(c_raw_half, c_rnd_half)
# Whole-unit edges: the rounded data no longer match the raw data
whole <- seq(min(r) - 1, max(r) + 1, by = 1)
c_raw_whole <- hist(z[z > min(whole) & z <= max(whole)], breaks = whole, plot = FALSE)$counts
c_rnd_whole <- hist(r, breaks = whole, plot = FALSE)$counts
sum(abs(c_raw_whole - c_rnd_whole)) # observations placed in a different bin
# 3. A bin width that is not a multiple of the rounding unit
w15 <- seq(min(r) - 0.5, max(r), by = 1.5)
vals_per_bin <- hist(min(r):max(r), breaks = w15, plot = FALSE)$counts
vals_per_bin # bins hold 1 or 2 possible values in turn
hist(r, breaks = w15, plot = FALSE)$counts
# Width 2 with half-unit edges: every bin holds exactly 2 possible values
w2 <- seq(min(r) - 0.5, max(r) + 1.5, by = 2)
hist(min(r):max(r), breaks = w2, plot = FALSE)$counts
hist(r, breaks = w2, plot = FALSE)$countsPython
import numpy as np
import pandas as pd
rng = np.random.default_rng(677365)
def right_closed(v, edges):
# R's hist() default: (a, b] bins, first bin closed
return pd.Series(pd.cut(v, edges, right=True, include_lowest=True)).value_counts(sort=False).to_numpy()
# 1. Five values, 20 of each, binned with edges on the values themselves
x = np.repeat(np.arange(5), 20)
print(right_closed(x, np.arange(5))) # (a, b], first bin closed
print(np.histogram(x, bins=np.arange(5))[0]) # NumPy: [a, b), last bin closed
print(np.histogram(x, bins=np.arange(-0.5, 5, 1))[0]) # edges halfway between values
# 2. Continuous measurements recorded to the nearest whole unit
z = rng.normal(10, 2, 1000)
r = np.round(z)
print(r.min(), r.max())
print(pd.Series(r).value_counts().sort_index().to_dict())
# Half-unit edges: binning the rounded values gives exactly the counts of the raw values
half = np.arange(r.min() - 0.5, r.max() + 1, 1)
c_raw = np.histogram(z[(z > half[0]) & (z < half[-1])], bins=half)[0]
c_rnd = np.histogram(r, bins=half)[0]
print(np.array_equal(c_raw, c_rnd))
# Whole-unit edges: the rounded data no longer match the raw data
whole = np.arange(r.min() - 1, r.max() + 2, 1)
c_raw = right_closed(z[(z > whole[0]) & (z <= whole[-1])], whole)
c_rnd = right_closed(r, whole)
print(np.abs(c_raw - c_rnd).sum()) # observations placed in a different bin
# 3. A bin width that is not a multiple of the rounding unit
vals = np.arange(r.min(), r.max() + 1)
w15 = np.arange(r.min() - 0.5, r.max() + 1e-9, 1.5)
print(right_closed(vals, w15)) # bins hold 1 or 2 possible values in turn
print(right_closed(r, w15))
# Width 2 with half-unit edges: every bin holds exactly 2 possible values
w2 = np.arange(r.min() - 0.5, r.max() + 1.5 + 1e-9, 2)
print(np.histogram(vals, bins=w2)[0])
print(np.histogram(r, bins=w2)[0])The figures below come from one seeded run of the R code. The five-value counts have no randomness and match exactly in Python. Python's random numbers differ from R's, so its simulated counts differ slightly, but they show the same pattern (its sample also stops at 16 rather than 17, so its last width-2 bin holds one value).
- Edges on the values merge two of them. R's default gives 40, 20, 20, 20 and the left-closed convention gives 20, 20, 20, 40. Half-unit edges give five bars of 20.
- Half-unit edges lose nothing to rounding. The rounded values ran from 4 to 17, and their histogram was identical to the histogram of the unrounded measurements.
- Whole-unit edges do. With edges on the integers, 218 of the 1,000 rounded values fell in a different bin from their unrounded originals.
- A width of 1.5 creates a sawtooth. The bins held 2, 1, 2, 1, … possible values, with counts 13, 24, 180, 190, 381, 107, 94, 8 and 3. The bar for 9 alone (190) is barely taller than the bar for 7 and 8 together (180) and half the height of the bar for 10 and 11 (381), although 9 (190 cases) was nearly as common as 10 (211) or 11 (170).
- A width of 2 is smooth. Every bin held two possible values, and the counts rose and fell once: 13, 87, 307, 381, 170, 39, 3.
How to report a histogram in APA style (7th edition)
A histogram is reported as a figure. In APA 7, the figure number goes in bold above the figure (Figure 1), followed on the next line by a short title in italic title case. State the binning in a note below the figure, so readers know the bars were not shaped by an arbitrary choice:
"Note. Scores were recorded as whole numbers (N = 1,000). Each bar spans two possible scores, with bin edges placed halfway between scores (3.5, 5.5, …, 17.5)."
In the text, refer to the figure and summarise what it shows with descriptive statistics, for example: "Scores were roughly symmetric and ranged from 4 to 17 (see Figure 1)." For count variables shown as a bar chart, say so in the note, for example: "Note. Bars show the number of participants reporting each number of errors."
Related tools and guides
- APA 7 formatter for descriptive statistics
- Which statistical test should I use?
- The Data Audit Checklist to Run First
- Histogram (Wikipedia)
- How to interpret a Q-Q plot
More answered questions
- Log-transforming data: when it helps, and what it changes
- What does kurtosis actually tell you about your data?
- Kolmogorov-Smirnov, t test or Mann-Whitney: which detects a change from a baseline?
Working with your own data?
General answers only go so far. Send us your situation and we'll reply by email within two business days.
Ask your questionWritten by AskStats with AI assistance. This is general information, not advice for your specific data or study. When the results matter, check your approach with a qualified statistician.