How to compute elasticities and semi-elasticities for marginal effects

Prerequisites

Problem statement

You want to express marginal effects as elasticities — percentage changes rather than level changes. You need the full elasticity (ey/ex), semi-elasticity (dy/ex or ey/dx), or want to combine elasticity methods with custom scales.

Minimal working solution

Pass method= to dydx with one of "eyex", "dyex", or "eydx". Continuous variables only — discrete variables raise.

import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
from smmargins import Margins

rng = np.random.default_rng(7)
N = 5_000
df = pd.DataFrame({
    "age": rng.normal(45, 12, N).clip(18, 90),
    "income": rng.lognormal(10.5, 0.4, N),
    "educ": rng.choice(["hs", "college", "grad"], N, p=[0.4, 0.4, 0.2]),
    "female": rng.integers(0, 2, N),
    "region": rng.choice(["north", "south", "east", "west"], N),
})
df["voted"] = (rng.uniform(0, 1, N) < 1 / (1 + np.exp(-(
    -4 + 0.05 * df.age + 0.00001 * df.income
    + 0.8 * (df.educ == "college") + 1.4 * (df.educ == "grad")
    + 0.3 * df.female - 0.0004 * df.age * df.female
)))).astype(int)

fit = smf.logit("voted ~ age + income + C(educ) + female + age:female", data=df).fit(disp=False)
M = Margins(fit)

# Full elasticity: (dy/dx) * (x/y)
print("Full elasticity ey/ex for age:")
print(M.dydx("age", method="eyex"))

# Semi-elasticity: (dy/dx) * x
print("\nSemi-elasticity dy/ex for age:")
print(M.dydx("age", method="dyex"))

# Semi-elasticity: (dy/dx) / y
print("\nSemi-elasticity ey/dx for age:")
print(M.dydx("age", method="eydx"))

Variations

Elasticity at means (MEM-style)

print("MEM elasticity of age at covariate means:")
print(M.dydx("age", method="eyex", at="mean"))

Elasticity on the linear scale

# ey/ex on the linear predictor scale (beta * x / eta)
print("Linear-scale elasticity of income:")
print(M.dydx("income", method="eyex", scale="linear"))

Elasticity with counterfactual values

# Elasticity of age when income is at median
print("Elasticity of age at median income:")
print(M.dydx("age", method="eyex", values={"income": "p50"}))

See How to set covariate profiles with values=, Expr, and newdata= for the full values= DSL.

When to use this

Use method="eyex" when you want to interpret the effect as “a 1% increase in X leads to a _% change in Y.” Use method="dyex" for “a 1% increase in X leads to a _-unit change in Y.” Use method="eydx" for “a 1-unit increase in X leads to a _% change in Y.” Elasticities are especially useful when variables are measured on different scales.

When NOT to use this

⚠️ Trade-off: Do not use elasticity methods on discrete variables — they raise ValueError. For binary or categorical variables, stick with method="dydx" (the default). Do not use method="eyex" when predictions can be zero or negative (e.g., OLS with negative fitted values) — the division by y is undefined.

See also