Skip to content
Back to skills

Scaled Pinball Loss

ASecurity

Scaled Pinball Loss (SPL) metric for evaluating quantile forecasts, normalized by mean absolute successive differences of training data

  • 61 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 12, 2026
datapython

Works with

  • cli

Security analysis

A100/100

Scanned September 12, 2026

npx -y skills add wenmin-wu/ds-skills --skill scaled-pinball-loss --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Scaled Pinball Loss?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Scaled Pinball Loss
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/wenmin-wu-scaled-pinball-loss/badge)](https://www.skillsdirectory.com/skills/wenmin-wu-scaled-pinball-loss)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: timeseries-scaled-pinball-loss
description: Scaled Pinball Loss (SPL) metric for evaluating quantile forecasts, normalized by mean absolute successive differences of training data
---

# Scaled Pinball Loss

## Overview

The Scaled Pinball Loss (SPL) evaluates quantile forecast accuracy while being scale-independent across time series. The numerator is the standard pinball (quantile) loss; the denominator normalizes by the mean absolute successive difference of the training data, similar to how MASE normalizes by naive forecast error. This makes SPL comparable across series with different scales.

## Quick Start

```python
import numpy as np

def scaled_pinball_loss(y_true, y_pred, quantile, y_train):
    """Compute SPL for a single quantile forecast."""
    h = len(y_true)

    # Pinball numerator
    errors = y_true - y_pred
    pinball = np.where(errors >= 0, quantile * errors, (quantile - 1) * errors)
    numerator = np.sum(pinball)

    # Scale denominator: mean absolute successive differences
    N = len(y_train)
    denom = np.sum(np.abs(np.diff(y_train))) / (N - 1)

    return numerator / (h * denom)

def mean_spl(y_true, quantile_preds, quantiles, y_train):
    """Average SPL across all quantiles."""
    scores = []
    for q, pred in zip(quantiles, quantile_preds):
        scores.append(scaled_pinball_loss(y_true, pred, q, y_train))
    return np.mean(scores)

quantiles = [0.005, 0.025, 0.165, 0.25, 0.5, 0.75, 0.835, 0.975, 0.995]
score = mean_spl(y_val, preds_per_quantile, quantiles, y_train)
```

## Workflow

1. Compute pinball loss: `q * e` if `e >= 0`, else `(q-1) * e`
2. Sum over the forecast horizon
3. Compute denominator: mean absolute first-difference of training data
4. Divide numerator by `(h * denominator)` for scale-free metric
5. Average across all quantiles for the final SPL score

## Key Decisions

- **Denominator**: successive differences capture the series' natural variability
- **Per-quantile vs average**: report both for diagnostics; average for leaderboard
- **Flat series**: if `denom ≈ 0`, the series is constant — clip to a small epsilon
- **vs CRPS**: SPL evaluates specific quantiles; CRPS integrates over all quantiles

## References

- [M5 - Sales Uncertainty Prediction](https://www.kaggle.com/code/allunia/m5-sales-uncertainty-prediction)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.