Annotating Regression with smoothLabels()

The geomSmooth() layer includes a labels parameter designed to display statistical summaries of the fitted model directly on the plot. This parameter accepts a smoothLabels() object, which provides access to model-specific variables like $R^2$ and the regression equation.

smoothLabels() is a specialized annotation helper designed for smooth layers. It shares the same API as the standard layerLabels() function, supporting familiar methods like .line(), .format(), and .size().

However, it is uniquely equipped to handle regression-specific statistics and markers that are not available in other layers.

Supported variables and markers:

  • ..r2..R² (coefficient of determination). A goodness-of-fit measure showing what fraction of the variance in the response is explained by the fitted model. Values are typically between 0 and 1 (higher means the model explains more of the observed variation).
  • ..adjr2..adjusted R². A variant of R² that accounts for model complexity: it penalizes adding extra terms/parameters and is therefore more suitable for comparing models with different numbers of predictors (e.g., different polynomial degrees). Adjusted R² can be lower than R² and may even be negative for a very poor fit.
  • ..aic..Akaike Information Criterion (AIC) of the fitted model.
  • ..bic..Bayesian Information Criterion (BIC) of the fitted model.
  • ..f.. — F-statistic for the overall model significance test.
  • ..df1.. — numerator degrees of freedom for the F-test.
  • ..df2.. — denominator degrees of freedom for the F-test.
  • ..p.. — p-value for the overall model F-test.
  • ..method.. — smoothing method label (lm or loess).
  • ..n.. — number of observations used in model fitting.
  • ..cilevel.. — confidence level used for the R² confidence interval.
  • ..cilow.. — lower bound of the confidence interval for R².
  • ..cihigh.. — upper bound of the confidence interval for R².
  • ~eqfitted equation. Inserts the model equation into the annotation (can be configured with eq()).
In [1]:
%useLatestDescriptors
%use lets-plot
In [2]:
LetsPlot.getInfo()
Out[2]:
Lets-Plot Kotlin API v.4.13.0. Frontend: Notebook with dynamically loaded JS. Lets-Plot JS v.4.9.0.
Outputs: Web (HTML+JS), Kotlin Notebook (Swing), Static SVG (hidden)
In [3]:
val data = mapOf(
    "x" to listOf(0, 1.5, 1.7, 2),
    "y" to listOf(0, 1, 1.8, 4)
)

val plot = letsPlot(data) { x = "x"; y = "y" } + geomPoint()

Basic Annotation

By default, smoothLabels() adds the Coefficient of Determination ($R^2$) to the plot without requiring additional configuration.

In [4]:
plot + geomSmooth(
    deg = 2,
    labels = smoothLabels()    // <-- Default displays R² value.
)
Out[4]:
R^2 = 0.995796 0 0.5 1 1.5 2 -3 -2 -1 0 1 2 3 4 5 6 y x

Customizing Content and Style

In [5]:
plot + geomSmooth(
    deg = 2,
    labels = smoothLabels()
        .line("""\(R\^2=\)@..r2..""")   // Add custom R² label using LaTeX notation
        .line("~eq")                    // Add the auto-generated equation on a separate line
        .size(20)
) +
theme(
    labelText = elementText(
        family = "DejaVu Sans",
        face = "bold-italic",
        color = "gray60"
    )
)
Out[5]:
R 2=0.995796 y=2.75384x 2 - 3.53506x + 0.00304105 0 0.5 1 1.5 2 -3 -2 -1 0 1 2 3 4 5 6 y x

Grouping and Equation Customization

In [6]:
val t = listOf(0.0, 1.5, 1.7, 2.0, 0.0, 0.5, 0.7, 2.0)
val y = listOf(0.0, 1.0, 1.8, 4.0, 2.0, 5.5, 6.0, 4.5)
val g = listOf("a", "a", "a", "a", "b", "b", "b", "b")

val plotGroups = letsPlot(mapOf("t" to t, "y" to y, "g" to g)) {
    x = "t"; y = "y"; color = "g"
} + geomPoint(showLegend = false)

plotGroups + geomSmooth(
    deg = 2,
    showLegend = false,
    labels = smoothLabels()
        .line("""\(R\^2=\)@..r2.., \(R_{{adj}}\^2=\)@..adjr2.., ~eq""")   // Combine all markers into a single line
        .eq(lhs = "y(t)", rhs = "t", format = ".3f", threshold = 0.01)    // Set variable name, precision, and hide small coefficients
        .format("..r2..", ".4f")
        .format("..adjr2..", ".4f")
        .labelX("right", "left")                       // Position labels horizontally
        .labelY("bottom", "top")                       // Position labels vertically
        .inheritColor()
        .size(20)
) + themeClassic()
Out[6]:
R 2=0.9958, R adj 2=0.9874, y(t)=2.754t 2 - 3.535t R 2=0.9945, R adj 2=0.9836, y(t)=3.567t 2 + 8.362t + 2.037 0 0.5 1 1.5 2 -2 0 2 4 6 8 y t