Reputation: 12642
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
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
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
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
ceiling()
to move us on to the next 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:
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:
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:
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
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