pretty_axis: Define pretty limits and axes for publication-quality plots

View source: R/pretty_axis.R

pretty_axisR Documentation

Define pretty limits and axes for publication-quality plots

Description

This function is used to define pretty limits and axes on plots. The function can handle numeric, time stamp (i.e. Dates or DateTimeClasses) or factor data. Usually, arguments are passed from a plotting function (e.g., pretty_plot) to this function via pretty_axis_args, although it can be called directly too. In the latter case, generally, the best approach is to implement the function prior to creating a plot. Based on the data to be plotted, the function defines axes limits and corresponding 'pretty' axis tick marks and labels, returning a list of outputs. Then, a plot can be created using limits defined by the function, after which point axes can be added to the plot by passing the list back to the function. Axis limits, placement, the number of ticks, labels and other axes properties can be determined automatically (in which case the function tries hard to create 'pretty' axes), adjusted (e.g. via adjustments to the number of 'pretty' breaks) or user-defined (e.g. by specifying axis breaks). Each axis can be controlled independently (e.g., one axis can be user-defined while another axis can be determined automatically and the function defines appropriate limits and axis placement). The function is very flexible (see Examples).

Usage

pretty_axis(
  side = 1:4,
  x = list(),
  lim = list(),
  pretty = list(n = 5),
  units = list(),
  axis = list(),
  pi_notation = NULL,
  control_axis = list(las = TRUE),
  control_sci_notation = list(),
  control_digits = NULL,
  control_factor_lim = 0.5,
  axis_ls = NULL,
  add = FALSE,
  return_list = TRUE,
  ...
)

Arguments

side

A numeric input specifying the side(s) of a plot for which pretty axes should be defined.

x

A list, with one element for each side, defining the values to be plotted on that side of the plot. Numeric, time stamp (i.e. Dates or DateTimeClasses) or factor data are supported. Character vectors will be converted to factors for plotting.

lim

(optional) A list, with one element for each side, containing a vector of (one or both) axes limits for that axis. If provided, then axis tick marks (pretty or regular) are forced to lie within provided limits. Otherwise, suitable limits can be suggested by the function based on the data provided in x. It is possible to fix only the lower or upper limit by specifying c(user_specified_limit, NA) to fix the first limit (or simply or user_specified_limit in which case the first limit is taken as the one that should be fixed), or c(NA, user_specified_limit) to fix the upper limit; the other limit is then chosen automatically depending on the inputs to other function arguments. For factors, user-supplied limits are ignored. For factors with one level, limits are set to 0.75 and 1.25; for factors with multiple levels, limits are set to 1 and the number of factor levels. However, factor limits can be adjusted by control_factor_lim (see below).

pretty

A named list arguments that are used to create pretty axis tick marks. This list is passed to pretty (for numeric data), pretty_dates (for time stamp data) or to an internal function (for factors) to create pretty axes. If pretty = list(), pretty sequences for an axis/axes are not created and a user-defined sequence is implemented instead (see below). If each axis should be controlled by the same pretty parameters, these can be specified in the pretty argument in a single list. If each axis should be controlled by different parameters, a nested list is required, with a list of arguments for each axis provided within the overall list (see Examples). The default option is to create pretty axes with approximately n = 5 breaks. For factors, the only implemented argument is n, which defines the number of pretty breaks; any other supplied arguments are silently ignored.

units

(optional) A list of units for each side. If pretty = list(), then a regular sequence of values between axes limits will be defined. This can be controlled by supplying the distance between sequential values to this argument (otherwise, a default value is used). For numeric axes, this is a number; for POSIXct axes, this is a character which specifies the duration between sequential ticks (e.g. "secs").

axis

(optional) A named list of arguments that are supplied to axis, axis.POSIXct or axis.Date that control axes (e.g. cex.axis, pos, col, etc.). As for the pretty argument, a single list of arguments will affect all axes; otherwise, a nested list can be provided so that each axis can be controlled independently (see Examples).

pi_notation

(optional) A named list of arguments, passed to pi_notation, to implement \pi notation for axis labels. The default option (NULL) suppresses this argument; a single list (list()) implements \pi notation for all axes using default arguments; and a nested list (e.g., list(list(), NULL)) implements \pi notation for specific axes.

control_axis

(optional) A named list of arguments that affect all axes. This is only useful if a nested list is provided to axis (see above). In this case, any arguments that should affect all axes can be provided via control_axis so that these do not need to be provided to each list in axis. (This is simply for convenience.)

control_sci_notation

A named list of arguments that controls scientific notation (see sci_notation).

control_digits

(optional) An integer which defines the number of decimal places on numeric axes. If NULL, the number of decimal places is set automatically. This argument affects all numeric axes, unless (a) axis labels are all integers; (b) pretty_axis is implemented via pretty_plot and x is plotted against an index; or (c) sci_notation is implemented in which case decimal places should be controlled independently for axes with scientific notation via the digits argument in control_sci_notation (see sci_notation).

control_factor_lim

(optional) A number which specifies an additive adjustment to limits for a factor axis. For factors, with one or more level, limits become (0.75 - control_factor_lim) and (1.25 + control_factor_lim) or (1 - control_factor_lim) and (the number of factor levels + control_factor_lim) respectively.

axis_ls

(optional) The output of a call to pretty_axis. If this is provided, the function skips the definition of axis parameters and simply adds axes to a plot (see add below).

add

A logical input specifying whether or not to plot the axes. Usually, prettier plots result when pretty_axis is called prior to plotting to define axis limits; then, the plot can be created with those limits; and then the list created by the first call to pretty_axis can be supplied to the function again via the axis_ls argument, with add = TRUE, to add the axes (see Examples).

return_list

(depreciated) A logical input defining whether or not to return a list of axis parameters defined by the function.

...

Arguments (cex.axis, cex.lab, col.axis, col.lab, font.axis, font.lab, las) passed from other methods that are added to the control_axis argument and affect all axes.

Value

The function returns a list of parameters with an element for each side. Each element is a list which contains two elements: 'lim', a vector of the lower and upper axis limits for that side and 'axis', a list of parameters that define the axis for that side.

Author(s)

Edward Lavender

Examples

#### Generate some data for numeric examples
set.seed(1)
x <- 1:10
y <- rnorm(length(x), x*5-10, 5)

#### The default options
# The default options define the parameters of pretty axes with approx. n = 5 breaks
# apply pretty_axis to generate a list of parameters for pretty axes;
# we'll use the outputs to set the limits of the graph and then add pretty axes:
axis_args <-
  pretty_axis(side = 1:2,
              x = list(x, y),
              pretty = list(n = 5),
              add = FALSE,
              return_list = TRUE
  )
# plot the graph using limits defined by function
plot(x, y, axes = FALSE, xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
# add pretty axes by passing the axis_args list back to the function
pretty_axis(axis_ls = axis_args, add = TRUE)

#### Adjusting pretty axes via the pretty argument
# We can add arguments to the pretty list that are passed to pretty() or lubridate::pretty_dates()
# ... to adjust the pretty axes produced.
# ... For example, we can adjust the minimum number of intervals on all axes if required:
axis_args <-
  pretty_axis(side = 1:2,
              x = list(x, y),
              pretty = list(n = 5, min.n = 5),
              add = FALSE,
              return_list = TRUE
  )
plot(x, y, axes = FALSE, xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
pretty_axis(axis_ls = axis_args, add = TRUE)

#### Adjusting different axes differently
# In example 1 and 2, the changes to the pretty argument affect all axes
# ... added to all specified sides. To make changes side-specific,
# ... we need to specify the arguments for each side in a separate list.
# For example, to have approx. 5 breaks on the first axis and 20 breaks on the second:
axis_args <-
  pretty_axis(side = 1:2,
              x = list(x, y),
              pretty = list(list(n = 5), list(n = 20)),
              add = FALSE,
              return_list = TRUE
  )
plot(x, y, axes = FALSE, xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
pretty_axis(axis_ls = axis_args, add = TRUE)

#### Pretty labels can be constrained within limits:
# Pretty labels are forced to be within limits if these are specified by the user
axis_args <-
  pretty_axis(side = 1:2,
              x = list(x, y),
              lim = list(x = c(-3, 15), y = c(-19, 49)),
              pretty = list(n = 5),
              add = FALSE,
              return_list = TRUE
  )
plot(x, y, axes = FALSE, xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
pretty_axis(axis_ls = axis_args, add = TRUE)

# Axes limits can be specified for some axes but not others by suppling
# ... NULL elements to the lim list, as follows:
axis_args <-
  pretty_axis(side = 1:2,
              x = list(x, y),
              lim = list(NULL, c(-19, 49)),
              pretty = list(n = 5),
              add = FALSE,
              return_list = TRUE
  )
plot(x, y, axes = FALSE, xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
pretty_axis(axis_ls = axis_args, add = TRUE)

#### For any axis, only one limit can be fixed and the other will be define internally:
# If only one limit is provided, this is assumed to be the first limit:
pretty_axis(side = 1:2, x = list(x, y), lim = list(0, NULL))[[1]]$lim
# The second limit can be coded explicitly as NA:
pretty_axis(side = 1:2, x = list(x, y), lim = list(c(0, NA), NULL))[[1]]$lim
# To hard-code the second limit, you must specify the first limit as NA:
pretty_axis(side = 1:2, x = list(x, y), lim = list(c(0, NA), NULL))[[1]]$lim
# Times work in the same way (see also later exampes):
pretty_axis(side = 1:2,
            x = list(as.Date(c("2016-01-04", "2016-05-01")), 1:2),
            lim = list(as.Date("2016-01-01"), NULL))[[1]]$lim
pretty_axis(side = 1:2,
            x = list(as.POSIXct(c("2016-01-04", "2016-05-01")), 1:2),
            lim = list(as.POSIXct("2016-01-01"), NULL))[[1]]$lim

#### We can create regular sequences instead of pretty ones
# Instead of creating 'pretty' axes, we can choose to create a regular sequence
# ... and specify the total number of units between the start and end points
# ... (if we don't specify this, the default is 5 units or
# ... an automatically determined number of time steps, see below).
# Note that because units only takes one argument for each axes,
# ... we do not specify a nested list like we do for the pretty argument
# ... (or, as we'll see below) for the axis argument,
# ... in which all the arguments for each side need to be grouped into a list.
axis_args <-
  pretty_axis(side = 1:2,
              x = list(x, y),
              lim = list(x = c(-2, 12), y = c(-10, 41)),
              units = list(5, 3),
              add = FALSE,
              return_list = TRUE
  )
plot(x, y, axes = FALSE, xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
pretty_axis(axis_ls = axis_args, add = TRUE)

#### More on controlling each axis separately
# Of course, we can have some axes with pretty axes and others with user defined units
axis_args <-
  pretty_axis(side = 1:2,
              x = list(x, y),
              lim = list(x = c(-2, 12), y = c(-10, 41)),
              pretty = list(list(), list(n = 5)),
              units = list(5, NULL),
              axis_ls = NULL,
              add = FALSE,
              return_list = TRUE
  )
plot(x, y, axes = FALSE, xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
pretty_axis(axis_ls = axis_args, add = TRUE)

#### Arguments are passed to axis(), axis.POSIXct() or axis.Date() via the axis argument
# As above, if we supply these once they will affect all graphs:
axis_args <-
  pretty_axis(side = 1:2,
              x = list(x, y),
              lim = list(x = c(-2, 12), y = c(-10, 41)),
              pretty = list(list(), list(n = 5)),
              units = list(5, NULL),
              axis = list(col = "red", cex.axis = 1.5),
              axis_ls = NULL,
              add = FALSE,
              return_list = TRUE
  )
plot(x, y, axes = FALSE, xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
pretty_axis(axis_ls = axis_args, add = TRUE)

#### Graphical properties of each axis can be controlled separately
# We can change individual axes by creating a list of arguments for each axis
# changes to individual axes need to be specified via individual lists;
# e.g. to make the first axis red and the second blue, and to have 10 pretty labels
# ... on the first axis and 5 on the second we need to:
axis_args <-
  pretty_axis(side = 1:2,
              x = list(x, y),
              lim = list(x = c(-2, 12), y = c(-10, 41)),
              pretty = list(list(n = 10), list(n = 5)),
              units = list(),
              axis = list(list(col = "blue", cex.axis = 1.5), list(col = "red", cex.axis = 1.5)),
              axis_ls = NULL,
              add = FALSE,
              return_list = TRUE
  )
plot(x, y, axes = FALSE, xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
pretty_axis(axis_ls = axis_args, add = TRUE)

#### Shared arguments for each axis (e.g., cex.axis) can be passed via control_axis
# ... list to reduce typing in cases where other axis parameters require separate control
# ... (i.e., to avoid having to pass these arguments to every list in a nested list).
axis_args <-
  pretty_axis(side = 1:2,
              x = list(x, y),
              lim = list(x = c(-2, 12), y = c(-10, 41)),
              pretty = list(list(n = 10), list(n = 5)),
              units = list(),
              axis = list(list(col = "blue"), list(col = "red")),
              control_axis = list(cex.axis = 1.5),
              axis_ls = NULL,
              add = FALSE,
              return_list = TRUE
  )
axis_args[[1]]$axis$cex.axis
axis_args[[2]]$axis$cex.axis

#### Control the number of decimal places for numeric axes via control_digits
# axis label decimal places are chosen automatically with the default setting control_digits = NULL:
axis_ls <- pretty_axis(side = 1, x = list(seq(0, 1, by = 0.1)), control_digits = 1)
axis_ls[[1]]$axis$labels
# user-specified decimal places:
axis_ls <- pretty_axis(side = 1, x = list(seq(0, 1, by = 0.1)), control_digits = 3)
axis_ls[[1]]$axis$labels

#### Generate time stamp data
# Generate some x and y values, where x values are time stamps
# ... in POSIXct format. Note the incorporation of tz.
x <- seq.POSIXt(as.POSIXct("2016-01-01", tz = "UTC"),
                as.POSIXct("2016-01-02", tz = "UTC"), by = "2 mins")
y <- rnorm(length(x), as.numeric(x)*1e-6 + 100, 50)

#### We can use this function with time stamps in POSIXct format too.
# Apply pretty_axis() function prior to plot to obtain suitable limits:
axis_args <-
  pretty_axis(side = 1:4,
              x = list(x, y),
              lim = list(),
              pretty = list(n = 5),
              axis = list(list(),
                          list(las = TRUE),
                          list(labels = FALSE),
                          list(labels = FALSE)),
              axis_ls = NULL,
              add = FALSE,
              return_list = TRUE
  )
# Plot graph using pretty_axis() limits:
plot(x, y,
     type = "l",
     axes = FALSE,
     xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim
)
# Add pretty axes by passing the list of axis_args back to the function
pretty_axis(axis_ls = axis_args, add = TRUE)

#### Axis parameters for time stamps are passed to axis.POSIXct() or axis.Date()
# ... which can incorporate other options
axis_args <-
  pretty_axis(side = 1:4,
              x = list(x, y),
              lim = list(),
              pretty = list(n = 5),
              axis = list(list(format = "%H"),
                           list(las = TRUE),
                           list(labels = FALSE),
                           list(labels = FALSE)),
              axis_ls = NULL,
              add = FALSE,
              return_list = TRUE
  )
plot(x, y,
     type = "l",
     axes = FALSE,
     xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
pretty_axis(axis_ls = axis_args, add = TRUE)

#### Regular sequences, instead of 'pretty' axes can be generated with dates too
# To do this, units takes a character input (e.g. "mins" or "hours")
axis_args <-
  pretty_axis(side = 1:4,
              x = list(x, y),
              lim = list(),
              pretty = list(list(), list(n = 3)),
              units = list("hours", NULL),
              axis = list(list(format = "%H"),
                          list(las = TRUE),
                          list(labels = FALSE),
                          list(labels = FALSE)),
              axis_ls = NULL,
              add = FALSE,
              return_list = TRUE
  )
plot(x, y,
     type = "l",
     axes = FALSE,
     xlim = axis_args$`1`$lim, ylim = axis_args$`2`$lim)
pretty_axis(axis_ls = axis_args, add = TRUE)

#### For factors, pretty axes can be created via pretty or via units.
# .. If pretty is supplied, approximately n factor levels are retained:
# ... and corresponding labels are added by default.
# Example data:
dx <- factor(LETTERS[1:10])
dy <- 1:10
# Example with tick for every level:
axis_ls <-
  pretty_axis(side = 1:2,
              x = list(dx, dy),
              pretty = list(n = length(dx)),
              add = FALSE,
              return_list = TRUE
  )
axis_ls[[1]]
# Note that x limits are automatically defined between 1 and the maximum number of factors:
axis_ls[[1]]; length(levels(dx))
# However, default factor limits can be extended via control_factor_lim
axis_ls <-
  pretty_axis(side = 1:2,
              x = list(dx, dy),
              pretty = list(n = length(dx)),
              control_factor_lim = 0.5,
              add = FALSE,
              return_list = TRUE
  )
axis_ls[[1]]$lim
# Example with tick mark for every other level
axis_ls <-
  pretty_axis(side = 1:2,
              x = list(dx, dy),
              pretty = list(n = length(dx)/2),
              add = FALSE,
              return_list = TRUE
  )
axis_ls[[1]]

#### For factors, pretty axes can also be specified via units:
# For example, to select every factor level:
axis_ls <-
  pretty_axis(side = 1:2,
              x = list(dx, dy),
              pretty = list(list(), list(n = 5)),
              units = list(1, NULL),
              add = FALSE,
              return_list = TRUE
  )
axis_ls[[1]]
# Or, to select every other factor level:
axis_ls <-
  pretty_axis(side = 1:2,
              x = list(dx, dy),
              pretty = list(list(), list(n = 5)),
              units = list(2, NULL),
              add = FALSE,
              return_list = TRUE
  )
axis_ls[[1]]

#### Examples with pi notation
## Define an example lunar phase time series
x <- seq.Date(as.Date("2016-01-01"), as.Date("2016-02-01"), by = 1)
y <- lunar::lunar.phase(x)
## Under the default options, pi notation is suppressed
x <- 1:10
y <- 1:10
axis_ls <- pretty_axis(side = 1:2,
                       x = list(x, y),
                       pretty = list(n = 5),
                       return_list = TRUE
                       )
lapply(axis_ls, function(x) x$axis[c("at", "labels")])
## To use pi notation for all axes with default args, specify list()
axis_ls <- pretty_axis(side = 1:2,
                       x = list(x, y),
                       pretty = list(n = 5),
                       pi_notation = list(),
                       return_list = TRUE
                       )
lapply(axis_ls, function(x) x$axis[c("at", "labels")])
## To use pi notation for specific axes, specify a nested list():
axis_ls <- pretty_axis(side = 1:2,
                       x = list(x, y),
                       pretty = list(n = 5),
                       pi_notation = list(NULL, list()),
                       return_list = TRUE
                       )
lapply(axis_ls, function(x) x$axis[c("at", "labels")])
## Pass arguments to pi_notation() to customise the result
axis_ls <- pretty_axis(side = 1:2,
                       x = list(x, y),
                       pretty = list(n = 5),
                       pi_notation = list(NULL, list(as_fraction = FALSE))
                       )
lapply(axis_ls, function(x) x$axis[c("at", "labels")])

#### The influence of NAs
# For all data types, NAs are removed with a message:
pretty_axis(x = list(factor(c(1, 2, NA)), 1:3))


edwardlavender/prettyGraphics documentation built on Jan. 19, 2025, 2:47 p.m.