Pretty ticks for log normal scale using ggplot2 (dynamic not manual)

后端 未结 5 711
挽巷
挽巷 2020-11-28 06:23

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.

相关标签:
5条回答
  • 2020-11-28 06:40

    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())
    

    0 讨论(0)
  • 2020-11-28 06:40

    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))
    

    0 讨论(0)
  • 2020-11-28 06:41

    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:

    1. The axis labels are shown as powers of ten - which I like
    2. The minor grid line is in the middle of the major grid lines (compare this plot with the grid lines in Andrie's answer).
    3. The x-axis is nicer. For some reason in Andrie's plot, the x-axis range is different.

    To give

    enter image description here

    0 讨论(0)
  • 2020-11-28 06:41

    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")
    

    enter image description here

    0 讨论(0)
  • 2020-11-28 06:50

    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())
    

    breaks at powers of ten

    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:

    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())
    

    enter image description here

    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())
    

    enter image description here

    0 讨论(0)
提交回复
热议问题