142

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.

2
  • 2
    Have you looked at any of the scale functions at all? scale_y_continuous maybe? Commented Mar 25, 2013 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... Commented Mar 25, 2013 at 18:36

14 Answers 14

114

Try breaks_pretty.

library(scales)
q + geom_bar(position = 'dodge', colour = 'black') + 
scale_y_continuous(breaks = breaks_pretty())

Before scales 0.2.2:

q + geom_bar(position = 'dodge', colour = 'black') + 
scale_y_continuous(breaks = pretty_breaks())
Sign up to request clarification or add additional context in comments.

6 Comments

This seemed to do nearly what the default method does and I still had decimal points in the breaks.
Where does pretty_breaks() come from?
pretty_breaks() are pretty, but not always integers. Obviously there is beauty in decimals...
Does not work. Still getting decimal places
'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() there's a suggestion: "This is primarily useful for date/times, as extended_breaks() should do a slightly better job for numeric scales."
|
80

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(min(x), (max(x) + 1) * 1.1)))))

5 Comments

This is the first answer that works, but an explainer would be more than welcome.
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
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)
also, seq(…) will fail if the scale of the axis is enormous
Replying here to reiterate that this still works perfectly. Thanks!
55

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))

2 Comments

This solution is only good for situations where you know which values are on the axes. Not a good general solution.
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))
32

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))

5 Comments

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.
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).
this is the best answer
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 would be helpful? I think Joshua Cook's solution 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.
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 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.
28

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())

3 Comments

Best answer here
I concur with Martin — Thanks for putting through the effort of providing a fully working example. I notice that Daniel Gardiner's answer 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,
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.
17

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/posts/2019-11-09_integer-values-ggplot-axis/

3 Comments

This function should be the correct answer. Works more easily than any!
This answer is great. Few other answers here fall apart with values between 0 and 1.
I would recommend taking unique(breaks) before returning, since this can easily generate duplicated breaks, which can lead to e.g. artifacts from overplotting.
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)

2 Comments

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))
This may cause rounding of the labels, instead of actually fixing the breaks themselves, and therefore should not be recommended.
7

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).

1 Comment

I come back to this answer again and again. Wish it was way higher up in the rankings.
7

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 Comment

I think this should say unique(round(pretty(.)))
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

Comments

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 :)

2 Comments

The other solutions here didn't actually work for me, or seemed ridiculously complicated. This one worked and was simple to do.
thanks @BrianDoherty, simplicity is the key for most things...
4

This is what I did

scale_x_continuous(labels = function(x) round(as.numeric(x)))

Comments

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)

Comments

1

The existing answers have some problems:

  • Some require you to know the range of your data in advance.
  • Some assume that you want a break at every integer, which isn't going to work when you have a lot of data.
  • breaks_extended() and its older friends don't guarantee integer breaks.
  • Other answers make you write your own function, which isn't ideal

A simple solution is:

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

The Q argument is passed on labeling::extended. Simply by omitting 2.5 from the default Q, you make sure you don't get non-integer breaks - or at least, much less likely unless your data is radically unsuited for integer breaks:

z <- rnorm(50) * 3 + 2
scales::breaks_extended()(z)
# [1] -5.0 -2.5  0.0  2.5  5.0  7.5
scales::breaks_extended(Q = c(1,5,2,3,4))(z)
# [1] -4  0  4  8

# Very small values get you non-integers:
scales::breaks_extended(Q = c(1,5,2,3,4))(z/100)
# [1] -0.04  0.00  0.04  0.08

1 Comment

Interesting solution. But still doesn't work for z = c(1, 3), which is my limits for geom_count.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.