Cookie
Cookie

Reputation: 12642

R Plot Specify number of time tickmarks - time/date equivalent to pretty

I was wondering how I could plot more tick marks when plotting time on the x-axis.

Basically, a time equivalent to pretty. Pretty obviously doesn't work so well with times, as it uses factors of 1,2,5 and 10. For time one probably wants e.g. hours, half hours, ...

plot(as.POSIXct(x,origin="1960-01-01"),y,type="l",xlab="Time")

gives really too few and widely spaced tickmarks.

zoox<-zoo(y,as.POSIXct(stats$Time,origin="1960-01-01"))
plot(zoox)

gives the same.

Thanks

EDIT:

Just to clarify (so far answers don't address my issue): What I am looking for is a function like pretty for dates, e.g. a function, that takes a start date, an end date, a number of ticks, and outputs the location of the ticks. That is, I am well aware it is possible to plot hours, to plot minutes, and what else, but pretty automates the tick distance for numbers, and a resulting function for dates should decide by itself whether to use days, hours, minutes, second, milliseconds, microseconds, 30 minutes, 500 micros, 5 seconds, etc. intervals. That is what pretty does for numbers, anyway.

EDIT2:

This is the function I currently use to decide the format for the time axis (note that this doesn't work for dates):

mydiff <- end-start
if(mydiff>1800) {
    axis.POSIXct(1,xrange,format="%H:%M")
} else if(mydiff>30) {
    axis.POSIXct(1,xrange,format="%H:%M:%S")
} else if(mydiff>0.5) {
    axis.POSIXct(1,xrange,format="%H:%M:%OS3")
} else
    axis.POSIXct(1,xrange,format="%H:%M:%OS6")
}

I don't have a function that increase tick marks, so I use the default number of tick marks

Upvotes: 16

Views: 8531

Answers (4)

Gavin Simpson
Gavin Simpson

Reputation: 174788

axis.POSIXct() already works quite hard to guess suitable pretty values for the axis, so I would start by hacking that. At the moment, it relies internally on using pretty() applied to the some function of the datetimes. It uses the defaults for pretty() so you could hack the function to add an n or min.n argument which would increase the number of pretty marks selected.

Copy axis.POSIXct() to your own function/file (give it a new name). Add a n or min.n argument to the definition, probably with larger values as defaults than those used by the pretty() function. And pass that to each of the pretty() calls that is made.

Try it out. If it works reasonably well, then you can do fixInNamespace(axis.POSIXct) to make the same changes to the actual function so it gets used on all plots for which it is called.

P.S. Here is a possible hack

function (side, x, at, format, labels = TRUE, n = 5, ...) {
  mat <- missing(at) || is.null(at)
  if (!mat) 
    x <- as.POSIXct(at)
  else x <- as.POSIXct(x)
  range <- par("usr")[if (side%%2) 
    1L:2L
    else 3L:4L]
  d <- range[2L] - range[1L]
  z <- c(range, x[is.finite(x)])
  attr(z, "tzone") <- attr(x, "tzone")
  if (d < 1.1 ) {
    sc <- 0.001
    if (missing(format)) 
      format <- "%H:%M:%OS6"
  }
  else if (d < 1.1 * 30) {
    sc <- 1
    if (missing(format)) 
      format <- "%H:%M:%OS3"
  }
  else if (d < 1.1 * 60) {
    sc <- 1
    if (missing(format)) 
      format <- "%H:%M:%S"
  }
  else if (d < 1.1 * 30 * 60) {
    sc <- 60
    if (missing(format)) 
      format <- "%H:%M:%S"
  }
  else if (d < 1.1 * 60 * 60) {
    sc <- 60
    if (missing(format)) 
      format <- "%H:%M"
  }
  else if (d < 1.3 * 60 * 60 * 24) {
    sc <- 60 * 60
    if (missing(format)) 
      format <- "%H:%M"
  }
  else if (d < 2 * 60 * 60 * 24) {
    sc <- 60 * 60
    if (missing(format)) 
      format <- "%a %H:%M"
  }
  else if (d < 7 * 60 * 60 * 24) {
    sc <- 60 * 60 * 24
    if (missing(format)) 
      format <- "%a"
  }
  else {
    sc <- 60 * 60 * 24
  }
  if (d < 60 * 60 * 24 * 50) {
    zz <- pretty(z/sc,n=n)
    z <- zz * sc
    z <- .POSIXct(z, attr(x, "tzone"))
    if (sc == 60 * 60 * 24) 
      z <- as.POSIXct(round(z, "days"))
    if (missing(format)) 
      format <- "%b %d"
  }
  else if (d < 1.1 * 60 * 60 * 24 * 365) {
    z <- .POSIXct(z, attr(x, "tzone"))
    zz <- as.POSIXlt(z)
    zz$mday <- zz$wday <- zz$yday <- 1
    zz$isdst <- -1
    zz$hour <- zz$min <- zz$sec <- 0
    zz$mon <- pretty(zz$mon,n=n)
    m <- length(zz$mon)
    M <- 2 * m
    m <- rep.int(zz$year[1L], m)
    zz$year <- c(m, m + 1)
    zz <- lapply(zz, function(x) rep(x, length.out = M))
    zz <- .POSIXlt(zz, attr(x, "tzone"))
    z <- as.POSIXct(zz)
    if (missing(format)) 
      format <- "%b"
  }
  else {
    z <- .POSIXct(z, attr(x, "tzone"))
    zz <- as.POSIXlt(z)
    zz$mday <- zz$wday <- zz$yday <- 1
    zz$isdst <- -1
    zz$mon <- zz$hour <- zz$min <- zz$sec <- 0
    zz$year <- pretty(zz$year,n=n)
    M <- length(zz$year)
    zz <- lapply(zz, function(x) rep(x, length.out = M))
    z <- as.POSIXct(.POSIXlt(zz))
    if (missing(format)) 
      format <- "%Y"
  }
  if (!mat) 
    z <- x[is.finite(x)]
  keep <- z >= range[1L] & z <= range[2L]
  z <- z[keep]
  if (!is.logical(labels)) 
    labels <- labels[keep]
  else if (identical(labels, TRUE)) 
    labels <- format(z, format = format)
  else if (identical(labels, FALSE)) 
    labels <- rep("", length(z))
  axis(side, at = z, labels = labels, ...)
}

Differences to the original function can be seen here

Upvotes: 7

Andre Michaud
Andre Michaud

Reputation: 128

Suppress the defaults available for the axis in plot ( axes = FALSE, or individual axis) and use the function axis to detail what you want for the axis (specifically the at argument). see ?axis

plot(cars, axes = FALSE)
axis( 1, at = c(5,6,7,10,11,12,21,22,23) )

Upvotes: 0

Gavin Simpson
Gavin Simpson

Reputation: 174788

Using a reproducible example

set.seed(1)
x <- as.POSIXct(sort(sample(100000, 100)), origin="1960-01-01")
y <- rpois(100, 5)
plot(x, y, type = "l", xlab = "Time")

we can make use of the axis.POSIXct() function (one could also use the Axis() S3 generic as well) to add a custom axis to the plot. The main point here is that you, the user, are in full control of where ticks are draw and how they are labelled, you just need to work a little harder if the defaults don;t work for you.

First we plot the data but suppress drawing of the x-axis:

plot(x, y, type = "l", xlab = "Time", xaxt = "n")

Next I will add a major tick mark at the start of each hour. For this I create a sequence of datetimes that goes

  1. from the rounded hour of the first observation in the series,
  2. to the end of the last hour in which an observation was made (using ceiling() to move us on to the next hour),
  3. incrementing the sequence in 1 hour (by = "1 hour") units.

This sequence is supplied to the the at argument of axis.POSIXct(). The rest should be easy to follow if not read ?axis.POSIXct and ?par

## add axis tick at each hour:
axis.POSIXct(side = 1, x = x,
             at = seq(from = round(x[1], "hours"),
                      to = x[1] + ceiling(difftime(tail(x, 1), head(x, 1), 
                                                   units = "hours")),
                      by = "1 hour"),
             las = 2)

The resulting figure looks like this:

enter image description here

To show finer control, I now add minor tick marks at each half-hour location, but suppress the annotation of those ticks (via the labels argument) and also make the minor ticks shorter (via graphics parameter tcl). Notice how the seq() method's by argument can take a numeric amount of the stated interval

## add minor ticks at 30 min intervals to above plot
axis.POSIXct(side = 1, x = x,
             at = seq(from = round(x[1], "hours"),
                      to = x[1] + ceiling(difftime(tail(x, 1), head(x, 1), 
                                                   units = "hours")),
                       by = "30 mins"),
             las = 2, tcl = -0.2, labels = FALSE)

The plot now looks like this:

enter image description here

You can add your own labels rather than the ones that the axis.POSIXct function comes up with. If you want to do this, then we should assign the output from seq() to an object that we can then use the format() function on. For example:

plot(x, y, type = "l", xlab = "Time", xaxt = "n")
tseq <- seq(from = round(x[1], "hours"),
            to = x[1] + ceiling(difftime(tail(x, 1), head(x, 1), 
                                         units = "hours")),
            by = "1 hour")
axis.POSIXct(side = 1, x = x, at = tseq,
             labels = format(tseq, format = "%H:%M"), las = 2)

The resulting plot is shown below:

enter image description here

format() returns a character string of the formatted datetime. You can paste() on anything else you want or look at the other placeholders that can be used to format datetime objects in ?strftime

Upvotes: 10

plannapus
plannapus

Reputation: 18749

I tend to use function axis.POSIXct and/or function cut.POSIXt. Let's say your vector of date is time and your vector of value associated x:

plot(time,x,xaxt="n")
axis.POSIXct(side=1,at=seq(min(time),max(time),by="week"),format="%d-%m") #For instance

And, going further, with cut.POSIXt:

plot(time,x,xaxt="n")
axis.POSIXct(side=1,at=cut(time, breaks="week"),format="%d-%m")

Upvotes: 3

Related Questions