Use coord_cartesian instead of scale_x_continuous . coord_cartesian sets the range of axes without affecting how the data is displayed. Even with coord_cartesian you can still use scale_x_continuous to set breaks , but coord_cartesian override any scale_x_continuous action on how the data is displayed.
In the fake data below, note that I have added data for several very small bars.
set.seed(4958) dat = data.frame(value=c(rnorm(5000, 10, 1), rep(15:20,1:6))) ggplot(dat, aes(value)) + geom_histogram(binwidth=0.5, color="black", fill="grey") + theme_bw() + scale_x_continuous(limits=c(5,25), breaks=5:25) + ggtitle("scale_x_continuous") ggplot(dat, aes(value)) + geom_histogram(binwidth=0.5, color="black", fill="grey") + theme_bw() + coord_cartesian(xlim=c(5,25)) + scale_x_continuous(breaks=5:25) + ggtitle("coord_cartesian")

As you can see in the above graphs, if there are cells with count = 0 in the data range, ggplot will add a zero line, even with coord_cartesian . This makes it difficult to view the strip at a height of 15 = 1. You can make the border thinner with the argument lwd ("line width") so that smaller stripes are less obscured:
ggplot(dat, aes(value)) + geom_histogram(binwidth=0.5, color="black", fill="grey", lwd=0.3) + theme_bw() + coord_cartesian(xlim=c(5,25)) + scale_x_continuous(breaks=5:25) + ggtitle("coord_cartesian")

Another option is to pre-sum the data and the graph using geom_bar to get spaces between the columns and thus avoid the need for border lines to mark the borders of the bar:
library(dplyr) library(tidyr) library(zoo) bins = seq(floor(min(dat$value)) - 1.75, ceiling(max(dat$value)) + 1.25, 0.5) dat.binned = dat %>% count(bin=cut(value, bins, right=FALSE)) %>%
