0

For some of my charts I would like to change the formatting of the numbers on the x axis for each facet conditionally on the label of the facet e.g. based on the order of magnitude (e.g. k, M, B etc.).

In the example below with the mtcars data, I would like the facet with gear==3 to keep the original number, with gear==4 create a "k" format and with gear==5 an "M" format. So that the two latter would be expressed in thousands and millions respectively.

Is is possible to use the label_value from the labeller to apply conditionally a format/function?

library(tidyverse)

d<-mtcars%>%mutate(mpg2=ifelse(gear==5,mpg*1000000,mpg))

ggplot(d) +
      geom_point(aes(mpg2, cyl)) +
      facet_wrap(~ gear, scales = "free")+
      scale_x_continuous(name = NULL,  labels = function(l) {
          return(paste0(round(l / 1000, 1), "K"))
      })
Lod
  • 609
  • 7
  • 19
  • I'm not sure how to make the gear change the format, but you could certainly make different scales of numbers be displayed differently for all facets... `ggplot(d) + geom_point(aes(mpg2, cyl)) + facet_wrap(~ gear, scales = "free") + scale_x_continuous(name = NULL, labels = function(l) { scaled = case_when(l > 1E6 ~ paste0(formatC(l/1E6, digits = 0, big.mark = ",", format = "f"), "M"), l > 1E3 ~ paste0(formatC(l/1E3, digits = 0, big.mark = ",", format = "f"), "K"), TRUE ~ paste0(l)) return(scaled) })` – Jon Spring Oct 22 '18 at 22:16
  • @JonSpring I think that qualifies as a pretty good answer, why not post it? – Marius Oct 22 '18 at 22:21

1 Answers1

2

If you're looking to allow the scale formatting to vary according the value, here's a way using dplyr::case_when. (I don't know how to show the same numbers differently based on the facet, though.)

Edit to show wider range in example:

d <- mtcars %>% mutate(mpg2 = mpg ^ gear / carb)


ggplot(d) +
  geom_point(aes(mpg2, cyl)) +
  facet_wrap(~ gear, scales = "free")+
  scale_x_continuous(name = NULL,  trans = "log2", labels = function(l) {
     scaled = case_when(l >= 1E6 ~ paste0(formatC(l/1E6, digits = 0, big.mark = ",", format = "f"), "M"),
                        l >= 1E3 ~ paste0(formatC(l/1E3, digits = 0, big.mark = ",", format = "f"), "K"),
                        TRUE     ~ paste0(l))
     return(scaled)
})

enter image description here

I think it works pretty well.

If you need specific control over the labeling by facet, you might consider either faking the facet axis text with geom_text(aes(label = your_preferred_label)) and turning clipping off in coord_ so you can put text outside the plot range, or (more simply) combining plots with patchwork or cowplot. I'm not aware of a way to make the labeller itself aware of facet info.

Jon Spring
  • 55,165
  • 4
  • 35
  • 53
  • It's a good start that would do in most use cases. but imagine that you have a range of 900 to 15000 then it would result – Lod Oct 23 '18 at 05:37
  • 1
    Updated my answer to show what happens when you have a range across scales like you mention. – Jon Spring Oct 23 '18 at 05:53
  • In a way that could be perceived as inconsistent, likewise in cases where the order of magnitude is the same but the unit is different (e.g. currencies) this approach wouldn't work. I am looking for how toread the labeller's label_value in such a way that I can use it in the labels as a condition, to avoid entering into solutions based on grobs. – Lod Oct 23 '18 at 05:58
  • I will keep it open a little more to see whether someone can clarify the "reading label_value" bit, but it helps me already for many of my charts – Lod Oct 23 '18 at 06:05
  • 1
    Here's some discussion on why this is not built into ggplot2: https://stackoverflow.com/questions/11585954/varying-axis-labels-formatter-per-facet-in-ggplot-r and https://github.com/tidyverse/ggplot2/issues/1613 – Jon Spring Oct 23 '18 at 06:41