|
22 | 22 | _confusion_matrix, |
23 | 23 | _get_error_scale, |
24 | 24 | _get_quantile_intervals, |
| 25 | + _get_tolerance_levels, |
25 | 26 | _get_values_or_raise, |
26 | 27 | _get_wrapped_metric, |
27 | 28 | _LabelReduction, |
@@ -2403,6 +2404,186 @@ def coefficient_of_variation( |
2403 | 2404 | ) |
2404 | 2405 |
|
2405 | 2406 |
|
| 2407 | +@multi_ts_support |
| 2408 | +@multivariate_support |
| 2409 | +def _tolerance_coverages( |
| 2410 | + actual_series: Union[TimeSeries, Sequence[TimeSeries]], |
| 2411 | + pred_series: Union[TimeSeries, Sequence[TimeSeries]], |
| 2412 | + intersect: bool = True, |
| 2413 | + *, |
| 2414 | + min_tolerance: float = 0.0, |
| 2415 | + max_tolerance: float = 1.0, |
| 2416 | + step: float = 0.01, |
| 2417 | + q: Optional[Union[float, list[float], tuple[np.ndarray, pd.Index]]] = None, |
| 2418 | + component_reduction: Optional[Callable[[np.ndarray], float]] = np.nanmean, |
| 2419 | + series_reduction: Optional[Callable[[np.ndarray], Union[float, np.ndarray]]] = None, |
| 2420 | + n_jobs: int = 1, |
| 2421 | + verbose: bool = False, |
| 2422 | +) -> METRIC_OUTPUT_TYPE: |
| 2423 | + """Computes the tolerance coverages for different tolerance levels. |
| 2424 | +
|
| 2425 | + More info in metric `autc()`. |
| 2426 | + """ |
| 2427 | + y_true, y_pred = _get_values_or_raise( |
| 2428 | + actual_series, |
| 2429 | + pred_series, |
| 2430 | + intersect, |
| 2431 | + remove_nan_union=True, |
| 2432 | + q=q, |
| 2433 | + ) |
| 2434 | + |
| 2435 | + # range of actual values (max - min) for each component |
| 2436 | + y_range = np.nanmax(y_true, axis=TIME_AX) - np.nanmin(y_true, axis=TIME_AX) |
| 2437 | + |
| 2438 | + # handle case where range is zero (constant series) |
| 2439 | + if np.any(y_range == 0): |
| 2440 | + raise ValueError( |
| 2441 | + "The range of actual values (max - min) must be strictly positive for all " |
| 2442 | + "components to compute the AUTC. Found zero range for at least one component." |
| 2443 | + ) |
| 2444 | + |
| 2445 | + tolerances = _get_tolerance_levels( |
| 2446 | + min_tolerance=min_tolerance, |
| 2447 | + max_tolerance=max_tolerance, |
| 2448 | + step=step, |
| 2449 | + ) |
| 2450 | + |
| 2451 | + # compute absolute errors normalized by half the range |
| 2452 | + abs_errors = np.abs(y_true - y_pred) |
| 2453 | + half_range = y_range / 2 |
| 2454 | + normalized_errors = abs_errors / half_range |
| 2455 | + |
| 2456 | + # get coverage for each tolerance level (fraction of points within tolerance) |
| 2457 | + # -> (n components, n quantiles, n coverages) |
| 2458 | + coverages = np.nanmean( |
| 2459 | + np.expand_dims(normalized_errors, -1) <= tolerances, axis=TIME_AX |
| 2460 | + ) |
| 2461 | + # 'abuse' the first dimension which is normally the time dimension for the coverages |
| 2462 | + # -> (n coverages, n components, n quantiles) |
| 2463 | + return coverages.transpose((2, 0, 1)) |
| 2464 | + |
| 2465 | + |
| 2466 | +@multi_ts_support |
| 2467 | +@multivariate_support |
| 2468 | +def autc( |
| 2469 | + actual_series: Union[TimeSeries, Sequence[TimeSeries]], |
| 2470 | + pred_series: Union[TimeSeries, Sequence[TimeSeries]], |
| 2471 | + intersect: bool = True, |
| 2472 | + *, |
| 2473 | + min_tolerance: float = 0.0, |
| 2474 | + max_tolerance: float = 1.0, |
| 2475 | + step: float = 0.01, |
| 2476 | + q: Optional[Union[float, list[float], tuple[np.ndarray, pd.Index]]] = None, |
| 2477 | + component_reduction: Optional[Callable[[np.ndarray], float]] = np.nanmean, |
| 2478 | + series_reduction: Optional[Callable[[np.ndarray], Union[float, np.ndarray]]] = None, |
| 2479 | + n_jobs: int = 1, |
| 2480 | + verbose: bool = False, |
| 2481 | +) -> METRIC_OUTPUT_TYPE: |
| 2482 | + """Area Under Tolerance Curve (AUTC). |
| 2483 | +
|
| 2484 | + AUTC measures the overall alignment between actual and predicted series across a range of tolerance levels. |
| 2485 | + For each tolerance level, it computes the fraction of points where the prediction is within ±X% of the actual |
| 2486 | + value. |
| 2487 | + The AUTC is the normalized area under this curve, providing a single score in [0, 1] where higher is better. |
| 2488 | +
|
| 2489 | + For the true series :math:`y` and predicted series :math:`\\hat{y}` of length :math:`T`, tolerance levels |
| 2490 | + :math:`\\tau \\in [0, 1]`, and half-range :math:`H = (\\max(y) - \\min(y)) / 2`: |
| 2491 | +
|
| 2492 | + .. math:: |
| 2493 | +
|
| 2494 | + \\text{Coverage}(\\tau) = \\frac{1}{T} \\sum_{t=1}^{T} \\mathbb{1}\\left[\\frac{|y_t - \\hat{y}_t|}{H} |
| 2495 | + \\leq \\tau\\right] |
| 2496 | +
|
| 2497 | + \\text{AUTC} = \\int_0^1 \\text{Coverage}(\\tau) \\, d\\tau |
| 2498 | +
|
| 2499 | + At tolerance :math:`\\tau`, a prediction is within tolerance if the error is within :math:`\\pm\\tau` of the |
| 2500 | + actual value (as a fraction of half the range). For example, at 10% tolerance, the prediction must be within |
| 2501 | + ±10% of the half-range, i.e., within ±5% of the full range. |
| 2502 | +
|
| 2503 | + If :math:`\\hat{y}_t` are stochastic (contains several samples) or quantile predictions, use parameter `q` to |
| 2504 | + specify on which quantile(s) to compute the metric on. By default, it uses the median 0.5 quantile |
| 2505 | + (over all samples, or, if given, the quantile prediction itself). |
| 2506 | +
|
| 2507 | + Parameters |
| 2508 | + ---------- |
| 2509 | + actual_series |
| 2510 | + The (sequence of) actual series. |
| 2511 | + pred_series |
| 2512 | + The (sequence of) predicted series. |
| 2513 | + intersect |
| 2514 | + For time series that are overlapping in time without having the same time index, setting `True` |
| 2515 | + will consider the values only over their common time interval (intersection in time). |
| 2516 | + min_tolerance |
| 2517 | + The minimum tolerance level as a fraction of the series half-range. Default is 0.0 (0%). |
| 2518 | + max_tolerance |
| 2519 | + The maximum tolerance level as a fraction of the series half-range. Default is 1.0 (100%). |
| 2520 | + step |
| 2521 | + The step size between tolerance levels. Default is 0.01 (1%). |
| 2522 | + For example, with defaults, tolerances are [0.0, 0.01, 0.02, ..., 1.0]. |
| 2523 | + q |
| 2524 | + Optionally, the quantile (float [0, 1]) or list of quantiles of interest to compute the metric on. |
| 2525 | + component_reduction |
| 2526 | + Optionally, a function to aggregate the metrics over the component/column axis. It must reduce a `np.ndarray` |
| 2527 | + of shape `(t, c)` to a `np.ndarray` of shape `(t,)`. The function takes as input a ``np.ndarray`` and a |
| 2528 | + parameter named `axis`, and returns the reduced array. The `axis` receives value `1` corresponding to the |
| 2529 | + component axis. If `None`, will return a metric per component. |
| 2530 | + series_reduction |
| 2531 | + Optionally, a function to aggregate the metrics over multiple series. It must reduce a `np.ndarray` |
| 2532 | + of shape `(s, t, c)` to a `np.ndarray` of shape `(t, c)` The function takes as input a ``np.ndarray`` and a |
| 2533 | + parameter named `axis`, and returns the reduced array. The `axis` receives value `0` corresponding to the |
| 2534 | + series axis. For example with `np.nanmean`, will return the average over all series metrics. If `None`, will |
| 2535 | + return a metric per component. |
| 2536 | + n_jobs |
| 2537 | + The number of jobs to run in parallel. Parallel jobs are created only when a ``Sequence[TimeSeries]`` is |
| 2538 | + passed as input, parallelising operations regarding different ``TimeSeries``. Defaults to `1` |
| 2539 | + (sequential). Setting the parameter to `-1` means using all the available processors. |
| 2540 | + verbose |
| 2541 | + Optionally, whether to print operations progress. |
| 2542 | +
|
| 2543 | + Raises |
| 2544 | + ------ |
| 2545 | + ValueError |
| 2546 | + If :math:`\\max_t{y_t} = \\min_t{y_t}` (constant series with zero range). |
| 2547 | +
|
| 2548 | + Returns |
| 2549 | + ------- |
| 2550 | + float |
| 2551 | + A single metric score in [0, 1] (when `len(q) <= 1`) for: |
| 2552 | +
|
| 2553 | + - a single univariate series. |
| 2554 | + - a single multivariate series with `component_reduction`. |
| 2555 | + - a sequence (list) of uni/multivariate series with `series_reduction` and `component_reduction`. |
| 2556 | + np.ndarray |
| 2557 | + A numpy array of metric scores. The array has shape (n components * n quantiles,) without component reduction, |
| 2558 | + and shape (n quantiles,) with component reduction and `len(q) > 1`. |
| 2559 | + For: |
| 2560 | +
|
| 2561 | + - the same input arguments that result in the `float` return case from above but with `len(q) > 1`. |
| 2562 | + - a single multivariate series and at least `component_reduction=None`. |
| 2563 | + - a sequence of uni/multivariate series including `series_reduction` and `component_reduction=None`. |
| 2564 | + list[float] |
| 2565 | + Same as for type `float` but for a sequence of series. |
| 2566 | + list[np.ndarray] |
| 2567 | + Same as for type `np.ndarray` but for a sequence of series. |
| 2568 | +
|
| 2569 | + See Also |
| 2570 | + -------- |
| 2571 | + :func:`~darts.utils.statistics.plot_tolerance_curve` : Plot the tolerance curve for visual inspection. |
| 2572 | + """ |
| 2573 | + coverages = _get_wrapped_metric(_tolerance_coverages)( |
| 2574 | + actual_series, |
| 2575 | + pred_series, |
| 2576 | + intersect, |
| 2577 | + q=q, |
| 2578 | + ) |
| 2579 | + tolerances = _get_tolerance_levels( |
| 2580 | + min_tolerance=min_tolerance, |
| 2581 | + max_tolerance=max_tolerance, |
| 2582 | + step=step, |
| 2583 | + ) |
| 2584 | + return np.trapezoid(coverages, tolerances, axis=0) |
| 2585 | + |
| 2586 | + |
2406 | 2587 | # Dynamic Time Warping |
2407 | 2588 | @multi_ts_support |
2408 | 2589 | @multivariate_support |
|
0 commit comments