2

Using R, I want to create a ggplot facet chart that includes a zero line (using geom_hline) in each facet having data that straddles the zero line, while excluding the zero line in each facet having exclusively positive or exclusively negative data. Here is a reprex.

library(ggplot)
dfw <- data.frame(
  date=c(1,2,3,4,5,6),
  A=c(50,53,62,56,54,61),
  B=c(-3,-1,5,7,4,-2),
  C=c(6,4,5,2,3,-2)
)
dfl <- pivot_longer(dfw,2:4,names_to="nms",values_to="val")
# With no zero line: works fine
ggplot(dfl)+
  geom_line(aes(x=date,y=val))+
  facet_wrap(~nms,scales="free_y")
# With zero line for all facets: works fine
ggplot(dfl)+
  geom_hline(yintercept=0)+
  geom_line(aes(x=date,y=val))+
  facet_wrap(~nms,scales="free_y")
# With zero line, but only for facets with some data points greater than zero
# and other data points less than zero: Does not work
c0 <- ggplot(dfl)+
  geom_line(aes(x=date,y=val))
if (min(y)>0 | max(y)<0) { # Error: object 'y' not found
  c0 <- c0+geom_hline(yintercept=0) 
} 
c0 <- c0+facet_wrap(~nms,scales="free_y")
Phil Smith
  • 430
  • 2
  • 12

2 Answers2

4

You can compute y-intercepts for each facet before plotting, setting to 0 if the data includes 0 and NA otherwise:

library(ggplot2)
library(dplyr)

dfl <- dfl %>% 
  mutate(
    zline = ifelse(min(val) < 0 & max(val > 0), 0, NA),
    .by = nms    # `.by` argument added in dplyr v1.1.0
  )

ggplot(dfl) +
  geom_hline(aes(yintercept = zline), na.rm = TRUE) +
  geom_line(aes(x = date, y = val)) +
  facet_wrap(~nms, scales = "free_y")

zephryl
  • 14,633
  • 3
  • 11
  • 30
  • Thanks. I tried this code, but for me all three facets had zero lines, including the one for A. Any ideas why might it be that it works for you but not for me? I use RStudio Version 2022.12.0+353 (2022.12.0+353) and I restarted R before running the code to clear the memory. I used library (tidyverse) rather than dplyr because of the pivot_longer function. – Phil Smith Feb 03 '23 at 19:57
  • Further testing indicates that your code to calculate zline is not working properly, for me at least. When I changed the 'A' values for zline in dfl from 0 to NA, using a for loop, it worked. That's good enough me. Thanks so much. – Phil Smith Feb 03 '23 at 20:04
  • @PhilSmith it may be because you don’t have the latest version of dplyr, which includes the `.by` argument for `mutate()`. You could try `install.packages("dplyr")` to update. Alternatively you could use `group_by(nms) %>% mutate(zline = ifelse(min(val) < 0 & max(val > 0), 0, NA)) %>% ungroup()` in place of the above `mutate()` call. – zephryl Feb 03 '23 at 20:21
  • Yes, reinstalling dyplr did the trick. Thanks again. – Phil Smith Feb 03 '23 at 20:50
0

Here is another solution using a trick:

We say: if all val are > 0 then color our hline white (then it will be invisible). The underlying trick is to use I(). I learned this from @tjebo here How to conditionally highlight points in ggplot2 facet plots - mapping color to column

dfl %>% 
  group_by(nms) %>% 
  mutate(color = ifelse(all(val > 0), "white", "black")) %>% 
  ggplot(aes(x=date,y=val))+
  geom_hline(aes(color = I(color), yintercept=0))+
  geom_line()+
  facet_wrap(~nms,scales="free_y")

enter image description here

TarJae
  • 72,363
  • 6
  • 19
  • 66
  • Thanks. This does not give what I want though, because zero still appears on the val scale for A and the values for A are all at the top of the plot. I want the val scale to be limited between the min and max values for A. I guess my phrasing of the question was unclear. – Phil Smith Feb 03 '23 at 20:20