Skip to contents

stat_ma_eq fits model II regressions. From the fitted model it generates several labels including the equation, p-value, coefficient of determination (R^2), and number of observations.


  mapping = NULL,
  data = NULL,
  geom = "text_npc",
  position = "identity",
  formula = NULL,
  method = "lmodel2:MA",
  method.args = list(),
  n.min = 2L,
  range.y = NULL,
  range.x = NULL,
  nperm = 99,
  eq.with.lhs = TRUE,
  eq.x.rhs = NULL,
  small.r = FALSE,
  small.p = FALSE,
  coef.digits = 3,
  coef.keep.zeros = TRUE,
  decreasing = FALSE,
  rr.digits = 2,
  theta.digits = 2,
  p.digits = max(1, ceiling(log10(nperm))),
  label.x = "left",
  label.y = "top",
  hstep = 0,
  vstep = NULL,
  output.type = NULL,
  na.rm = FALSE,
  orientation = NA,
  parse = NULL,
  show.legend = FALSE,
  inherit.aes = TRUE



The aesthetic mapping, usually constructed with aes. Only needs to be set at the layer level if you are overriding the plot defaults.


A layer specific dataset, only needed if you want to override the plot defaults.


The geometric object to use display the data


The position adjustment to use for overlapping points on this layer


other arguments passed on to layer. This can include aesthetics whose values you want to set, not map. See layer for more details.


a formula object. Using aesthetic names x and y instead of original variable names.


function or character If character, "MA", "SMA" , "RMA" or "OLS", alternatively "lmodel2" or the name of a model fit function are accepted, possibly followed by the fit function's method argument separated by a colon (e.g. "lmodel2:MA"). If a function different to lmodel2(), it must accept arguments named formula, data, range.y, range.x and nperm and return a model fit object of class lmodel2.


named list with additional arguments.


integer Minimum number of distinct values in the explanatory variable (on the rhs of formula) for fitting to the attempted.

range.y, range.x

character Pass "relative" or "interval" if method "RMA" is to be computed.


integer Number of permutation used to estimate significance.


If character the string is pasted to the front of the equation label before parsing or a logical (see note).


character this string will be used as replacement for "x" in the model equation when generating the label before parsing it.

small.r, small.p

logical Flags to switch use of lower case r and p for coefficient of determination and p-value.


integer Number of significant digits to use for the fitted coefficients.


logical Keep or drop trailing zeros when formatting the fitted coefficients and F-value.


logical It specifies the order of the terms in the returned character string; in increasing (default) or decreasing powers.

rr.digits, theta.digits, p.digits

integer Number of digits after the decimal point to use for R^2, theta and P-value in labels. If Inf, use exponential notation with three decimal places.

label.x, label.y

numeric with range 0..1 "normalized parent coordinates" (npc units) or character if using geom_text_npc() or geom_label_npc(). If using geom_text() or geom_label() numeric in native data units. If too short they will be recycled.

hstep, vstep

numeric in npc units, the horizontal and vertical step used between labels for different groups.


character One of "expression", "LaTeX", "text", "markdown" or "numeric".


a logical indicating whether NA values should be stripped before the computation proceeds.


character Either "x" or "y" controlling the default for formula.


logical Passed to the geom. If TRUE, the labels will be parsed into expressions and displayed as described in ?plotmath. Default is TRUE if output.type = "expression" and FALSE otherwise.


logical. Should this layer be included in the legends? NA, the default, includes if any aesthetics are mapped. FALSE never includes, and TRUE always includes.


If FALSE, overrides the default aesthetics, rather than combining with them. This is most useful for helper functions that define both data and aesthetics and shouldn't inherit behaviour from the default plot specification, e.g. borders.


A data frame, with a single row and columns as described under

Computed variables. In cases when the number of observations is less than n.min a data frame with no rows or columns is returned rendered as an empty/invisible plot layer.


This stat can be used to automatically annotate a plot with \(R^2\), \(P\)-value, \(n\) and/or the fitted model equation. It supports linear major axis (MA), standard major axis (SMA) and ranged major axis (RMA) regression by means of function lmodel2. Formulas describing a straight line and includinmg an intercept are the only ones currently supported. Please see the documentation, including the vignette of package 'lmodel2' for details. The parameters in stat_ma_eq() follow the same naming as in function lmodel2().

It is important to keep in mind that although the fitted line does not depend on whether the \(x\) or \(y\) appears on the rhs of the model formula, the numeric estimates for the parameters do depend on this.

A ggplot statistic receives as data a data frame that is not the one passed as argument by the user, but instead a data frame with the variables mapped to aesthetics. stat_ma_eq() mimics how stat_smooth() works, except that only linear regression can be fitted. Similarly to these statistics the model fits respect grouping, so the scales used for x and y should both be continuous scales rather than discrete.

The minimum number of observations with distinct values can be set through parameter n.min. The default n.min = 2L is the smallest possible value. However, model fits with very few observations are of little interest and using a larger number for n.min than the default is usually wise.


For backward compatibility a logical is accepted as argument for eq.with.lhs. If TRUE, the default is used, either "x" or "y", depending on the argument passed to formula. However, "x" or "y" can be substituted by providing a suitable replacement character string through eq.x.rhs. Parameter orientation is redundant as it only affects the default for formula but is included for consistency with ggplot2::stat_smooth().

Methods in lmodel2 are all computed always except for RMA that requires a numeric argument to at least one of range.y or range.x. The results for specific methods are extracted a posteriori from the model fit object. When a function is passed as argument to method, the method can be passed in a list to method.args as member method. More easily, the name of the function can be passed as a character string together with the lmodel2-supported method.

R option OutDec is obeyed based on its value at the time the plot is rendered, i.e., displayed or printed. Set options(OutDec = ",") for languages like Spanish or French.


stat_ma_eq understands x and y, to be referenced in the formula while the weight aesthetic is ignored. Both x and y must be mapped to numeric variables. In addition, the aesthetics understood by the geom ("text" is the default) are understood and grouping respected.

Transformation of x or y within the model formula is not supported by stat_ma_eq(). In this case, transformations should never be applied in the model formula, but instead in the mapping of the variables within aes.

Computed variables

If output.type is different from "numeric" the returned tibble contains columns listed below. If the fitted model does not contain a given value, the label is set to character(0L).


x position


y position


equation for the fitted polynomial as a character string to be parsed


\(R^2\) of the fitted model as a character string to be parsed


P-value if available, depends on method.


Angle in degrees between the two OLS lines for lines estimated from y ~ x and x ~ y linear model (lm) fits.


Number of observations used in the fit.


Set according to mapping in aes.


Set according method used.

r.squared, theta, p.value, n

numeric values, from the model fit object

If output.type is "numeric" the returned tibble contains columns listed below. If the model fit function used does not return a value, the variable is set to NA_real_.


x position


y position

list containing the "coefficients" matrix from the summary of the fit object

r.squared, theta, p.value, n

numeric values, from the model fit object


Set according to mapping in aes.


TRUE is polynomial is forced through the origin


One or two columns with the coefficient estimates

To explore the computed values returned for a given input we suggest the use of geom_debug as shown in the last examples below.

See also

The major axis regression model is fitted with function lmodel2, please consult its documentation. Statistic stat_ma_eq() can return different ready formatted labels depending on the argument passed to output.type. If ordinary least squares polynomial regression is desired, then stat_poly_eq. If quantile-fitted polynomial regression is desired, stat_quant_eq should be used. For other types of models such as non-linear models, statistics stat_fit_glance and stat_fit_tidy should be used and the code for construction of character strings from numeric values and their mapping to aesthetic label explicitly supplied in the call.

Other ggplot statistics for major axis regression: stat_ma_line()


# generate artificial data
set.seed(98723) <- data.frame(x = rnorm(100) + (0:99) / 10 - 5,
                      y = rnorm(100) + (0:99) / 10 - 5,
                      group = c("A", "B"))

# using defaults (major axis regression)
ggplot(, aes(x, y)) +
  geom_point() +
  stat_ma_line() +

ggplot(, aes(x, y)) +
  geom_point() +
  stat_ma_line() +
  stat_ma_eq(mapping = use_label("eq"))

ggplot(, aes(x, y)) +
  geom_point() +
  stat_ma_line() +
  stat_ma_eq(mapping = use_label("eq"), decreasing = TRUE)

# use_label() can assemble and map a combined label
ggplot(, aes(x, y)) +
  geom_point() +
  stat_ma_line(method = "MA") +
  stat_ma_eq(mapping = use_label(c("eq", "R2", "P")))

ggplot(, aes(x, y)) +
  geom_point() +
  stat_ma_line(method = "MA") +
  stat_ma_eq(mapping = use_label(c("R2", "P", "theta", "method")))

# using ranged major axis regression
ggplot(, aes(x, y)) +
  geom_point() +
  stat_ma_line(method = "RMA",
               range.y = "interval",
               range.x = "interval") +
  stat_ma_eq(mapping = use_label(c("eq", "R2", "P")),
             method = "RMA",
             range.y = "interval",
             range.x = "interval")

# No permutation-based test
ggplot(, aes(x, y)) +
  geom_point() +
  stat_ma_line(method = "MA") +
  stat_ma_eq(mapping = use_label(c("eq", "R2")),
             method = "MA",
             nperm = 0)
#> No permutation test will be performed

# explicit formula "x explained by y"
ggplot(, aes(x, y)) +
  geom_point() +
  stat_ma_line(formula = x ~ y) +
  stat_ma_eq(formula = x ~ y,
             mapping = use_label(c("eq", "R2", "P")))

# modifying both variables within aes()
ggplot(, aes(log(x + 10), log(y + 10))) +
  geom_point() +
  stat_poly_line() +
  stat_poly_eq(mapping = use_label("eq"),
               eq.x.rhs = "~~log(x+10)",
               eq.with.lhs = "log(y+10)~~`=`~~")

# grouping
ggplot(, aes(x, y, color = group)) +
  geom_point() +
  stat_ma_line() +

# labelling equations
       aes(x, y,  shape = group, linetype = group, grp.label = group)) +
  geom_point() +
  stat_ma_line(color = "black") +
  stat_ma_eq(mapping = use_label(c("grp", "eq", "R2"))) +

# Inspecting the returned data using geom_debug()
# This provides a quick way of finding out the names of the variables that
# are available for mapping to aesthetics with after_stat().

gginnards.installed <- requireNamespace("gginnards", quietly = TRUE)

if (gginnards.installed)

# default is output.type = "expression"
if (gginnards.installed)
  ggplot(, aes(x, y)) +
    geom_point() +
    stat_ma_eq(geom = "debug")

#> [1] "PANEL 1; group(s) -1; 'draw_function()' input 'data' (head):"
#>   npcx npcy                  label                               eq.label
#> 1   NA   NA italic(R)^2~`=`~"0.79" italic(y)~`=`~0.171 + 0.948*~italic(x)
#>                 rr.label              p.value.label              theta.label
#> 1 italic(R)^2~`=`~"0.79" italic(P)[perm]~`=`~"0.01" italic(theta)~`=`~"6.67"
#>             n.label grp.label         method.label r.squared    theta p.value
#> 1 italic(n)~`=`~100        -1 "method: lmodel2:MA" 0.7917998 6.665222    0.01
#>     n  fm.method fm.class fm.formula fm.formula.chr        x        y PANEL
#> 1 100 lmodel2:MA  lmodel2      y ~ x          y ~ x -6.56061 5.687505     1
#>   group orientation
#> 1    -1           x

if (FALSE) {
if (gginnards.installed)
  ggplot(, aes(x, y)) +
    geom_point() +
    stat_ma_eq(mapping = aes(label = after_stat(eq.label)),
               geom = "debug",
               output.type = "markdown")

if (gginnards.installed)
  ggplot(, aes(x, y)) +
    geom_point() +
    stat_ma_eq(geom = "debug", output.type = "text")

if (gginnards.installed)
  ggplot(, aes(x, y)) +
    geom_point() +
    stat_ma_eq(geom = "debug", output.type = "numeric")