pravi
pravi

Reputation: 2219

Logarithmic grid for plot with 'ggplot2'

I am trying to create a plot with logarithmically spaced grids using ggplot2 just like in the below figure. I get equidistant grids, but not log spaced ones. I know I am missing some parameter which I don't seem to get as of now. I have seen a lot of questions on the topic like Pretty ticks for log normal scale using ggplot2 (dynamic not manual), but do not solve the problem I am looking for.

set.seed(5)
x <- rlnorm(1000, meanlog=3.5, sdlog=1)
y <- rlnorm(1000, meanlog=4.0, sdlog=1)
d <- data.frame(x, y)

plot(x, y, log="xy", las=1)
grid(nx=NULL, ny=NULL, col= "blue", lty="dotted", equilogs=FALSE)
library(magicaxis)
magaxis(side=1:2, ratio=0.5, unlog=FALSE, labels=FALSE)

enter image description here

library(ggplot2)
library(MASS)
library(scales)
a <- ggplot(d, aes(x=x, y=y)) + geom_point() +
     scale_x_log10(limits = c(1, NA), 
                   labels = trans_format("log10", math_format(10^.x)),
                   breaks=trans_breaks("log10", function(x) 10^x, n=4)) +
     scale_y_log10(limits = c(1, NA),
                   labels = trans_format("log10", math_format(10^.x)),
                   breaks=trans_breaks("log10", function(x) 10^x, n=4)) +
     theme_bw() + theme(panel.grid.minor = element_line(color="blue", linetype="dotted"), panel.grid.major = element_line(color="blue", linetype="dotted"))
a + annotation_logticks(base = 10)

enter image description here

Upvotes: 14

Views: 15928

Answers (5)

p4010
p4010

Reputation: 943

I know I'm coming late to the party, but I am reckoning that this solution might turn out useful. While the outer product trick is clever and useful, the scales package has us covered:

library(ggplot2)
set.seed(5)

tibble(
  x = rlnorm(1000, meanlog=3.5, sdlog=1), 
  y = rlnorm(1000, meanlog=4.0, sdlog=1)
) %>% 
  ggplot(aes(x=x, y=y)) + 
  geom_point() +
  scale_x_log10(minor_breaks=scales::minor_breaks_n(10)) +
  scale_y_log10(minor_breaks=scales::minor_breaks_n(10)) +
  theme_bw() + 
  theme(
    panel.grid.minor = element_line(color="blue", linetype="dotted"), 
    panel.grid.major = element_line(color="blue", linetype="dotted")
  )

Upvotes: 0

Ian Kent
Ian Kent

Reputation: 791

To build on Gabor's answer, it is unnecessary to define the ticks only in the exact range of the plot. You can instead define breaks for a large range of values that will cover pretty much everything you'd ever expect to see and use those to create nice grid lines for any plot. While perhaps not the most elegant solution, it's easy and more generalizable than having to manually figure out ranges every time.

breaks <- 10^(-10:10)
minor_breaks <- rep(1:9, 21)*(10^rep(-10:10, each=9))

d %>% 
    ggplot(aes(x, y)) +
        geom_point() +
        scale_x_log10(breaks = breaks, minor_breaks = minor_breaks) +
        scale_y_log10(breaks = breaks, minor_breaks = minor_breaks) +
        annotation_logticks() +
        coord_equal() +
        theme_bw()

Link to image of plot

Upvotes: 21

Sameh Magdeldin
Sameh Magdeldin

Reputation: 473

Are you looking for diminishing spacing grid like this?

ggplot(d, aes(x=x, y=y)) + geom_point() + 
  coord_trans(y="log10", x="log10") +
  scale_y_continuous(trans = log10_trans(),
                     breaks = trans_breaks("log10", function(x) 10^x),
                     labels = trans_format("log10", math_format(10^.x))) +
  scale_x_continuous(trans = log10_trans(),
                     breaks = trans_breaks("log10", function(x) 10^x),
                     labels = trans_format("log10", math_format(10^.x)))

enter image description here

Upvotes: 5

Gabor Szarnyas
Gabor Szarnyas

Reputation: 5057

You can define the breaks manually.

> ticks <- 2:10
> ooms <- 10^(0:3)
> breaks <- as.vector(ticks %o% ooms)
> breaks
 [1]     2     3     4     5     6     7     8     9    10
[10]    20    30    40    50    60    70    80    90   100
[19]   200   300   400   500   600   700   800   900  1000
[28]  2000  3000  4000  5000  6000  7000  8000  9000 10000

This snippet defines the breaks, hides some of them, and generates the plot.

library(ggplot2)

set.seed(5)
x <- rlnorm(1000, meanlog=3.5, sdlog=1)
y <- rlnorm(1000, meanlog=4.0, sdlog=1)
d <- data.frame(x, y)

ticks <- 2:10
# define the OOMs (orders of magnitudes)
ooms <- 10^(0:3)
breaks <- as.vector(ticks %o% ooms)

# select the labels to show
show.labels <- c(T, F, F, T, F, F, F, F, T)
labels <- as.character(breaks * show.labels)
labels <- gsub("^0$", "", labels)

p <- ggplot(d, aes(x=x, y=y)) + geom_point() +
  scale_x_log10(limits = c(1, NA), labels = labels, breaks = breaks) +
  scale_y_log10(limits = c(1, NA), labels = labels, breaks = breaks) +
  theme_bw() + theme(panel.grid.minor = element_line(color="blue", linetype="dotted"), panel.grid.major = element_line(color="blue", linetype="dotted")) +
  annotation_logticks(base = 10)
p

Scatterplot with logaritmic axes and custom ticks

Upvotes: 2

ivangreg
ivangreg

Reputation: 31

For logarithmically spaced grids using ggplot2, the answer provided by Samehmagd shines for its simplicity, requires no manual settings, and it answers the question.

On using it on my own data, I discovered something that seems to be a bug. In my hands at least, this strategy fails when the values to be plotted are under 1 (thus generating negative values when log10 is applied).

This is the example, as contributed by Samehmagd, plus my contribution towards the end:

library(ggplot2)
library(scales)

set.seed(5)
x <- rlnorm(1000, meanlog=3.5, sdlog=1)
y <- rlnorm(1000, meanlog=4.0, sdlog=1)
d <- data.frame(x, y)

# peek at d
head(d)
#            x         y
# 1  14.284064  12.74253
# 2 132.205740 189.53295
# 3   9.435773  35.44751
# 4  35.521664  54.97449
# 5 183.358064  61.84004
# 6  18.121372  36.24753

# Plot successfully (this figure is identical to Samehmagd's)
ggplot(d, aes(x=x, y=y)) + geom_point() + coord_trans(y="log10", x="log10") + scale_y_continuous(trans=log10_trans(), breaks=trans_breaks("log10", function(x) 10^x), labels=trans_format("log10", math_format(10^.x))) + scale_x_continuous(trans=log10_trans(), breaks=trans_breaks("log10", function(x) 10^x), labels=trans_format("log10", math_format(10^.x)))

# Now, here is when it breaks
f <- 1/d

# peek at f
head(f)
#             x           y
# 1 0.070008087 0.078477335
# 2 0.007563968 0.005276127
# 3 0.105979655 0.028210728
# 4 0.028151834 0.018190255
# 5 0.005453810 0.016170753
# 6 0.055183459 0.027588083

# Get the plotting to fail just by using f instead of d, no other change.
ggplot(f, aes(x=x, y=y)) + geom_point() + coord_trans(y="log10", x="log10") + scale_y_continuous(trans=log10_trans(), breaks=trans_breaks("log10", function(x) 10^x), labels=trans_format("log10", math_format(10^.x))) + scale_x_continuous(trans=log10_trans(), breaks=trans_breaks("log10", function(x) 10^x), labels=trans_format("log10", math_format(10^.x)))
Error in if (zero_range(range)) { : missing value where TRUE/FALSE needed
In addition: Warning message:
In trans$transform(out$range) : NaNs produced

Whether or not this failure is a bug or a feature, it is well worth documenting.

Upvotes: 3

Related Questions