3

I am trying to do 2 things:

First and most important is to somehow get the y axis to vary by the outcome_type2 variable, since they all have fairly different ranges. So the "C" have their own axis range, the "Z" have their own axis range, and the "SS" have their own axis range.

Then the secondary thing would be to somehow adjust the spacing of columns, so that there's a bit of space by those same groups--the 3 "C" columns would be close together, with a bit of extra white space between them and the "Z", then same between "Z" and "SS". Just to differentiate a little more between those three groups.

I tried tinkering with faceting on outcome_type2 instead of outcome_type but to no avail.

This is current base code, which technically works fine, but as you'll see, having them all use the same Y axis really swamps the "Z" and "SS" panels.

ggplot(dtest, aes(x = var2, y = avg2, fill = var2)) +
  geom_bar(stat = "identity",
           width = 1) +
  facet_grid(wave ~ forcats::fct_relevel(outcome_type, "CT", "CI", "CE", "FZ", "MZ", "PSS", "CSS"),
             scales = "free_y",
             space = "free_y") +
  theme_minimal() +
  theme(legend.position = "none")
dtest <- structure(list(outcome_type = c("CT", "CT", "CT", "CI", "CI", 
"CI", "CE", "CE", "CE", "FZ", "FZ", "MZ", "MZ", "PSS", "PSS", 
"CSS", "CSS", "CT", "CT", "CT", "CI", "CI", "CI", "CE", "CE", 
"CE", "FZ", "FZ", "MZ", "MZ", "PSS", "PSS", "CSS", "CSS"), wave = c("Wave 1", 
"Wave 2", "Wave 3", "Wave 1", "Wave 2", "Wave 3", "Wave 1", "Wave 2", 
"Wave 3", "Wave 2", "Wave 3", "Wave 2", "Wave 3", "Wave 1", "Wave 3", 
"Wave 1", "Wave 3", "Wave 1", "Wave 2", "Wave 3", "Wave 1", "Wave 2", 
"Wave 3", "Wave 1", "Wave 2", "Wave 3", "Wave 2", "Wave 3", "Wave 2", 
"Wave 3", "Wave 1", "Wave 3", "Wave 1", "Wave 3"), var2 = c("Skipped", 
"Skipped", "Skipped", "Skipped", "Skipped", "Skipped", "Skipped", 
"Skipped", "Skipped", "Skipped", "Skipped", "Skipped", "Skipped", 
"Skipped", "Skipped", "Skipped", "Skipped", "Attended", "Attended", 
"Attended", "Attended", "Attended", "Attended", "Attended", "Attended", 
"Attended", "Attended", "Attended", "Attended", "Attended", "Attended", 
"Attended", "Attended", "Attended"), avg2 = c(30.21, 20.88, 25.43, 
7.68, 8.26, 7.89, 11.15, 8, 5.99, 1.64, 0.43, 0.6, 0.77, 0.01, 
-0.09, -0.2, -0.01, 24.01, 19.98, 29.04, 9.82, 12.41, 12.99, 
14.35, 11.01, 10, 2.36, 2.3, 1.51, 0.91, -0.23, -0.35, -0.17, 
-0.14), outcome_type2 = c("C", "C", "C", "C", "C", "C", "C", 
"C", "C", "Z", "Z", "Z", "Z", "SS", "SS", "SS", "SS", "C", "C", 
"C", "C", "C", "C", "C", "C", "C", "Z", "Z", "Z", "Z", "SS", 
"SS", "SS", "SS")), class = c("spec_tbl_df", "tbl_df", "tbl", 
"data.frame"), row.names = c(NA, -34L), spec = structure(list(
    cols = list(outcome_type = structure(list(), class = c("collector_character", 
    "collector")), wave = structure(list(), class = c("collector_character", 
    "collector")), var2 = structure(list(), class = c("collector_character", 
    "collector")), avg2 = structure(list(), class = c("collector_double", 
    "collector")), outcome_type2 = structure(list(), class = c("collector_character", 
    "collector"))), default = structure(list(), class = c("collector_guess", 
    "collector")), skip = 1L), class = "col_spec"))

enter image description here

a_todd12
  • 449
  • 2
  • 12

2 Answers2

4

One option would be to create separate plots for each group of panels and glue them together using patchwork. Doing so you get "free" scale for each group of panels automatically and also have one (and only one) axis for each panel group.

To this end first add a group column to your data which could be used to split your dataset by facet panel group. Additionally, for convenience I use a plotting function which also removes the y axis strip texts for the first two groups of panels and as an important step completes each dataset so that all combinations of wave, outcome_type and var2 are present in each sub-dataset.

library(ggplot2)
library(patchwork)
library(magrittr)

dtest$group <- dplyr::case_when(
  grepl("SS$", dtest$outcome_type) ~ "SS",
  grepl("Z$", dtest$outcome_type) ~ "Z",
  TRUE ~ "C"
)
dtest$group <- factor(dtest$group, c("C", "Z", "SS"))

plot_fun <- function(.data) {
  remove_facet <- if (unique(.data$group) %in% c("C", "Z")) {
    theme(strip.text.y = element_blank())
  }

  .data$outcome_type <- forcats::fct_relevel(
    .data$outcome_type,
    "CT", "CI", "CE", "FZ", "MZ", "PSS", "CSS"
  )

  .data |>
    tidyr::complete(outcome_type, wave = unique(dtest$wave), var2) %>%
    ggplot(aes(x = var2, y = avg2, fill = var2)) +
    geom_bar(
      stat = "identity",
      width = 1
    ) +
    facet_grid(wave ~ outcome_type) +
    theme_minimal() +
    remove_facet
}

dtest_split <- split(dtest, dtest$group)

lapply(dtest_split, plot_fun) %>%
  wrap_plots() +
  plot_layout(widths = c(3, 2, 2), guides = "collect") &
  labs(x = NULL, y = NULL, fill = NULL) &
  theme(axis.text.x = element_blank())
#> Warning: 4 unknown levels in `f`: FZ, MZ, PSS, and CSS
#> Warning: 5 unknown levels in `f`: CT, CI, CE, PSS, and CSS
#> Warning: 5 unknown levels in `f`: CT, CI, CE, FZ, and MZ
#> Warning: Removed 4 rows containing missing values (`position_stack()`).
#> Removed 4 rows containing missing values (`position_stack()`).

stefan
  • 90,330
  • 6
  • 25
  • 51
  • 2
    Wow clever, thanks for this. I was not familiar with ```patchwork``` – a_todd12 Jan 26 '23 at 18:35
  • I get a few errors when trying to run your code, @stefan. I think the first one is a simple fix, adding c() when turning outcome_type into a factor. The other is more complicated and I can't figure out: for plot_fun <- I get an "argument is of length zero" error. – a_todd12 Jan 26 '23 at 19:13
  • 1
    Ooops. (; That happens when one uses copy and paste instead of running a true reprex. Should be fixed now and I switched back to your approach using `forcats::fct_relevel`. Haven't realized the issue with my factor. – stefan Jan 26 '23 at 19:17
  • 1
    Ha, I am a serial copy and paster myself. Thanks for the code and the updates! – a_todd12 Jan 26 '23 at 19:21
  • Hm, I am still getting an "argument is of length zero" error for plot_fun. I think the () and {} are lined up appropriately. I am trying to figure out if there is some empty data object in there that I am not recognizing? – a_todd12 Jan 26 '23 at 19:29
  • :D This time I don't know what might be the issue. I just made a second edit. This time I have run the code via the `reprex` package. So, in principle it should work now except for the case that your package or R versions differ from mine. – stefan Jan 26 '23 at 19:39
  • I will try to update some packages and see how it goes! Thanks again. – a_todd12 Jan 26 '23 at 19:45
  • 2
    For the record I get the same error and my packages (and R) are serially under-updated, so I'm guessing that that is the issue. – Blaise S Jan 26 '23 at 20:03
  • @stefan updating packages seemed to do the trick! Works perfectly now. The last thing I'm trying to figure out is how to add just one legend, which delineates Attended vs. Skipped, and remove the "Attended" and "Skipped" labels along the x axis since, as you can see in your example as well, it just gets a little crowded. But I can't find any documentation on adding just one legend within ```wrap_plots()``` or ```plot_annotation()```, and if I do it within ```ggplot()``` it creates a legend for each of the 3 "figures". – a_todd12 Jan 26 '23 at 23:55
  • 1
    Great. Just added an edit. The argument you are looking for is `guides="collect"` and has to be added in plot_layout. – stefan Jan 27 '23 at 00:17
  • Tremendous. Thanks again. It looks like ```guide_area()``` might be needed if I want to place the legend along the bottom (or top) of the figure? – a_todd12 Jan 27 '23 at 02:03
  • Ah okay, I see I can just add ```legend.position = "bottom"``` – a_todd12 Jan 27 '23 at 02:23
1

Here is a solution where we first identify those avg2 < 5, then make a list of two data frames and plot for each data frame the corresponding plot:

library(tidyverse)
require(gridExtra)

my_list <- dtest %>% 
  pivot_longer(c(contains("type"))) %>% 
  mutate(value = fct_relevel(value, "CT", "CI", "CE", "FZ", "MZ", "PSS", "CSS")) %>% 
  arrange(value) %>% 
  mutate(x = ifelse(avg2 <5, 1, 0)) %>% 
  group_split(x)
  

plot1 <- ggplot(my_list[[1]], aes(x = var2, y = avg2, fill = var2))+
  geom_col()+
  facet_grid(wave ~ value) +
  theme_minimal() +
  theme(legend.position = "none",
        strip.text.y = element_blank()
        )

plot2 <- ggplot(my_list[[2]], aes(x = var2, y = avg2, fill = var2))+
  geom_col()+
  facet_grid(wave ~ value)+
  theme_minimal() +
  theme(legend.position = "none")+
  labs(y="")

  

grid.arrange(plot1, plot2, ncol=2)

enter image description here

TarJae
  • 72,363
  • 6
  • 19
  • 66
  • 2
    Interesting thanks. Toying with this a bit because I am hoping to get different y axes for the Z and SS, and also I don't need the outcome_type2 columns in there. But this is a nice solution. – a_todd12 Jan 26 '23 at 19:30