Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions questions/189_calculate-the-pinball-loss/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement a function to calculate the mean Pinball Loss (also known as the Quantile Loss) between arrays of actual and predicted values for a given quantile level. The Pinball Loss is the standard metric used to evaluate quantile regression models: unlike symmetric losses such as MAE or MSE, it penalizes under-predictions and over-predictions differently depending on the target quantile.
5 changes: 5 additions & 0 deletions questions/189_calculate-the-pinball-loss/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"input": "y_true = np.array([3, -0.5, 2, 7]), y_pred = np.array([2.5, 0.0, 2, 8]), quantile = 0.5",
"output": "0.25",
"reasoning": "The per-sample errors (y_true - y_pred) are [0.5, -0.5, 0, -1]. With quantile 0.5 each error contributes 0.5 * |error|, giving [0.25, 0.25, 0, 0.5]. The mean of these values is 1.0 / 4 = 0.25."
}
59 changes: 59 additions & 0 deletions questions/189_calculate-the-pinball-loss/learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## Pinball Loss (Quantile Loss)

The **Pinball Loss**, also called the **Quantile Loss**, measures how well a model predicts a specific quantile $\tau \in (0, 1)$ of the target distribution. When $\tau = 0.5$ it corresponds (up to a factor of 2) to the Mean Absolute Error, but for other quantiles it becomes an **asymmetric** loss, which is exactly what makes it useful for quantile regression.

1. **Per-sample Formula**:

For a single observation with true value $y_i$ and prediction $\hat{y}_i$, let the error be $e_i = y_i - \hat{y}_i$. The pinball loss is:

$$
L_\tau(y_i, \hat{y}_i) =
\begin{cases}
\tau \, (y_i - \hat{y}_i) & \text{if } y_i \geq \hat{y}_i \\[4pt]
(1 - \tau)(\hat{y}_i - y_i) & \text{if } y_i < \hat{y}_i
\end{cases}
$$

A convenient closed form that avoids the branch is:

$$
L_\tau(y_i, \hat{y}_i) = \max\big(\tau \, e_i,\; (\tau - 1)\, e_i\big)
$$

2. **Mean over the dataset**:

The reported loss is the average over all $n$ observations:

$$
\text{PinballLoss} = \frac{1}{n} \sum_{i=1}^{n} L_\tau(y_i, \hat{y}_i)
$$

3. **Intuition**:
- When the model **under-predicts** ($\hat{y}_i < y_i$), the error is weighted by $\tau$.
- When the model **over-predicts** ($\hat{y}_i > y_i$), the error is weighted by $1 - \tau$.
- Choosing a **high** quantile (e.g. $\tau = 0.9$) makes under-prediction expensive, pushing predictions upward. Choosing a **low** quantile (e.g. $\tau = 0.1$) does the opposite.

4. **Example Calculation**:

For the values:
```
y_true = [3, -0.5, 2, 7]
y_pred = [2.5, 0.0, 2, 8]
quantile = 0.5
```

Per-sample errors $e_i = y_i - \hat{y}_i$ are $[0.5, -0.5, 0, -1]$, giving:

$$
\begin{align*}
\text{PinballLoss} &= \frac{1}{4}\big(0.5\cdot0.5 + 0.5\cdot0.5 + 0 + 0.5\cdot1\big) \\
&= \frac{1}{4}(0.25 + 0.25 + 0 + 0.5) \\
&= \frac{1.0}{4} = 0.25
\end{align*}
$$

5. **Properties**:
- Pinball loss is always non-negative: $\text{PinballLoss} \geq 0$.
- Perfect predictions give a loss of $0$.
- At $\tau = 0.5$ the loss equals $\tfrac{1}{2}\,\text{MAE}$.
- It is convex in the prediction, so it can be minimized with gradient-based optimization.
15 changes: 15 additions & 0 deletions questions/189_calculate-the-pinball-loss/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"id": "189",
"title": "Calculate the Pinball Loss",
"difficulty": "easy",
"category": "Machine Learning",
"video": "",
"likes": "0",
"dislikes": "0",
"contributor": [
{
"profile_link": "https://github.com/thealper2",
"name": "Alper Karaca"
}
]
}
24 changes: 24 additions & 0 deletions questions/189_calculate-the-pinball-loss/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import numpy as np

def pinball_loss(y_true, y_pred, quantile=0.5):
"""
Calculate the mean Pinball (Quantile) Loss between two arrays.

Parameters:
y_true (numpy.ndarray): Array of true values
y_pred (numpy.ndarray): Array of predicted values
quantile (float): Target quantile in the open interval (0, 1)

Returns:
float: Mean Pinball Loss rounded to 3 decimal places
"""
if y_true.shape != y_pred.shape:
raise ValueError("Arrays must have the same shape")
if y_true.size == 0:
raise ValueError("Arrays cannot be empty")
if not 0 < quantile < 1:
raise ValueError("quantile must be in the open interval (0, 1)")

error = y_true - y_pred
loss = np.maximum(quantile * error, (quantile - 1) * error)
return round(float(np.mean(loss)), 3)
17 changes: 17 additions & 0 deletions questions/189_calculate-the-pinball-loss/starter_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import numpy as np

def pinball_loss(y_true, y_pred, quantile=0.5):
"""
Calculate the mean Pinball (Quantile) Loss between two arrays.

Parameters:
y_true (numpy.ndarray): Array of true values
y_pred (numpy.ndarray): Array of predicted values
quantile (float): Target quantile in the open interval (0, 1)

Returns:
float: Mean Pinball Loss rounded to 3 decimal places
"""
# Your code here
pass
return round(val, 3)
22 changes: 22 additions & 0 deletions questions/189_calculate-the-pinball-loss/tests.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"test": "print(pinball_loss(np.array([3, -0.5, 2, 7]), np.array([2.5, 0.0, 2, 8]), 0.5))",
"expected_output": "0.25"
},
{
"test": "print(pinball_loss(np.array([10, 20, 30]), np.array([12, 18, 32]), 0.9))",
"expected_output": "0.733"
},
{
"test": "print(pinball_loss(np.array([1, 2, 3, 4]), np.array([1.5, 2.5, 2.5, 5]), 0.1))",
"expected_output": "0.463"
},
{
"test": "print(pinball_loss(np.array([5, 5, 5]), np.array([5, 5, 5]), 0.5))",
"expected_output": "0.0"
},
{
"test": "print(pinball_loss(np.array([[1, 2], [3, 4]]), np.array([[1.5, 1], [2, 5]]), 0.7))",
"expected_output": "0.462"
}
]