The solutions provided by @FJCC will work quite well, especially if you are plotting this sort of thing as a one-off visualization. If, however, you find yourself doing many of these types of plots, you might want to think first about employing the transformation abilities of the scale_y_ and scale_x_ functions, then later you can craft your own to fine tune the results.
First our data:
library(ggplot2)
df <- data.frame(a = 1:4, b = c(5, 32, 256, 4781))
Using trans =
We can choose a transformation function for our axis using the trans argument. Per the help file ?scale_y_continuous we have access to several pre-made transformations,
Built-in transformations include "asn", "atanh", "boxcox", "date", "exp",
"hms", "identity", "log", "log10", "log1p", "log2", "logit", "modulus",
"probability", "probit", "pseudo_log", "reciprocal", "reverse", "sqrt" and "time".
and in your case using trans = "log" works quite well,
ggplot(df, aes(a, b)) +
geom_point() +
scale_y_continuous(trans = "log")

NOTE: If you used trans = "log10" this results in a plot identical to using scale_y_log10()` as in the second example from FJCC.
ggplot(df, aes(a, b)) +
geom_point() +
scale_y_continuous(trans = "log10")

Having more control
Now that we've seen we can set a transformation on the y scale, we can extend that and make our own. We do that with the trans_new() function in the scales package.
The usage of trans_new() looks like this,
trans_new(name,
transform,
inverse,
breaks = extended_breaks(),
minor_breaks = regular_minor_breaks(),
format = format_format(),
domain = c(-Inf, Inf))
Custom Breaks Fucntion
Since we are interested in where the breaks fall, we can consider writing a custom breaks function.
# We will use the default breaks function from trans_new()
# as a template to build our new breaks function
scales::extended_breaks
#> function (n = 5, ...)
#> {
#> n_default <- n
#> function(x, n = n_default) {
#> x <- x[is.finite(x)]
#> if (length(x) == 0) {
#> return(numeric())
#> }
#> rng <- range(x)
#> labeling::extended(rng[1], rng[2], n, ...)
#> }
#> }
#> <bytecode: 0x000000001a7e6298>
#> <environment: namespace:scales>
# the idea is to,
# compute the log of our response
# find attractive break points
# resolve the values back to their original scale
logexp_breaks <- function (n = 5, sigdig = 2, ...) {
n_default <- n
function (x, n = n_default) {
x <- x[is.finite(x)]
if (length(x) == 0) {
return(numeric())
}
rng <- range(log(x))
breaks <- labeling::extended(rng[1],
rng[2],
n,
...)
signif(exp(breaks), sigdig)
}
}
Custom Transformation Function
Now that we have the breaks function written which our transformation function will use we can start on the transformation.
We'll base our custom transformation function on log_trans(), so let's look at the code before we get started.
#
scales::log_trans
#> function (base = exp(1))
#> {
#> force(base)
#> trans <- function(x) log(x, base)
#> inv <- function(x) base^x
#> trans_new(paste0("log-", format(base)), trans, inv, log_breaks(base = base),
#> domain = c(1e-100, Inf))
#> }
#> <bytecode: 0x0000000019bdac98>
#> <environment: namespace:scales>
Now, in exploring, I thought it would be good to allow our transformer to take some arguments and send them along to our breaks function. We could have sent some args to the actual transformation and inverse as seen in the log_trans() example, but I wanted to keep this simple_-ish_, so we'll just let elm() accept the three dots and pass them straight on through to logexp_breaks(). The parameters of interest for the breaks function will be n (a rough guide for how many major breaks to make), sigdig (the number of significant digits to set our breaks at), and w (the weights to pass on to the labeling function which does the heavy lifting about where to set the breaks).
NOTE: The weights are a bit of a mystery to me. The help file ?labeling::extended has this to say about them,
w weights applied to the four optimization components (simplicity,
coverage, density, and legibility)
You should play around with them to find a balance you like (or just trust the default).
elm <- function(...) {
scales::trans_new("elm",
"log",
"exp",
logexp_breaks(...))
}
Putting it into practice.
Now that we've done all that, we can start to put it into practice and see what we get.
ggplot(df, aes(a, b)) +
geom_point() +
scale_y_continuous(trans = elm(n = 10, sigdig = 1, w = c(0.01, 0.1, 0.7, 0.05)))

ggplot(df, aes(a, b)) +
geom_point() +
scale_y_continuous(trans = elm(n = 4, sigdig = 2))

Created on 2020-09-01 by the reprex package (v0.3.0)