title: R: ggplot with durations
body: <p>Question: Your advice for handling <strong>durations</strong> with <code>ggplot2</code> (author: Hadley Wickham). Specifically: reproduce the plots below with custom breaks and suitable labels. Preference for minimal use of custom functions and/or refactoring of data. Suggestions with packages I have not cited are welcome.  </p>
  
  <p>The data is stored in seconds (see <code>df</code> below). I would like to display human-eye readable breaks and labels, e.g. days instead of thousands of seconds, where the breaks occurs at 0, 1, 2... days instead of awkward fractions. </p>
  
  <p>Proof of effort: The first example below deals with durations as integers and achieves the objective by appropriate case-by-case division by multiples of 60/24/365, etc. The second example uses the base <code>R</code> <code>difftime</code> objects. To get it right in this case, I had to use the <code>strptime</code> function and subtract <code>1</code>. Have I missed something? The third example uses the <code>duration</code> class from the <code>lubridate</code> package. While specifying labels was quite easy with the <code>day()</code> and <code>seconds_to_period()</code> functions, I didn't do such a good job at setting custom breaks. The fourth example uses the <code>hms</code> class. I managed to specify breaks, but not the labels. Any suggestions on how to write shorter lines of code for each of the examples below are also welcome.  </p>
  
  <pre><code># Data
  df = data.frame(x = 1:6, 
      num = c(374400, 343500, 174000, 193500, 197700, 270300))
  
  # base/difftime
  df$difftime &lt;- as.difftime(df$num, units = "secs")
  
  # lubridate/duration
  library("lubridate")  # devtools::install_github("tidyverse/lubridate") # the dev version fixes a bug
  df$duration &lt;- duration(df$num, units = "seconds")
  
  # hms/hms
  library("hms")
  df$hms &lt;- as.hms(df$num) 
  
  library("ggplot2")
  library("scales")
  
  # 1: data is base/numeric
  # Pro: no package dependence
  # Con: Hard work 
  breaks = seq(0, 100*60*60, 20*60*60)
  labels = function(x) round(x/60/60/24, 0)
  ggplot(data = df, aes(x = x, y = num)) +
      geom_bar(stat = "identity", fill = "lightblue") +
      scale_y_continuous(name = "Duration (Days)", 
                         breaks = breaks,
                         labels = labels) +
      labs(title = "Data stored as numeric (seconds)", 
           subtitle = "breaks = seq(0, 100*60*60, 20*60*60)\nlabels = function(x) round(x/60/60/24, 0)",
           x = NULL) 
  ggsave("base-num.png")
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/mbY0S.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/mbY0S.png" alt="enter image description here"></a></p>
  
  <pre><code># 2: data is base/difftime
  # Pro: simple once you get over the ``strftime(x, "%d")`` syntax.
  # Unresolved: Why do I need to subtract a day?
  labels = function(x) as.integer(strftime(x, "%d"))-1
  ggplot(data = df, aes(x = x, y = difftime)) +
      geom_bar(stat = "identity", fill = "lightblue") +
      scale_y_time(name = "Duration (Days)", 
          labels = labels) +
      labs(title = "Data stored as difftime (seconds)", 
           subtitle = "default breaks\nlabels = function(x) as.integer(strftime(x, '%d'))-1",
           x = NULL) 
  ggsave("base-difftime.png")
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/qSl1D.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/qSl1D.png" alt="enter image description here"></a></p>
  
  <pre><code># 3: data is lubridate/duration
  # Pro: intuitive combination of day() and seconds_to_period() functions
  # Unresolved: a better way to make own breaks?
  breaks = as.duration(seq(0, 5, 1)*60*60*24)
  labels = function(x) day(seconds_to_period(x))
  ggplot(data = df, aes(x = x, y = duration)) +
      geom_bar(stat = "identity", fill = "lightblue") +
      scale_y_continuous(name = "Duration (Days)", 
          breaks = breaks,
          labels = labels) +
      labs(title = "Data stored as duration (seconds)", 
           subtitle = "breaks = as.duration(seq(0, 5, 1)*60*60*24)\nlabels = function(x)lubridate::day(lubridate::seconds_to_period(x))",
           x = NULL) 
  ggsave("lubridate-duration.png")
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/K2HTx.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/K2HTx.png" alt="enter image description here"></a></p>
  
  <pre><code># 4: data is hms/hms
  # Pro: Immediately generates plot with acceptable labels
  # Unresolved: how to make own labels:  Failed attempts:
  labels = 0:(length(breaks)-1)
  labels = function(x)lubridate::day(x)
  
  breaks = seq(0, 5, 1)*60*60*24
  ggplot(data = df, aes(x = x, y = hms)) +
      geom_bar(stat = "identity", fill = "lightblue") +
      scale_y_continuous(name = "Duration (Seconds)",
          breaks = breaks) +
      labs(title = "Data stored as hms (seconds)", 
           subtitle = "breaks = seq(0, 5, 1)*60*60*24\ndefault labels",
           x = NULL) 
  ggsave("hms-hms.png")
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/1bYAV.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/1bYAV.png" alt="enter image description here"></a></p>
  
  <p><strong>EDIT</strong> Following Axeman's suggestion in the comments section, this is how to combine <code>ggplot</code> with <code>hms</code> objects. This looks to me like the most convenient of the 4, though admittedly having to subtract <code>1</code> is unexpected. Axeman, do you want to post this as an answer?</p>
  
  <pre><code>breaks = hms::hms(days = 0:4)
  labels = function(x) lubridate::day(x)-1
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/4pnE7.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/4pnE7.png" alt="enter image description here"></a></p>