<a id="skfolio-attribution-predicted-factor-attribution"></a>

# skfolio.attribution.predicted_factor_attribution

<a id="skfolio.attribution.predicted_factor_attribution"></a>

### skfolio.attribution.predicted_factor_attribution(weights, loading_matrix, factor_covariance, idio_covariance, asset_names, factor_names, factor_families=None, factor_mu=None, idio_mu=None, annualization_factor=252.0, compute_asset_breakdowns=True)

Compute predicted (ex-ante) factor volatility and return attribution.

The volatility attribution follows the exposure-volatility-correlation framework
(also called $x-\sigma-\rho$). It decomposes portfolio volatility into
systematic (factor) and idiosyncratic (specific) contributions.

The return attribution decomposes portfolio expected return into spanned
(factor-explained) and orthogonal expected return contributions.

**Factor Model:**

The asset covariance matrix is modeled as:

$$
\Sigma = B F B^\top + D

$$

where $B$ is the asset-by-factor loading matrix, $F$ is the factor
covariance matrix and $D$ is the idiosyncratic covariance matrix.

The expected return vector is modeled as:

$$
\mu = B \mu_f + \mu_\perp

$$

where $\mu_f$ contains the expected factor returns (factor premia),
$B \mu_f$ is the factor-spanned expected return and
$\mu_\perp$ is the factor-orthogonal expected return, also called
orthogonal alpha.

**Portfolio Variance Decomposition:**

Let $w$ be portfolio weights and $b = B^\top w$ be the portfolio factor
: exposure vector. Then:

$$
\sigma_P^2 = w^\top \Sigma w = b^\top F b + w^\top D w.
$$

**Portfolio Expected Return Decomposition:**

$$
\mu_P = w^\top \mu = b^\top \mu_f + w^\top \mu_\perp.
$$

**Volatility Contributions:**

The contribution of factor $k$ to portfolio volatility is defined as:

$$
\operatorname{VolContrib}_k =
\frac{b_k (F b)_k}{\sigma_P}.
$$

where $\sigma_P = \sqrt{w^\top \Sigma w}$ is total portfolio volatility.

These contributions are additive: they sum to the systematic component of
volatility.

$$
\sum_k \operatorname{VolContrib}_k = \frac{b^\top F b}{\sigma_P}.
$$

The systematic vs. idiosyncratic vs. total component contributions are:

$$
\operatorname{VolContrib}_{\mathrm{sys}} = \frac{b^\top F b}{\sigma_P},
\qquad
\operatorname{VolContrib}_{\mathrm{idio}} = \frac{w^\top D w}{\sigma_P},
\qquad
\operatorname{VolContrib}_{\mathrm{total}} = \sigma_P.
$$

and sum exactly:
$\operatorname{VolContrib}_{\mathrm{sys}} + \operatorname{VolContrib}_{\mathrm{idio}} = \sigma_P$.

**Expected Return Contributions:**

The contribution of each factor to spanned expected return is:

$$
\operatorname{MuContrib}_k = b_k \mu_{f,k}.
$$

These are also additive:

$$
\sum_k \operatorname{MuContrib}_k = b^\top \mu_f.
$$

**Correlation (x-sigma-rho framework):**

Let $\sigma_k = \sqrt{F_{kk}}$ be factor $k$ standalone
volatility. The correlation of factor $k$ with the portfolio return is:

$$
\rho_{k,P} = \frac{(F b)_k}{\sigma_k \sigma_P}.
$$

The factor volatility contribution can then be written as:

$$
\operatorname{VolContrib}_k = b_k \sigma_k \rho_{k,P}.
$$

**Percentage of Total Variance:**

The variance share of each factor is:

$$
\operatorname{PctTotalVariance}_k =
\frac{\operatorname{VolContrib}_k}{\sigma_P}.
$$

**NaN handling**:

`loading_matrix`, `idio_covariance` and `idio_mu` may contain NaN for non-investable
assets (delisted, not-yet-listed, warm-up). For `idio_covariance`, inactive assets
are identified by NaN diagonal entries, following the covariance estimator
convention. When a non-zero weight falls on such an asset a warning is emitted and
the asset’s contribution is effectively zeroed out. `weights`, `factor_covariance`
and `factor_mu` must be finite.

* **Parameters:**
  **weights** *array-like of shape (n_assets,)*
  : Portfolio weights vector.

  **loading_matrix** *array-like of shape (n_assets, n_factors)*
  : Asset-by-factor loading (exposure) matrix.. NaN entries for non-investable
    assets are filled with 0 (requires corresponding weights to be zero).

  **factor_covariance** *array-like of shape (n_factors, n_factors)*
  : Covariance matrix of the factors. Must be per-period (e.g., daily covariance if
    using daily data). Use `annualization_factor` to scale to annualized values.

  **idio_covariance** *array-like of shape (n_assets,) or (n_assets, n_assets)*
  : Idiosyncratic (specific) covariance. If 1D, treated as diagonal variances.
    If 2D, used as full covariance matrix. Must be per-period, same as
    `factor_covariance`. NaN entries for non-investable assets are filled with 0.

  **asset_names** *array-like of shape (n_assets,)*
  : Names for each asset (e.g., [“AAPL”, “GOOGL”, “MSFT”]).

  **factor_names** *array-like of shape (n_factors,)*
  : Names for each factor (e.g., [“Momentum”, “Value”, “Size”]).

  **factor_families** *array-like of shape (n_factors,), optional*
  : Family/category for each factor (e.g., “Style”, “Industry”). If provided,
    enables family-level aggregation in DataFrame output.

  **factor_mu** *array-like of shape (n_factors,), optional*
  : Expected returns of each factor (factor premia), $\mu_f$. Defaults to
    zeros if not provided. Must be per-period (e.g., daily expected returns if using
    daily data). All inputs (`factor_covariance`, `idio_covariance`, `factor_mu`,
    `idio_mu`) must share the same periodicity. Use `annualization_factor` to scale
    outputs to annualized values.

  **idio_mu** *array-like of shape (n_assets,), optional*
  : Factor-orthogonal expected return for each asset, $\mu_\perp$. It is
    distinct from the time-series mean of `idio_returns`, which is not enforced to
    be factor-orthogonal. Defaults to zeros if not provided. Must be per-period,
    same as `factor_mu`. NaN entries for non-investable assets are filled with 0
    (requires corresponding weights to be zero).
    <br/>
    #### NOTE
    This vector **must already be orthogonal** to the column span of the
    loading matrix $B$ (with respect to your chosen regression metric,
    e.g., OLS or GLS). This function does **not** perform any orthogonalization.
    It assumes `idio_mu` satisfies the decomposition
    $\mu = B \mu_f + \mu_\perp$ where
    $B^\top \mu_\perp = 0$ (or the appropriate weighted inner product
    equals zero for GLS). Typically, this is the residual vector from regressing
    expected asset returns onto the factor loadings.

  **annualization_factor** *float, default=252.0*
  : Used to annualize expected returns, variances and volatilities. Use 1.0 to
    disable annualization. Common values: 252 for daily data, 12 for monthly data.

  **compute_asset_breakdowns** *bool, default=True*
  : If True, compute asset-level attribution (systematic/idiosyncratic
    decomposition). Set to False to skip asset attribution for faster computation.
* **Returns:**
  **attribution** *Attribution*
  : The [`Attribution`](https://skfolio.org/generated/skfolio.attribution.Attribution.html.md#skfolio.attribution.Attribution) dataclass containing component-level, factor-level and
    optionally asset-level attribution results. Use `attribution.summary_df()`,
    `attribution.factors_df()`, `attribution.assets_df()` to convert to pandas
    DataFrames.
* **Raises:**
  ValueError
  : If input dimensions are inconsistent, if `weights`,\`factor_covariance\` or
    `factor_mu` contain NaN, or if total variance is non-positive.

### Examples

```pycon
>>> from skfolio.attribution import predicted_factor_attribution
>>> import numpy as np
>>>
>>> # Volatility attribution only
>>> attribution = predicted_factor_attribution(
...     weights=np.array([0.4, 0.3, 0.3]),
...     loading_matrix=loading_matrix,
...     factor_covariance=factor_cov,
...     idio_covariance=idio_cov,
...     factor_names=["Momentum", "Value", "Size"],
... )
>>> print(f"Total volatility: {attribution.total.vol:.2%}")
>>> print(f"Factor exposures: {attribution.factors.exposure}")
>>>
>>> # With families
>>> attribution = predicted_factor_attribution(
...     weights=np.array([0.4, 0.3, 0.3]),
...     loading_matrix=loading_matrix,
...     factor_covariance=factor_cov,
...     idio_covariance=idio_cov,
...     factor_names=["Momentum", "Value", "Size"],
...     factor_families=["Style", "Style", "Size"],
... )
>>> print(f"Family names: {attribution.families.names}")
>>> print(f"Family vol contribs: {attribution.families.vol_contrib}")
>>>
>>> # Volatility and return attribution
>>> attribution = predicted_factor_attribution(
...     weights=np.array([0.4, 0.3, 0.3]),
...     loading_matrix=loading_matrix,
...     factor_covariance=factor_cov,
...     idio_covariance=idio_cov,
...     factor_names=["Momentum", "Value", "Size"],
...     factor_mu=np.array([0.05, 0.03, 0.02]),
... )
>>> attribution.summary_df()
```

