125

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
  • 4,190
  • 18
  • 47
  • 84
  • 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

13 Answers13

108

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
  • 3,467
  • 4
  • 19
  • 19
  • 24
    This seemed to do nearly what the default method does and I still had decimal points in the breaks. – kory Nov 21 '17 at 16:05
  • Where does `pretty_breaks()` come from? – Marian Dec 05 '18 at 07:57
  • https://github.com/r-lib/scales/blob/e6d490f2fc44d2871e48eab135361372861f3c92/R/breaks.r#L12 – Sealander Dec 06 '18 at 16:25
  • 27
    ``pretty_breaks()`` are pretty, but not always integers. Obviously there is beauty in decimals... – PatrickT Feb 28 '19 at 06:57
  • 1
    Does not work. Still getting decimal places – Isaac Liu May 20 '22 at 16:58
  • 4
    'pretty_breaks()' was deprecated in scales 0.2.2 (2012-09-04), but was "... kept for backward compatibility; you should switch to `breaks_pretty()` for new code." At [the CRAN page for breaks_pretty()](https://scales.r-lib.org/reference/breaks_pretty.html) there's a suggestion: "This is primarily useful for date/times, as `extended_breaks()` should do a slightly better job for numeric scales." – Clark Thomborson Dec 09 '22 at 19:00
66

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
  • 303
  • 5
  • 8
Daniel Gardiner
  • 936
  • 8
  • 11
  • 2
    This is the first answer that works, but an explainer would be more than welcome. – DomQ Dec 09 '20 at 08:07
  • 1
    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
  • 2
    This works in the given example, but in not overall a good solution. Firstly, it should be `seq(min(x), …` instead of `seq(0, …)`. Furthermore, `* 1.1` only adds padding if the data is positive, so should be `*(1 + sign(max(x)) * 0.1)` – mzuba Sep 19 '22 at 13:28
  • also, `seq(…)` will fail if the scale of the axis is enormous – mzuba Sep 19 '22 at 13:45
48

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
  • 95,661
  • 14
  • 264
  • 201
  • 80
    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
30

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
  • 32,068
  • 8
  • 81
  • 94
  • 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
  • this is the best answer – mzuba Sep 19 '22 at 13:44
  • Thanks for posting -- it's a nice idea! However I'd feel a *lot* better about using your code in the package I'm developing, if your solution weren't subsetting the output of `pretty()`. Perhaps the `eps.correct` parameter of [pretty](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/pretty) would be helpful? I think [Joshua Cook's solution](https://joshuacook.netlify.app/post/integer-values-ggplot-axis/) which truncates rather than subsets is a more robust approach; so I'm downvoting your answer and upvoting the one below which aimed me at Joshua's code. – Clark Thomborson Dec 09 '22 at 19:54
  • @ClarkThomborson, I think Joshua's solution is also nice, but it can give duplicated breaks, right? So I think my solution is safer? Not sure why subsetting the output of `pretty` makes you uncomfortable. – Axeman Dec 09 '22 at 20:42
  • Yeah Axeman I had also noticed this hazard in Joshua's solution. They had used a `floor()` where a `round()` would have been much more appropriate. I have posted a comment on https://joshuacook.netlify.app/post/integer-values-ggplot-axis/ to this effect. My concern about subsetting is that it'll obliterate some breaks. As kory has noted in an earlier comment, in an extreme case you could end up with an axis that has only a single break -- so there'd be no indication of scale. That said... @kory didn't provide a test case which exhibits this defect -- so I'd class it as a hazard. – Clark Thomborson Dec 09 '22 at 21:36
24

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,473
  • 35
  • 181
  • 253
Nat
  • 565
  • 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
  • This solves the OP's problem, however (as DomQ points out, and as I discovered independently ;-) sometimes it is wildly inappropriate to break at every integer. – Clark Thomborson Dec 09 '22 at 19:14
14

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/

Axeman
  • 32,068
  • 8
  • 81
  • 94
Bruno Vidigal
  • 176
  • 1
  • 6
  • This function should be the correct answer. Works more easily than any! – zdebruine Dec 05 '20 at 16:45
  • This answer is great. Few other answers here fall apart with values between 0 and 1. – Brad Oct 05 '22 at 02:00
  • I would recommend taking `unique(breaks)` before returning, since this can easily generate duplicated breaks, which can lead to e.g. artifacts from overplotting. – Axeman Dec 10 '22 at 00:21
8

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
  • 935
  • 9
  • 12
  • 3
    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
  • 2
    This may cause rounding of the labels, instead of actually fixing the breaks themselves, and therefore should not be recommended. – Axeman Jun 13 '22 at 19:40
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
  • 496
  • 4
  • 7
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
  • 3,022
  • 1
  • 19
  • 40
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,623
  • 1
  • 24
  • 30
4

One answer is indeed inside the documentation of the pretty() function. As pointed out here Setting axes to integer values in 'ggplot2' the function contains already the solution. You have just to make it work for small values. One possibility is writing a new function like the author does, for me a lambda function inside the breaks argument just works:

... + scale_y_continuous(breaks = ~round(unique(pretty(.))

It will round the unique set of values generated by pretty() creating only integer labels, no matter the scale of values.

1

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)

1

This is what I did

scale_x_continuous(labels = function(x) round(as.numeric(x)))
xilliam
  • 2,074
  • 2
  • 15
  • 27
aus_fas
  • 53
  • 1
  • 8