How can one (easier) create good x-axis ticks (i.e. Pi / 2, pi, 3pi / 2, ...) in ggplot2?
I would like to create a graph where an alternate ticking of the x axis can be seen, for example. pi / 2, pi, 3pi / 2, etc. So far it only works with relatively unmanaged code (look at the line where I create pi.halfs
, pi.fulls
and combine them later vec.expr
):
require (ggplot2)
# Create vectors: breaks and corresponding labels as multiples of pi/2
vec.breaks <- seq(from = pi/2, to = 7*pi/2, by = pi/2)
pi.halfs <- c(paste(expression(pi), "/2"),
paste(seq(from = 3, to = 21, by = 2), "*" , expression(pi), "/2"))
pi.fulls <- c(paste(expression(pi)),
paste(seq(from = 2, to = 11, by = 1), "*" , expression(pi)))
vec.expr <- parse(text = c(rbind(pi.halfs, pi.fulls)))[1:7]
# Create some time and signal
time <- seq(from = 0, to = 4*pi, by = 0.01)
signal <- sin(time)
df <- data.frame(time,signal)
# Now plot the signal with the new x axis labels
fig <- ggplot(data = df, aes(x = time, y = signal)) +
geom_line() +
scale_x_continuous(breaks=vec.breaks, labels=vec.expr)
print(fig)
... as a result ...
Does anyone know of an easier approach where one can change the base of some x-axis labeling in ggplot2 for example. how here from decimal to multiples of pi? Are there any good packages that I have missed so far? I found several duplicates of this question, but only in other languages ââ...
source to share
Here is a function for creating fractional labels (a bit clunky perhaps). It uses the fractions
from package MASS
and allows you to change the multiplier you want to use on the x-axis. You just pass it a character (ie "Pi"). If the symbol matters, ticks will be scaled by the width value *, otherwise just by the width.
# Now plot the signal with the new x axis labels
p <- ggplot(data = df, aes(x = time, y = signal)) +
geom_line()
## Convert x-ticks to fractional x-ticks with a symbol multiplier
fracAx <- function(p, symbol, width=0.5) {
require(MASS) # for fractions
val <- tryCatch(eval(parse(text=symbol)), error=function(e) 1)
info <- ggplot_build(p)
xrange <- info[[2]]$ranges[[1]]$x.range/val # get the x-range of figure
vec.breaks <- seq(floor(xrange[1]), ceiling(xrange[2]), by=width)
fracs <- strsplit(attr(fractions(vec.breaks), "fracs"), "/") # convert to fractions
labels <- sapply(fracs, function(i)
if (length(i) > 1) { paste(i[1], "*", symbol, "/", i[2]) }
else { paste(i, "*", symbol) })
p + scale_x_continuous(breaks=vec.breaks*val, labels=parse(text=labels))
}
## Make the graph with pi axis
fracAx(p, "pi")
## Make the graph with e axis, for example
e <- exp(1)
fracAx(p, "e")
## Make the graph with a symbol that has no value
fracAx(p, "theta", width=2)
source to share
You're looking for the scales package , which lets you create custom formatting functions for scales, and has a number of useful formatting functions already built in. Looking through the help in the package weights, I was surprised I didn't find a radian scale, but you can create one with math_formatter()
. This code gets the same results, but not with fractions.
library(ggplot2)
library(scales)
time <- seq(from = 0, to = 4*pi, by = 0.01)
signal <- sin(time)
df <- data.frame(time,signal)
pi_scales <- math_format(.x * pi, format = function(x) x / pi)
fig <- ggplot(data = df, aes(x = time, y = signal)) +
geom_line() +
scale_x_continuous(labels = pi_scales, breaks = seq(pi / 2, 7 * pi / 2, pi / 2))
print(fig)
source to share
Based on the other answers here, I was able to piece together some functionality that implements a common format radians
that can be used independently of iterating over internals ggplot2
.
numerator <- function(x) { f = attr(x, "fracs") s <- as.integer(sign(x)) ifelse(is.finite(x), as.integer(stringr::str_extract(f, "^[^/]*")), s) } denominator <- function(x) { f = attr(x, "fracs") s <- as.integer(sign(x)) ratio <- str_detect(f, "/") d <- as.integer(stringr::str_extract(f, "[^/]*$")) ifelse(is.finite(x), ifelse(ratio, d, 1L), 0L) } char_sign <- function(x) { s <- sign(x) ifelse(s == 1, "+", ifelse(s == -1, "-", "")) } #' Convert value to radians formatting radians <- function(x) { y = x/pi f = suppressWarnings(MASS::as.fractions(y)) n = suppressWarnings(numerator(f)) d = suppressWarnings(denominator(f)) s <- char_sign(x) o <- vector(mode = "character", length = length(x)) o <- ifelse(d == 0 & n != 0, paste0(s, "â"), o) o <- ifelse(n == 0 & d != 0, "0", o) o <- ifelse(n != 0 & d != 0, paste0(n, "Ī/", d), o) o <- ifelse(n == 1 & d != 0, paste0("Ī/", d), o) o <- ifelse(n == -1 & d == 1, paste0(s, "Ī"), o) o <- ifelse(n == -1 & d != 0 & d != 1, paste0(s, "Ī/", d), o) o <- ifelse(d == 1 & n != 0 & abs(n) != 1, paste0(n, "Ī"), o) o <- ifelse(n == d & is.finite(n), "Ī", o) o }
This is used here:
'''r
time <- seq(from = 0, to = 4*pi, by = 0.01)
signal <- sin(time)
df <- data.frame(time,signal)
ggplot(df, aes(time, signal)) +
geom_line() +
scale_x_continuous(labels = trans_format(radians, force),
breaks = seq(0, 4*pi, pi/2))
source to share