<a id="skfolio-prior-opinionpooling"></a>

# skfolio.prior.OpinionPooling

<a id="skfolio.prior.OpinionPooling"></a>

### *class* skfolio.prior.OpinionPooling(estimators, opinion_probabilities=None, prior_estimator=None, is_linear_pooling=True, divergence_penalty=0.0, n_jobs=None)

Opinion Pooling estimator.

Opinion Pooling (also called Belief Aggregation or Risk Aggregation) is a process
in which different probability distributions (opinions), produced by different
experts, are combined to yield a single probability distribution (consensus).

Expert opinions (also called individual prior distributions) can be
**elicited** from domain experts or **derived** from quantitative analyses.

The `OpinionPooling` estimator takes a list of prior estimators, each of which
produces scenario probabilities (which we use as `sample_weight`), and pools them
into a single consensus probability .

You can choose between linear (arithmetic) pooling or logarithmic (geometric)
pooling, and optionally apply robust pooling using a Kullback-Leibler divergence
penalty to down-weight experts whose views deviate strongly from the group
consensus.

* **Parameters:**
  **estimators** *list of (str, BasePrior)*
  : A list of [prior estimators](https://skfolio.org/user_guide/prior.html.md#prior) representing opinions to be pooled
    into a single consensus.
    Each element of the list is defined as a tuple of string (i.e. name) and an
    estimator instance. Each must expose `sample_weight` such as in
    [`EntropyPooling`](https://skfolio.org/generated/skfolio.prior.EntropyPooling.html.md#skfolio.prior.EntropyPooling).

  **opinion_probabilities** *array-like of float, optional*
  : Probability mass assigned to each opinion, in [0,1] summing to ≤1.
    Any leftover mass is assigned to the uniform (uninformative) prior.
    The default (None), is to assign the same probability to each opinion.

  **prior_estimator** *BasePrior, optional*
  : Common prior for all `estimators`. If provided, each estimator from `estimators`
    will be fitted using this common prior before pooling. Setting `prior_estimator`
    inside individual `estimators` is disabled to avoid mixing different prior
    scenarios (each estimator must have the same underlying distribution).
    For example, using `prior_estimator = SyntheticData(n_samples=10_000)` will
    generate 10,000 synthetic data points from a Vine Copula before fitting the
    estimators on this common distribution.

  **is_linear_pooling** *bool, default=True*
  : If True, combine each opinion via Linear Opinion Pooling
    (arithmetic mean); if False, use Logarithmic Opinion Pooling (geometric
    mean).
    <br/>
    Linear Opinion Pooling:
    : * Retains all nonzero support (no “zero-forcing”).
      * Produces an averaging that is more evenly spread across all expert opinions.
    <br/>
    Logarithmic Opinion Pooling:
    : * Zero-Preservation. Any scenario assigned zero probability by any expert
        remains zero in the aggregate.
      * Information-Theoretic Optimality. Yields the distribution that minimizes
        the weighted sum of KL-divergences from each expert’s distribution.
      * Robust to Extremes: down-weight extreme or contrarian views more severely.

  **divergence_penalty** *float, default=0.0*
  : Non-negative factor ($\alpha$) that penalizes each opinion’s divergence
    from the group consensus, yielding more robust pooling.
    A higher value more strongly down-weights deviating opinions.
    <br/>
    The robust opinion probabilities are given by:
    $$
    \tilde{p}_i = \frac{p_i \exp\bigl(-\alpha D_i\bigr)}
    {\displaystyle \sum_{k=1}^N p_k \exp\bigl(-\alpha D_k\bigr)}
    \quad\text{for }i = 1,\dots,N
    <br/>
    $$
    <br/>
    where
    * $N$ is the number of experts `len(estimators)`
    * $M$ is the number of scenarios `len(observations)`
    * $D_i$ is the KL-divergence of expert *i*’s distribution from consensus:
      $$
      D_i = \mathrm{KL}\bigl(w_i \,\|\, c\bigr)
          = \sum_{j=1}^M w_{ij}\,\ln\!\frac{w_{ij}}{c_j}
      \quad\text{for }i = 1,\dots,N.
    <br/>
      $$
    * $w_i$ is the sample-weight vector (scenario probabilities) from expert
      *i*,  with $\sum_{j=1}^M w_{ij} = 1$.
    * $p_i$ is the initial opinion probability of expert *i*, with
      $\sum_{i=1}^N p_i \le 1$ (any leftover mass goes to a uniform prior).
    * $c_j$ is the consensus of scenario $j$:
      $$
      c_j = \sum_{i=1}^N p_i \, w_{ij} \quad\text{for }j = 1,\dots,M.
    <br/>
      $$

  **n_jobs** *int, optional*
  : The number of jobs to run in parallel for `fit` of all `estimators`.
    The value `-1` means using all processors.
    The default (`None`) means 1 unless in a `joblib.parallel_backend` context.
* **Attributes:**
  **return_distribution_** *ReturnDistribution*
  : Fitted [`ReturnDistribution`](https://skfolio.org/generated/skfolio.prior.ReturnDistribution.html.md#skfolio.prior.ReturnDistribution) to be used by the optimization
    estimators, containing the assets distribution, moments estimation and the
    opinion-pooling sample weights.

  **estimators_** *list[BasePrior]*
  : The elements of the `estimators` parameter, having been fitted on the
    training data.

  **named_estimators_** *dict[str, BasePrior]*
  : Attribute to access any fitted sub-estimators by name.

  **prior_estimator_** *BasePrior*
  : Fitted `prior_estimator` if provided.

  **opinion_probabilities_** *ndarray of shape (n_opinions,)*
  : Final opinion probabilities after applying the KL-divergence penalty.
    If the initial `opinion_probabilities` doesn’t sum to one, the last element of
    `opinion_probabilities_` is the probability assigned to the uniform prior.

  **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.

### Methods

| [`fit`](#skfolio.prior.OpinionPooling.fit)(X[, y])            | Fit the Opinion Pooling estimator.                    |
|-------------------------------------------------------------------------|-------------------------------------------------------|
| [`get_metadata_routing`](#skfolio.prior.OpinionPooling.get_metadata_routing)() | Get metadata routing of this object.                  |
| [`get_params`](#skfolio.prior.OpinionPooling.get_params)([deep])     | Get the parameters of an estimator from the ensemble. |
| [`set_params`](#skfolio.prior.OpinionPooling.set_params)(\*\*params) | Set the parameters of an estimator from the ensemble. |

### References

* <a id='rf208bb6d5a69-1'>**[1]**</a> “Probabilistic opinion pooling generalized”, Social Choice and Welfare, Dietrich & List (2017)
* <a id='rf208bb6d5a69-2'>**[2]**</a> “Opinion Aggregation and Individual Expertise”, Oxford University Press, Martini & Sprenger (2017)
* <a id='rf208bb6d5a69-3'>**[3]**</a> “Rational Decisions”, Journal of the Royal Statistical Society, Good  (1952)

### Examples

For a full tutorial on entropy pooling, see [Opinion Pooling](https://skfolio.org/auto_examples/entropy_pooling/plot_2_opinion_pooling.html.md#sphx-glr-auto-examples-entropy-pooling-plot-2-opinion-pooling-py).

```pycon
>>> from skfolio import RiskMeasure
>>> from skfolio.datasets import load_sp500_dataset
>>> from skfolio.preprocessing import prices_to_returns
>>> from skfolio.prior import EntropyPooling, OpinionPooling
>>> from skfolio.optimization import RiskBudgeting
>>>
>>> prices = load_sp500_dataset()
>>> X = prices_to_returns(prices)
>>>
>>> # We consider two expert opinions, each generated via Entropy Pooling with
>>> # user-defined views.
>>> # We assign probabilities of 40% to Expert 1, 50% to Expert 2, and by default
>>> # the remaining 10% is allocated to the prior distribution:
>>> opinion_1 = EntropyPooling(cvar_views=["AMD == 0.10"])
>>> opinion_2 = EntropyPooling(
...     mean_views=["AMD >= BAC", "JPM <= prior(JPM) * 0.8"],
...     cvar_views=["GE == 0.12"],
... )
>>>
>>> opinion_pooling = OpinionPooling(
...     estimators=[("opinion_1", opinion_1), ("opinion_2", opinion_2)],
...     opinion_probabilities=[0.4, 0.5],
... )
>>>
>>> opinion_pooling.fit(X)
OpinionPooling(...)
>>>
>>> print(opinion_pooling.return_distribution_.sample_weight)
[0.000117... 0.000117... 0.000117... ... 0.000117... 0.000117...
 0.000117...]
>>>
>>> # CVaR Risk Parity optimization on opinion Pooling
>>> model = RiskBudgeting(
...     risk_measure=RiskMeasure.CVAR,
...     prior_estimator=opinion_pooling
... )
>>> model.fit(X)
>>> print(model.weights_)
>>>
>>> # Stress Test the Portfolio
>>> opinion_1 = EntropyPooling(cvar_views=["AMD == 0.05"])
>>> opinion_2 = EntropyPooling(cvar_views=["AMD == 0.10"])
>>> opinion_pooling = OpinionPooling(
...     estimators=[("opinion_1", opinion_1), ("opinion_2", opinion_2)],
...     opinion_probabilities=[0.6, 0.4],
... )
>>> opinion_pooling.fit(X)
OpinionPooling(...)
>>>
>>> stressed_dist = opinion_pooling.return_distribution_
>>>
>>> stressed_ptf = model.predict(stressed_dist)
```

<a id="skfolio.prior.OpinionPooling.fit"></a>

#### fit(X, y=None, \*\*fit_params)

Fit the Opinion Pooling 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.

  **\*\*fit_params** *dict*
  : Parameters to pass to the underlying estimators.
    Only available if `enable_metadata_routing=True`, which can be
    set by using `sklearn.set_config(enable_metadata_routing=True)`.
    See [Metadata Routing User Guide](https://skfolio.org/user_guide/metadata_routing.html.md#metadata-routing) for
    more details.
* **Returns:**
  **self** *OpinionPooling*
  : Fitted estimator.

<a id="skfolio.prior.OpinionPooling.get_metadata_routing"></a>

#### get_metadata_routing()

Get metadata routing of this object.

Please check [User Guide](https://skfolio.org/user_guide/metadata_routing.html.md#metadata-routing) on how the routing
mechanism works.

* **Returns:**
  **routing** *MetadataRequest*
  : A `MetadataRequest` encapsulating
    routing information.

<a id="skfolio.prior.OpinionPooling.get_params"></a>

#### get_params(deep=True)

Get the parameters of an estimator from the ensemble.

Returns the parameters given in the constructor as well as the
estimators contained within the `estimators` parameter.

* **Parameters:**
  **deep** *bool, default=True*
  : Setting it to True gets the various estimators and the parameters
    of the estimators as well.
* **Returns:**
  **params** *dict*
  : Parameter and estimator names mapped to their values or parameter
    names mapped to their values.

<a id="skfolio.prior.OpinionPooling.named_estimators"></a>

#### *property* named_estimators

Dictionary to access any fitted sub-estimators by name.

* **Returns:**
  `Bunch`

<a id="skfolio.prior.OpinionPooling.set_params"></a>

#### set_params(\*\*params)

Set the parameters of an estimator from the ensemble.

Valid parameter keys can be listed with `get_params()`. Note that you
can directly set the parameters of the estimators contained in
`estimators`.

* **Parameters:**
  **\*\*params** *keyword arguments*
  : Specific parameters using e.g.
    `set_params(parameter_name=new_value)`. In addition, to setting the
    parameters of the estimator, the individual estimator of the
    estimators can also be set, or can be removed by setting them to
    ‘drop’.
* **Returns:**
  **self** *object*
  : Estimator instance.

