rdatabaseggplot2

Bind two ggplots in R one likert chart and one bar chart in one plot


i have a data frame called df in R :

# Load necessary libraries
library(tibble)
library(tidyverse)
library(ggplot2)
library(ggpubr)
library(ggstats)

# Define categories and Likert levels
var_levels <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q")

likert_levels <- c(
  "Strongly disagree",
  "Disagree",
  "Neither agree nor disagree",
  "Agree",
  "Strongly agree"
)

# Set seed for reproducibility
set.seed(42)

# Create the dataframe with three Likert response columns
df <- tibble(
  var = sample(var_levels, 50, replace = TRUE),  # Random values from A to Q
  val1 = sample(likert_levels, 50, replace = TRUE) # Random values from Likert levels
  
)

# View the first few rows of the dataframe
print(df)


i am using the solution previously asked here
to expand it and ask for a another bar plot now to be added next to it that will contain the counts of each category. Each bar i want to match the horizontal likert scale on the left. How can i succed it in R ?

ibrary(tidyverse)
library(ggstats)

dat <- df |>
  mutate(
    across(-var, ~ factor(.x, likert_levels))
  ) |>
  pivot_longer(-var, names_to = "group") |>
  count(var, value, group) |>
  complete(var, value, group, fill = list(n = 0)) |>
  mutate(
    prop = n / sum(n),
    prop_lower = sum(prop[value %in% c("Strongly disagree", "Disagree")]),
    prop_higher = sum(prop[value %in% c("Strongly agree", "Agree")]),
    .by = c(var, group)
  ) |>
  arrange(group, prop_lower) |>
  mutate(
    y_sort = paste(var, group, sep = "."),
    y_sort = fct_inorder(y_sort)
  )

top10 <- dat |>
  distinct(group, var, prop_lower) |>
  slice_max(prop_lower, n = 10, by = group)

dat <- dat |>
  semi_join(top10)

dat_tot <- dat |>
  distinct(group, var, y_sort, prop_lower, prop_higher) |>
  pivot_longer(-c(group, var, y_sort),
    names_to = c(".value", "name"),
    names_sep = "_"
  ) |>
  mutate(
    hjust_tot = ifelse(name == "lower", 1, 0),
    x_tot = ifelse(name == "lower", -1, 1)
  )

ggplot(dat, aes(y = y_sort, x = prop, fill = value)) +
  geom_col(position = position_likert(reverse = FALSE)) +
  geom_text(
    aes(
      label = label_percent_abs(hide_below = .05, accuracy = 1)(prop),
      color = after_scale(hex_bw(.data$fill))
    ),
    position = position_likert(vjust = 0.5, reverse = FALSE),
    size = 3.5
  ) +
  geom_label(
    aes(
      x = x_tot,
      label = label_percent_abs(accuracy = 1)(prop),
      hjust = hjust_tot,
      fill = NULL
    ),
    data = dat_tot,
    size = 3.5,
    color = "black",
    fontface = "bold",
    label.size = 0,
    show.legend = FALSE
  ) +
  scale_y_discrete(labels = \(x) gsub("\\..*$", "", x)) +
  scale_x_continuous(
    labels = label_percent_abs(),
    expand = c(0, .15)
  ) +
  scale_fill_brewer(palette = "BrBG") +
  facet_wrap(~group,
    scales = "free_y", ncol = 1,
    strip.position = "right"
  ) +
  theme_light() +
  theme(
    legend.position = "bottom",
    panel.grid.major.y = element_blank()
  ) +
  labs(x = NULL, y = NULL, fill = NULL)

enter image description here

My effort


dat%>%
  select(var,n)%>%
  group_by(var)%>%
  summarise(count = sum(n))%>%
  ggplot(., aes(y = var, x = count)) +
  geom_bar(stat = "identity", fill = "lightgrey")+labs(x="Response Count",y="")+
  geom_text(aes(label = count),position = position_stack(vjust = .5)) +
  theme_bw()+
  theme(
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank(),
    axis.text.x = element_blank(),   # Remove x-axis text
    axis.ticks.x = element_blank()    # Remove x-axis ticks
  )


enter image description here but how i match bind them next to each other to match also the two bars ?


Solution

  • You could do this with the patchwork package, if I'm understanding your question correctly. Adding the plots together will put them side-by-side matching up the plotting regions:

    Data

    library(tibble)
    library(tidyverse)
    library(ggplot2)
    library(ggpubr)
    library(ggstats)
    
    # Define categories and Likert levels
    var_levels <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q")
    
    likert_levels <- c(
      "Strongly disagree",
      "Disagree",
      "Neither agree nor disagree",
      "Agree",
      "Strongly agree"
    )
    
    # Set seed for reproducibility
    set.seed(42)
    
    # Create the dataframe with three Likert response columns
    df <- tibble(
      var = sample(var_levels, 50, replace = TRUE),  # Random values from A to Q
      val1 = sample(likert_levels, 50, replace = TRUE) # Random values from Likert levels
      
    )
    
    
    library(tidyverse)
    library(ggstats)
    
    dat <- df |>
      mutate(
        across(-var, ~ factor(.x, likert_levels))
      ) |>
      pivot_longer(-var, names_to = "group") |>
      count(var, value, group) |>
      complete(var, value, group, fill = list(n = 0)) |>
      mutate(
        prop = n / sum(n),
        prop_lower = sum(prop[value %in% c("Strongly disagree", "Disagree")]),
        prop_higher = sum(prop[value %in% c("Strongly agree", "Agree")]),
        .by = c(var, group)
      ) |>
      arrange(group, prop_lower) |>
      mutate(
        y_sort = paste(var, group, sep = "."),
        y_sort = fct_inorder(y_sort)
      )
    
    top10 <- dat |>
      distinct(group, var, prop_lower) |>
      slice_max(prop_lower, n = 10, by = group)
    
    dat <- dat |>
      semi_join(top10)
    #> Joining with `by = join_by(var, group, prop_lower)`
    
    dat_tot <- dat |>
      distinct(group, var, y_sort, prop_lower, prop_higher) |>
      pivot_longer(-c(group, var, y_sort),
                   names_to = c(".value", "name"),
                   names_sep = "_"
      ) |>
      mutate(
        hjust_tot = ifelse(name == "lower", 1, 0),
        x_tot = ifelse(name == "lower", -1, 1)
      )
    

    Plot 1

    p1 <- ggplot(dat, aes(y = y_sort, x = prop, fill = value)) +
      geom_col(position = position_likert(reverse = FALSE)) +
      geom_text(
        aes(
          label = label_percent_abs(hide_below = .05, accuracy = 1)(prop),
          color = after_scale(hex_bw(.data$fill))
        ),
        position = position_likert(vjust = 0.5, reverse = FALSE),
        size = 3.5
      ) +
      geom_label(
        aes(
          x = x_tot,
          label = label_percent_abs(accuracy = 1)(prop),
          hjust = hjust_tot,
          fill = NULL
        ),
        data = dat_tot,
        size = 3.5,
        color = "black",
        fontface = "bold",
        label.size = 0,
        show.legend = FALSE
      ) +
      scale_y_discrete(labels = \(x) gsub("\\..*$", "", x)) +
      scale_x_continuous(
        labels = label_percent_abs(),
        expand = c(0, .15)
      ) +
      scale_fill_brewer(palette = "BrBG") +
      facet_wrap(~group,
                 scales = "free_y", ncol = 1,
                 strip.position = "right"
      ) +
      theme_light() +
      theme(
        legend.position = "bottom",
        panel.grid.major.y = element_blank()
      ) +
      labs(x = NULL, y = NULL, fill = NULL)
    

    Plot 2

    p2 <- dat%>%
      select(var,n)%>%
      group_by(var)%>%
      summarise(count = sum(n))%>%
      ggplot(., aes(y = var, x = count)) +
      geom_bar(stat = "identity", fill = "lightgrey")+labs(x="Response Count",y="")+
      geom_text(aes(label = count),position = position_stack(vjust = .5)) +
      theme_bw()+
      theme(
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        axis.text.x = element_blank(),   # Remove x-axis text
        axis.ticks.x = element_blank()    # Remove x-axis ticks
      )
    

    Combine

    library(patchwork)
    p1 + p2 + plot_layout(guides = "collect") & theme(legend.position="bottom")
    

    Created on 2024-11-13 with reprex v2.1.0