Use Plotly Express OLS Trendlines for Quick Linear Fit Checks
Add ordinary least squares trendlines to Plotly Express scatter plots, with limits and verification steps.
05 Dec 2025, 08:06 UTC

Start here
You want a straight-line fit on a scatter plot without switching to a modeling library. Plotly Express can add an ordinary least squares (OLS) trendline directly inside `px.scatter`, but the feature has boundaries that matter before you use it in a report or dashboard.
Useful takeaway: `px.scatter(df, x='engine_size', y='mpg', trendline='ols')` fits one linear regression per color group or facet using statsmodels, draws it as a separate trace labeled "OLS fit," and works best for exploratory correlation checks. It is not a replacement for residual analysis, it requires an optional Python package, and it fits in linear space even when an axis is logarithmic.
How the trendline is built
When you set `trendline='ols'`, Plotly Express imports statsmodels and fits an ordinary least squares model—ordinary least squares means it minimizes the sum of squared vertical distances between the observed points and the fitted line—for each subset defined by `color` or `facet_col`. The fit is added as its own scatter trace, so it appears in the legend and can be toggled like any other trace.
Worked configuration
Run this in a Python environment where Plotly and pandas are available:
import pandas as pd
import plotly.express as px
df = pd.DataFrame({
"engine_size": [1.6, 2.0, 2.5, 3.0, 3.5, 4.0],
"mpg": [32, 28, 24, 20, 16, 12],
"cylinders": [4, 4, 6, 6, 8, 8]
})
fig = px.scatter(
df,
x="engine_size",
y="mpg",
color="cylinders",
trendline="ols",
labels={"engine_size": "Engine size (L)", "mpg": "Miles per gallon"}
)
fig.update_layout(title="Fuel economy vs engine size with OLS trendline")
fig.show()
Expected check: after `fig.show()`, the legend should include entries such as "4", "6", "8", and "OLS fit" for each group. Inspect `fig.data` to confirm a trace named "OLS fit" exists for each color group.
To verify the dependency before plotting, run:
python -c "import statsmodels; print(statsmodels.__version__)"
Where to run: your local terminal or notebook cell. Required permissions: none beyond normal Python execution. Risk: if statsmodels is missing, Plotly Express cannot compute the fit when `trendline='ols'` is used. Install it with `pip install statsmodels` and retry.
Limits and common mistakes
- Linear only. The feature fits straight lines. It does not support polynomial, exponential, or robust regression. If the relationship is curved, the OLS line can be misleading.
- statsmodels required. `trendline='ols'` is an optional dependency path. Without statsmodels installed, Plotly Express cannot compute the fit.
- Small or degenerate groups. Groups with fewer than two points, or where every x value is identical, cannot produce a slope. Plotly Express may skip the fit or raise an error depending on the release. Check groups with constant x before assuming every category gets a line.
- Log axes are fitted in linear space. If you set `log_x=True` or `log_y=True`, the regression is still computed on the raw numeric values, then drawn on the transformed axis. The visual line may look curved on a log scale even though the fit is linear in the original units.
- Categorical x needs encoding. Passing string categories directly to `x` without numeric encoding will not produce a meaningful OLS line because OLS requires numeric independent variables.
- Coefficients are hidden. The figure shows the line, but the slope and intercept are not exposed in the layout or hover template by default. Extract them separately with statsmodels if you need to report the equation.
- FigureWidget updates require restyle. If you change the underlying data in a FigureWidget, the trendline trace does not automatically recompute. You must regenerate the figure or explicitly update the trace.
Diagnostic decision: when to use it
| Situation | Recommendation |
|---|---|
| Quick exploratory check of linear correlation | Use `trendline='ols'`; verify with `fig.data` |
| Reporting slope and intercept in a paper | Fit with statsmodels directly; do not read coefficients from the plot |
| Curved or exponential relationship | Transform variables first or use a different modeling tool |
| Many small facet groups with one or two points | Inspect group sizes; OLS may be skipped or unstable |
| Log-scaled axes | Remember the fit is linear in raw space; interpret visual curvature carefully |
Practical verification
After building the figure, run this check in the same Python session:
fit_traces = [t for t in fig.data if getattr(t, "name", "") == "OLS fit"]
print(f"OLS fit traces found: {len(fit_traces)}")
If the count is zero, statsmodels may be missing, a group may be degenerate, or `trendline` was not applied. Confirm statsmodels imports cleanly and that each group has at least two distinct x values.
OLS is sensitive to outliers and high-leverage points. A single distant point can rotate the line and change the apparent relationship. Inspect residuals separately before making causal claims; the trendline trace is descriptive, not inferential.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.