parsnip mars() in R: Adaptive Regression Splines
The parsnip mars() function defines a multivariate adaptive regression splines model for tidymodels. It gives you one interface for a model that fits non-linear relationships automatically, by stitching together hinged line segments through the earth engine.
mars() # default spec, earth engine mars() |> set_mode("regression") # predict a numeric outcome mars() |> set_mode("classification") # classify a factor outcome mars(num_terms = 10) # cap the number of model terms mars(prod_degree = 2) # allow pairwise interactions mars(prune_method = "backward") # choose the pruning algorithm fit(spec, mpg ~ ., data = mtcars) # train on a dataset
Need explanation? Read on for examples and pitfalls.
What mars() does
mars() is a model specification, not a fitted model. It records your choice of a multivariate adaptive regression splines model and its hyperparameters, but no data touches it until you call fit(). This separation lets you reuse one specification across many datasets or resampling folds.
A MARS model captures curves and interactions without you naming them. It builds hinge functions, paired line segments of the form max(0, x - knot) and max(0, knot - x), then chooses knots in a forward pass and trims weak terms in a backward pruning pass. The result is a piecewise linear surface that bends where the data bends.
The function belongs to the tidymodels framework. Because parsnip standardizes the interface, mars() shares the same fit() and predict() verbs used by every other parsnip model.
mars() syntax and arguments
mars() takes three hyperparameters and two setup verbs. The arguments shape how many spline terms the model keeps, while set_engine() and set_mode() finish the specification.
The num_terms argument caps how many hinge terms survive pruning, so a smaller value gives a simpler model. The prod_degree argument controls interactions: 1 keeps the model additive, while 2 lets two predictors combine in a single term. The prune_method argument names the backward-pass algorithm, with "backward" as the earth default.
earth, so you need the earth package installed before you fit. Install it first, or R reports that the engine is not available. When num_terms and prod_degree are left NULL, earth picks sensible values from the data and its own defaults.Fit a MARS model: four examples
Every example below uses a built-in R dataset. The mtcars data drives the regression examples and iris drives the classification example, so the code runs anywhere with no downloads.
Example 1: Fit a regression MARS on mtcars
Build the specification, then fit it to data. Leaving the hyperparameters unset lets earth choose the number of terms and the knots.
Squaring the correlation between predicted and actual mpg gives a training R-squared near 0.89. MARS found the knots where fuel economy changes pace, mostly along weight and horsepower.
Example 2: Predict mpg for new rows
predict() returns a tidy tibble with one row per input row. Each prediction comes from the piecewise linear surface MARS built.
The .pred column holds the predicted miles per gallon as a number. The output keeps the same row order as the input, so you can bind it back to sample_rows with bind_cols().
Example 3: Allow interactions with prod_degree
Set prod_degree to 2 and MARS may combine two predictors in one term. An additive model misses effects that only appear when variables move together.
With prod_degree = 2, earth can build a hinge on weight that also depends on horsepower. Keep this value low to avoid overfitting.
Example 4: Classify iris species with mars
Switch the mode to "classification" and the same function predicts a factor. earth fits one MARS surface per class and picks the highest score.
Comparing the predicted labels to the true species gives a training accuracy near 97%. The hinge functions bend the class boundary where petal measurements separate versicolor from virginica.
prod_degree from 1 to 2 often recovers more signal than switching algorithms. It lets the model express interactions it could not see as a purely additive surface, at a small and controllable cost in complexity.mars() vs linear_reg() vs decision_tree()
parsnip ships several ways to fit a non-linear or linear surface. They share the same verbs, so swapping between them is a one-line change.
| Function | Shape it fits | Handles interactions | Use when |
|---|---|---|---|
linear_reg() |
One straight slope per predictor | Only if you add terms by hand | The relationship is close to linear |
mars() |
Piecewise linear hinges | Automatically, via prod_degree |
Curves and interactions are unknown |
decision_tree() |
Axis-aligned step regions | Automatically, through splits | The surface has sharp jumps |
Start with linear_reg() when a straight line is plausible, reach for mars() when the relationship curves but stays smooth, and use decision_tree() when the response jumps in steps.
Common pitfalls
Three mistakes catch most newcomers to mars(). Each one below shows the problem and the fix.
The most common is forgetting to set the mode. A MARS model can predict a number or a class, so parsnip cannot guess which one you want and fit() fails until you call set_mode().
The second pitfall is pushing prod_degree too high. A degree of 3 or more lets the model build deep interactions that memorize noise instead of signal. The third is treating num_terms as a target rather than a cap, since earth still prunes below it whenever the extra terms do not help.
prod_degree at 1 or 2 for most problems, and tune it across resampling folds rather than trusting training accuracy.Try it yourself
Try it: Fit an additive regression MARS on mtcars with prod_degree = 1, then predict mpg for the first row. Save the prediction to ex_pred.
Click to reveal solution
Explanation: Setting prod_degree = 1 keeps the model additive, so each predictor contributes its own hinge terms with no cross-products. Row 1 of mtcars is the Mazda RX4, whose true mpg is 21, so the MARS surface lands close.
Related parsnip functions
mars() works alongside the rest of the parsnip model family. These functions cover the neighboring tasks in a tidymodels project.
linear_reg()defines a linear regression model with one slope per predictor.decision_tree()defines a single tree of axis-aligned splits.bag_mars()defines a bagged ensemble of MARS models for steadier predictions.set_engine()chooses the computational backend for any specification.fit()trains a specification on data and returns a model object.
FAQ
What package is mars() in?
mars() ships in core parsnip, so library(tidymodels) or library(parsnip) makes it available. The function only describes the model; the actual fitting happens in an engine package. The standard registered engine is earth, which implements the MARS algorithm, so install the earth package separately before you call fit().
What is the difference between mars() and linear_reg()?
linear_reg() fits one straight slope for each predictor across its whole range. mars() places knots where the slope should change and joins straight segments at those knots, so it can model curves that a linear model cannot. MARS also adds interactions on its own when prod_degree is above 1. Choose linear_reg() when a straight line is plausible and mars() when the relationship clearly bends.
What does prod_degree control in mars()?
The prod_degree argument sets the highest interaction degree the model may build. A value of 1 keeps the model additive, so each term involves a single predictor. A value of 2 lets earth multiply two hinge functions, capturing effects that appear only when two variables move together. Higher values are rarely worth the overfitting risk, so 1 and 2 cover almost every practical case.
Does mars() work for classification?
Yes. Call set_mode("classification") and the earth engine fits a MARS surface for each class, then predicts the class with the highest score. predict() returns a .pred_class column, and predict(fit, type = "prob") returns one .pred_<class> probability column per class. MARS classification works best when the class boundary curves smoothly rather than jumping in steps.
How do I tune mars() hyperparameters?
Mark the arguments with tune(), as in mars(num_terms = tune(), prod_degree = tune()), then pass the specification to tune_grid() with a resampling object such as vfold_cv(). The framework scores a grid of term counts and interaction degrees with cross-validation. Use select_best() to pick the winner, then finalize_workflow() to lock the values before the final fit.
For the full argument reference, see the parsnip mars() documentation.