13

First let's create some example data. The times are stored using lubridate's hm as this seems the most suitable.

library(tibble)
library(lubridate)
#> 
#> Attaching package: 'lubridate'
#> The following object is masked from 'package:base':
#> 
#>     date

(
  data <- tibble(
    Time = hm('09:00', '10:30'),
    Value = 1
  )
)
#> # A tibble: 2 x 2
#>   Time         Value
#>   <S4: Period> <dbl>
#> 1 9H 0M 0S         1
#> 2 10H 30M 0S       1

Here's how I'd like the plot to look. For now I've specified the breaks manually at half-hour intervals.

library(ggplot2)
library(scales)

ggplot(data, aes(Time, Value)) +
  geom_point() +
  scale_x_time(breaks = hm('09:00', '09:30', '10:00', '10:30'))

I'd like to create these breaks automatically at half-hour intervals. Trying to use scales::date_breaks gives an error.

ggplot(data, aes(Time, Value)) +
  geom_point() +
  scale_x_time(breaks = date_breaks('30 mins'))
#> Error in UseMethod("fullseq"): no applicable method for 'fullseq' applied to an object of class "c('hms', 'difftime')"

Trying to create the breaks using seq also gives an error.

seq(hm('09:00'), hm('10:30'), hm('00:30'))
#> Note: method with signature 'Period#ANY' chosen for function '-',
#>  target signature 'Period#Period'.
#>  "ANY#Period" would also be valid
#> estimate only: convert to intervals for accuracy
#> Error in if (sum(values - trunc(values))) {: argument is not interpretable as logical
Greg
  • 487
  • 5
  • 15

3 Answers3

12

The error message you got, dealing with a method applied to an object of class "c('hms', 'difftime')", should give you a clue that there's a class issue here. First thing to do is check the class of your times, and check the docs (?hm), both of which will show you that hm actually returns a period object, not a datetime.

library(tidyverse)
library(lubridate)

class(data$Time)
#> [1] "Period"
#> attr(,"package")
#> [1] "lubridate"

So you need to change Time into a Date or similar object. There are different ways to do this, but I just quickly pasted today's date and Time together, then converting to a datetime object. The date I used doesn't matter if you don't actually need a date; it's basically a dummy for creating the object you need.

You also want scale_x_datetime rather than scale_x_date. Without setting a date_labels argument, you would have labels like "2018-05-28 09:00:00", so you can format those as just times by giving a formatting string to date_labels.

data %>%
  mutate(time2 = paste(today(), Time) %>% as_datetime()) %>%
  ggplot(aes(time2, Value)) +
  geom_point() +
  scale_x_datetime(breaks = scales::date_breaks("30 mins"), date_labels = "%H:%M")

Created on 2018-05-28 by the reprex package (v0.2.0).

camille
  • 16,432
  • 18
  • 38
  • 60
  • Thanks @camille, that works nicely although it seems strange to have to translate the times to a date/time. N.B. I originally used `scale_x_time` not `scale_x_date` as you suggested in your final paragraph. – Greg May 28 '18 at 19:07
  • I agree it's strange. There might be a way to make a time-only object rather than datetime, but I don't know of one – camille May 28 '18 at 19:33
9

Using new breaks_width() function from the package scales.

ggplot(data, aes(Time, Value)) +
  geom_point() +
  scale_x_time(breaks = scales::breaks_width("30 min"))
malexan
  • 140
  • 1
  • 5
4

One approach is to transform your Period object into POSIXct, which then allows you to use scale_x_datetime and the date_breaks argument, e.g.

data %>%
  mutate(Time = as.POSIXct(Time, origin = "2018-01-01", tz = "GMT")) %>%
  ggplot(aes(Time, Value)) +
  geom_point() +
  scale_x_datetime(date_breaks = "30 min", date_labels = "%H:%M")
Weihuang Wong
  • 12,868
  • 2
  • 27
  • 48