<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
>>> import numpy as np
>>> from skfolio.attribution import predicted_factor_attribution
>>> asset_names = ["AAPL", "GOOGL", "MSFT"]
>>> factor_names = ["Momentum", "Value", "Size"]
>>> weights = np.array([0.4, 0.3, 0.3])
>>> loading_matrix = np.array(
...     [
...         [1.0, 0.5, 0.2],
...         [0.8, 1.0, 0.1],
...         [0.3, 0.2, 1.0],
...     ]
... )
>>> # Synthetic daily covariances; idiosyncratic residuals are uncorrelated.
>>> factor_covariance = np.array(
...     [
...         [0.00016, 0.00004, 0.00000],
...         [0.00004, 0.00012, 0.00000],
...         [0.00000, 0.00000, 0.00008],
...     ]
... )
>>> idio_variances = np.array([0.00004, 0.00006, 0.00008])
```

Compute annualized volatility and return contributions:

```pycon
>>> attribution = predicted_factor_attribution(
...     weights=weights,
...     loading_matrix=loading_matrix,
...     factor_covariance=factor_covariance,
...     idio_covariance=idio_variances,
...     asset_names=asset_names,
...     factor_names=factor_names,
...     factor_families=["Style", "Style", "Size"],
...     factor_mu=np.array([0.0002, 0.00012, 0.00008]),  # Daily expected returns
... )
>>> print(f"Annualized volatility: {attribution.total.vol:.2%}")
Annualized volatility: 21.77%
>>> print(f"Factor exposures: {attribution.factors.exposure}")
Factor exposures: [0.73 0.56 0.41]
```

```pycon
>>> attribution.summary_df()
              Volatility Contribution  ... Expected Return Contribution
Component                              ...
Systematic                     19.57%  ...                        6.20%
Idiosyncratic                   2.20%  ...                        0.00%
Total                          21.77%  ...                        6.20%

[3 rows x 3 columns]
```

```pycon
>>> attribution.families_df()
       Exposure  ... Expected Return Contribution
Family           ...
Style    1.2900  ...                        5.37%
Size     0.4100  ...                        0.83%

[2 rows x 4 columns]
```

