liberata_metrics.metrics.portfolio_metrics module

Portfolio metrics computation module.

This module provides classes and functions for computing and analyzing portfolio performance metrics, including returns, risk, correlation, and spectral properties.

liberata_metrics.metrics.portfolio_metrics.academic_capital(capital: spmatrix, contributor_index_map_subset: Dict[str, int]) float[source]

Compute total academic capital for a subset of contributors.

Parameters:
  • capital (scipy.sparse.spmatrix) – Sparse matrix where each row is a manuscript and each column is a contributor. Entries represent capital allocated from a contributor to a manuscript.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index in capital. Only columns referenced by this mapping are included in the total.

Returns:

Total academic capital aggregated across the specified contributor columns. Returns 0.0 if the mapping is empty.

Return type:

float

Raises:

TypeError – If capital is not a scipy sparse matrix.

Notes

The function sums the selected columns of the sparse matrix and returns the scalar total.

liberata_metrics.metrics.portfolio_metrics.allocation_weights(capital: spmatrix, contributor_index_map_subset: Dict[str, int]) Dict[int, float][source]

Compute allocation weights for each manuscript in a portfolio, i.e., the fraction of total portfolio capital contributed due to each manuscript. :param capital: Sparse matrix where each row represents a manuscript and each column represents a contributor.

Entries represent the amount of capital allocated from a contributor to a manuscript. This function requires a scipy sparse matrix input and will raise TypeError if given a non-sparse array.

Parameters:

contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index in capital. Only the columns specified by the values of this mapping are considered. If this mapping is empty, the function returns an empty dictionary.

Returns:

A dictionary mapping manuscript row indices (int) to their allocation weight (float). Each weight equals the manuscript’s aggregated capital from the selected contributors divided by the total capital across the portfolio. Manuscripts with zero aggregated capital are omitted. If the total portfolio capital is zero, an empty dictionary is returned.

Return type:

Dict[int, float]

Raises:
  • TypeError – If capital is not a scipy sparse matrix.

  • IndexError, ValueError, or other slicing-related exceptions – May be raised if provided column indices are out of bounds or otherwise invalid. The function attempts a direct column slice and falls back to constructing the submatrix by stacking individual columns.

Notes

  • The returned weights sum to 1.0 up to floating-point precision when non-empty.

  • Implementation details: the function first tries to slice capital using the provided column indices. If slicing fails, it falls back to horizontally stacking individual columns via capital.getcol.

  • Output contains only manuscripts with non-zero aggregated capital.

Examples

>>> # capital: scipy CSR matrix with shape (num_manuscripts, num_contributors)
>>> # contributor_index_map_subset = {'alice': 0, 'bob': 2}
>>> allocation_weights(capital, {'alice': 0, 'bob': 2})
{0: 0.5, 3: 0.25, 7: 0.25}
liberata_metrics.metrics.portfolio_metrics.get_arc(capital_history: List[spmatrix], contributor_index_map_subset: Dict[str, int]) float[source]

Compute the Academic Returns-to-Capital ratio (ARC) for the most recent return period divided by the current academic capital of the portfolio.

Parameters:
  • capital_history (List[scipy.sparse.spmatrix]) – Chronological list of sparse academic-capital matrices. The final two entries are used to compute the most recent return.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifiers to column indices.

Returns:

Phase-sensitive ARC value. Returns 0.0 if insufficient history or if current capital is zero.

Return type:

float

liberata_metrics.metrics.portfolio_metrics.get_col_indices(capital: spmatrix, contributor_index_map_subset: Dict[str, int]) ndarray[source]

Find the column indices corresponding to a subset of contributors, accounting for where contributor columns are repeated three times.

Parameters:
  • capital (scipy.sparse.spmatrix) – Sparse capital matrix of shape (M, M + C), where M is the number of manuscripts and C is the total number of contributor columns.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to their base column index

Returns:

Array of absolute column indices for the given contributors, spanning all role blocks if the layout is role-blocked.

Return type:

np.ndarray

liberata_metrics.metrics.portfolio_metrics.get_diversification_ratio(capital_history: List[spmatrix], contributor_index_map_subset: Dict[str, int]) float[source]

Compute the Diversification Ratio of a portfolio. The Diversification Ratio is defined as the weighted average of the volatilities of the individual assets divided by the volatility of the entire portfolio.

Parameters:
  • capital_history (List[scipy.sparse.spmatrix]) – A chronological list of at least two sparse matrices representing portfolio capital states over time.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifiers to their corresponding column indices in the capital matrices.

Returns:

The Diversification Ratio. Returns 0.0 if the portfolio volatility is zero

Return type:

float

Raises:
  • TypeError – If any element in capital_history is not a scipy sparse matrix.

  • ValueError – If capital_history contains fewer than two entries.

  • Returns 1.0 if the portfolio consists of a single asset or perfectly correlated assets.

liberata_metrics.metrics.portfolio_metrics.get_expected_proportional_returns(capital_history: List[spmatrix], contributor_index_map_subset: Dict[str, int]) float[source]

Compute the expected proportional returns based on a historical sequence of sparse capital matrices

Parameters:
  • capital_history (List[scipy.sparse.spmatrix]) – A chronological list of at least two sparse matrices representing the portfolio states over time.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index.

Returns:

The expected return, calculated as the arithmetic mean of the historical returns

Return type:

float

Raises:
  • TypeError – If any element in capital_history is not a scipy sparse matrix.

  • ValueError – If capital_history contains fewer than 2 entries, which is insufficient to compute returnss

liberata_metrics.metrics.portfolio_metrics.get_expected_returns(capital_history: List[spmatrix], time_history: List[float], contributor_index_map_subset: Dict[str, int]) float[source]

Compute the expected returns based on a historical sequence of sparse capital matrices and corresponding time intervals

Parameters:
  • capital_history (List[scipy.sparse.spmatrix]) – A chronological list of at least two sparse matrices representing the portfolio states over time.

  • time_history (List[float]) – A list of positive floats representing the time points at which the capital states are recorded. Must have length equal to capital_history.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index.

Returns:

The expected return per unit time, calculated as the arithmetic mean of the historical returns per unit time.

Return type:

float

Raises:
  • TypeError – If any element in capital_history is not a scipy sparse matrix.

  • ValueError – If capital_history contains fewer than 2 entries, or if any time interval is not positive.

liberata_metrics.metrics.portfolio_metrics.get_funding_efficiency(capital: spmatrix, contributor_index_map_subset: Dict[str, int], funding: float) float[source]

Compute the funding efficiency of a portfolio.

Funding efficiency measures the academic capital generated per unit of external funding, analogous to return on investment (ROI) for research grants.

Parameters:
  • capital (scipy.sparse.spmatrix) – Sparse matrix where each row represents a manuscript and each column represents a contributor.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index in capital.

  • funding (float) – Total funding associated with the portfolio. Must be non-negative.

Returns:

The funding efficiency ratio (academic capital / funding). Returns 0.0 if funding is zero.

Return type:

float

Raises:
  • TypeError – If capital is not a scipy sparse matrix.

  • ValueError – If funding is negative.

liberata_metrics.metrics.portfolio_metrics.get_manuscript_memberships_matrix(manuscript_tags: Dict[str, sparray]) Tuple[spmatrix, List[str]][source]

Build a stacked membership matrix from a tag-to-membership mapping.

Parameters:

manuscript_tags (Dict[str, sparse.sparray]) – Mapping from tag name to a sparse one-hot row array of length num_manuscripts indicating which manuscripts belong to that tag.

Returns:

  • manuscript_memberships (scipy.sparse.spmatrix) – CSR matrix of shape (num_tags, num_manuscripts) where rows are tags and columns are manuscripts. Entry [i, j] is 1 if manuscript j belongs to tag i.

  • keys (List[str]) – Ordered list of tag names corresponding to the rows of the matrix.

liberata_metrics.metrics.portfolio_metrics.get_per_manuscript_cap(capital: spmatrix, contributor_index_map_subset: Dict[str, int]) spmatrix[source]

Extract per-manuscript capital for a subset of contributors. :param capital: Sparse matrix where each row represents a manuscript and each column represents a contributor.

Entries represent the amount of capital allocated from a contributor to a manuscript.

Parameters:

contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index in capital. Only the columns specified by the values of this mapping are considered.

Returns:

Sparse matrix containing the per-manuscript capital aggregated over the specified contributor columns.

Return type:

scipy.sparse.spmatrix

Raises:
  • TypeError – If capital is not a scipy sparse matrix.

  • ValueError – If contributor_index_map_subset is empty.

Notes

  • The returned matrix has the same number of rows as capital and a single column,

    where each entry represents the total capital for that manuscript from the specified contributors.

  • Implementation details: the function first tries to slice capital using the provided column indices. If slicing fails, it falls back to horizontally stacking individual columns via capital.getcol.

Examples

>>> # capital: scipy CSR matrix with shape (num_manuscripts, num_contributors)
>>> # contributor_index_map_subset = {'alice': 0, 'bob': 2}
>>> get_per_manuscript_cap(capital, {'alice': 0, 'bob   : 2})
<2x1 sparse matrix of type '<class 'numpy.float64'>'
    with 3 stored elements in Compressed Sparse Row format>
liberata_metrics.metrics.portfolio_metrics.get_proportional_return(capital_start: spmatrix, capital_end: spmatrix, contributor_index_map_subset: Dict[str, int]) float[source]

Compute the returns over a given time interval, defined by the proportional increase in academic capital over an interval

Parameters:
  • capital_start (scipy.sparse.spmatrix) – Sparse matrix representing the academic capital at the start of the interval.

  • capital_end (scipy.sparse.spmatrix) – Sparse matrix representing the academic capital at the end of the interval.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index. Only columns referenced by this mapping are included in the calculation.

Returns:

The proportional return during the interval

Return type:

float

Raises:
  • TypeError – If either capital_start or capital_end is not a scipy sparse matrix.

  • ValueError – If the starting capital is zero, which would make return calculation undefined.

Notes

  • This function relies on two distinct sparse capital matrices

liberata_metrics.metrics.portfolio_metrics.get_proportional_split(capital: spmatrix, mask_by_role: slice, contributor_index_map_subset: Dict[str, int]) float[source]

Compute the Proportional Split metric.

This metric measures the proportion of academic capital a contributor (or group) derives from a specific role (e.g., peer review or replication) relative to their total academic capital.

Parameters:
  • capital (scipy.sparse.spmatrix) – Sparse matrix where each row represents a manuscript and each column represents a contributor.

  • mask_by_role (slice or sparse matrix mask) – A mask or slice that isolates the specific role’s capital within the capital matrix. Should be compatible with capital.tocsc()[mask_by_role].

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifiers to the corresponding column index in capital.

Returns:

The proportional split value (0.0 to 1.0). Returns 0.0 if total capital is zero.

Return type:

float

Raises:

TypeError – If capital is not a scipy sparse matrix.

liberata_metrics.metrics.portfolio_metrics.get_returns(capital_start: spmatrix, capital_end: spmatrix, contributor_index_map_subset: Dict[str, int], time_interval: float = 1) float[source]

Compute the returns, defined as the change in returns over the change in time.

Parameters:
  • capital_start (scipy.sparse.spmatrix) – Sparse matrix representing the academic capital at the start of the interval.

  • capital_end (scipy.sparse.spmatrix) – Sparse matrix representing the academic capital at the end of the interval.

  • time_interval (float) – The time interval over which to compute returns. Must be positive.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index.

Returns:

The return per unit time during the interval.

Return type:

float

Raises:
  • TypeError – If either capital_start or capital_end is not a scipy sparse matrix.

  • ValueError – If time_interval is not positive, which would make return calculation undefined.

liberata_metrics.metrics.portfolio_metrics.get_risk_asymmetry(capital_history: List[spmatrix], contributor_index_map_subset: Dict[str, int], expected_return: float | None = None, volatility: float | None = None) float[source]

Compute the risk asymmetry of a portfolio from a historical sequence of sparse academic-capital matrices.

Parameters:
  • capital_history (List[scipy.sparse.spmatrix]) – A chronological list of at least two sparse matrices representing portfolio capital states over time.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifiers to their corresponding column indices in the capital matrices.

Returns:

The risk asymmetry of the portfolio.

Return type:

float

Raises:

TypeError – If any element in capital_history is not a scipy sparse matrix. Returns 0.0 if capital_history contains fewer than two entries.

liberata_metrics.metrics.portfolio_metrics.get_sharpe_ratio(capital_history: List[spmatrix], contributor_index_map_subset: Dict[str, int]) float[source]

Compute the Sharpe ratio of a portfolio from a historical sequence of sparse academic-capital matrices.

Parameters:
  • capital_history (List[scipy.sparse.spmatrix]) – A chronological list of at least two sparse matrices representing portfolio capital states over time.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifiers to their corresponding column indices in the capital matrices.

Returns:

The Sharpe ratio of the portfolio. Returns 0.0 if volatility is zero or if capital_history contains fewer than two entries.

Return type:

float

Raises:

TypeError – If any element in capital_history is not a scipy sparse matrix.

liberata_metrics.metrics.portfolio_metrics.get_time_efficiency(capital_history: List[spmatrix], contributor_index_map_subset: Dict[str, int], time_period: float | None = None) float[source]

Compute the time efficiency of a portfolio.

Time efficiency measures the average rate of academic capital accumulation per time period, computed as the total change in academic capital divided by the number of elapsed time periods.

Parameters:
  • capital_history (List[scipy.sparse.spmatrix]) – A chronological list of at least two sparse matrices representing portfolio capital states over time. Consecutive entries are assumed to be uniformly spaced in time.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifiers to their corresponding column indices in the capital matrices.

Returns:

The average academic capital accumulated per time period. Returns 0.0 if capital_history contains fewer than two entries.

Return type:

float

Raises:

TypeError – If any element in capital_history is not a scipy sparse matrix.

liberata_metrics.metrics.portfolio_metrics.get_volatility(capital_history: List[spmatrix], contributor_index_map_subset: Dict[str, int]) tuple[float, float][source]

Compute the volatility of portfolio returns, defined as the population standard deviation of returns, from a historical sequence of sparse academic-capital matrices.

Parameters:
  • capital_history (List[scipy.sparse.spmatrix]) – A chronological list of at least two sparse matrices representing portfolio capital states over time.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifiers to their corresponding column indices in the capital matrices.

Returns:

A tuple of (volatility, expected_returns) where volatility is the population standard deviation of portfolio returns.

Return type:

tuple[float, float]

Raises:
  • TypeError – If any element in capital_history is not a scipy sparse matrix.

  • ValueError – If capital_history contains fewer than 2 entries

liberata_metrics.metrics.portfolio_metrics.mix_by_role(capital, contributor_index_map_subset, mask_by_role, manuscript_memberships)[source]

Compute total capital and tag-wise capital mix for a specific role.

Parameters:
  • capital (scipy.sparse.spmatrix) – Full capital matrix

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to column index

  • mask_by_role (slice) – Slice selecting the columns in the capital matrix that correspond to the role (Example: np.s_[:, 10:60] selects all rows and columns 10-59)

  • manuscript_memberships (scipy.sparse.spmatrix (num_tags, num_manuscripts)) – Entry [t, i] is 1 if manuscript i belongs to tag t

Returns:

  • rolewise_cap_tot (float) – Total capital attributed to this role across all manuscripts and contributors

  • rolewise_capmix_by_tag (sparse.sparray (num_tags, num_contributors)) – Entry [t, j] is the total capital of contributor j in manuscripts belonging to tag t

liberata_metrics.metrics.portfolio_metrics.mix_by_tag(capital_sub: sparray, manuscript_memberships: spmatrix) sparray[source]

Find capital by tag and contributor

Parameters:
  • capital_sub (sparse.sparray (num_manuscripts, num_contributors)) – A capital matrix where entry [i, j] is the capital of contributor j on manuscript i

  • manuscript_memberships (scipy.sparse.spmatrix (num_tags, num_manuscripts)) – Matrix where entry [t, i] is 1 if manuscript i belongs to tag t and 0 otherwise

Returns:

Entry [t, j] is the total capital of contributor j across all manuscripts belonging to tag t

Return type:

sparse.sparray (num_tags, num_contributors)

liberata_metrics.metrics.portfolio_metrics.portfolio_gini(capital: spmatrix, contributor_index_map_subset: Dict[str, int]) float[source]

Compute the Gini coefficient of a portfolio. :param capital: Sparse matrix where each row represents a manuscript and each column represents a contributor.

Entries represent the amount of capital allocated from a contributor to a manuscript.

Parameters:

contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index in capital.

Returns:

The Gini coefficient, a measure of inequality in the portfolio. Returns 0.0 if the portfolio is empty or if there is only one manuscript.

Return type:

float

Raises:

TypeError – If capital is not a scipy sparse matrix.

Notes

  • The Gini coefficient is calculated based on the allocation weights.

Examples

>>> portfolio_gini(capital, {'alice': 0, 'bob': 2})
0.25
liberata_metrics.metrics.portfolio_metrics.portfolio_hhi(capital: spmatrix, contributor_index_map_subset: Dict[str, int]) float[source]

Compute the Herfindahl-Hirschman Index (HHI) of a portfolio. :param capital: Sparse matrix where each row represents a manuscript and each column represents a contributor.

Entries represent the amount of capital allocated from a contributor to a manuscript.

Parameters:

contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index in capital.

Returns:

The HHI value, a measure of concentration in the portfolio. Returns 0.0 if the portfolio is empty or if all weights are zero.

Return type:

float

Raises:

TypeError – If capital is not a scipy sparse matrix.

Notes

  • The HHI is calculated as the sum of the squares of the allocation weights.

Examples

>>> portfolio_hhi(capital, {'alice': 0, 'bob': 2})
0.375
liberata_metrics.metrics.portfolio_metrics.portfolio_normalized_entropy(capital: spmatrix, contributor_index_map_subset: Dict[str, int]) float[source]

Compute the normalized entropy of a portfolio. :param capital: Sparse matrix where each row represents a manuscript and each column represents a contributor.

Entries represent the amount of capital allocated from a contributor to a manuscript.

Parameters:

contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index in capital.

Returns:

The normalized entropy, a measure of diversity in the portfolio. Returns 0.0 if the portfolio is empty or if there is only one manuscript.

Return type:

float

Raises:

TypeError – If capital is not a scipy sparse matrix.

Notes

  • The normalized entropy is calculated based on the allocation weights.

Examples

>>> portfolio_normalized_entropy(capital, {'alice': 0, 'bob': 2})
0.85
liberata_metrics.metrics.portfolio_metrics.query_portfolio_mix(capital: spmatrix, mask_authors: slice, mask_reviewers: slice, mask_replicators: slice, manuscript_memberships: spmatrix, contributor_index_map_subset: Dict[str, int], by: str = 'role') Tuple[List[float], List[sparray]][source]

Query the portfolio mix by specified criteria. This function computes allocation weights across a portfolio of manuscripts and aggregates them based on the specified grouping criterion (by role or by tag). :param capital: A scipy sparse matrix representing capital allocation. :type capital: sparse.spmatrix :param contributor_index_map_subset: A dictionary mapping contributor names

to their indices, representing a subset of all contributors.

Parameters:
  • manuscript_index_map (Dict[str, int]) – A dictionary mapping manuscript names to their indices.

  • manuscript_tags (Dict[str, sparse.spmatrix]) – A dictionary mapping tag names to sparse binary matrices indicating manuscript membership in each tag.

  • by (str, optional) – The criterion for grouping the portfolio mix. Either ‘role’ or ‘tag’. Defaults to ‘role’.

Returns:

A dictionary mapping role or tag names to their corresponding

allocation weights in the portfolio.

Return type:

dict[str, float]

Raises:

TypeError – If capital is not a scipy sparse matrix.

Notes

  • When by=’tag’: For each tag, a sparse mask identifies manuscripts belonging to that tag, and weights are intersected with the mask.

  • When by=’role’: Capital is subsampled by role, and allocation weights are computed for each role separately.

  • Returns an empty dictionary if no weights can be computed or if n <= 1.

liberata_metrics.metrics.portfolio_metrics.role_based_proportional_loss(capital: spmatrix, retractions_capital: spmatrix, mask_by_role: slice, contributor_index_map_subset: Dict[str, int]) float[source]

Compute the proportion of capital loss for a specific role in the portfolio. :param capital: Sparse matrix where each row represents a manuscript and each column represents a contributor.

Entries represent the amount of capital accrued from a manuscript by a contributor.

Parameters:
  • retraction_capital (scipy.sparse.spmatrix) – Sparse matrix of the same shape as capital, representing capital associated with retracted manuscripts.

  • mask_by_role (scipy.sparse.spmatrix) – Sparse binary matrix indicating which entries in capital correspond to the specified role.

  • contributor_index_map_subset (Dict[str, int]) – Mapping from contributor identifier to the corresponding column index in capital.

Returns:

The proportion of capital loss for the specified role in the portfolio. Returns 0.0 if the total capital is zero.

Return type:

float

Raises:

TypeError – If capital or mask_by_role is not a scipy sparse matrix.

Notes

  • The function computes the total capital for the specified role and compares it to the overall total capital to determine the proportion of loss.