Matlab style plots using ggplot2 (Part 2)

Create Matlab style plots in ggplot2 facets

By Pallav Routh in Manuscript Visualization

January 14, 2022

In an earlier post I demonstrate how ggplot can be used to replicate Matlab style plots. In this short post I demonstrate how to apply the same design to ggplot2 facets.

Here is the “scientific” theme from the previous post

theme_scientific <-
  theme(axis.line = element_line(color = "black"),
        axis.text.x = element_text(color = "black",
                                   size = 12,
                                   margin = unit(c(0.2, 0.1, 0.1, 0.5), "cm")),
        axis.text.y = element_text(color = "black",
                                   size = 12,
                                   margin = unit(c(0.1, 0.1, 0.2, 0.1), "cm")),
        axis.title = element_text(colour = "black", size = 12),
        axis.ticks.length = unit(-0.30, "cm"),
        panel.border = element_rect(colour = "black", fill = NA, size = 0.2),
        panel.background = element_rect(fill = "white", colour = "white"),
        panel.grid.minor = element_blank(),
        panel.grid.major = element_line(colour = "#ddd8d8", linetype = 1, size = 0.5),
        strip.placement = "outside")

Let’s use this theme to create our facetted plot. In this example, we will create a time series plot showing historical stock prices for 4 major companies using the FANG dataset -

data("FANG", package = "tidyquant")

FANG %>% 
  group_by(symbol) %>% 
    mutate(`time period` = 1:n()) %>% 
  ungroup() %>% 
  ggplot(aes(x = `time period`, y = adjusted)) +
   geom_line() +
   facet_wrap(~symbol, scales = "free") +
   theme_scientific

A major issue with using facets is that one cannot pick custom breaks and limits for the axis of each facet (let alone automating it). Thankfully, there is a funcation called facetted_pos_scales() from the ggh4x package that lets you do that.

To use this function, we need to custom breaks and limits for each facet. However a better approach would be to automate this task. Lets create a function that create such breaks and limits for the x and y axis -

auto_breaks <- function(series, num_breaks = 4, adjustment_factor = 2) {
  min_series <- min(series, na.rm = TRUE)
  max_series <- max(series, na.rm = TRUE)
  skips <- (max_series - min_series) / num_breaks
  min_adjusted <- min_series - (skips / adjustment_factor)
  max_adjusted <- max_series + (skips / adjustment_factor)
  return(
    c(min_adjusted,
    seq(min_series,max_series,skips),
    max_adjusted)
    )
}

auto_lims <- function(breakvals) {
  min_break <- min(breakvals, na.rm = TRUE)
  max_break <- max(breakvals, na.rm = TRUE)
  return(c(min_break, max_break))
}

Now all I need to do is filter the data from each company in FANG and feed it to the above function. Here is one way you can do that `

FANG %>% 
  group_by(symbol) %>% 
    mutate(`time period` = 1:n()) %>% 
  ungroup() %>% 
  {
    temp_df <- .
    symbs <- c("AMZN","FB","GOOG","NFLX")
    symb_dfs <- purrr::map(symbs, ~ filter(temp_df, symbol == .x))
    breaks_list <- purrr::map(symb_dfs, ~ round(auto_breaks(.$adjusted,
                                                            num_breaks = 3,
                                                            adjustment_factor = 1)
                                                ,0))
    print(breaks_list)
    lims_list <- purrr::map(breaks_list, auto_lims)
  }
## [[1]]
## [1]   50  248  447  646  844 1043
## 
## [[2]]
## [1] -14  23  60  96 133 170
## 
## [[3]]
## [1] 197 351 505 659 813 967
## 
## [[4]]
## [1] -26  13  52  92 131 170

It works! It chooses breaks and limits for each company.

Now the trick is to save these breaks into a list and then use it within facetted_pos_scales() from the ggh4x package.

FANG %>% 
  group_by(symbol) %>% 
    mutate(`time period` = 1:n()) %>% 
  ungroup() %>% 
  {
    temp_df <- .
    symbs <- c("AMZN","FB","GOOG","NFLX")
    symb_dfs <- purrr::map(.x = symbs, .f = ~ filter(temp_df, symbol == .x))
    breaks_list <- purrr::map(.x = symb_dfs, .f = ~ round(auto_breaks(.$adjusted,
                                                                      num_breaks = 3,
                                                                      adjustment_factor = 1),
                                                          0))
    lims_list <- purrr::map(.x = breaks_list, .f = auto_lims)
    scales_listy <- purrr::map2(.x = breaks_list, 
                                .y = lims_list, 
                                .f = ~ scale_y_continuous(breaks = .x, 
                                                          limits = .y, 
                                                          sec.axis = dup_axis(name = " ", 
                                                                             labels = NULL),
                                                          expand = expansion(add = c(0,0))))
    
    scales_listx <- rep(list(scale_x_continuous(breaks = seq(0,1000,200),
                                                limits = c(0,1000),
                                                sec.axis = dup_axis(name = " ", labels = NULL),
                                                expand = expansion(add = c(0,0)))),
                        4)
    
    ggplot(.,aes(x = `time period`, y = adjusted)) +
     geom_line(color = "blue") +
     facet_wrap(~symbol, scales = "free") +
     facetted_pos_scales(y = scales_listy, x = scales_listx) + 
     theme_scientific
  }

Here is the final plot. Beautiful!