I am trying to use ggplot2 to create a performance chart with a log normal y scale. Unfortunately I\'m not able to produce nice ticks as for the base plot function.
This issue has finally been solved with the release of scales 1.0.0
and the new function log_breaks(), which returns integer multiples of integer powers of base.
library(ggplot2)
ggplot(M, aes(x = X,y = Y)) +
geom_line() +
scale_y_log10(breaks = log_breaks())
This function allows to specify both the desired number of major and minor ticks. It must be specified twice for that effect:
#' log scale
#'
#' Creates a function which returns ticks for a given data range. It uses some
#' code from scales::log_breaks, but in contrast to that function it not only
#' the exponentials of the base b, but log minor ticks (f*b^i, where f and i are
#' integers), too.
#'
#' @param n Approximate number of ticks to produce
#' @param base Logarithm base
#'
#' @return
#'
#' A function which expects one parameter:
#'
#' * **x**: (numeric vector) The data for which to create a set of ticks.
#'
#' @export
logTicks <- function(n = 5, base = 10){
# Divisors of the logarithm base. E.g. for base 10: 1, 2, 5, 10.
divisors <- which((base / seq_len(base)) %% 1 == 0)
mkTcks <- function(min, max, base, divisor){
f <- seq(divisor, base, by = divisor)
return(unique(c(base^min, as.vector(outer(f, base^(min:max), `*`)))))
}
function(x) {
rng <- range(x, na.rm = TRUE)
lrng <- log(rng, base = base)
min <- floor(lrng[1])
max <- ceiling(lrng[2])
tck <- function(divisor){
t <- mkTcks(min, max, base, divisor)
t[t >= rng[1] & t <= rng[2]]
}
# For all possible divisors, produce a set of ticks and count how many ticks
# result
tcks <- lapply(divisors, function(d) tck(d))
l <- vapply(tcks, length, numeric(1))
# Take the set of ticks which is nearest to the desired number of ticks
i <- which.min(abs(n - l))
if(l[i] < 2){
# The data range is too small to show more than 1 logarithm tick, fall
# back to linear interpolation
ticks <- pretty(x, n = n, min.n = 2)
}else{
ticks <- tcks[[i]]
}
return(ticks)
}
}
Your example:
library(ggplot2)
library(scales)
# fix RNG
set.seed(seed=1)
# simulate returns
y=rnorm(999,0.02,0.2)
# M$Y are the cummulative returns (like an index)
M=data.frame(X=1:1000,Y=100)
for (i in 2:1000)
M[i,"Y"]=M[i-1,"Y"]*(1+y[i-1])
ggplot(M,aes(x=X,y=Y))+geom_line()+
scale_y_log10(breaks = logTicks(n = 4), minor_breaks = logTicks(n = 40))
When I constructing graphs on the log scale, I find the following works pretty well:
library(ggplot2)
library(scales)
g = ggplot(M,aes(x=X,y=Y)) + geom_line()
g + scale_y_continuous(trans = 'log10',
breaks = trans_breaks('log10', function(x) 10^x),
labels = trans_format('log10', math_format(10^.x)))
A couple of differences:
To give
The base graphics function axTicks()
returns the axis breaks for the current plot. So, you can use this to return breaks identical to base graphics. The only downside is that you have to plot the base graphics plot first.
library(ggplot2)
library(scales)
plot(M, type="l",log="y")
breaks <- axTicks(side=2)
ggplot(M,aes(x=X,y=Y)) + geom_line() +
scale_y_continuous(breaks=breaks) +
coord_trans(y="log")
The base graphics behaviour can be reproduced using a custom breaks function:
base_breaks <- function(n = 10){
function(x) {
axisTicks(log10(range(x, na.rm = TRUE)), log = TRUE, n = n)
}
}
Applying this to the example data gives the same result as using trans_breaks('log10', function(x) 10^x)
:
ggplot(M, aes(x = X, y = Y)) + geom_line() +
scale_y_continuous(trans = log_trans(), breaks = base_breaks()) +
theme(panel.grid.minor = element_blank())
However we can use the same function on a subset of the data, with y values between 50 and 600:
M2 <- subset(M, Y > 50 & Y < 600)
ggplot(M2, aes(x = X, y = Y)) + geom_line() +
scale_y_continuous(trans = log_trans(), breaks = base_breaks()) +
theme(panel.grid.minor = element_blank())
As powers of ten are no longer suitable here, base_breaks
produces alternative pretty breaks:
Note that I have turned off minor grid lines: in some cases it will make sense to have grid lines halfway between the major gridlines on the y-axis, but not always.
Edit
Suppose we modify M so that the minimum value is 0.1:
M <- M - min(M) + 0.1
The base_breaks() function still selects pretty breaks, but the labels are in scientific notation, which may not be seen as "pretty":
ggplot(M, aes(x = X, y = Y)) + geom_line() +
scale_y_continuous(trans = log_trans(), breaks = base_breaks()) +
theme(panel.grid.minor = element_blank())
We can control the text formatting by passing a text formatting function to the labels
argument of scale_y_continuous
. In this case prettyNum
from the base package does the job nicely:
ggplot(M, aes(x = X, y = Y)) + geom_line() +
scale_y_continuous(trans = log_trans(), breaks = base_breaks(),
labels = prettyNum) +
theme(panel.grid.minor = element_blank())