Source code for skfolio.distance._distance_correlation

"""Distance correlation estimator."""

# Copyright (c) 2023-2026
# Author: Hugo Delatte <hugo.delatte@skfoliolabs.com>
# SPDX-License-Identifier: BSD-3-Clause

from __future__ import annotations

import numpy as np
import scipy.spatial.distance as scd
import sklearn.utils.validation as skv

from skfolio.distance._base import BaseDistance
from skfolio.typing import ArrayLike, FloatArray


[docs] class DistanceCorrelation(BaseDistance): """Distance Correlation estimator. Distance Correlation was introduced by Szekely [1]_ to capture non-linear dependencies. Parameters ---------- threshold : float, default=0.5 Distance correlation threshold. Attributes ---------- codependence_ : ndarray of shape (n_assets, n_assets) Codependence matrix. distance_ : ndarray of shape (n_assets, n_assets) Distance matrix. n_features_in_ : int Number of assets seen during `fit`. feature_names_in_ : ndarray of shape (`n_features_in_`,) Names of assets seen during `fit`. Defined only when `X` has assets names that are all strings. References ---------- .. [1] "Measuring and testing independence by correlation of distances" Gábor J. Szekely , 2005 """ def __init__(self, threshold: float = 0.5) -> None: self.threshold = threshold @staticmethod def _dcorr(x: FloatArray, y: FloatArray) -> float: """Calculate the distance correlation between two variables.""" x = scd.squareform(scd.pdist(x.reshape(-1, 1))) y = scd.squareform(scd.pdist(y.reshape(-1, 1))) x = x - x.mean(axis=0)[np.newaxis, :] - x.mean(axis=1)[:, np.newaxis] + x.mean() y = y - y.mean(axis=0)[np.newaxis, :] - y.mean(axis=1)[:, np.newaxis] + y.mean() value = np.sqrt((x * y).sum()) / np.sqrt( np.sqrt((x**2).sum()) * np.sqrt((y**2).sum()) ) return value
[docs] def fit(self, X: ArrayLike, y: None = None) -> DistanceCorrelation: """Fit the Distance Correlation estimator. Parameters ---------- X : array-like of shape (n_observations, n_assets) Price returns of the assets. y : Ignored Not used, present for API consistency by convention. Returns ------- self : DistanceCorrelation Fitted estimator. """ X = skv.validate_data(self, X) n_assets = X.shape[1] corr = np.ones((n_assets, n_assets)) # TODO: parallelize for i, j in zip(*np.triu_indices(n_assets, 1), strict=True): corr[i, j] = self._dcorr(x=X[:, i], y=X[:, j]) corr[j, i] = corr[i, j] self.codependence_ = corr self.distance_ = np.sqrt(np.clip(1 - self.codependence_, a_min=0.0, a_max=1.0)) return self