ggplot: How to get continuous breaks when using facet_wrap(scales="free_x)

Viewed 48

This is a rather basic question, but I would like to double check something.

I have a counts of an item for the years 1970 to 1999. For some years, I have no values (implicit NAs). The yearly counts have grouped into five years intervals. The 5 year intervals are the facets in my ggplot.

When I produce the plot I use scales="free_x" with the intent do get the x breaks only for the years covered by the relevant facet. My problem now is that the missing years are not displayed. What I would like to have is all 5 relevant years/breaks displayed in each facet, not only those for which data are available.

Here is my question: I am correct that I can remedy this issue only by making the missing years explicit, ie. creating 0 or NA values for each missing year; or by creating ordered factors and printing all their levels on the x axis?

I am just intrigued that there is no more straightforward approach with continuous scales, particularly when I have anyway a vector defining all the breaks.

Below is my example. Many thanks.

library(tidyverse)
#> Warning: package 'tidyverse' was built under R version 4.2.1
#> Warning: package 'tibble' was built under R version 4.2.1

df_data <- data.frame(
        year = c(1971L,1972L,1973L,1976L,1977L,1979L,
                 1980L,1981L,1982L,1983L,1989L,1990L,1991L,1992L,1993L,
                 1994L,1995L,1996L,1998L,1999L),
       value = c(2L,3L,4L,7L,8L,10L,11L,12L,13L,14L,
                 20L,21L,22L,23L,24L,25L,26L,27L,29L,30L)
)

df_data <- df_data %>%
mutate(year_5=cut(year, breaks=c(Inf, seq(1970, 1999, 5)), right=T))

class(df_data$year)
#> [1] "integer"



df_data %>%
ggplot()+
geom_bar(aes(x=year, y=value),
stat="identity")+
facet_wrap(vars(year_5))


df_data %>%
ggplot()+
geom_bar(aes(x=year, y=value),
stat="identity")+
scale_x_continuous(breaks=seq(1970,1999,1))+
facet_wrap(vars(year_5), 
scales="free_x")

Created on 2022-09-17 with reprex v2.0.2

2 Answers

Besides filling up or completing the data to include all years another option would be to use ggh4x::facetted_pos_scales which adds the flexibility to set the scale for each facet separately. Doing so allows to set the limits/breaks according to the displayed interval per facet. To this end I use a custom function to create the scale for all facets using lapply:

library(tidyverse)
library(ggh4x)

year_ranges <- levels(df_data$year_5)

make_scale <- function(x) {
  x <- as.numeric(str_extract(x, "\\d+")[[1]])
  xmin <- x[[1]] + 1
  xmax <- x[[1]] + 5
  scale_x_continuous(limits = c(xmin, xmax), breaks = seq(xmin, xmax, 1), expand = c(0, .6))
}

x_scales <- lapply(year_ranges, make_scale)

ggplot(df_data, aes(x = year, y = value)) +
  geom_bar(,
    stat = "identity"
  ) +
  facet_wrap(vars(year_5), scales = "free_x") +
  ggh4x::facetted_pos_scales(x = x_scales)

enter image description here

It's pretty straightforward to just fill the missing years with zeros:

library(tidyverse)

df_data <- data.frame(
  year = c(1971L,1972L,1973L,1976L,1977L,1979L,
           1980L,1981L,1982L,1983L,1989L,1990L,1991L,1992L,1993L,
           1994L,1995L,1996L,1998L,1999L),
  value = c(2L,3L,4L,7L,8L,10L,11L,12L,13L,14L,
            20L,21L,22L,23L,24L,25L,26L,27L,29L,30L)
)

df_data %>%
  complete(year = 1970:1999, fill = list(value = 0)) %>%
  mutate(year_5 = paste(5 * floor(year/5), 5 * floor(year/5) + 4, sep ="-")) %>%
  ggplot(aes(year, value)) +
  geom_col() +
  facet_wrap(vars(year_5), scales = "free_x")

Created on 2022-09-17 with reprex v2.0.2

Related