Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make a discontinuous axis in R with ggplot2?

Tags:

r

ggplot2

I have a dataframe (dat) with two columns 1) Month and 2) Value. I would like to highlight that the x-axis is not continuous in my boxplot by interrupting the x-axis with two angled lines on the x-axis that are empty between the angled lines.

Example Data and Boxplot

library(ggplot2)
set.seed(321)
dat <- data.frame(matrix(ncol = 2, nrow = 18))
x <- c("Month", "Value")
colnames(dat) <- x
dat$Month <- rep(c(1,2,3,10,11,12),3)
dat$Value <- rnorm(18,20,2)

ggplot(data = dat, aes(x = factor(Month), y = Value)) +
  geom_boxplot() +
  labs(x = "Month") +
  theme_bw() +
  theme(panel.grid = element_blank(),
        text = element_text(size = 16),
        axis.text.x = element_text(size = 14, color = "black"),
        axis.text.y = element_text(size = 14, color = "black"))

The ideal figure would look something like below. How can I make this discontinuous axis in ggplot?

enter image description here

like image 423
tassones Avatar asked Oct 12 '21 01:10

tassones


People also ask

How do I add a break in ggplot2?

To add axis breaks in ggplot2 plots in R, we use scale_x_break() and scale_y_break() functions. These functions take a vector as a parameter that has breakpoints.

How do I customize axis in R?

You can create custom axes using the axis( ) function. axis(side, at=, labels=, pos=, lty=, col=, las=, tck=, ...) the coordinate at which the axis line is to be drawn. If you are going to create a custom axis, you should suppress the axis automatically generated by your high level plotting function.

How to change axis ranges in ggplot2?

We have basically two alternatives, if we want to change our ggplot2 axis ranges. The first alternative is based on the scale_x_continuous function: Figure 2: ggplot2 Density Plot with Broader x-Axis due to scale_x_continuous Function.

How to draw a ggplot2 plot with two Y-axes in R?

Now, we can use the scale_y_continuous & sec_axis axis functions to draw a ggplot2 plot with two y-axes as shown below: As shown in Figure 1, the previously shown R syntax created a ggplot2 scatterplot. This plot has two y-axes.

How to create a simple ggplot2 scatterplot?

Let’s start with a very basic ggplot2 scatterplot. The axis usually looks very good with default option as you can see here. Two basic options that are used in almost every charts are xlab () and xlim () to control the axis title and the axis limits respectively. Note : it is possible to specify only the lower or upper bound of a limit.

What is the range of Scale_X_continuous in ggplot2?

Figure 2: ggplot2 Density Plot with Broader x-Axis due to scale_x_continuous Function. As you can see based on the previous R syntax, we specified the axis limits within the scale_x_continuous command to be within the range of -10 and 10.


Video Answer


3 Answers

You could make use of the extended axis guides in the ggh4x package. Alas, you won't easily be able to create the "separators" without a hack similar to the one suggested by user Zhiqiang Wang

guide_axis_truncated accepts vectors to define lower and upper trunks. This also works for units, by the way, then you have to pass the vector inside the unit function (e.g., trunc_lower = unit(c(0,.45), "npc") !

library(ggplot2)
library(ggh4x)

set.seed(321)
dat <- data.frame(matrix(ncol = 2, nrow = 18))
x <- c("Month", "Value")
colnames(dat) <- x
dat$Month <- rep(c(1,2,3,10,11,12),3)
dat$Value <- rnorm(18,20,2)

# this is to make it slightly more programmatic
x1end <- 3.45
x2start <- 3.55

p <-
ggplot(data = dat, aes(x = factor(Month), y = Value)) +
  geom_boxplot() +
  labs(x = "Month") +
  theme_classic() +
  theme(axis.line = element_line(colour = "black"))

p +
  guides(x = guide_axis_truncated(
    trunc_lower = c(-Inf, x2start),
    trunc_upper = c(x1end, Inf)
  ))

Created on 2021-11-01 by the reprex package (v2.0.1)

The below is taking user Zhiqiang Wang's hack a step further. You will see I am using simple trigonometry to calculate the segment coordinates. in order to make the angle actually look as it is defined in the function, you would need to set coord_equal.

# a simple function to help make the segments
add_separators <- function(x, y = 0, angle = 45, length = .1){
  add_y <-  length * sin(angle * pi/180)
  add_x <- length * cos(angle * pi/180)
  ## making the list for your segments
  myseg <- list(x = x - add_x, xend = x + add_x, 
                y = rep(y - add_y, length(x)), yend = rep(y + add_y, length(x)))
  ## this function returns an annotate layer with your segment coordinates
  annotate("segment", 
           x = myseg$x, xend = myseg$xend,
           y = myseg$y, yend = myseg$yend) 
}

# you will need to set limits for correct positioning of your separators
# I chose 0.05 because this is the expand factor by default 
y_sep <- min(dat$Value) -0.05*(min(dat$Value))

p +
  guides(x = guide_axis_truncated(
    trunc_lower = c(-Inf, x2start),
    trunc_upper = c(x1end, Inf)
  )) +
  add_separators(x = c(x1end, x2start), y = y_sep, angle = 70) +
  # you need to set expand to 0
  scale_y_continuous(expand = c(0,0)) +
  ## to make the angle look like specified, you would need to use coord_equal()
  coord_cartesian(clip = "off", ylim = c(y_sep, NA)) 

like image 70
tjebo Avatar answered Nov 09 '22 23:11

tjebo


I think it is possible to get what you want. It may take some work.

Here is your graph:

library(ggplot2)
set.seed(321)
dat <- data.frame(matrix(ncol = 2, nrow = 18))
x <- c("Month", "Value")
colnames(dat) <- x
dat$Month <- rep(c(1,2,3,10,11,12),3)
dat$Value <- rnorm(18,20,2)

p <- ggplot(data = dat, aes(x = factor(Month), y = Value)) +
  geom_boxplot() +
  labs(x = "Month") +
  theme_bw() +
  theme(panel.grid = element_blank(),
        text = element_text(size = 16),
        axis.text.x = element_text(size = 14, color = "black"),
        axis.text.y = element_text(size = 14, color = "black"))

Here is my effort:

p + annotate("segment", x = c(3.3, 3.5), xend = c(3.6, 3.8), y = c(14, 14), yend = c(15, 15))+
  coord_cartesian(clip = "off", ylim = c(15, 25)) 

Get something like this:

enter image description here

If you want to go further, it may take several tries to get it right:

p + annotate("segment", x = c(3.3, 3.5), xend = c(3.6, 3.8), y = c(14, 14), yend = c(15, 15))+
  annotate("segment", x = c(0, 3.65), xend = c(3.45, 7), y = c(14.55, 14.55), yend = c(14.55, 14.55)) +
  coord_cartesian(clip = "off", ylim = c(15, 25)) +
  theme_classic()+
  theme(axis.line.x = element_blank())
  

Just replace axis with two new lines. This is a rough idea, it may take some time to make it perfect.

enter image description here

like image 33
Zhiqiang Wang Avatar answered Nov 09 '22 23:11

Zhiqiang Wang


You could use facet_wrap. If you assign the first 3 months to one group, and the other months to another, then you can produce two plots that are side by side and use a single y axis.

It's not exactly what you want, but it will show the data effectively, and highlights the fact that the x axis is not continuous.

dat$group[dat$Month %in% c("1", "2", "3")] <- 1
dat$group[dat$Month %in% c("10", "11", "12")] <- 2

ggplot(data = dat, aes(x = factor(Month), y = Value)) +
  geom_boxplot() +
  labs(x = "Month") +
  theme_bw() +
  theme(panel.grid = element_blank(),
        text = element_text(size = 16),
        axis.text.x = element_text(size = 14, color = "black"),
        axis.text.y = element_text(size = 14, color = "black")) +
  facet_wrap(~group, scales = "free_x")

* Differences in the plot are likely due to using different versions of R where the set.seed gives different result

enter image description here

like image 36
Tjn25 Avatar answered Nov 09 '22 22:11

Tjn25