3

I have been trying to find a solution to this problem for a little while now and all the answers don't seem to be quite what I'm looking for.

I'm sure the answer to this is probably simple and I'm overthinking it.

I've been trying to have a table next to a barplot which corresponds to the same observations in the table. However, the table doesn't seem to line up with the size of the plot because it has too much white space or is too small.

Is there a way that I can have the title of the plot and the title of the columns in the table lineup?

data(mtcars)
library(ggplot2)
library(dplyr)
library(grid)
library(gridExtra)
library(cowplot)

data <- mtcars %>% select(mpg, disp, cyl, qsec) %>% tibble::rownames_to_column("Car Name") %>% slice(1:7)

data$`Car Name` <- factor(data$`Car Name`, levels = data$`Car Name`)

t <- tableGrob(data %>% slice(1:7) %>% select(-mpg), 
               theme = ttheme_minimal(), 
               rows = NULL)
plot(t)

p <- ggplot(data = data, aes(x = mpg, y = `Car Name`)) + 
  geom_bar(stat = "identity", fill = "white", color = "black", alpha = 0.3, size = .75) + theme_classic() +
  theme(axis.text.y = element_blank(),
        axis.title.y = element_blank(),
        axis.title.x = element_blank(),
        plot.title =  element_text(face = "bold")) +
  ggtitle("No. of mpg") +
  scale_x_continuous(expand = expansion(mult = c(0, .1)), limits = c(0,30)) +
  scale_y_discrete(limits=rev)
p

grid.arrange(t, p, nrow = 1)

This is what I have done to make the table and plot. I have a basic grid.arrange at the bottom to highlight my issue.

The image here highlights the differences in size between the table and the plot enter image description here

jpsmith
  • 11,023
  • 5
  • 15
  • 36
AJPomp96
  • 31
  • 2

2 Answers2

3
library(dplyr); library(gt); library(gtExtras)
mtcars %>%
  tibble::rownames_to_column("Car Name") %>%
  select(1, disp, cyl, qsec, mpg) %>%
  slice(1:7) %>%
  gt() %>%
  gt_plt_bar(column = mpg, color = "gray80", scale_type = "number", 
             text_color = "gray30")

enter image description here

Alternatively, we could use gt::ggplot_image to create custom plots and put them in each row. (This took me a fair bit of fiddling and it's still hard to get the aspect ratios where I wanted them.)

library(tidyverse); library(gt)
plot_mpg <- function(df) {
  ggplot(data = df, aes(mpg, "a")) +
    geom_col(orientation = "y", fill = NA, color = "gray20", size = 4) +
    geom_text(aes(label = mpg), hjust = 1.1, size = 60) +
    coord_fixed(ratio = 5, xlim = c(0, 30)) +
    theme_void()
}

mtcars %>%
  tibble::rownames_to_column("Car Name") %>%
  nest(data = mpg) %>%
  mutate(plot = map(data, plot_mpg)) %>%
  select(1, disp, cyl, qsec, plot) %>%
  mutate(mpg = NA) %>% # placeholder column
  slice(1:7) -> a

gt(a) %>%
  cols_width(mpg ~ px(80)) %>%
  tab_options(data_row.padding = px(2)) %>%
  text_transform(
    locations = cells_body(mpg),
    fn = function(x) { map(a$plot, ~ggplot_image(., height = px(20), aspect_ratio = 6)) }
  ) %>%
  cols_hide(plot)
  

enter image description here

Jon Spring
  • 55,165
  • 4
  • 35
  • 53
2

The simplest way I've found in most posts before is to insert the text as another plot rather than a table, and use patchwork to line them up:

library(tidyverse)
library(patchwork)

data <- mtcars %>% select(mpg, disp, cyl, qsec) %>% rownames_to_column("Car Name") %>% slice(1:7)

data$order <- factor(as.integer(factor(data$`Car Name`)))

data_rs <- data |> 
  select(order, disp, cyl, qsec, `Car Name`) |> 
  pivot_longer(-order, names_to = "var", values_to = "val", values_transform = as.character)

p1 <- ggplot(data = data, aes(x = mpg, y = order)) + 
  geom_bar(stat = "identity", fill = "white", color = "black", alpha = 0.3, linewidth = .75) +
  theme_classic() +
  theme(axis.text.y = element_blank(),
        axis.title.y = element_blank(),
        axis.title.x = element_blank(),
        strip.background = element_blank(),
        strip.text =  element_text(face = "bold", size = 12, hjust = 0)) +
  scale_x_continuous(expand = expansion(mult = c(0, .1)), limits = c(0,30)) +
  scale_y_discrete(limits=rev) +
  facet_wrap(~"No. of mpg")

p2 <- ggplot(data_rs, (aes(x = 1, y = order, label = val))) +
  geom_text() +
  facet_wrap(~fct_inorder(var), nrow = 1) +
  scale_y_discrete(limits=rev) +
  theme(axis.title.y = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks = element_blank(),
        axis.text.x = element_blank(),
        axis.title.x = element_blank(),
        panel.grid = element_blank(),
        strip.background = element_blank(),
        panel.background = element_blank(),
        strip.clip = "off",
        strip.text =  element_text(face = "bold", size = 12)) +
  coord_cartesian(clip = "off")

p2 + p1 + plot_layout(widths = c(0.7, 0.3))

This first pivots your dataframe, then uses each variable column you want to position as a facet, with the column name at the top. You can play around with display using the theme elements to tidy up a bit, but hopefully a helpful way to do this neatly and quickly!

Andy Baxter
  • 5,833
  • 1
  • 8
  • 22
  • 1
    This is exactly what I was looking for thank you! I thought gridextra was going to be the answer but this looks much nicer I believe. Is there a way to add `Car Name` as another column on this table and remove the text on the left? – AJPomp96 Nov 29 '22 at 23:43
  • Just updated to leave the `Car Name` column in, doing this by creating an anonymous, ordered `order` factor column before splitting datasets. Also turned clipping off for text as the names were sometimes getting cut short. Hope that helps! – Andy Baxter Nov 29 '22 at 23:53
  • If the facet sizes and plot sizes are getting fiddly, using the similar principle of creating a pseudo-table using `ggplot2` text can be helpful too - I have used [this approach](https://rpubs.com/mbounthavong/forest_plots_r) for forest plots in publications before. – Andy Baxter Nov 29 '22 at 23:57
  • Another thing I have noticed is that these column titles are in alphabetical order. If I wanted Car Name to be last how would I go about editing the order of the columns? – AJPomp96 Nov 30 '22 at 02:32
  • Simplest way would be to order the columns in your data before pivoting, then using `facet_wrap(~fct_inorder(var))` to tell ggplot to put the facets in that order. Have done that in edited answer above :) – Andy Baxter Nov 30 '22 at 08:57