107

I have the following plot:

library(reshape)
library(ggplot2)
library(gridExtra)
require(ggplot2)



data2<-structure(list(IR = structure(c(4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L
), .Label = c("0.13-0.16", "0.17-0.23", "0.24-0.27", "0.28-1"
), class = "factor"), variable = structure(c(1L, 1L, 1L, 1L, 
2L, 2L, 2L, 2L), .Label = c("Real queens", "Simulated individuals"
), class = "factor"), value = c(15L, 11L, 29L, 42L, 0L, 5L, 21L, 
22L), Legend = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L), .Label = c("Real queens", 
"Simulated individuals"), class = "factor")), .Names = c("IR", 
"variable", "value", "Legend"), row.names = c(NA, -8L), class = "data.frame")
p <- ggplot(data2, aes(x =factor(IR), y = value, fill = Legend, width=.15))


data3<-structure(list(IR = structure(c(4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L
), .Label = c("0.13-0.16", "0.17-0.23", "0.24-0.27", "0.28-1"
), class = "factor"), variable = structure(c(1L, 1L, 1L, 1L, 
2L, 2L, 2L, 2L), .Label = c("Real queens", "Simulated individuals"
), class = "factor"), value = c(2L, 2L, 6L, 10L, 0L, 1L, 4L, 
4L), Legend = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L), .Label = c("Real queens", 
"Simulated individuals"), class = "factor")), .Names = c("IR", 
"variable", "value", "Legend"), row.names = c(NA, -8L), class = "data.frame")
q<- ggplot(data3, aes(x =factor(IR), y = value, fill = Legend, width=.15))


##the plot##
q + geom_bar(position='dodge', colour='black') + ylab('Frequency') + xlab('IR')+scale_fill_grey() +theme(axis.text.x=element_text(colour="black"), axis.text.y=element_text(colour="Black"))+ opts(title='', panel.grid.major = theme_blank(),panel.grid.minor = theme_blank(),panel.border = theme_blank(),panel.background = theme_blank(), axis.ticks.x = theme_blank())

I want the y-axis to display only integers. Whether this is accomplished through rounding or through a more elegant method isn't really important to me.

Atticus29
  • 3,780
  • 18
  • 42
  • 78
  • 2
    Have you looked at any of the scale functions at all? `scale_y_continuous` maybe? – joran Mar 25 '13 at 18:26
  • I read some answers to similar questions and was under the impression that scale_y_continuous converted from other numerical formats (e.g., scientific notation), but didn't accommodate the real number to integer conversion I was looking for. I might be mistaken... – Atticus29 Mar 25 '13 at 18:36

11 Answers11

99

If you have the scales package, you can use pretty_breaks() without having to manually specify the breaks.

q + geom_bar(position='dodge', colour='black') + 
scale_y_continuous(breaks= pretty_breaks())
Sealander
  • 2,967
  • 4
  • 18
  • 19
64

This is what I use:

ggplot(data3, aes(x = factor(IR), y = value, fill = Legend, width = .15)) +
  geom_col(position = 'dodge', colour = 'black') + 
  scale_y_continuous(breaks = function(x) unique(floor(pretty(seq(0, (max(x) + 1) * 1.1)))))
deSKase
  • 193
  • 3
  • 7
Daniel Gardiner
  • 909
  • 8
  • 10
  • 1
    This is the first answer that works, but an explainer would be more than welcome. – DomQ Dec 09 '20 at 08:07
  • Here's an explanation: First, The `breaks` argument in scale_y_continuous() can take the form of a function of the plot's input data (x in this case) Second, `seq(0, (max(x) + 1) * 1.1)` First we make a sequence between 0 and the maximum value of the x-axis, plus some extra padding ((x+1)*1.1) Third, `pretty()` turns this sequence into a sequence of "pretty" values (meaning 1, 2, or 5 times a power of 10) Fourth, `floor()` rounds down – frandude May 28 '22 at 23:46
44

With scale_y_continuous() and argument breaks= you can set the breaking points for y axis to integers you want to display.

ggplot(data2, aes(x =factor(IR), y = value, fill = Legend, width=.15)) +
    geom_bar(position='dodge', colour='black')+
    scale_y_continuous(breaks=c(1,3,7,10))
Didzis Elferts
  • 90,455
  • 13
  • 256
  • 198
  • 74
    This solution is only good for situations where you know which values are on the axes. Not a good general solution. – swolf Jun 08 '18 at 10:15
  • 4
    Note for posterity: `geom_bar` no longer works with y aesthetic (replace with `geom_col`). And, while not a general solution, in this example calling pretty with a specific n can fix the original issue (and is more flexible than hard-coding breaks): `q + geom_col(position='dodge', colour='black') + xlab('IR')+scale_fill_grey() + theme_bw() + scale_y_continuous('Frequency', breaks=function(x) pretty(x, n=6))` – helmingstay Nov 06 '19 at 04:24
23

You can use a custom labeller. For example, this function guarantees to only produce integer breaks:

int_breaks <- function(x, n = 5) {
  l <- pretty(x, n)
  l[abs(l %% 1) < .Machine$double.eps ^ 0.5] 
}

Use as

+ scale_y_continuous(breaks = int_breaks)

It works by taking the default breaks, and only keeping those that are integers. If it is showing too few breaks for your data, increase n, e.g.:

+ scale_y_continuous(breaks = function(x) int_breaks(x, n = 10))
Axeman
  • 29,327
  • 7
  • 77
  • 89
  • This one causes you to lose the integer 1 if you have data only from 0 - 1.25 or what have you. I only see 0 on the x-axis. – kory Nov 21 '17 at 16:11
  • 1
    I like this for simplicity sake. Note that `n` could use some tweaking depending on your value range. it seems to determine how many breaks there will be (roughly). – Marian Dec 05 '18 at 08:00
20

These solutions did not work for me and did not explain the solutions.

The breaks argument to the scale_*_continuous functions can be used with a custom function that takes the limits as input and returns breaks as output. By default, the axis limits will be expanded by 5% on each side for continuous data (relative to the range of data). The axis limits will likely not be integer values due to this expansion.

The solution I was looking for was to simply round the lower limit up to the nearest integer, round the upper limit down to the nearest integer, and then have breaks at integer values between these endpoints. Therefore, I used the breaks function:

brk <- function(x) seq(ceiling(x[1]), floor(x[2]), by = 1)

The required code snippet is:

scale_y_continuous(breaks = function(x) seq(ceiling(x[1]), floor(x[2]), by = 1))

The reproducible example from original question is:

data3 <-
  structure(
    list(
      IR = structure(
        c(4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L),
        .Label = c("0.13-0.16", "0.17-0.23", "0.24-0.27", "0.28-1"),
        class = "factor"
      ),
      variable = structure(
        c(1L, 1L, 1L, 1L,
          2L, 2L, 2L, 2L),
        .Label = c("Real queens", "Simulated individuals"),
        class = "factor"
      ),
      value = c(2L, 2L, 6L, 10L, 0L, 1L, 4L,
                4L),
      Legend = structure(
        c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L),
        .Label = c("Real queens",
                   "Simulated individuals"),
        class = "factor"
      )
    ),
    row.names = c(NA,-8L),
    class = "data.frame"
  )

ggplot(data3, aes(
  x = factor(IR),
  y = value,
  fill = Legend,
  width = .15
)) +
  geom_col(position = 'dodge', colour = 'black') + ylab('Frequency') + xlab('IR') +
  scale_fill_grey() +
  scale_y_continuous(
    breaks = function(x) seq(ceiling(x[1]), floor(x[2]), by = 1),
    expand = expand_scale(mult = c(0, 0.05))
    ) +
  theme(axis.text.x=element_text(colour="black", angle = 45, hjust = 1), 
        axis.text.y=element_text(colour="Black"),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.border = element_blank(),
        panel.background = element_blank(), 
        axis.ticks.x = element_blank())
Eric Aya
  • 69,000
  • 34
  • 174
  • 243
Nat
  • 485
  • 4
  • 11
  • 3
    Best answer here – Martin Mar 28 '20 at 10:26
  • I concur with Martin — Thanks for putting through the effort of providing a fully working example. I notice that [Daniel Gardiner's answer](https://stackoverflow.com/a/39877048/435004) uses a better breaks function, which won't cause clutter when the axis range is in the hundreds or more. Also, as a matter of taste, I feel that defining and using a separate `breaks_integers` function could be more helpful to beginners. Best, – DomQ Dec 09 '20 at 08:17
9

You can use the accuracy argument of scales::label_number() or scales::label_comma() for this:

fakedata <- data.frame(
  x = 1:5,
  y = c(0.1, 1.2, 2.4, 2.9, 2.2)
)

library(ggplot2)

# without the accuracy argument, you see .0 decimals
ggplot(fakedata, aes(x = x, y = y)) +
  geom_point() +
  scale_y_continuous(label = scales::comma)

# with the accuracy argument, all displayed numbers are integers
ggplot(fakedata, aes(x = x, y = y)) +
  geom_point() +
  scale_y_continuous(label = ~ scales::comma(.x, accuracy = 1))

# equivalent
ggplot(fakedata, aes(x = x, y = y)) +
  geom_point() +
  scale_y_continuous(label = scales::label_comma(accuracy = 1))

# this works with scales::label_number() as well
ggplot(fakedata, aes(x = x, y = y)) +
  geom_point() +
  scale_y_continuous(label = scales::label_number(accuracy = 1))

Created on 2021-08-27 by the reprex package (v2.0.0.9000)

Droplet
  • 731
  • 8
  • 11
  • Note that this approach might lead to unexpected rounding of the axes where the graph might appear inaccurate. For example, the code below leads to y-axis ticks with equally spaced intervals at 0, 2, __5__, 8, 10. `ggplot(data.frame(x = c("a", "b"), y = c(3, 10)), aes(x = x, y = y)) + geom_bar(stat = "identity") + s 4cale_y_continuous(label = scales::label_number(accuracy = 1))` – HBat Apr 14 '22 at 12:48
6

All of the existing answers seem to require custom functions or fail in some cases.

This line makes integer breaks:

bad_scale_plot +
  scale_y_continuous(breaks = scales::breaks_extended(Q = c(1, 5, 2, 4, 3)))

For more info, see the documentation ?labeling::extended (which is a function called by scales::breaks_extended).

Basically, the argument Q is a set of nice numbers that the algorithm tries to use for scale breaks. The original plot produces non-integer breaks (0, 2.5, 5, and 7.5) because the default value for Q includes 2.5: Q = c(1,5,2,2.5,4,3).

EDIT: as pointed out in a comment, non-integer breaks can occur when the y-axis has a small range. By default, breaks_extended() tries to make about n = 5 breaks, which is impossible when the range is too small. Quick testing shows that ranges wider than 0 < y < 2.5 give integer breaks (n can also be decreased manually).

Nick
  • 446
  • 4
  • 6
6

I found this solution from Joshua Cook and worked pretty well.

integer_breaks <- function(n = 5, ...) {
fxn <- function(x) {
breaks <- floor(pretty(x, n, ...))
names(breaks) <- attr(breaks, "labels")
breaks
}
return(fxn)
}

q + geom_bar(position='dodge', colour='black') + 
scale_y_continuous(breaks = integer_breaks())

The source is: https://joshuacook.netlify.app/post/integer-values-ggplot-axis/

Bruno Vidigal
  • 96
  • 1
  • 4
4

This answer builds on @Axeman's answer to address the comment by kory that if the data only goes from 0 to 1, no break is shown at 1. This seems to be because of inaccuracy in pretty with outputs which appear to be 1 not being identical to 1 (see example at the end).

Therefore if you use

int_breaks_rounded <- function(x, n = 5)  pretty(x, n)[round(pretty(x, n),1) %% 1 == 0]

with

+ scale_y_continuous(breaks = int_breaks_rounded)

both 0 and 1 are shown as breaks.

Example to illustrate difference from Axeman's

testdata <- data.frame(x = 1:5, y = c(0,1,0,1,1))

p1 <- ggplot(testdata, aes(x = x, y = y))+
  geom_point()


p1 + scale_y_continuous(breaks = int_breaks)
p1 + scale_y_continuous(breaks =  int_breaks_rounded)

Both will work with the data provided in the initial question.

Illustration of why rounding is required

pretty(c(0,1.05),5)
#> [1] 0.0 0.2 0.4 0.6 0.8 1.0 1.2
identical(pretty(c(0,1.05),5)[6],1)
#> [1] FALSE
Sarah
  • 2,566
  • 16
  • 35
4

Google brought me to this question. I'm trying to use real numbers in a y scale. The y scale numbers are in Millions.

The scales package comma method introduces a comma to my large numbers. This post on R-Bloggers explains a simple approach using the comma method:

library(scales)

big_numbers <- data.frame(x = 1:5, y = c(1000000:1000004))

big_numbers_plot <- ggplot(big_numbers, aes(x = x, y = y))+
geom_point()

big_numbers_plot + scale_y_continuous(labels = comma)

Enjoy R :)

Tony Cronin
  • 1,525
  • 1
  • 22
  • 29
0

If your values are integers, here is another way of doing this with group = 1 and as.factor(value):

library(tidyverse)

data3<-structure(list(IR = structure(c(4L, 3L, 2L, 1L, 4L, 3L, 2L, 1L
), .Label = c("0.13-0.16", "0.17-0.23", "0.24-0.27", "0.28-1"
), class = "factor"), variable = structure(c(1L, 1L, 1L, 1L, 
                                             2L, 2L, 2L, 2L), .Label = c("Real queens", "Simulated individuals"
                                             ), class = "factor"), value = c(2L, 2L, 6L, 10L, 0L, 1L, 4L, 
                                                                             4L), Legend = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L), .Label = c("Real queens", 
                                                                                                                                                   "Simulated individuals"), class = "factor")), .Names = c("IR", 
                                                                                                                                                                                                            "variable", "value", "Legend"), row.names = c(NA, -8L), class = "data.frame")
data3 %>% 
  mutate(value = as.factor(value)) %>% 
  ggplot(aes(x =factor(IR), y = value, fill = Legend, width=.15)) +
  geom_col(position = 'dodge', colour='black', group = 1) 

Created on 2022-04-05 by the reprex package (v2.0.1)