I would like to know how to make a plot in R where the y-axis is inverted such that the plotted data appears in what would be the fourth quadrant (IV) of a cartesian plane, as opposed to the first (I) quadrant.
For reference, the plot I am trying to make looks very similar to the following (source):
I have found a number of questions online pertaining to reversing the numbering on the y-axis, but these all still plot the data in the first quadrant. Can anyone suggest how I might produce a plot similar to that shown above?
Just to provide a worked out answer, following the comments of #timriffe and #joran...
Use the function for minor log ticks from this answer:
minor.ticks.axis <- function(ax,n,t.ratio=0.5,mn,mx,...){
lims <- par("usr")
if(ax %in%c(1,3)) lims <- lims[1:2] else lims[3:4]
major.ticks <- pretty(lims,n=5)
if(missing(mn)) mn <- min(major.ticks)
if(missing(mx)) mx <- max(major.ticks)
major.ticks <- major.ticks[major.ticks >= mn & major.ticks <= mx]
labels <- sapply(major.ticks,function(i)
as.expression(bquote(10^ .(i)))
)
axis(ax,at=major.ticks,labels=labels,...)
n <- n+2
minors <- log10(pretty(10^major.ticks[1:2],n))-major.ticks[1]
minors <- minors[-c(1,n)]
minor.ticks = c(outer(minors,major.ticks,`+`))
minor.ticks <- minor.ticks[minor.ticks > mn & minor.ticks < mx]
axis(ax,at=minor.ticks,tcl=par("tcl")*t.ratio,labels=FALSE)
}
Make some reproducible example data:
x <- 1:8
y <- 10^(sort(runif(8, 1, 10), decreasing = TRUE))
Plot without axes:
plot(x, log10(y), # function to plot
xlab="", # suppress x labels
type = 'l', # specify line graph
xlim = c(min(x), (max(x)*1.3)), # extend axis limits to give space for text annotation
ylim = c(0, max(log10(y))), # ditto
axes = FALSE) # suppress both axes
Add fancy log axis and turn tick labels right way up (thanks #joran!):
minor.ticks.axis(2, 9, mn=0, mx=10, las=1)
Add x-axis up the top:
axis(3)
Add x-axis label (thanks for the tip, #WojciechSobala)
mtext("x", side = 3, line = 2)
And add an annotation to the end of the line
text(max(x), min(log10(y)), "Example", pos = 1)
Here's the result:
Answering the question in the title, the best/easiest way to invert the axis is to flip the limit variables around:
> plot(1:10, xlim=c(1,10));
> plot(1:10, xlim=c(10,1));
> plot(1:10, ylim=c(10,1));
Doing it this way means that you don't need to mess around with axes that are different from the image coordinates.
This can be combined with the 'xaxt="n"' parameter and an additional axis command to place an axis on another side:
> plot(1:10, ylim=c(10,1), xaxt="n"); axis(3);
It's now quite easy to reverse the y-axis using scale_y_reverse and specify position = "top" for the x-axis in ggplot2
Example
library(ggplot2)
library(scales)
set.seed(99)
Date <- seq(from = as.Date("2017-12-01"), to = as.Date("2017-12-15"),
by = "days")
Flux <- runif(length(Date), 1, 10000)
Flux_df <- data.frame(Date, Flux)
p1 <- ggplot(Flux_df, aes(Date, Flux)) +
geom_col() +
xlab("") +
scale_x_date(position = "top", breaks = pretty_breaks(), expand = c(0, 0)) +
scale_y_reverse(expand = expand_scale(mult = c(0.2, 0))) +
theme_bw(base_size = 16) +
theme(panel.border = element_blank(),
panel.grid.major.x = element_blank(),
panel.grid.minor = element_blank(),
axis.line = element_line()) +
theme(legend.position = "none")
p1
If we want both logarithmic and reverse axis, we need a workaround suggested here as ggplot2 does not have that option atm
reverselog_trans <- function(base = exp(1)) {
trans <- function(x) -log(x, base)
inv <- function(x) base^(-x)
scales::trans_new(paste0("reverselog-", format(base)), trans, inv,
scales::log_breaks(base = base), domain = c(1e-100, Inf))
}
p1 + scale_y_continuous(trans = reverselog_trans(10),
breaks = scales::trans_breaks("log10", function(x) 10^x),
labels = scales::trans_format("log10", scales::math_format(10^.x)),
expand = expand_scale(mult = c(0.2, 0))) +
annotation_logticks()
Related
I'd like to make a plot using ggplot2 where some of the fill values are clipped, i.e. values above or below the limits of the color scale are displayed as the minimum/maximum color. I can get this to work like this, using a combination of limit and oob (out of bounds):
library(ggplot2)
library(scales)
ggplot() + ... + scale_fill_viridis(na.value="white", limit=c(0, 10), oob=squish)
But there is no information in the colorbar that indicates there are values present outside of the limits.
How can I reproduce this matplotlib example in ggplot: https://stackoverflow.com/a/32072348
Specifically, how to get the triangles at the end of the colorbar?
As far as I'm aware there is not a package that implements triangle ends for colourbars in ggplot2 (but please let me know if there is!). However, we can implement our own. We'd need a constructor for our custom guide and a way to draw it. Most of the stuff is already implemented in guide_colourbar() and methods for their class, so what we need to do is just tag on our own class and expand the guide_gengrob method. The code below should work for vertically oriented colourbars. You'd need to know some stuff about the grid package and gtable package to follow along.
library(ggplot2)
library(gtable)
library(grid)
my_triangle_colourbar <- function(...) {
guide <- guide_colourbar(...)
class(guide) <- c("my_triangle_colourbar", class(guide))
guide
}
guide_gengrob.my_triangle_colourbar <- function(...) {
# First draw normal colourbar
guide <- NextMethod()
# Extract bar / colours
is_bar <- grep("^bar$", guide$layout$name)
bar <- guide$grobs[[is_bar]]
extremes <- c(bar$raster[1], bar$raster[length(bar$raster)])
# Extract size
width <- guide$widths[guide$layout$l[is_bar]]
height <- guide$heights[guide$layout$t[is_bar]]
short <- min(convertUnit(width, "cm", valueOnly = TRUE),
convertUnit(height, "cm", valueOnly = TRUE))
# Make space for triangles
guide <- gtable_add_rows(guide, unit(short, "cm"),
guide$layout$t[is_bar] - 1)
guide <- gtable_add_rows(guide, unit(short, "cm"),
guide$layout$t[is_bar])
# Draw triangles
top <- polygonGrob(
x = unit(c(0, 0.5, 1), "npc"),
y = unit(c(0, 1, 0), "npc"),
gp = gpar(fill = extremes[1], col = NA)
)
bottom <- polygonGrob(
x = unit(c(0, 0.5, 1), "npc"),
y = unit(c(1, 0, 1), "npc"),
gp = gpar(fill = extremes[2], col = NA)
)
# Add triangles to guide
guide <- gtable_add_grob(
guide, top,
t = guide$layout$t[is_bar] - 1,
l = guide$layout$l[is_bar]
)
guide <- gtable_add_grob(
guide, bottom,
t = guide$layout$t[is_bar] + 1,
l = guide$layout$l[is_bar]
)
return(guide)
}
You can then use your custom guide as the guide argument in a scale.
g <- ggplot(mtcars, aes(mpg, wt)) +
geom_point(aes(colour = drat))
g + scale_colour_viridis_c(
limits = c(3, 4), oob = scales::oob_squish,
guide = my_triangle_colourbar()
)
There isn't really a natural way to colour out-of-bounds values differently, but you can make very small slices near the extremes a different colour.
g + scale_colour_gradientn(
colours = c("red", scales::viridis_pal()(255), "hotpink"),
limits = c(3, 4), oob = scales::oob_squish,
guide = my_triangle_colourbar()
)
Created on 2021-07-19 by the reprex package (v1.0.0)
library(gg.layers)
library(ggplot2)
library(rcolors)
brk <- c(-Inf, -1, 0, 1, 3, 6, 9, Inf)
nbrk <- length(brk) - 1
cols <- get_color(rcolors$amwg256, nbrk)
g <- make_colorbar(
at = brk, col = cols, height = 1,
tck = 0.4,
space = "right",
legend.text.location = c(0.3, 0.5),
legend.text.just = c(0.5, 0.5),
# legend.text = list(fontfamily = "Times", cex = 1.1),
hjust = 0.05
)
p <- ggplot(mtcars, aes(mpg, disp)) + geom_point()
p + g
https://github.com/rpkgs/gg.layers
Triangles? No idea. Colors? You can set a gradient with custom values where your normal range is manually defined and your extremes are something else.
library(ggplot2)
# example taken from ?viridis::scale_colour_viridis, even if I don't use that function
dsub <- subset(diamonds, x > 5 & x < 6 & y > 5 & y < 6)
dsub$diff <- with(dsub, sqrt(abs(x-y))* sign(x-y))
d <- ggplot(dsub, aes(x, y, colour=diff)) + geom_point()
d +
scale_color_gradientn(
colours=c("red", "red", "blue", "green", "yellow", "red", "red"),
values = c(0, 0.1-1e-9, 0.1, 0.5, 0.9, 0.9+1e-9, 1),
breaks = c(-0.51, -.4, 0, .4, .62),
label = function(z) replace(z, c(1, length(z)), c("Min", "Max"))) +
theme_bw()
I doubled "red" on each end so that there would be no gradient transition with the neighboring colors. You can choose a different color for one end (while in this case it's clear if it's extreme-high or extreme-low).
I chose to manually control values= and labels= to include arbitrary points and labels for the extremes. This can be improved based on your preferences.
The disadvantage to this is that you have to define the viridis colors manually; should not be too difficult. I've hastily approximated it here, I'm confident you can choose better colors for the internal gradient portion.
I'd like to make a plot using ggplot2 where some of the fill values are clipped, i.e. values above or below the limits of the color scale are displayed as the minimum/maximum color. I can get this to work like this, using a combination of limit and oob (out of bounds):
library(ggplot2)
library(scales)
ggplot() + ... + scale_fill_viridis(na.value="white", limit=c(0, 10), oob=squish)
But there is no information in the colorbar that indicates there are values present outside of the limits.
How can I reproduce this matplotlib example in ggplot: https://stackoverflow.com/a/32072348
Specifically, how to get the triangles at the end of the colorbar?
As far as I'm aware there is not a package that implements triangle ends for colourbars in ggplot2 (but please let me know if there is!). However, we can implement our own. We'd need a constructor for our custom guide and a way to draw it. Most of the stuff is already implemented in guide_colourbar() and methods for their class, so what we need to do is just tag on our own class and expand the guide_gengrob method. The code below should work for vertically oriented colourbars. You'd need to know some stuff about the grid package and gtable package to follow along.
library(ggplot2)
library(gtable)
library(grid)
my_triangle_colourbar <- function(...) {
guide <- guide_colourbar(...)
class(guide) <- c("my_triangle_colourbar", class(guide))
guide
}
guide_gengrob.my_triangle_colourbar <- function(...) {
# First draw normal colourbar
guide <- NextMethod()
# Extract bar / colours
is_bar <- grep("^bar$", guide$layout$name)
bar <- guide$grobs[[is_bar]]
extremes <- c(bar$raster[1], bar$raster[length(bar$raster)])
# Extract size
width <- guide$widths[guide$layout$l[is_bar]]
height <- guide$heights[guide$layout$t[is_bar]]
short <- min(convertUnit(width, "cm", valueOnly = TRUE),
convertUnit(height, "cm", valueOnly = TRUE))
# Make space for triangles
guide <- gtable_add_rows(guide, unit(short, "cm"),
guide$layout$t[is_bar] - 1)
guide <- gtable_add_rows(guide, unit(short, "cm"),
guide$layout$t[is_bar])
# Draw triangles
top <- polygonGrob(
x = unit(c(0, 0.5, 1), "npc"),
y = unit(c(0, 1, 0), "npc"),
gp = gpar(fill = extremes[1], col = NA)
)
bottom <- polygonGrob(
x = unit(c(0, 0.5, 1), "npc"),
y = unit(c(1, 0, 1), "npc"),
gp = gpar(fill = extremes[2], col = NA)
)
# Add triangles to guide
guide <- gtable_add_grob(
guide, top,
t = guide$layout$t[is_bar] - 1,
l = guide$layout$l[is_bar]
)
guide <- gtable_add_grob(
guide, bottom,
t = guide$layout$t[is_bar] + 1,
l = guide$layout$l[is_bar]
)
return(guide)
}
You can then use your custom guide as the guide argument in a scale.
g <- ggplot(mtcars, aes(mpg, wt)) +
geom_point(aes(colour = drat))
g + scale_colour_viridis_c(
limits = c(3, 4), oob = scales::oob_squish,
guide = my_triangle_colourbar()
)
There isn't really a natural way to colour out-of-bounds values differently, but you can make very small slices near the extremes a different colour.
g + scale_colour_gradientn(
colours = c("red", scales::viridis_pal()(255), "hotpink"),
limits = c(3, 4), oob = scales::oob_squish,
guide = my_triangle_colourbar()
)
Created on 2021-07-19 by the reprex package (v1.0.0)
library(gg.layers)
library(ggplot2)
library(rcolors)
brk <- c(-Inf, -1, 0, 1, 3, 6, 9, Inf)
nbrk <- length(brk) - 1
cols <- get_color(rcolors$amwg256, nbrk)
g <- make_colorbar(
at = brk, col = cols, height = 1,
tck = 0.4,
space = "right",
legend.text.location = c(0.3, 0.5),
legend.text.just = c(0.5, 0.5),
# legend.text = list(fontfamily = "Times", cex = 1.1),
hjust = 0.05
)
p <- ggplot(mtcars, aes(mpg, disp)) + geom_point()
p + g
https://github.com/rpkgs/gg.layers
Triangles? No idea. Colors? You can set a gradient with custom values where your normal range is manually defined and your extremes are something else.
library(ggplot2)
# example taken from ?viridis::scale_colour_viridis, even if I don't use that function
dsub <- subset(diamonds, x > 5 & x < 6 & y > 5 & y < 6)
dsub$diff <- with(dsub, sqrt(abs(x-y))* sign(x-y))
d <- ggplot(dsub, aes(x, y, colour=diff)) + geom_point()
d +
scale_color_gradientn(
colours=c("red", "red", "blue", "green", "yellow", "red", "red"),
values = c(0, 0.1-1e-9, 0.1, 0.5, 0.9, 0.9+1e-9, 1),
breaks = c(-0.51, -.4, 0, .4, .62),
label = function(z) replace(z, c(1, length(z)), c("Min", "Max"))) +
theme_bw()
I doubled "red" on each end so that there would be no gradient transition with the neighboring colors. You can choose a different color for one end (while in this case it's clear if it's extreme-high or extreme-low).
I chose to manually control values= and labels= to include arbitrary points and labels for the extremes. This can be improved based on your preferences.
The disadvantage to this is that you have to define the viridis colors manually; should not be too difficult. I've hastily approximated it here, I'm confident you can choose better colors for the internal gradient portion.
I have a grid composed of several ggplots and want to add an x axis, where axis ticks and annotations are added between the plots. I could not came up with a better solution than to create a custom plot for the axis and adding it below with arrangeGrob. But they do not align with the plots (I draw arrows where the numbers should be). Also there is a large white space below which I don't want.
I will also need an analogue for the y-axis.
library(ggplot2)
library(gridExtra)
library(ggpubr)
library(grid)
# Create a grid with several ggplots
p <-
ggplot(mtcars, aes(wt, mpg)) +
geom_point() +
theme_transparent() +
theme(plot.background = element_rect(color = "black"))
main.plot <- arrangeGrob(p, p, p, p, p, p, p, p, ncol = 4, nrow = 2)
# grid.draw(main.plot)
# Now add an x axis to the main plot
x.breaks <- c(0, 1, 2.5, 8, 10)
p.axis <- ggplot() +
ylim(-0.1, 0) +
xlim(1, length(x.breaks)) +
ggpubr::theme_transparent()
for (i in seq_along(x.breaks)) {
p.axis <- p.axis +
geom_text(aes_(x = i, y = -0.01, label = as.character(x.breaks[i])), color = "red")
}
# p.axis
final.plot <- arrangeGrob(main.plot, p.axis, nrow = 2)
grid.draw(final.plot)
Any help appreciated.
Note: In the code below, I assume each plot in your grid has equal width / height, & used equally spaced label positions. If that's not the case, you'll have to adjust the positions yourself.
Adding x-axis to main.plot:
library(gtable)
# create additional row below main plot
# height may vary, depending on your actual plot dimensions
main.plot.x <- gtable_add_rows(main.plot, heights = unit(20, "points"))
# optional: check results to verify position of the new row
dev.off(); gtable_show_layout(main.plot.x)
# create x-axis labels as a text grob
x.axis.grob <- textGrob(label = x.breaks,
x = unit(seq(0, 1, length.out = length(x.breaks)), "npc"),
y = unit(0.75, "npc"),
just = "top")
# insert text grob
main.plot.x <- gtable_add_grob(main.plot.x,
x.axis.grob,
t = nrow(main.plot.x),
l = 1,
r = ncol(main.plot.x),
clip = "off")
# check results
dev.off(); grid.draw(main.plot.x)
You can do the same for the y-axis:
# create additional col
main.plot.xy <- gtable_add_cols(main.plot.x, widths = unit(20, "points"), pos = 0)
# create y-axis labels as a text grob
y.breaks <- c("a", "b", "c") # placeholder, since this wasn't specified in the question
y.axis.grob <- textGrob(label = y.breaks,
x = unit(0.75, "npc"),
y = unit(seq(0, 1, length.out = length(y.breaks)), "npc"),
just = "right")
# add text grob into main plot's gtable
main.plot.xy <- gtable_add_grob(main.plot.xy,
y.axis.grob,
t = 1,
l = 1,
b = nrow(main.plot.xy) - 1,
clip = "off")
# check results
dev.off(); grid.draw(main.plot.xy)
(Note that the above order of x-axis followed by y-axis should not be switched blindly. If you are adding rows / columns, it's good habit to use gtable_show_layout() frequently to check the latest gtable object dimensions, & ensure that you are inserting new grobs into the right cells.)
Finally, let's add some buffer on all sides, so that the labels & plot borders don't get cut off:
final.plot <- gtable_add_padding(main.plot.xy,
padding = unit(20, "points"))
dev.off(); grid.draw(final.plot)
in R, with ecdf I can plot a empirical cumulative distribution function
plot(ecdf(mydata))
and with hist I can plot a histogram of my data
hist(mydata)
How I can plot the histogram and the ecdf in the same plot?
EDIT
I try make something like that
https://mathematica.stackexchange.com/questions/18723/how-do-i-overlay-a-histogram-with-a-plot-of-cdf
Also a bit late, here's another solution that extends #Christoph 's Solution with a second y-Axis.
par(mar = c(5,5,2,5))
set.seed(15)
dt <- rnorm(500, 50, 10)
h <- hist(
dt,
breaks = seq(0, 100, 1),
xlim = c(0,100))
par(new = T)
ec <- ecdf(dt)
plot(x = h$mids, y=ec(h$mids)*max(h$counts), col = rgb(0,0,0,alpha=0), axes=F, xlab=NA, ylab=NA)
lines(x = h$mids, y=ec(h$mids)*max(h$counts), col ='red')
axis(4, at=seq(from = 0, to = max(h$counts), length.out = 11), labels=seq(0, 1, 0.1), col = 'red', col.axis = 'red')
mtext(side = 4, line = 3, 'Cumulative Density', col = 'red')
The trick is the following: You don't add a line to your plot, but plot another plot on top, that's why we need par(new = T). Then you have to add the y-axis later on (otherwise it will be plotted over the y-axis on the left).
Credits go here (#tim_yates Answer) and there.
There are two ways to go about this. One is to ignore the different scales and use relative frequency in your histogram. This results in a harder to read histogram. The second way is to alter the scale of one or the other element.
I suspect this question will soon become interesting to you, particularly #hadley 's answer.
ggplot2 single scale
Here is a solution in ggplot2. I am not sure you will be satisfied with the outcome though because the CDF and histograms (count or relative) are on quite different visual scales. Note this solution has the data in a dataframe called mydata with the desired variable in x.
library(ggplot2)
set.seed(27272)
mydata <- data.frame(x= rexp(333, rate=4) + rnorm(333))
ggplot(mydata, aes(x)) +
stat_ecdf(color="red") +
geom_bar(aes(y = (..count..)/sum(..count..)))
base R multi scale
Here I will rescale the empirical CDF so that instead of a max value of 1, its maximum value is whatever bin has the highest relative frequency.
h <- hist(mydata$x, freq=F)
ec <- ecdf(mydata$x)
lines(x = knots(ec),
y=(1:length(mydata$x))/length(mydata$x) * max(h$density),
col ='red')
you can try a ggplot approach with a second axis
set.seed(15)
a <- rnorm(500, 50, 10)
# calculate ecdf with binsize 30
binsize=30
df <- tibble(x=seq(min(a), max(a), diff(range(a))/binsize)) %>%
bind_cols(Ecdf=with(.,ecdf(a)(x))) %>%
mutate(Ecdf_scaled=Ecdf*max(a))
# plot
ggplot() +
geom_histogram(aes(a), bins = binsize) +
geom_line(data = df, aes(x=x, y=Ecdf_scaled), color=2, size = 2) +
scale_y_continuous(name = "Density",sec.axis = sec_axis(trans = ~./max(a), name = "Ecdf"))
Edit
Since the scaling was wrong I added a second solution, calculatin everything in advance:
binsize=30
a_range= floor(range(a)) +c(0,1)
b <- seq(a_range[1], a_range[2], round(diff(a_range)/binsize)) %>% floor()
df_hist <- tibble(a) %>%
mutate(gr = cut(a,b, labels = floor(b[-1]), include.lowest = T, right = T)) %>%
count(gr) %>%
mutate(gr = as.character(gr) %>% as.numeric())
# calculate ecdf with binsize 30
df <- tibble(x=b) %>%
bind_cols(Ecdf=with(.,ecdf(a)(x))) %>%
mutate(Ecdf_scaled=Ecdf*max(df_hist$n))
ggplot(df_hist, aes(gr, n)) +
geom_col(width = 2, color = "white") +
geom_line(data = df, aes(x=x, y=Ecdf*max(df_hist$n)), color=2, size = 2) +
scale_y_continuous(name = "Density",sec.axis = sec_axis(trans = ~./max(df_hist$n), name = "Ecdf"))
As already pointed out, this is problematic because the plots you want to merge have such different y-scales. You can try
set.seed(15)
mydata<-runif(50)
hist(mydata, freq=F)
lines(ecdf(mydata))
to get
Although a bit late... Another version which is working with preset bins:
set.seed(15)
dt <- rnorm(500, 50, 10)
h <- hist(
dt,
breaks = seq(0, 100, 1),
xlim = c(0,100))
ec <- ecdf(dt)
lines(x = h$mids, y=ec(h$mids)*max(h$counts), col ='red')
lines(x = c(0,100), y=c(1,1)*max(h$counts), col ='red', lty = 3) # indicates 100%
lines(x = c(which.min(abs(ec(h$mids) - 0.9)), which.min(abs(ec(h$mids) - 0.9))), # indicates where 90% is reached
y = c(0, max(h$counts)), col ='black', lty = 3)
(Only the second y-axis is not working yet...)
In addition to previous answers, I wanted to have ggplot do the tedious calculation (in contrast to #Roman's solution, which was kindly enough updated upon my request), i.e., calculate and draw the histogram and calculate and overlay the ECDF. I came up with the following (pseudo code):
# 1. Prepare the plot
plot <- ggplot() + geom_hist(...)
# 2. Get the max value of Y axis as calculated in the previous step
maxPlotY <- max(ggplot_build(plot)$data[[1]]$y)
# 3. Overlay scaled ECDF and add secondary axis
plot +
stat_ecdf(aes(y=..y..*maxPlotY)) +
scale_y_continuous(name = "Density", sec.axis = sec_axis(trans = ~./maxPlotY, name = "ECDF"))
This way you don't need to calculate everything beforehand and feed the results to ggpplot. Just lay back and let it do everything for you!
I have a normal distribution plot and a histogram plot with x axis in log scale displaying 0, 10^0, 10^1 ... I want to include minor ticks between the major ones. Actually I was able to change the major ticks format from 1, 2, 3 and so on to 10^0, 10^1, 10^2, 10^3 using the solution given to me in my previous question. I used the following code for the major ticks :
major.ticks <- axTicks(1)
labels <- sapply(major.ticks,function(i)
as.expression(bquote(10^ .(i)))
)
axis(1,at=major.ticks,labels=labels)
Can this be edited to just mark the minor ticks without labeling them?
There is a function minor.tick in the package Hmisc, but that one deals poorly with logarithmical scales. I use the following function for getting minor ticks that follow the logarithmical scale. ax is the axis you use it on (same as for the function axis), n is the number of minor ticks (default to 9), t.ratio is the ratio between the major and the minor ticks, and with ... you can pass extra parameters to axis
edit : Nice idea in the comments, so I edited my function. There are two extra parameters, mn and mx for the minimum and the maximum on the logarithmic scale (mn=0 thus means the minimum is 10^0 or 1 !)
The function:
minor.ticks.axis <- function(ax,n,t.ratio=0.5,mn,mx,...){
lims <- par("usr")
if(ax %in%c(1,3)) lims <- lims[1:2] else lims[3:4]
major.ticks <- pretty(lims,n=5)
if(missing(mn)) mn <- min(major.ticks)
if(missing(mx)) mx <- max(major.ticks)
major.ticks <- major.ticks[major.ticks >= mn & major.ticks <= mx]
labels <- sapply(major.ticks,function(i)
as.expression(bquote(10^ .(i)))
)
axis(ax,at=major.ticks,labels=labels,...)
n <- n+2
minors <- log10(pretty(10^major.ticks[1:2],n))-major.ticks[1]
minors <- minors[-c(1,n)]
minor.ticks = c(outer(minors,major.ticks,`+`))
minor.ticks <- minor.ticks[minor.ticks > mn & minor.ticks < mx]
axis(ax,at=minor.ticks,tcl=par("tcl")*t.ratio,labels=FALSE)
}
This can be applied as follows :
x <- 10^(0:8)
y <- 1:9
plot(log10(x),y,xaxt="n",xlab="x",xlim=c(0,9))
minor.ticks.axis(1,9,mn=0,mx=8)
Gives :
Here is a simple function to to this:
log10.axis <- function(side, at, ...) {
at.minor <- log10(outer(1:9, 10^(min(at):max(at))))
lab <- sapply(at, function(i) as.expression(bquote(10^ .(i))))
axis(side=side, at=at.minor, labels=NA, tcl=par("tcl")*0.5, ...)
axis(side=side, at=at, labels=lab, ...)
}
Here is an example:
x <- exp(rnorm(200, 5))
hist(log(x), 20, xaxt="n", xlim=c(0, 8))
log10.axis(1, at=seq(0, 8, 2))
Gives:
Try magaxis in package magicaxis.
In ggplot2, we can use annotation_logticks together with scales::trans_breaks and scales::trans_format. Below is an example taken from the link above.
library(ggplot2)
a <- ggplot(msleep, aes(bodywt, brainwt)) +
geom_point(na.rm = TRUE) +
scale_x_log10(
breaks = scales::trans_breaks("log10", function(x) 10^x),
labels = scales::trans_format("log10", scales::math_format(10^.x))
) +
scale_y_log10(
breaks = scales::trans_breaks("log10", function(x) 10^x),
labels = scales::trans_format("log10", scales::math_format(10^.x))
) +
theme_bw()
a + annotation_logticks() # Default: log ticks on bottom and left
There is the minorAxis function in the StratigrapheR package, that can be used for any kind of minor ticks. It can be used with the seq_log function to make logarithmic ticks:
library(StratigrapheR)
x <- exp(rnorm(200, 5))
hist(log10(x), 20, xaxt="n", xlim=c(0, 4), xlab = "x", main = "Logarithmic Histogram of x")
ticks <- seq_log(10^0,10^4, divide = T)
lab <- sapply(0:4, function(i) as.expression(bquote(10^ .(i))))
minorAxis(1, at.maj = log10(ticks[[1]]), at.min = log10(ticks[[2]]), labels = lab)
Gives:
Use "" for the labels of the minor ticks.
There was a small error,
lims<-lims[3:4] was missing
minor.ticks.axis <- function(ax,n,t.ratio=0.5,mn,mx,...){
lims <- par("usr")
if(ax %in%c(1,3)) lims <- lims[1:2] else lims <- lims[3:4]
major.ticks <- pretty(lims,n=5)
if(missing(mn)) mn <- min(major.ticks)
if(missing(mx)) mx <- max(major.ticks)
major.ticks <- major.ticks[major.ticks >= mn & major.ticks <= mx]
labels <- sapply(major.ticks,function(i)
as.expression(bquote(10^ .(i)))
)
axis(ax,at=major.ticks,labels=labels,...)
n <- n+2
minors <- log10(pretty(10^major.ticks[1:2],n))-major.ticks[1]
minors <- minors[-c(1,n)]
minor.ticks = c(outer(minors,major.ticks,`+`))
minor.ticks <- minor.ticks[minor.ticks > mn & minor.ticks < mx]
axis(ax,at=minor.ticks,tcl=par("tcl")*t.ratio,labels=FALSE)
}
The x axis labels in the first plot on this page is in error.
The minor ticks are not properly distributed.