1

Sometimes I'd like to present data that refer to periods (not to points in time) as a step function. When e.g. data are per-period averages, this seems more appropriate than using a line connecting points (with geom_line). Consider, as a MWE, the following:

df = data.frame(x=1:8,y=rnorm(8,5,2))
ggplot(df,aes(x=x,y=y))+geom_step(size=1)+scale_x_continuous(breaks=seq(0,8,2))

This gives

enter image description here

However, the result is not fully satisfactory, as (1) I'd like the final observation to be represented by an horizontal segment and (2) I'd like to have labels on the x-axis aligned at the center of the horizontal line. What I want can be obtained with some hacking:

df %>% rbind(tail(df,1) %>% mutate(x=x+1)) %>%
   ggplot(aes(x,y))+geom_step(size=1)+
   scale_x_continuous(breaks=seq(0,12,2))+
   theme(axis.ticks.x=element_blank(),axis.text.x=element_text(hjust=-2))

which produces:

enter image description here

This corresponds to what I am looking for (except that the horizontal alignment of labels requires some fine tuning and is not perfect). However, I am not sure this is the best way to proceed and I wonder if there is a better way.

Massimo2013
  • 533
  • 4
  • 17
  • 1
    I asked a separate question about `geom_step()` and I accepted an answer that also addresses this issue. Check out the second half of [this answer](https://stackoverflow.com/a/58014575/9855745). – OTStats Dec 14 '20 at 13:35

1 Answers1

1

Does this work for you? It comes down to altering the data as it is passed rather than changing the plotting code per se (as is often the case in ggplot)

Essentially what we do is add an extra copy of the final y value on to the end of the data frame at an incremented x value.

To make the horizontal segments line up to the major axis breaks, we simply subtract 0.5 from the x value.

ggplot(rbind(df, data.frame(x = 9, y = tail(df$y, 1))),
             aes(x = x - 0.5, y = y)) + 
  geom_step(size = 1)+
  scale_x_continuous(breaks = seq(0, 8, 2), name = "x",
                     minor_breaks = seq(0, 8, 1) + 0.5) +
  theme_bw() +
  theme(panel.grid.major.x = element_blank(),
        panel.grid.minor = element_line())

enter image description here

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • This is basically what I did and reported in my question, with the difference that here vertical grid lines cut the horizontal lines in the middle (which I don't want) – Massimo2013 Dec 14 '20 at 13:56
  • @Massimo2013 see my update. Is that what you mean? – Allan Cameron Dec 14 '20 at 14:02
  • Yes, but again this is equivalent to my initial solution. I was wondering if there is a more «direct» solution that requires neither a modification of the database nor hacking grids/labels – Massimo2013 Dec 14 '20 at 14:37
  • @Massimo2013 the values at the tick marks on the x axis represent the acutal numbers, so if your data frame has the point (x = 4, y = 6) in it, then that's where it will be plotted. If you don't want it there you have to change either the data or the x axis. My solution isn't quite the same as yours - you are trying to change the position of the text labels using hjust, which I agree isn't satisfactory, but my solution shows how to do it with perfect, rescalable and reproducible alignment every time. I could write a whole new geom to do the same thing, but I think that's overkill – Allan Cameron Dec 14 '20 at 14:37
  • @Massimo2013 you don't need to make any changes to your data frame, you are just modifying the copy that is passed to the function. – Allan Cameron Dec 14 '20 at 14:38