Contents
Deep Breakdown
What it tells me (Situation Awareness): The absolute value distributes over division: the absolute value of a quotient equals the quotient of the absolute values. This holds for any . In DA/DS, this property is used every time you compute a relative or normalized error metric: absolute relative error is — absolute numerator divided by absolute denominator. MAPE (Mean Absolute Percentage Error) is built on exactly this. It also appears in z-score interpretation ( when ), in computing absolute log-returns, and in any situation where you need the magnitude of a ratio regardless of the signs of the numerator and denominator. The property eliminates case-by-case sign analysis: instead of checking whether is positive or negative, you can just take absolute values of numerator and denominator independently.
Flowchart
Real-world use
Where it is used
Absolute relative error: — used when you need the error as a fraction of the true value, regardless of whether the prediction over- or under-shoots. MAPE = . Absolute z-score: — the number of standard deviations a value lies from the mean, always non-negative. Signal-to-noise ratio magnitude: . Absolute log-returns: .
Problem it solves
When computing a ratio , the sign of the result depends on the signs of both and . If you only care about the magnitude (how large is the relative deviation, regardless of direction?), you need to apply absolute value to the quotient. Without this property, you would have to enumerate four sign cases: , , , . The distributive property reduces this to: just take .
How it solves the problem
because the absolute value of a product (or quotient) equals the product (or quotient) of the absolute values: . Applying this to : . The sign of the quotient is discarded; only the magnitudes remain.
Example in a business
A forecasting analyst computes MAPE for a revenue model. For one period, actual revenue is $-\$2{,}000-$1{,}500$. The signed error is $-2000 - (-1500) = -500$. The relative error is $\frac{-500}{-2000} = 0.25\frac{|-500|}{|-2000|} = \frac{500}{2000} = 0.25 = 25%$. The property $\left|\frac{a}{b}\right| = \frac{|a|}{|b|}$ makes the sign handling transparent and automatic.
The formula, piece by piece
Breaking the notation:
- : the absolute value of the quotient — the magnitude of the ratio.
- : the numerator — may be positive, negative, or zero.
- : the denominator — must be nonzero (); may be positive or negative.
- : the magnitude of the numerator, always .
- : the magnitude of the denominator, always (since ).
- : the result — always , equal to the magnitude of the quotient. When : .
The ratio is zero with zero magnitude. Connection to absolute value multiplication: . Division is multiplication by the reciprocal: , so . Absolute relative error formula: — valid when .
The formula in words
"The absolute value of a fraction equals the fraction of the absolute values: numerator's magnitude over denominator's magnitude. The sign of neither numerator nor denominator matters — you just strip both signs. In Python: abs(a / b) gives the same result as abs(a) / abs(b). This is how MAPE is computed: take the absolute value of (actual minus forecast), then divide by the absolute value of actual, giving the unsigned relative deviation."
Critical Concepts
- Distributes over division (and multiplication): and . Absolute value is multiplicative — it distributes over both multiplication and division, but NOT over addition or subtraction (that's what the triangle inequality is about).
- MAPE undefined when actuals are zero: is undefined when . Always check for zero actuals before computing MAPE. Use SMAPE (Symmetric MAPE) or MASE as alternatives when actuals can be zero.
- Sign of denominator does not affect magnitude: . Whether the denominator is positive or negative, the absolute ratio is the same. This eliminates the need for conditional sign logic.
- Absolute z-score: is always because . This is just since .
- Order of operations: in Python,
abs(a / b)andabs(a) / abs(b)give identical results (floating-point rounding aside).
For clean code in error metric pipelines, use abs(a) / abs(b) to make it explicit that both numerator and denominator are treated as magnitudes.
Step-by-step calculation (tiny example)
Task: compute the absolute relative error for a prediction where actual = and forecast = .
Step 1: Compute the raw error .
Step 2: Compute the absolute values . .
Step 3: Apply the formula .
Step 4: Verify directly . . Same result.
Step 5: Interpret The prediction is off by 25% of the actual value, regardless of the fact that both values are negative.
Code examples
Beginner example (Python)
# |a/b| = |a|/|b| in Python
# Basic verification across all sign combinations
pairs = [ ( 6, 3), # 6 / 3 = 2.0 → |2.0| = 2.0
(-6, 3), # -6 / 3 = -2.0 → |-2.0| = 2.0
( 6, -3), # 6 / -3 = -2.0 → |-2.0| = 2.0
(-6, -3), # -6 / -3 = 2.0 → |2.0| = 2.0
( 0, 5), # 0 / 5 = 0.0
]
print(f"{'a':>6} {'b':>6} {'|a/b|':>8} {'|a|/|b|':>10} {'equal':>6}")
for a, b in pairs:
direct_abs = abs(a / b)
split_abs = abs(a) / abs(b)
print(f"{a:>6} {b:>6} {direct_abs:>8.4f} {split_abs:>10.4f} {str(abs(direct_abs - split_abs) < 1e-12):>6}") # Absolute relative error for forecasting
actuals = [100.0, -80.0, 50.0, 200.0]
predictions = [ 90.0, -60.0, 55.0, 210.0]
print("\nAbsolute Relative Error = |actual - forecast| / |actual|:")
print(f"{'actual':>8} {'forecast':>10} {'raw_error':>10} {'ARE':>8}")
for y, y_hat in zip(actuals, predictions):
raw_error = y - y_hat
are = abs(raw_error) / abs(y) # |a|/|b|
print(f"{y:>8.1f} {y_hat:>10.1f} {raw_error:>10.1f} {are:>8.4f}")
mape = sum(abs(y - y_hat) / abs(y) for y, y_hat in zip(actuals, predictions)) / len(actuals)
print(f"\nMAPE = {mape:.4f} = {mape:.1%}")Beginner example (R)
# |a/b| = |a|/|b| in R # Basic verification
pairs <- list(c(6, 3), c(-6, 3), c(6, -3), c(-6, -3), c(0, 5))
cat(sprintf("%-6s %-6s %-8s %-10s\n", "a", "b", "|a/b|", "|a|/|b|"))
for (p in pairs) { a <- p[1]; b <- p[2]
cat(sprintf("%-6.0f %-6.0f %-8.4f %-10.4f\n", a, b, abs(a / b), abs(a) / abs(b)))
} # Absolute relative error
actuals <- c(100, -80, 50, 200)
predictions <- c( 90, -60, 55, 210)
raw_errors <- actuals - predictions
are <- abs(raw_errors) / abs(actuals) # |a| / |b|
mape <- mean(are)
cat("\nAbsolute Relative Error:\n")
for (i in seq_along(actuals)) { cat(sprintf(" actual=%6.1f forecast=%6.1f raw=%6.1f ARE=%.4f\n", actuals[i], predictions[i], raw_errors[i], are[i]))
}
cat(sprintf("\nMAPE = %.4f = %.1f%%\n", mape, mape * 100))Professional example (Python)
import pandas as pd
import numpy as np # Scenario: forecast evaluation with MAPE and SMAPE
# Demonstrates |a/b| = |a|/|b| in MAPE computation
# Also shows why zero actuals require SMAPE as a fallback
np.random.seed(42)
N_PERIODS = 60
MAPE_UNDEFINED = float("nan") # sentinel for zero-actual periods
def absolute_relative_error(actual: float, forecast: float) -> float:
""" Compute absolute relative error for a single period. |actual - forecast| / |actual| — undefined if actual is zero. Args: actual: Observed value. forecast: Predicted value. Returns: Absolute relative error as a float, or nan if actual is zero. """
if actual == 0:
return MAPE_UNDEFINED
return abs(actual - forecast) / abs(actual) # |a|/|b|
def symmetric_absolute_percentage_error(actual: float, forecast: float) -> float:
""" Compute SMAPE — handles zero actuals by using average of |actual| and |forecast|. Args: actual: Observed value. forecast: Predicted value. Returns: Symmetric absolute percentage error. """
denom = (abs(actual) + abs(forecast)) / 2.0
if denom == 0:
return 0.0
return abs(actual - forecast) / denom # Simulate actuals with a few zero-actual periods
actuals = np.random.normal(1000, 100, N_PERIODS)
actuals[5] = 0.0 # inject zero actual
predictions = actuals * (1 + np.random.normal(0, 0.05, N_PERIODS))
df = pd.DataFrame({"actual": actuals, "forecast": predictions})
df["raw_error"] = df["actual"] - df["forecast"]
df["are"] = df.apply(lambda row: absolute_relative_error(row["actual"], row["forecast"]), axis=1)
df["smape"] = df.apply(lambda row: symmetric_absolute_percentage_error(row["actual"], row["forecast"]), axis=1)
valid_are = df["are"].dropna()
mape = valid_are.mean()
smape = df["smape"].mean()
print("Forecast Evaluation: |a/b| = |a|/|b| (MAPE and SMAPE)")
print("=" * 72)
print(df[["actual", "forecast", "raw_error", "are", "smape"]].round(4).head(10).to_string(index=False))
print(f"\nMAPE (excludes {df['are'].isna().sum()} zero-actual periods): {mape:.4f} = {mape:.2%}")
print(f"SMAPE (includes all periods): {smape:.4f} = {smape:.2%}")
print(f"\nNote: MAPE uses |actual - forecast| / |actual| — undefined when actual = 0.")
print(f" SMAPE uses denominator (|actual| + |forecast|) / 2 — handles zero actuals.")Professional example (R)
# MAPE and SMAPE using |a/b| = |a|/|b|; handle zero actuals
set.seed(42)
N_PERIODS <- 60
actuals <- rnorm(N_PERIODS, 1000, 100)
actuals[6] <- 0
predictions <- actuals * (1 + rnorm(N_PERIODS, 0, 0.05))
absolute_relative_error <- function(actual, forecast) { if (actual == 0)
return(NA_real_) abs(actual - forecast) / abs(actual)
}
symmetric_absolute_percentage_error <- function(actual, forecast) { denom <- (abs(actual) + abs(forecast)) / 2
if (denom == 0)
return(0) abs(actual - forecast) / denom
}
df <- data.frame(actual = actuals, forecast = predictions)
df$raw_error <- df$actual - df$forecast
df$are <- mapply(absolute_relative_error, df$actual, df$forecast)
df$smape <- mapply(symmetric_absolute_percentage_error, df$actual, df$forecast)
valid_are <- na.omit(df$are)
mape <- mean(valid_are)
smape <- mean(df$smape)
cat("Forecast Evaluation: |a/b| = |a|/|b| (MAPE and SMAPE)\n")
print(head(df[c("actual", "forecast", "raw_error", "are", "smape")], 10))
cat(sprintf("\nMAPE (excludes %d zero-actual periods): %.4f\n", sum(is.na(df$are)), mape))
cat(sprintf("SMAPE (all periods): %.4f\n", smape))Source: MRAF