Hey all. I’ve had reason to revisit this issue, and I’d like to add an alternative solution. The one I provided before based on Kruschke’s text works well if your goal is to convert the standardized model parameters themselves to an unstandardized metric. But if you are instead primarily interested in model predictions, there’s an easier way. To demonstrate, here we fit a simple Gaussian model with two continuous predictors. All variables have been standardized.
# Load the packages
library(tidyverse)
library(brms)
library(tidybayes)
# Add standardized variables to the data
mtcars <- mtcars |>
mutate(mpg_z = (mpg - mean(mpg)) / sd(mpg),
disp_z = (disp - mean(disp)) / sd(disp),
wt_z = (wt - mean(wt)) / sd(wt))
# Fit a simple Gaussian model with weakly-regularizing priors
my_fit <- brm(
data = mtcars,
mpg_z ~ disp_z + wt_z,
prior = prior(normal(0, 1), class = Intercept) +
prior(normal(0, 1), class = b) +
prior(exponential(1), class = sigma),
cores = 4, seed = 1)
# The model summary, for the curious
print(my_fit)
Family: gaussian
Links: mu = identity; sigma = identity
Formula: mpg_z ~ disp_z + wt_z
Data: mtcars (Number of observations: 32)
Draws: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
total post-warmup draws = 4000
Regression Coefficients:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
Intercept -0.00 0.09 -0.17 0.18 1.00 2778 2198
disp_z -0.36 0.19 -0.76 0.00 1.00 2222 2074
wt_z -0.54 0.19 -0.91 -0.14 1.00 2170 1694
Further Distributional Parameters:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
sigma 0.50 0.07 0.39 0.65 1.00 2262 1964
Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
and Tail_ESS are effective sample size measures, and Rhat is the potential
scale reduction factor on split chains (at convergence, Rhat = 1).
Let’s say you wanted to show the predicted values for the criterion variable mpg_z
across the observed range of the first predictor disp_z
, while holding the second predictor wt_z
at its mean. Here’s how you might plot the results against the data, using help from the {tidybayes} package.
# Define the predictor values, and save in a data frame
nd <- data.frame(
disp_z = seq(from = min(mtcars$disp_z), to = max(mtcars$disp_z), length.out = 101),
wt_z = 0)
# Pass the predictor values to the model
nd |>
add_epred_draws(my_fit) |>
# Plot
ggplot(aes(x = disp_z)) +
stat_lineribbon(aes(y = .epred),
.width = 0.95, color = "skyblue", fill = alpha("skyblue", 0.5)) +
geom_point(data = mtcars,
aes(y = mpg_z)) +
labs(y = "mpg_z",
title = "Standardized",
subtitle = "(holding wt_z at its mean)")
To put the results back on the original un-standardized metric, you simply un-standardize the primary predictor values (disp_z
) and the expected values (.epred
) with help from the sample statistics for disp
and mpg
. That can look like so.
# Compute and save the sample statistics
disp_mean <- mean(mtcars$disp)
disp_sd <- sd(mtcars$disp)
mpg_mean <- mean(mtcars$mpg)
mpg_sd <- sd(mtcars$mpg)
# Pass the predictor values to the model
nd |>
add_epred_draws(my_fit) |>
# Convert to the non-standardized metric
mutate(disp = (disp_z * disp_sd) + disp_mean,
mpg = (.epred * mpg_sd) + mpg_mean) |>
# Plot
ggplot(aes(x = disp, y = mpg)) +
stat_lineribbon(.width = 0.95, color = "skyblue", fill = alpha("skyblue", 0.5)) +
geom_point(data = mtcars) +
labs(y = "mpg",
title = "Un-standardized",
subtitle = "(holding wt_z at its mean)")
This basic approach will generalize to more complicated models, such as those with many predictors, and complications like multilevel models.