Title

qmodplot -- Quadratic Moderation Plot: visualise conditional
curves, marginal effects, and confidence bands from moderated regression

Syntax

qmodplot,
model(#)
[coefficient_options]
[variable_label_options]
[moderator_options]
[plot_options]
[ci_options]
[data_options]
[ereturn_options]

Model (required)
model(#) 1 = linear x; 2 = quadratic x; 3 = quadratic x + quadratic moderation

Coefficients (manual entry)
b0(#) Intercept (default 0)

bx(#) Coefficient on x

bxsq(#) Coefficient on x² (models 2 & 3)

bmod(#) Coefficient on m (moderator)

bxm(#) Coefficient on x·m (linear interaction)

bxsqm(#) Coefficient on x²·m (quadratic interaction; model 3)

Variable labels
xname(string) Label for x axis (default: "x")

mname(string) Label for moderator (default: "m")

yname(string) Label for y axis (default: "y")

Plot range
xrange(# #) Lower and upper x-grid bounds (default: -3 3)

npoints(#) Grid points for smooth curves (default: 200)

Moderator reference points (one required)
mvalues(numlist) Explicit moderator values — binary, ordinal, or any set of numbers

mdata(varname) Derive reference points as evenly-spaced quantiles from a variable

nquantiles(#) Number of quantile curves from mdata() (default: 3 → p25/p50/p75)

CI options
ci Add delta-method confidence bands (requires fromereturn)

level(#) Confidence level in % (default: 95)

Data options
xdata(varname) x variable in dataset (used for scatter and summary stats)

ydata(varname) y variable in dataset (used for scatter actual values and summary stats)

labelvar(varname) Label variable for cross-section/time-series scatter

panelid(varname) Panel ID variable — collapses to unit means before plotting

cutstats Print turning-point table (models 2 & 3)

scatter Plot fitted values for each observation (or panel mean) over model curves

savetable(filename) Save cutoff table and predictions to a CSV file

Graph options
title(string) Main title for the function graph

scheme(string) Stata graph scheme (default: s2color)

combine Stack function and ME graphs into a single combined panel

expgraph(filename) Export graph(s) to file (any format supported by graph export)

Load from last regression
fromereturn Read coefficients automatically from e(b)

xvar(name) Variable name for x in e(b)

xsqvar(name) Variable name for x² in e(b)

mvar(name) Variable name for m in e(b)

xmvar(name) Variable name for x·m in e(b)

xsqmvar(name) Variable name for x²·m in e(b)

Description

qmodplot visualises moderated regression models where the focal variable
x may enter linearly or as a quadratic, and the moderator m may
affect both the linear and quadratic components of x. This covers the full
range from a simple interaction to a fully moderated U-shape.

For every call, qmodplot produces two publication-quality graphs:

qmodplot_curves — the predicted function y = f(x) plotted at several
representative values of the moderator. When the model is quadratic, each
curve is a parabola whose vertex shifts as the moderator changes.

qmodplot_me — the marginal (partial) effect dy/dx plotted across the
x range. For linear models this is a horizontal line; for quadratic models
the slope of the ME line shows how the curvature changes with the moderator.

Two key design choices distinguish qmodplot from generic interaction
plotters:

1. Quadratic moderation (Model 3). Most tools only plot x·m interactions.
qmodplot also handles x²·m, so the vertex of the parabola itself
becomes a function of the moderator — the turning point moves both
horizontally and vertically as m changes. The cutstats table
reports exactly where each vertex falls and whether it lies within your
observed data range.

2. Multiple moderator reference points. You are not limited to the
conventional low/medium/high three-curve design. Specify as many reference
points as you need via mvalues() — theory-driven cut-points, round
numbers, meaningful thresholds, or every level of an ordinal variable.
Alternatively use mdata() with nquantiles() to let the data
determine where the reference points fall (p25/p50/p75, decile boundaries,
etc.). Each reference point generates one curve in both graphs.

The program never modifies the dataset in memory; it uses
preserve/restore throughout.

Key features at a glance

Feature Detail

Three nested models Linear, quadratic, and fully quadratic-moderated

Any number of reference points Use mvalues() or mdata()/nquantiles()

Delta-method CI bands Transparent shading; lines visible through bands

Turning-point table Vertex x* and y(x*) at every moderator value

Observation scatter Label each data point on the model curves

Panel support Collapses to unit means before scatter plot

Live data or manual entry Works with or without a dataset loaded

CSV export Full results table saved to file

Pure Stata No Mata; works in Stata 14+

Models

Model 1 — Linear x, Linear Moderation

y = b0 + bx·x + bmod·m + bxm·(x·m)

Marginal effect: dy/dx = bx + bxm·m (constant in x — horizontal line)

Model 2 — Quadratic x, Linear Moderation

y = b0 + bx·x + bxsq·x² + bmod·m + bxm·(x·m)

Marginal effect: dy/dx = bx + 2·bxsq·x + bxm·m

Turning point: x* = −(bx + bxm·m) / (2·bxsq)

(vertex shifts along x as m changes, but curvature bxsq is constant)

Model 3 — Quadratic x, Quadratic Moderation (fully moderated curvature)

y = b0 + bx·x + bxsq·x² + bmod·m + bxm·(x·m) + bxsqm·(x²·m)

Marginal effect: dy/dx = bx + 2·(bxsq + bxsqm·m)·x + bxm·m

Turning point: x* = −(bx + bxm·m) / [2·(bxsq + bxsqm·m)]

(both the vertex location AND the curvature change with m)

In Model 3 the moderator does not merely shift the parabola left/right
(as in Model 2); it also tilts and stretches it. A positive bxsqm means
the U-shape becomes steeper as m increases; a negative bxsqm flattens it.
Use cutstats to see how the vertex moves across the moderator values
you have specified.

Options in detail

model(#) sets the functional form. Must be 1, 2, or 3. Coefficients
not relevant to the chosen model are accepted but ignored with a note.

mvalues(numlist) specifies any number of moderator values at which to
draw curves. Examples: mvalues(0 1) for a binary moderator;
mvalues(-1 0 1) for a three-level ordinal; mvalues(2000 3000 4000)
for a continuous moderator at theory-driven cut-points; or even
mvalues(1 2 3 4 5) for all five levels of a Likert scale.
You may supply mdata() alongside mvalues() — in that case
mvalues() still drives the curves and mdata() is used only
for scatter predictions and summary statistics.

mdata(varname) names a numeric variable in memory. qmodplot
computes evenly-spaced quantile positions and uses those as the reference
points. With nquantiles(3) the positions are p25, p50, p75;
with nquantiles(5) they are p17, p33, p50, p67, p83; and so on up
to nquantiles(10).

ci adds % confidence bands around each conditional curve and
each marginal-effect curve using the delta method. The bands are drawn as
semi-transparent shading (25% opacity) so that the curve lines and any
overlapping bands from other moderator values remain clearly visible.
Requires fromereturn and all relevant variable-name options so that
the correct elements can be extracted from e(V).

cutstats prints a turning-point table for models 2 and 3. For each
moderator value the table shows: the computed vertex x*, the predicted
y(x*), and whether x* falls within the specified x range. A baseline
row at m = 0 is always included for reference. Not applicable to Model 1
(linear ME has no turning point).

scatter overlays labelled data points on the model curves.
For cross-section or time-series data, each row in memory becomes one
plotted point; use labelvar() to identify points by name.
For panel data, use panelid() — the program collapses x and m to
within-unit means before computing fitted values, and labels each point
with its panel identifier.

fromereturn reads all coefficients from the most recent estimation
results (e(b)). Combine with xvar(), xsqvar(),
mvar(), xmvar(), xsqmvar() to identify each term.

Examples

All examples use Stata's built-in auto dataset and can be
run directly after copying the commands.

Example 1 — Model 1: binary moderator, manual reference points

The simplest case: linear MPG effect moderated by car origin (0 = domestic,
1 = foreign). Two curves are drawn, one per value of the binary moderator.

. sysuse auto, clear

. gen mpgf = mpg * foreign

. regress price mpg foreign mpgf

. qmodplot, model(1) fromereturn

xvar(mpg) mvar(foreign) xmvar(mpgf)

xname(MPG) mname(Foreign) yname(Price)

xrange(12 41) mvalues(0 1) combine

Example 2 — Model 2: continuous moderator, multiple reference points, CI

Vehicle weight is a continuous moderator. Five reference points at
theory-driven round numbers show how the MPG–price parabola shifts and
how the turning point moves as cars get heavier.
ci adds 95% delta-method confidence bands.
cutstats reports each vertex and whether it lies within the data range.

. sysuse auto, clear

. gen mpgsq = mpg^2

. gen mpgwt = mpg * weight

. regress price mpg mpgsq weight mpgwt

. qmodplot, model(2) fromereturn

xvar(mpg) xsqvar(mpgsq) mvar(weight) xmvar(mpgwt)

xname(MPG) mname(Weight) yname(Price)

xrange(12 41) mvalues(2000 2500 3000 3500 4000)

ci level(95) cutstats combine

Alternatively, derive reference points automatically from the data:

. qmodplot, model(2) fromereturn

xvar(mpg) xsqvar(mpgsq) mvar(weight) xmvar(mpgwt)

xname(MPG) mname(Weight) yname(Price)

xrange(12 41) mdata(weight) nquantiles(4)

ci level(95) cutstats combine

(nquantiles(4) gives p20/p40/p60/p80 — four evenly-spaced
positions across the weight distribution.)

Example 3 — Model 3: fully moderated curvature, binary moderator, CI + scatter

In this model the quadratic term x²·m (mpgfsq) allows the curvature of the
price–MPG parabola to differ between domestic and foreign cars — not just
the vertex location. scatter overlays each car's fitted value and
labels it by origin. Results are saved to a CSV file.

. sysuse auto, clear

. gen mpgsq = mpg^2

. gen mpgf = mpg * foreign

. gen mpgfsq = mpgsq * foreign

. regress price mpg mpgsq foreign mpgf mpgfsq

. qmodplot, model(3) fromereturn

xvar(mpg) xsqvar(mpgsq) mvar(foreign) xmvar(mpgf) xsqmvar(mpgfsq)

xname(MPG) mname(Foreign) yname(Price)

xrange(12 41) mvalues(0 1)

xdata(mpg) ydata(price) mdata(foreign)

ci level(95) cutstats scatter labelvar(foreign)

combine savetable("qmodplot_ex3.csv")

Example 4 — Model 2: continuous moderator, panel-mean scatter

For panel data, use panelid() instead of labelvar().
The program collapses x and m to within-unit means, computes fitted values
at those means, and labels each plotted point with the panel identifier.
Here we treat rep78 (repair record, 1–5) as the panel ID for illustration.

. sysuse auto, clear

. drop if missing(rep78)

. gen mpgsq = mpg^2

. gen mpgwt = mpg * weight

. regress price mpg mpgsq weight mpgwt

. qmodplot, model(2) fromereturn

xvar(mpg) xsqvar(mpgsq) mvar(weight) xmvar(mpgwt)

xname(MPG) mname(Weight) yname(Price)

xrange(12 41) mdata(weight) nquantiles(3)

xdata(mpg) ydata(price)

cutstats scatter panelid(rep78) combine

Saved graph names

qmodplot_curves — conditional function curves

qmodplot_me — marginal effect curves

qmodplot_combined — stacked panel (combine only)

qmodplot_scatter — observation/panel scatter (scatter only)

All graphs remain in Stata's graph memory and can be redisplayed or
exported at any time:

. graph display qmodplot_curves

. graph export "fig1.png", name(qmodplot_curves) replace

Author

Noman Arshed

Department of Business Analytics, Sunway Business School

Sunway University, Malaysia

Installation

To install from SSC:

. ssc install qmodplot

To update an existing installation:

. ssc install qmodplot, replace

To install directly from a local file (development use):

. do qmodplot.ado

Citation

If you use qmodplot in published work, please cite:

Arshed, N. (2025). qmodplot: Quadratic Moderation Plot for Stata.
Statistical Software Components, Boston College Department of Economics.
Available from: https://ideas.repec.org/c/boc/bocode/

Also see

twoway

margins (model-based marginal effects)

graph export