Source code for otlingam.utils
"""Utility metrics for evaluating learned causal structures."""
import numpy as np
from sklearn.utils.validation import check_array, column_or_1d # type: ignore
[docs]
def disorder(
causal_order: np.typing.ArrayLike,
adjacency_matrix: np.typing.ArrayLike,
) -> int:
r"""Counts true edges reversed by a causal order.
Let :math:`\hat{\sigma}` be the estimated order. The disorder is given by
.. math::
\begin{aligned}
\mathrm{dis}(\hat{\sigma}) &= \#\left\{ (k, j) : B^\star_{jk} \neq 0, \\
&\quad \hat{\sigma}^{-1}(k) > \hat{\sigma}^{-1}(j) \right\}.
\end{aligned}
It is zero exactly when `causal_order` is a topological order of the true DAG.
Args:
causal_order (np.typing.ArrayLike): Node permutation from source to sink.
adjacency_matrix (np.typing.ArrayLike): Ground-truth weighted adjacency matrix
whose entry :math:`B_{jk}` represents the edge :math:`k \to j`.
Returns:
int: Number of reversed true edges.
Raises:
ValueError: If the matrix is not square or `causal_order` is not a permutation.
"""
order = column_or_1d(causal_order, dtype=int) # type: ignore
B = check_array(adjacency_matrix)
if B.shape[0] != B.shape[1]:
raise ValueError(
f"adjacency_matrix must be a square array, got shape {B.shape}."
)
d = B.shape[0]
if not np.array_equal(np.sort(order), np.arange(d)):
raise ValueError("causal_order must be a permutation of range(d).")
pos = np.empty(d, dtype=np.int64)
pos[order] = np.arange(d)
child, parent = np.nonzero(B)
return int(np.sum(pos[parent] > pos[child]))