Skip to content

Expectile and Quantile Regression Examples

This guide demonstrates expectile regression and compares it with quantile regression on heteroscedastic datasets using ExpectileCrossoverLoss and QuantileCrossoverLoss.

→ API: ExpectileLoss, QuantileLoss, MultiQuantileLoss. Guide: Quantile & expectile.

# Reference
1 Newey, W. K., & Powell, J. L. (1987). Asymmetric Least Squares Estimation and Testing. Econometrica.
2 Koenker, R., & Bassett Jr, G. (1978). Regression Quantiles. Econometrica.

Mathematical Formulations

Quantile Regression (Pinball Loss)

Quantile regression estimates the conditional quantiles of a response variable. The pinball loss (quantile loss) for a level \(q \in (0, 1)\) is defined as: $\(L_{q}(y, \hat{y}) = \begin{cases} q(y - \hat{y}) & \text{if } y > \hat{y} \\ (1-q)(\hat{y} - y) & \text{if } y \le \hat{y} \end{cases}\)$

Expectile Regression (Asymmetric Least Squares)

Expectile regression generalizes mean regression using asymmetric least squares. For an expectile level \(\tau \in (0, 1)\), the loss is: $\(L_{\tau}(y, \hat{y}) = \begin{cases} 2 \tau (y - \hat{y})^2 & \text{if } y > \hat{y} \\ 2 (1-\tau)(y - \hat{y})^2 & \text{if } y \le \hat{y} \end{cases}\)$

Crossover Penalties

When predicting multiple curves simultaneously, crossing curves violate the property that lower levels must be strictly smaller than higher levels. The crossover loss penalizes violations: $\(L_{\text{crossover}}(Y, \{\hat{y}_i\}) = L_{\text{base}}(Y, \{\hat{y}_i\}) + \gamma \sum_{i} \max(\hat{y}_i - \hat{y}_{i+1}, 0)\)$ where \(\gamma > 0\) is the penalty weight.


Code Example

Below is a complete, runnable example comparing expectile and quantile models on heteroscedastic data.

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torchregress.losses import ExpectileCrossoverLoss, QuantileCrossoverLoss

# Generate synthetic heteroscedastic data
np.random.seed(42)
n_samples = 1000
x = np.random.uniform(-2.0, 2.0, size=(n_samples, 1)).astype(np.float32)
mean = 0.5 * (x**3)
noise_std = 0.5 + 0.5 * (x**2)
y = mean + np.random.normal(0.0, noise_std).astype(np.float32)

x_tensor = torch.from_numpy(x)
y_tensor = torch.from_numpy(y)

# Model outputting multiple levels
class MultiLevelModel(nn.Module):
    def __init__(self, num_levels=3):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(1, 64),
            nn.ReLU(),
            nn.Linear(64, num_levels)
        )
    def forward(self, x):
        return self.net(x).unsqueeze(2)

# Train Expectile Model (levels 0.1, 0.5, 0.9)
model_exp = MultiLevelModel(num_levels=3)
opt_exp = optim.Adam(model_exp.parameters(), lr=0.01)
loss_exp = ExpectileCrossoverLoss(expectiles=[0.1, 0.5, 0.9], crossover_penalty=10.0)

for epoch in range(150):
    preds = model_exp(x_tensor)
    loss = loss_exp(preds, y_tensor)
    opt_exp.zero_grad()
    loss.backward()
    opt_exp.step()

print("Expectile crossover regression model trained successfully.")