mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-28 09:47:43 +00:00
75 lines
2.5 KiB
C#
75 lines
2.5 KiB
C#
using System.Runtime.CompilerServices;
|
|
|
|
namespace QuanTAlib;
|
|
|
|
/// <summary>
|
|
/// MAPE: Mean Absolute Percentage Error
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// MAPE measures the average absolute percentage error between actual and predicted values.
|
|
/// It expresses accuracy as a percentage, making it scale-independent.
|
|
///
|
|
/// Formula:
|
|
/// MAPE = (100/n) * Σ|((actual - predicted) / actual)|
|
|
///
|
|
/// Key properties:
|
|
/// - Scale-independent (expressed as percentage)
|
|
/// - Cannot be calculated when actual = 0
|
|
/// - Asymmetric: penalizes under-predictions more than over-predictions
|
|
/// - Undefined for zero actual values
|
|
/// </remarks>
|
|
[SkipLocalsInit]
|
|
public sealed class Mape : BiInputIndicatorBase
|
|
{
|
|
private const double Epsilon = 1e-10;
|
|
|
|
/// <summary>
|
|
/// Creates MAPE with specified period.
|
|
/// </summary>
|
|
/// <param name="period">Number of values to average (must be > 0)</param>
|
|
public Mape(int period) : base(period, $"Mape({period})") { }
|
|
|
|
/// <inheritdoc/>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
protected override double ComputeError(double actual, double predicted)
|
|
{
|
|
// Avoid division by zero - use small epsilon if actual is zero
|
|
double divisor = Math.Abs(actual) < Epsilon ? Epsilon : actual;
|
|
return 100.0 * Math.Abs((actual - predicted) / divisor);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates MAPE for entire series.
|
|
/// </summary>
|
|
public static TSeries Batch(TSeries actual, TSeries predicted, int period)
|
|
=> CalculateImpl(actual, predicted, period, Batch);
|
|
|
|
/// <summary>
|
|
/// Batch calculation using percentage error computation with rolling mean.
|
|
/// </summary>
|
|
public static void Batch(ReadOnlySpan<double> actual, ReadOnlySpan<double> predicted, Span<double> output, int period)
|
|
{
|
|
ValidateBatchInputs(actual, predicted, output, period);
|
|
|
|
int len = actual.Length;
|
|
if (len == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int StackAllocThreshold = 256;
|
|
Span<double> percentErrors = len <= StackAllocThreshold
|
|
? stackalloc double[len]
|
|
: new double[len];
|
|
|
|
ErrorHelpers.ComputePercentageErrors(actual, predicted, percentErrors, Epsilon);
|
|
ErrorHelpers.ApplyRollingMean(percentErrors, output, period);
|
|
}
|
|
|
|
public static (TSeries Results, Mape Indicator) Calculate(TSeries actual, TSeries predicted, int period)
|
|
{
|
|
var indicator = new Mape(period);
|
|
TSeries results = Batch(actual, predicted, period);
|
|
return (results, indicator);
|
|
}
|
|
} |