How to deal with time series data to create new x and y axis lines to ggplot graph?

57 Views Asked by At

I'm trying to present a ggplot graph similar to Base R graphs. The issue I have is dealing with time series data (date and time) to draw the x and y axis properly.

I used the same approach provided by Baptiste to create an x and y axis function: R-style axes with ggplot

However, it doesn't look right (see image below). I am not sure how to fix the y-axis and I also don't know how to fix the x-axis so that the limits are only between 07:00:00AM and 10:00:00AM. Any help would be appreciated!

enter image description here

#y-axis
base_breaks_y <- function(x){
  b <- pretty(x)
  d <- data.frame(x=as.POSIXct(c("2019-01-30 07:00:00")), xend=as.POSIXct(c("2019-01-30 10:00:00")), y=min(b), yend=max(b))
  list(geom_segment(data=d, aes(x=x, y=y, xend=xend, yend=yend), inherit.aes=FALSE),
       scale_y_continuous(breaks=b))
}

#x-axis 
base_breaks_x <- function(x){
  b <- pretty(x)
  d <- data.frame(y=-Inf, yend=-Inf, x=min(b), xend=max(b))
  list(geom_segment(data=d, aes(x=x, y=y, xend=xend, yend=yend), inherit.aes=FALSE),
       scale_x_datetime(breaks=b, labels=date_format("%H:%M")))
}
#plot 
ggplot(data=df1, aes(x=date.time, y=value)) +
  geom_line() +
  #labels 
  ylab("Value") +
  xlab("Time Series") +
  #aesthetics
  theme_bw() +
  theme(panel.border = element_blank(),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        text = element_text(size=11)) +
  #scale date time range 
  base_breaks_x(df1$date.time) +
  base_breaks_y(df1$value)

Example data:

 > dput(df1)
structure(list(date.time = structure(c(1548833480, 1548833555, 
1548833630, 1548833705, 1548833781, 1548833856, 1548833931, 1548834006, 
1548834083, 1548834158, 1548834233, 1548834308, 1548834384, 1548834459, 
1548834534, 1548834609, 1548834685, 1548834760, 1548834835, 1548834910, 
1548834987, 1548835062, 1548835137, 1548835212, 1548835288, 1548835363, 
1548835438, 1548835513, 1548835590, 1548835665, 1548835740, 1548835815, 
1548835891, 1548835966, 1548836041, 1548836116, 1548836192, 1548836267, 
1548836342, 1548836417, 1548836494, 1548836569, 1548836644, 1548836719, 
1548836795, 1548836870, 1548836945, 1548837020, 1548837096, 1548837171, 
1548837246, 1548837321, 1548837398, 1548837473, 1548837548, 1548837623, 
1548837699, 1548837774, 1548837849, 1548837924, 1548838000, 1548838075, 
1548838150, 1548838225, 1548838302, 1548838377, 1548838452, 1548838527, 
1548838603, 1548838678, 1548838753, 1548838828, 1548838905, 1548838980, 
1548839055, 1548839130, 1548839206, 1548839281, 1548839356, 1548839431, 
1548839507, 1548839582, 1548839657, 1548839732, 1548839809, 1548839884, 
1548839959, 1548840034, 1548840110, 1548840185, 1548840260, 1548840335, 
1548840411, 1548840486, 1548840561, 1548840636, 1548840713, 1548840788, 
1548840863, 1548840938), class = c("POSIXct", "POSIXt"), tzone = "UTC"), 
    value = c(3139, 2261, 2147, 2184, 2469, 2356, 2231, 1894, 
    1679, 1710, 1634, 1642, 1611, 1484, 1415, 1404, 1367, 1343, 
    1261, 1252, 1190, 1135, 1072, 1014, 979, 943, 935, 947, 952, 
    943, 937, 938, 920, 887, 867, 910, 954, 1036, 1106, 1160, 
    1216, 1226, 1288, 1368, 1348, 1363, 1474, 1420, 1276, 1310, 
    1357, 1207, 1051, 951, 923, 963, 1002, 1044, 1118, 1178, 
    1217, 1246, 1201, 1305, 1352, 1367, 1379, 1375, 1433, 1537, 
    1625, 1663, 1756, 1831, 1802, 1858, 2003, 1915, 1874, 1847, 
    1850, 2028, 2220, 2396, 2689, 2063, 2857, 2021, 2140, 2282, 
    2397, 2613, 2590, 2662, 2615, 2693, 2802, 2805, 2945, 3151
    )), row.names = c(NA, 100L), class = "data.frame")
2

There are 2 best solutions below

0
stefan On BEST ANSWER

One option to fix your issue would be to use x = xend = -Inf for the y axis as in the original post but instead of setting it via the aesthetics, which will result in an error

Error: Invalid input: time_trans works with objects of class POSIXct only

set them as arguments:

library(ggplot2)
library(scales)

base_breaks_y <- function(x) {
  b <- pretty(x)
  d <- data.frame(y = min(b), yend = max(b))
  list(
    geom_segment(
      data = d, aes(y = y, yend = yend),
      x = -Inf, xend = -Inf, inherit.aes = FALSE
    ),
    scale_y_continuous(breaks = b)
  )
}

ggplot(data = df1, aes(x = date.time, y = value)) +
  geom_line() +
  labs(y = "Value", x = "Time Series") +
  theme_bw() +
  theme(
    panel.border = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    text = element_text(size = 11)
  ) +
  base_breaks_x(df1$date.time) +
  base_breaks_y(df1$value)

enter image description here

EDIT Getting the limits for the x axis right requires slightly more work. First we have to set the limits. Second, we have to take account of the limits when computing the breaks and the range for the geom_segment:

base_breaks_x <- function(x, limits = NULL) {
  x <- c(x, limits)
  b <- pretty(x)
  
  d <- data.frame(y = -Inf, yend = -Inf, x = min(b), xend = max(b))
  list(
    geom_segment(data = d, aes(x = x, y = y, xend = xend, yend = yend), inherit.aes = FALSE),
    scale_x_datetime(
      breaks = b, labels = date_format("%H:%M"),
      limits = limits
    )
  )
}

ggplot(data = df1, aes(x = date.time, y = value)) +
  geom_line() +
  labs(y = "Value", x = "Time Series") +
  theme_bw() +
  theme(
    panel.border = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    text = element_text(size = 11)
  ) +
  base_breaks_x(
    df1$date.time,
    limits = as.POSIXct(c("2019-01-30 07:00:00", "2019-01-30 10:00:00"), tz = "GMT")
  ) +
  base_breaks_y(df1$value)

enter image description here

2
M-- On

It is a little bit confusing to me why you need to have custom functions. See the solution below for a straightforward way of using scale_... to get your desired output.

library(ggplot2)
library(scales)

ggplot(data=df1, aes(x=date.time, y=value)) +
  geom_line() +
  ylab("Value") +
  xlab("Time Series") +
  theme_bw() +
  theme(panel.border = element_blank(),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        axis.line = element_line(colour = "black"),
        text = element_text(size=11)) +
  scale_x_datetime(limits = range(pretty(df1$date.time)), 
                   labels=date_format("%H:%M"), 
                   expand = c(0,0)) +
  scale_y_continuous(limits=range(pretty(df1$value)), 
                     expand = c(0,0))



But if there are other reasons to use custom functions, then we need to change couple of things:

  • You need to set the timezones for your y segment (a.k.a y-axis) to be the same as your input data. Moreover, x and xend both should be set to your date.time minimum time.

  • You need to put your x segment (a.k.a x-axis) at minimum of Value, not -Inf. Hence, I added a second argument to take Value and pass that to geom_segment.

  • I have also used expand(c(0,0)) in both scale_... functions to remove the space between the tickmarks and the segments which are replacing the axes.

#y-axis
base_breaks_y <- function(y){
  b <- pretty(y)
  d <- data.frame(x=as.POSIXct(c("2019-01-30 07:30:00"), tz= "UTC"),
                  xend=as.POSIXct(c("2019-01-30 7:30:00"), tz= "UTC"), 
                  y=min(b), yend=max(b))
  list(geom_segment(data=d, aes(x=x, y=y, xend=xend, yend=yend), inherit.aes=FALSE),
       scale_y_continuous(breaks=b, expand = c(0,0)))
}

#x-axis 
base_breaks_x <- function(x,y){
  b <- pretty(x)
  c <- pretty(y)
  d <- data.frame(y=min(c), yend=min(c), x=min(b), xend=max(b))
  list(geom_segment(data=d, aes(x=x, y=y, xend=xend, yend=yend), inherit.aes=FALSE),
       scale_x_datetime(breaks=b, labels=date_format("%H:%M"), expand = c(0,0)))
}
#plot 
ggplot(data=df1, aes(x=date.time, y=value)) +
  geom_line() +
  #labels 
  ylab("Value") +
  xlab("Time Series") +
  #aesthetics
  theme_bw() +
  theme(panel.border = element_blank(),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        text = element_text(size=11)) +
  #scale date time range 
  base_breaks_x(df1$date.time, df1$value) +
  base_breaks_y(df1$value)

Created on 2023-05-22 with reprex v2.0.2