Skip to content
hundredfolds
intermediatetutorial

Polynomial Regression: Predict Ad Revenue

Part 5 of the ML Regression Lab. Capture nonlinear, diminishing-returns trends by adding a squared feature — fit a curve to ad-spend vs. revenue using plain LinearRegression.

@shvinn

Machine Learning Engineer

2 mins readFeb 7, 2026
View source repository

The whole series so far assumed relationships are straight lines. But ad spend vs. revenue usually shows diminishing returns — each extra dollar buys a little less. Part 5, the finale, fits that curve with polynomial regression — and the surprise is that we still use plain LinearRegression.

Follow along: 05_digital_marketing_ad_revenue_prediction in github.com/shvinn/ai-lab-99.

The key idea

A model is "linear" in its parameters, not its features. If we add a new feature equal to the square of the input, a linear model can fit a curve:

revenue=w2spend2+w1spend+b\text{revenue} = w_2 \cdot \text{spend}^2 + w_1 \cdot \text{spend} + b

That's just multiple linear regression where one "feature" happens to be spend2\text{spend}^2. The technique — inventing new columns from existing ones — is feature engineering.

Step 1 — Baseline: a plain line

First fit a straight line so you have something to beat.

import pandas as pd
from sklearn.linear_model import LinearRegression
 
df = pd.read_csv("digital_marketing_data.csv")
 
X = df[["ad_spend_usd"]]
y = df["revenue_usd"]
 
linear = LinearRegression().fit(X, y)

If the true relationship curves, a straight line will systematically under- and over-predict in different ranges — visible as structure in the residuals.

Step 2 — Engineer the squared feature

Add a column for spend2\text{spend}^2 alongside the original spend:

X_poly = pd.DataFrame()
X_poly["ad_spend_usd_squared"] = df["ad_spend_usd"] ** 2
X_poly["ad_spend_usd"] = df["ad_spend_usd"]

Step 3 — Train the polynomial model

Same estimator, richer features:

poly_model = LinearRegression().fit(X_poly, y)
 
poly_model.coef_        # → [-4.22e-04, 6.51]  (squared term, linear term)
poly_model.intercept_   # → 8123.73

The negative coefficient on the squared term is the math expressing diminishing returns — the curve bends downward as spend grows.

Step 4 — Evaluate against the baseline

from sklearn.metrics import mean_squared_error
 
for name, m, feats in [("linear", linear, X), ("polynomial", poly_model, X_poly)]:
    rmse = mean_squared_error(y, m.predict(feats)) ** 0.5
    print(f"{name:>10} RMSE: {rmse:.2f}")

The polynomial model should have the lower RMSE — proof the curve fits better than the line. (Beware: keep raising the degree and you'll overfit, chasing noise. Degree 2 is enough here.)

Step 5 — Deploy

app.py
import streamlit as st
 
 
def predict_revenue(ad_spend_usd):
    return (-4.21948692e-04 * ad_spend_usd ** 2
            + 6.51043749 * ad_spend_usd + 8123.725871275172)
 
 
st.title("📊 Predict Revenue from Ad Spend")
ad_spend = st.number_input("Ad Spend (USD)", min_value=0.0, value=1000.0, step=100.0)
 
if st.button("Predict"):
    st.metric("Estimated Revenue (USD)", f"${predict_revenue(ad_spend):,.2f}")

Series wrap-up

You built five deployable models and, with them, the core toolkit of classical regression:

  1. Simple linear regression — one feature, the full workflow.
  2. Multiple linear regression — many features.
  3. Binary encoding — two-value categoricals.
  4. One-hot encoding — multi-value categoricals.
  5. Polynomial regression — nonlinear trends via feature engineering.

Same LinearRegression estimator throughout — what changed each time was the features you fed it. That insight, more than any single model, is what carries you into the rest of machine learning. The complete code for all five is in github.com/shvinn/ai-lab-99.