ggplot - facet wrap - adjusting scale to show apparent differences between values

by user3206440   Last Updated January 19, 2018 01:26 AM

I have a dataframe like below:

text <- "
brand   a   b   c   d   e   f
nissan  99.21   99.78   6496    1.28    216 0.63
toyota  99.03   99.78   7652    1.39    205 0.60
"
df <- read.table(textConnection(text), sep="\t", header = T)

I'm trying to get all the variables for the two groups plotted in a single ggplot using face_wrapas below:

library(reshape2)
library(ggplot2)
library(ggthemes)
library(RColorBrewer)
ggplot(melt(df, id = "brand")) + 
  aes(brand, value, fill = brand) + 
  geom_bar(stat = "identity", position='dodge') +
  geom_text(data=melt(df, id = "brand"), angle = 0, 
               aes(brand, value,
                   label = ifelse(value > 100, round(value, 0), value) ) ) +
  facet_wrap(~ variable, scales = "free_y") + 
  scale_fill_brewer(palette = "Paired") + 
    theme(
      legend.position = "top",
      strip.text.y = element_text(angle = 0),
      axis.text.x = element_blank(),
      axis.text.y = element_blank(),
      axis.ticks = element_blank()
      )

It works well except for one thing. The apparent differences between the group for value of a variable is not coming out very well visually. For example for variable a I want the height of the bars to be such that it is clear which is the higher one in an easier fashion. How do I get the height difference between such close values to be larger?

enter image description here

Tags : r ggplot2


Answers 4


If you really need to use a bar plot, you can use geom_rect & manually define a different ymin for each facet.

Modify dataframe:

library(dplyr)

df2 <- melt(df, id = "brand") %>%
  group_by(variable) %>%
  mutate(ymax = value, 
         ymin = ifelse(diff(value) == 0, 0,
                       min(value) - (max(value) - min(value)) / 2),
         yblank = ifelse(diff(value) == 0, value * 2,
                         max(value) + (max(value) - min(value)) / 2),
         x = as.integer(brand),
         xmin = x - 0.4,
         xmax = x + 0.4,
         label = ifelse(value > 100, round(value, 0), value)) %>%
  ungroup()

> df2
# A tibble: 12 x 10
   brand  variable    value     ymax     ymin   yblank     x  xmin  xmax    label
   <fctr> <fctr>      <dbl>    <dbl>    <dbl>    <dbl> <int> <dbl> <dbl>    <dbl>
 1 nissan a          99.2     99.2     98.9     99.3       1 0.600  1.40   99.2  
 2 toyota a          99.0     99.0     98.9     99.3       2 1.60   2.40   99.0  
 3 nissan b          99.8     99.8      0      200         1 0.600  1.40   99.8  
 4 toyota b          99.8     99.8      0      200         2 1.60   2.40   99.8  
 5 nissan c        6496     6496     5918     8230         1 0.600  1.40 6496    
 6 toyota c        7652     7652     5918     8230         2 1.60   2.40 7652    
 7 nissan d           1.28     1.28     1.23     1.44      1 0.600  1.40    1.28 
 8 toyota d           1.39     1.39     1.23     1.44      2 1.60   2.40    1.39 
 9 nissan e         216      216      200      222         1 0.600  1.40  216    
10 toyota e         205      205      200      222         2 1.60   2.40  205    
11 nissan f           0.630    0.630    0.585    0.645     1 0.600  1.40    0.630
12 toyota f           0.600    0.600    0.585    0.645     2 1.60   2.40    0.600

This creates bars such that the shorter bar in each facet occupies one quarter of the facet's height, while the taller bar occupies three quarters. If the two bars are exactly the same height, they both occupy half the facet's height. If you want to tweak the appearance, just change ymin / yblank.

Plot:

ggplot(df2,
       aes(x = x, y = ymax, fill = brand)) + 
  geom_rect(aes(xmin = xmin, xmax = xmax,
                ymin = ymin, ymax = ymax)) +
  geom_text(aes(label = label), 
            vjust = -1) + # position labels slightly above top of each bar
  geom_blank(aes(y = yblank)) +
  facet_wrap(~ variable, scales = "free_y") + 
  scale_fill_brewer(palette = "Paired") + 
  theme(
    legend.position="top",
    strip.text.y = element_text(angle = 0),
    axis.text=element_blank(),
    axis.ticks = element_blank()
  )

plot

Z.Lin
Z.Lin
January 11, 2018 10:31 AM

I'm sure people will happily tell you how to hack ggplot2 so you can have bars start at an arbitrary y value. However, you need to be aware that the result will be a meaningless junk chart, in particular if you strip the y axis of axis ticks. I would recommend reading this blog post on the principle of proportional ink.

One solution that you could pursue is to plot bars that represent the ratio of the two variables, like so:

text <- "
brand   a   b   c   d   e   f
nissan  99.21   99.78   6496    1.28    216 0.63
toyota  99.03   99.78   7652    1.39    205 0.60
"

df_wide <- read.table(textConnection(text), sep="\t", header = T)

library(ggplot2)
library(tidyr)
library(dplyr)

df_long <- gather(df_wide, variable, value, -brand) %>%
  spread(brand, value) %>%
  mutate(ratio = nissan/toyota,
         label = paste(signif(nissan, 3), signif(toyota, 3), sep = " / "),
         vjust = ifelse(ratio >= 1, -.5, 1.5)) %>%
  mutate(ratio = ifelse(ratio == 1, 1.001, ratio))

ggplot(df_long, aes(variable, ratio, fill = (ratio>=1))) + 
  geom_col() +
  geom_text(aes(label = label, vjust = vjust)) +
  scale_y_log10(name = "ratio Nissan / Toyota",
                breaks = c(.85, .9, .95, 1, 1.05, 1.1),
                expand = c(.15, 0)) +
  scale_fill_brewer(palette = "Paired", guide = "none")

enter image description here

The result is only a single bar per pairing, but the bar height accurately reflects the relative magnitude of the two variables. And you don't seem to be interested in the absolute magnitude anyways, since you're using free y axis scaling in your original facet_wrap() command.

Claus Wilke
Claus Wilke
January 15, 2018 05:28 AM

I agree with the answers and comments that it is not a good idea to manipulate the relative bar length. If you just want to show which bar is higher in current plot, you can use another aesthetic to highlight it, for example put the higher bar in a box like the plot below.

enter image description here

library(reshape2)
library(ggplot2)
library(ggthemes)
library(RColorBrewer)

library(data.table)
library(magrittr)


# add a winner column to mark the winner in red
aaa <- melt(df, id = "brand") %>%
    setDT() %>%
    .[, winner := ifelse(value > mean(value), "red", "NA"), by = variable]

# plot and show the higher bar in read box
ggplot(aaa, aes(brand, value, fill = brand)) + 
    geom_bar(aes(color = winner), stat = "identity", position='dodge') +
    geom_text(data=melt(df, id = "brand"), angle = 0, 
              aes(brand, value,
                  label = ifelse(value > 100, round(value, 0), value) ) ) +
    facet_wrap(~ variable, scales = "free_y") + 
    scale_fill_brewer(palette = "Paired") + 
    scale_color_identity() +
    theme(
        legend.position = "top",
        strip.text.y = element_text(angle = 0),
        axis.text.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks = element_blank()
    )
GL_Li
GL_Li
January 18, 2018 21:49 PM

It's misleading to start scales at arbitrary y values as many on this thread have pointed out.

One answer that hasn't been considered is to put the scales on an log scale with scale_y_log10(), and combine with geom_text labels to show differences between facetted groups.

data

df <- data_frame(
  pair = letters[1:6] %>% rep(2),
  brand = c("nissan", "toyota") %>% rep(each = 6),
  value = c(99.21, 99.78, 6496, 1.28, 216, 0.63, 
            99.03, 99.78, 7652, 1.39, 205, 0.60)
)

plot

df %>% 
  ggplot(aes(x = brand, y = value)) +
  geom_col(aes(fill = brand)) +
  geom_text(aes(label = value), vjust = -1) +
  scale_y_log10() +
  facet_wrap(~pair) +
  theme_bw()

enter image description here

Rich Pauloo
Rich Pauloo
January 19, 2018 01:23 AM

Related Questions


R - How to find points within specific Contour

Updated May 28, 2015 23:11 PM

How to see the code of a stored plot (ggplot)

Updated May 29, 2015 01:11 AM

R ggplot remove certain items from legend

Updated April 03, 2015 23:11 PM

add second axis label

Updated April 10, 2015 23:11 PM

Error in facet_grid in ggplot2

Updated October 09, 2016 09:11 AM