How to Perform the Ramsey RESET Test in R

The Ramsey RESET test—Regression Equation Specification Error Test—is your first line of defense against a misspecified regression model. Developed by James Ramsey in 1969, this test answers a...

Key Insights

  • The Ramsey RESET test detects model misspecification by checking whether powers of fitted values significantly improve your regression, with a significant p-value indicating your model likely has omitted variables or incorrect functional form.
  • Use lmtest::resettest() with power = 2:3 as your default specification—this tests for both quadratic and cubic nonlinearities, giving you broad coverage without overfitting the auxiliary regression.
  • When RESET fails, don’t panic—the test tells you something is wrong but not what. Start with log transformations of skewed variables, then try polynomial terms, and finally consider interaction effects.

Introduction to the RESET Test

The Ramsey RESET test—Regression Equation Specification Error Test—is your first line of defense against a misspecified regression model. Developed by James Ramsey in 1969, this test answers a critical question: does your linear model capture the true relationship in your data, or are you missing something important?

Specification errors come in two main flavors. First, omitted variable bias: you’ve left out a relevant predictor that correlates with your included variables. Second, incorrect functional form: the true relationship is nonlinear, but you’ve forced a linear fit. Both problems produce biased, inconsistent coefficient estimates that can lead you to wrong conclusions.

You should run RESET as part of your standard regression diagnostic workflow, right after fitting your model and before interpreting coefficients. It’s particularly valuable when you’re uncertain about the functional form of relationships or when theory doesn’t strongly guide your specification choices.

The Theory Behind RESET

The intuition behind RESET is elegant. If your model is correctly specified, the fitted values should contain all the systematic information about the dependent variable. Powers of those fitted values—squared, cubed, and so on—shouldn’t add any explanatory power.

Here’s how it works mechanically. You fit your original model and extract the fitted values ŷ. Then you run an auxiliary regression that includes your original predictors plus powers of ŷ (typically ŷ² and ŷ³). If these additional terms are jointly significant, your original model is misspecified.

The null hypothesis states that the model is correctly specified—the coefficients on the powered fitted values are all zero. The alternative hypothesis indicates misspecification. The test uses an F-statistic to assess joint significance:

$$F = \frac{(SSR_{restricted} - SSR_{unrestricted}) / q}{SSR_{unrestricted} / (n - k - q)}$$

Where q is the number of added power terms, n is sample size, and k is the number of original predictors.

A significant result (typically p < 0.05) means you reject correct specification. Your model has problems. An insignificant result means you fail to find evidence of misspecification—not proof that your model is correct, but at least you haven’t detected obvious issues.

Setting Up Your R Environment

You need the lmtest package for the primary RESET implementation. The car package provides useful complementary diagnostics. Install and load them:

# Install packages if needed
install.packages("lmtest")
install.packages("car")

# Load packages
library(lmtest)
library(car)

The lmtest package contains resettest(), which handles all the heavy lifting. It’s well-maintained, widely used, and integrates smoothly with base R’s lm() objects.

Running RESET with the lmtest Package

The resettest() function takes an lm object and performs the test. Let’s work through a practical example using the mtcars dataset, predicting fuel efficiency from vehicle characteristics:

# Fit a linear model predicting mpg
model <- lm(mpg ~ hp + wt + qsec, data = mtcars)
summary(model)

# Run the RESET test
resettest(model)

This produces output like:

	RESET test

data:  model
RESET = 0.89052, df1 = 2, df2 = 25, p-value = 0.4232

The resettest() function has two key parameters you should understand:

power: Controls which powers of fitted values to include. Default is 2:3 (squared and cubed). You can specify 2 for just quadratic, 3 for just cubic, or 2:4 to include quartic terms.

type: Determines what gets powered. Options are "fitted" (powers of ŷ, the default), "regressor" (powers of each original predictor), or "princomp" (principal components of powered regressors). Stick with "fitted" unless you have specific reasons otherwise.

# Run RESET with different specifications
resettest(model, power = 2, type = "fitted")      # Only quadratic
resettest(model, power = 3, type = "fitted")      # Only cubic
resettest(model, power = 2:3, type = "fitted")    # Both (default)
resettest(model, power = 2:4, type = "fitted")    # Include quartic

Interpreting RESET Results

Let’s examine the output components systematically:

# Fit a potentially misspecified model
bad_model <- lm(mpg ~ hp, data = mtcars)
reset_result <- resettest(bad_model, power = 2:3)
print(reset_result)

Output:

	RESET test

data:  bad_model
RESET = 4.5637, df1 = 2, df2 = 28, p-value = 0.01917

RESET statistic (4.5637): The F-statistic testing joint significance of the added power terms. Higher values indicate stronger evidence against correct specification.

df1 (2): Numerator degrees of freedom—the number of power terms added (here, ŷ² and ŷ³).

df2 (28): Denominator degrees of freedom—residual degrees of freedom from the augmented regression.

p-value (0.01917): The probability of observing this F-statistic if the null hypothesis (correct specification) were true.

Decision rules are straightforward:

  • p < 0.05: Reject null. Your model is likely misspecified.
  • p ≥ 0.05: Fail to reject null. No evidence of misspecification detected.

Let’s compare results across different power specifications:

# Create a function to run multiple RESET specifications
compare_reset <- function(model) {
  results <- data.frame(
    Power = c("2", "3", "2:3", "2:4"),
    F_stat = numeric(4),
    p_value = numeric(4)
  )
  
  powers_list <- list(2, 3, 2:3, 2:4)
  
  for (i in 1:4) {
    test <- resettest(model, power = powers_list[[i]])
    results$F_stat[i] <- round(test$statistic, 4)
    results$p_value[i] <- round(test$p.value, 4)
  }
  
  return(results)
}

# Compare for our simple model
compare_reset(bad_model)

This might produce:

  Power  F_stat p_value
1     2  8.9214  0.0058
2     3  0.8847  0.3549
3   2:3  4.5637  0.0192
4   2:4  3.1425  0.0413

Notice how results vary by specification. The quadratic term alone is highly significant (p = 0.0058), while the cubic alone is not (p = 0.3549). This pattern suggests the misspecification involves a quadratic relationship. Use power = 2:3 as your default—it catches both types of nonlinearity without being too aggressive.

What to Do When RESET Fails

A significant RESET test tells you something is wrong but not what. Here’s a systematic approach to fixing misspecification:

Step 1: Examine residual plots

# Visual diagnostics
par(mfrow = c(2, 2))
plot(bad_model)

Look for curvature in the residuals vs. fitted plot. This often reveals the nature of the nonlinearity.

Step 2: Try log transformations

Many economic and biological relationships are multiplicative, not additive. Log transformations often linearize these:

# Try log transformation of dependent variable
log_model <- lm(log(mpg) ~ hp, data = mtcars)
resettest(log_model)

Step 3: Add polynomial terms

If the relationship appears quadratic or cubic, add those terms explicitly:

# Add quadratic term for horsepower
quad_model <- lm(mpg ~ hp + I(hp^2), data = mtcars)
summary(quad_model)
resettest(quad_model)

Output might show:

	RESET test

data:  quad_model
RESET = 0.42315, df1 = 2, df2 = 27, p-value = 0.6593

The p-value of 0.66 indicates we’ve resolved the misspecification by adding the quadratic term.

Step 4: Consider interaction effects

Sometimes the relationship between X and Y depends on another variable:

# Full model with interactions and polynomial terms
full_model <- lm(mpg ~ hp + wt + hp:wt + I(hp^2), data = mtcars)
resettest(full_model)

Step 5: Iterate and validate

Keep refining until RESET passes, but don’t overfit. Each added term should have theoretical justification, not just statistical significance.

# Final comparison
cat("Original model RESET p-value:", 
    resettest(bad_model)$p.value, "\n")
cat("Quadratic model RESET p-value:", 
    resettest(quad_model)$p.value, "\n")

Limitations and Best Practices

RESET is powerful but imperfect. Understand its limitations:

It’s a general test, not a specific diagnostic. RESET tells you something is wrong but can’t distinguish between omitted variables, wrong functional form, or measurement error. You need additional investigation to identify the actual problem.

It has low power against some alternatives. RESET may miss certain types of misspecification, particularly those that don’t manifest as polynomial nonlinearity in fitted values.

It assumes homoskedasticity. If your errors have non-constant variance, RESET results may be unreliable. Run a Breusch-Pagan test first:

library(lmtest)
bptest(model)  # Test for heteroskedasticity

Complementary diagnostics to run:

# Heteroskedasticity
bptest(model)

# Normality of residuals
shapiro.test(residuals(model))

# Influential observations
influenceIndexPlot(model)

# Variance inflation factors (multicollinearity)
vif(model)

Recommended workflow:

  1. Fit your initial model based on theory
  2. Run RESET with power = 2:3
  3. If significant, examine residual plots for patterns
  4. Try transformations and polynomial terms systematically
  5. Re-run RESET after each modification
  6. Stop when RESET passes and model makes theoretical sense
  7. Run complementary diagnostics before final interpretation

The RESET test should be a standard part of your regression toolkit. It takes seconds to run and can save you from publishing results based on a fundamentally flawed model. Use it early, use it often, and take its warnings seriously.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.